doeskeyvalue 0.2.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/Gemfile +14 -4
- data/Gemfile.lock +42 -7
- data/LICENSE.txt +1 -1
- data/README.rdoc +102 -67
- data/Rakefile +43 -19
- data/VERSION +1 -1
- data/doeskeyvalue.gemspec +43 -22
- data/lib/doeskeyvalue.rb +46 -79
- data/lib/doeskeyvalue/accessors.rb +136 -0
- data/lib/doeskeyvalue/column_storage.rb +95 -0
- data/lib/doeskeyvalue/configuration.rb +32 -0
- data/lib/doeskeyvalue/index.rb +145 -0
- data/lib/doeskeyvalue/state.rb +48 -0
- data/lib/doeskeyvalue/table_storage.rb +79 -0
- data/lib/doeskeyvalue/util.rb +25 -18
- data/lib/generators/doeskeyvalue/doeskeyvalue_generator.rb +9 -5
- data/lib/generators/doeskeyvalue/templates/create_key_value_index.rb +22 -7
- data/spec/doeskeyvalue/column_storage_spec.rb +139 -0
- data/spec/doeskeyvalue/table_storage_spec.rb +140 -0
- data/spec/spec_helper.rb +25 -0
- metadata +151 -57
- data/.document +0 -5
- data/lib/doeskeyvalue/indexes.rb +0 -104
- data/lib/doeskeyvalue/key_manager.rb +0 -116
- data/lib/doeskeyvalue/keys.rb +0 -46
data/lib/doeskeyvalue.rb
CHANGED
@@ -1,100 +1,67 @@
|
|
1
1
|
# AWEXOME LABS
|
2
2
|
# DoesKeyValue
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
|
9
|
-
require 'doeskeyvalue/key_manager'
|
10
|
-
require 'doeskeyvalue/keys'
|
11
|
-
require 'doeskeyvalue/indexes'
|
12
|
-
require 'doeskeyvalue/util'
|
4
|
+
require "doeskeyvalue"
|
5
|
+
require "active_record"
|
6
|
+
require "active_support"
|
7
|
+
require "hashie"
|
13
8
|
|
9
|
+
require "doeskeyvalue/configuration"
|
10
|
+
require "doeskeyvalue/state"
|
11
|
+
require "doeskeyvalue/util"
|
12
|
+
require "doeskeyvalue/index"
|
13
|
+
require "doeskeyvalue/accessors"
|
14
|
+
# require "doeskeyvalue/column_storage" # <= Deprecating in favor of combined Accessors
|
15
|
+
# require "doeskeyvalue/table_storage" # <= Deprecating in favor of combined Accessors
|
14
16
|
|
15
17
|
module DoesKeyValue
|
16
18
|
|
17
|
-
# Create a Rails Engine
|
18
|
-
class Engine < Rails::Engine
|
19
|
-
end
|
20
|
-
|
21
19
|
# Return the current working version from VERSION file:
|
22
20
|
def self.version
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
# Exception Types for DoesKeyValue
|
27
|
-
class NoColumnNameSpecified < Exception
|
28
|
-
def initialize(msg="A class column name must be provided for storing key values in blob"); super(msg); end
|
29
|
-
end
|
30
|
-
class NoKeyNameSpecified < Exception
|
31
|
-
def initialize(msg="A key name must be provided to build a DoesKeyValue key"); super(msg); end
|
21
|
+
Gem.loaded_specs["doeskeyvalue"].version.to_s
|
32
22
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def initialize(msg="Options passed to declarations of keys and indexes must of class Hash"); super(msg); end
|
38
|
-
end
|
39
|
-
class KeyValueIndexTableDoesNotExist < Exception
|
40
|
-
def initialize(msg="DoesKeyValue requires an index table be generated to use key indexes. Use generator to generate migration"); super(msg); end
|
23
|
+
|
24
|
+
# Log messages
|
25
|
+
def self.log(msg)
|
26
|
+
puts "DoesKeyValue: #{msg}" unless configuration.log_level == :silent
|
41
27
|
end
|
42
|
-
|
28
|
+
|
43
29
|
end # DoesKeyValue
|
44
30
|
|
45
31
|
|
46
32
|
module ActiveRecord
|
47
33
|
class Base
|
48
|
-
|
49
|
-
# Call this method within your class to establish key-
|
50
|
-
#
|
51
|
-
def self.
|
34
|
+
|
35
|
+
# Call this "acts as" method within your ActiveRecord class to establish key-
|
36
|
+
# value behavior and prepare internal storage structures
|
37
|
+
def self.does_keys(opts={})
|
38
|
+
DoesKeyValue.log("Adding key-value support to class #{self.to_s}")
|
39
|
+
# TODO: Raise exception to improper opts passed
|
52
40
|
self.instance_eval do
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
@column_name = column.to_sym
|
58
|
-
cattr_accessor :column_name
|
59
|
-
serialize @column_name, Hashie::Mash
|
60
|
-
|
61
|
-
# Add the column to the key and column manager so we can reference it later:
|
62
|
-
DoesKeyValue::KeyManager.instance.declare_column(self, @column_name)
|
41
|
+
DoesKeyValue::State.instance.add_class(self, opts)
|
42
|
+
self.send(:serialize, opts[:column], Hash) if opts[:column]
|
43
|
+
self.send(:attr_accessor, :key_value_cache) if opts[:table]
|
44
|
+
extend DoesKeyValue::Accessors
|
63
45
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
def key_value_columns
|
83
|
-
return DoesKeyValue::KeyManager.instance.columns_for(self)
|
84
|
-
end
|
85
|
-
|
86
|
-
def #{@column_name}_keys
|
87
|
-
column_name = :#{@column_name}
|
88
|
-
return DoesKeyValue::KeyManager.instance.keys_for(self, column_name)
|
89
|
-
end
|
90
|
-
|
91
|
-
def #{@column_name}_indexes
|
92
|
-
column_name = :#{@column_name}
|
93
|
-
return DoesKeyValue::KeyManager.instance.indexes_for(self, column_name)
|
94
|
-
end
|
95
|
-
EOS
|
46
|
+
|
47
|
+
|
48
|
+
# if storage_column = opts[:column]
|
49
|
+
# DoesKeyValue.log("Adding key-value support via column #{storage_column} to class #{self.to_s}")
|
50
|
+
# self.instance_eval do
|
51
|
+
# DoesKeyValue::State.instance.add_class(self, :column=>storage_column)
|
52
|
+
# self.send(:serialize, storage_column, Hash)
|
53
|
+
# extend DoesKeyValue::ColumnStorage
|
54
|
+
# end
|
55
|
+
|
56
|
+
# elsif storage_table = opts[:table]
|
57
|
+
# DoesKeyValue.log("Adding key-value support via table #{storage_table} to class #{self.to_s}")
|
58
|
+
# self.instance_eval do
|
59
|
+
# DoesKeyValue::State.instance.add_class(self, :table=>storage_table)
|
60
|
+
# extend DoesKeyValue::TableStorage
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
|
96
64
|
end
|
97
65
|
|
98
|
-
|
99
|
-
end # ActiveRecord::Base
|
66
|
+
end # Base
|
100
67
|
end # ActiveRecord
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# TableStorage -- Accessor and update methods for keys managed under the
|
5
|
+
# key-value store defined for given classes.
|
6
|
+
|
7
|
+
module DoesKeyValue
|
8
|
+
|
9
|
+
# Define our types for strongly-typed results storage:
|
10
|
+
SUPPORTED_DATA_TYPES = %w(string integer decimal boolean datetime)
|
11
|
+
|
12
|
+
module Accessors
|
13
|
+
|
14
|
+
# Define a supported key
|
15
|
+
def has_key(key_name, opts={})
|
16
|
+
opts = {
|
17
|
+
index: true,
|
18
|
+
type: "string",
|
19
|
+
default: nil
|
20
|
+
}.merge(opts)
|
21
|
+
|
22
|
+
# Table-based storage dictates an index:
|
23
|
+
opts[:index] = true if table_storage?
|
24
|
+
|
25
|
+
# Cache values of common options:
|
26
|
+
key_indexed = opts[:index]
|
27
|
+
key_type = opts[:type].to_sym
|
28
|
+
key_default_value = opts[:default]
|
29
|
+
|
30
|
+
# Ensure we are invoked with a supported data type:
|
31
|
+
raise Exception.new("Data type not supported: #{key_type}") unless DoesKeyValue::SUPPORTED_DATA_TYPES.include?(key_type.to_s)
|
32
|
+
|
33
|
+
# Save a representation of this key in the State:
|
34
|
+
define_key(key_name, opts)
|
35
|
+
|
36
|
+
# Accessor for new key with support for default value:
|
37
|
+
define_method(key_name) do
|
38
|
+
DoesKeyValue.log("Accessing key:#{key_name} for class:#{self.class}")
|
39
|
+
blob = if self.class.column_storage?
|
40
|
+
Hashie::Mash.new( self.send(:read_attribute, self.class.storage_column) || Hash.new )
|
41
|
+
elsif self.class.table_storage?
|
42
|
+
# TODO: Proper cache-through hash for Table-based storage
|
43
|
+
Hashie::Mash.new( {key_name => DoesKeyValue::Index.read_index(self, key_name)} )
|
44
|
+
end
|
45
|
+
|
46
|
+
value = blob.send(key_name)
|
47
|
+
|
48
|
+
return value if value
|
49
|
+
return key_default_value
|
50
|
+
end
|
51
|
+
|
52
|
+
# Manipulator for new key:
|
53
|
+
define_method("#{key_name}=") do |value|
|
54
|
+
DoesKeyValue.log("Modifying value for key:#{key_name} to value:#{value}")
|
55
|
+
|
56
|
+
if self.class.column_storage?
|
57
|
+
blob = Hashie::Mash.new( self.send(:read_attribute, self.class.storage_column) || Hash.new )
|
58
|
+
typed_value = DoesKeyValue::Util.to_type(value, key_type)
|
59
|
+
blob[key_name] = typed_value
|
60
|
+
self.send(:write_attribute, self.class.storage_column, blob.to_hash)
|
61
|
+
|
62
|
+
elsif self.class.table_storage?
|
63
|
+
# TODO: Proper cache-through hash for Table-based storage:
|
64
|
+
DoesKeyValue::Index.update_index(self, key_name, value) unless self.new_record?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# If key is indexed, add scopes, finders, and callbacks for index management:
|
69
|
+
if key_indexed
|
70
|
+
|
71
|
+
# With scope:
|
72
|
+
scope "with_#{key_name}", lambda {|value|
|
73
|
+
DoesKeyValue::Index.find_objects(self, key_name, value)
|
74
|
+
}
|
75
|
+
DoesKeyValue.log("Scope with_#{key_name} added for indexed key #{key_name}")
|
76
|
+
|
77
|
+
# Update the index after save:
|
78
|
+
if column_storage?
|
79
|
+
define_method("update_index_for_#{key_name}") do
|
80
|
+
DoesKeyValue::Index.update_index(self, key_name, self.send(key_name))
|
81
|
+
end
|
82
|
+
after_save "update_index_for_#{key_name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
# Delete the index after destroy:
|
86
|
+
define_method("destroy_index_for_#{key_name}") do
|
87
|
+
DoesKeyValue::Index.delete_index(self, key_name)
|
88
|
+
end
|
89
|
+
after_destroy "destroy_index_for_#{key_name}"
|
90
|
+
|
91
|
+
end # if key_indexed
|
92
|
+
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
# Return true if this class uses column-based storage for key-value pairs:
|
98
|
+
def column_storage?
|
99
|
+
!storage_column.nil?
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return the column used for storing key values for this class:
|
103
|
+
def storage_column
|
104
|
+
@storage_column ||= DoesKeyValue::State.instance.options_for_class(self)[:column]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return true if this class uses table-based storage for key-value pairs:
|
108
|
+
def table_storage?
|
109
|
+
!storage_table.nil?
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return the table used for storing key values for this class:
|
113
|
+
def storage_table
|
114
|
+
@storage_table ||= DoesKeyValue::State.instance.options_for_class(self)[:table]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Return a list of currently supported keys:
|
118
|
+
def keys
|
119
|
+
DoesKeyValue::State.instance.keys[self.to_s]
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return the specific configuration for a given key:
|
123
|
+
def key_options(key_name)
|
124
|
+
DoesKeyValue::State.instance.options_for_key(self, key_name)
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Define a new key for a class:
|
131
|
+
def define_key(key_name, opts={})
|
132
|
+
DoesKeyValue::State.instance.add_key(self, key_name, opts)
|
133
|
+
end
|
134
|
+
|
135
|
+
end # Accessors
|
136
|
+
end # DoesKeyValue
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# ColumnStorage -- Support and methods for key-value pairs stored within TEXT
|
5
|
+
# or BLOB fields on the same table as the parent object.
|
6
|
+
|
7
|
+
module DoesKeyValue
|
8
|
+
|
9
|
+
SUPPORTED_DATA_TYPES = %w(string integer decimal boolean datetime)
|
10
|
+
|
11
|
+
module ColumnStorage
|
12
|
+
|
13
|
+
# Define a supported key
|
14
|
+
def has_key(key_name, opts={})
|
15
|
+
opts = {
|
16
|
+
index: true,
|
17
|
+
type: "string",
|
18
|
+
default: nil
|
19
|
+
}.merge(opts)
|
20
|
+
|
21
|
+
key_indexed = opts[:index]
|
22
|
+
key_type = opts[:type].to_sym
|
23
|
+
key_default_value = opts[:default]
|
24
|
+
|
25
|
+
raise Exception.new("Data type not supported: #{key_type}") unless DoesKeyValue::SUPPORTED_DATA_TYPES.include?(key_type.to_s)
|
26
|
+
|
27
|
+
DoesKeyValue::State.instance.add_key(self, key_name, opts)
|
28
|
+
storage_column = DoesKeyValue::State.instance.options_for_class(self)[:column]
|
29
|
+
|
30
|
+
# Accessor for new key with support for default value:
|
31
|
+
define_method(key_name) do
|
32
|
+
DoesKeyValue.log("Accessing key:#{key_name} for class:#{self.class}")
|
33
|
+
blob = self.send(:read_attribute, storage_column) || Hash.new
|
34
|
+
blob = Hashie::Mash.new(blob)
|
35
|
+
value = blob.send(key_name)
|
36
|
+
|
37
|
+
if value
|
38
|
+
return value
|
39
|
+
elsif default_value = self.class.key_options(key_name)[:default]
|
40
|
+
return default_value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Manipulator for new key:
|
45
|
+
define_method("#{key_name}=") do |value|
|
46
|
+
DoesKeyValue.log("Modifying value for key:#{key_name} to value:#{value}")
|
47
|
+
blob = self.send(:read_attribute, storage_column) || Hash.new
|
48
|
+
blob = Hashie::Mash.new(blob)
|
49
|
+
|
50
|
+
typed_value = DoesKeyValue::Util.to_type(value, key_type)
|
51
|
+
|
52
|
+
blob[key_name] = typed_value
|
53
|
+
self.send(:write_attribute, storage_column, blob.to_hash)
|
54
|
+
end
|
55
|
+
|
56
|
+
# If key is indexed, add scopes, finders, and callbacks for index management:
|
57
|
+
if key_indexed
|
58
|
+
|
59
|
+
# With scope:
|
60
|
+
scope "with_#{key_name}", lambda {|value|
|
61
|
+
DoesKeyValue::Index.find_objects(self, key_name, value)
|
62
|
+
}
|
63
|
+
DoesKeyValue.log("Scope with_#{key_name} added for indexed key #{key_name}")
|
64
|
+
|
65
|
+
# Update the index after save:
|
66
|
+
define_method("update_index_for_#{key_name}") do
|
67
|
+
DoesKeyValue::Index.update_index(self, key_name, self.send(key_name))
|
68
|
+
end
|
69
|
+
after_save "update_index_for_#{key_name}"
|
70
|
+
|
71
|
+
# Delete the index after destroy:
|
72
|
+
define_method("destroy_index_for_#{key_name}") do
|
73
|
+
DoesKeyValue::Index.delete_index(self, key_name)
|
74
|
+
end
|
75
|
+
after_destroy "destroy_index_for_#{key_name}"
|
76
|
+
|
77
|
+
end # if key_indexed
|
78
|
+
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Return a list of currently supported keys:
|
84
|
+
def keys
|
85
|
+
DoesKeyValue::State.instance.keys[self.to_s]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return the specific configuration for a given key:
|
89
|
+
def key_options(key_name)
|
90
|
+
DoesKeyValue::State.instance.options_for_key(self, key_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
end # ColumnStorage
|
95
|
+
end # DoesKeyValue
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue : Configuration
|
3
|
+
|
4
|
+
module DoesKeyValue
|
5
|
+
|
6
|
+
class Configuration
|
7
|
+
|
8
|
+
# Configurable options:
|
9
|
+
attr_accessor :log_level
|
10
|
+
|
11
|
+
# Declare defaults on load:
|
12
|
+
def initialize
|
13
|
+
@log_level = :silent
|
14
|
+
end
|
15
|
+
|
16
|
+
end # Configuration
|
17
|
+
|
18
|
+
|
19
|
+
# Provide an accessor to the gem configuration:
|
20
|
+
class << self
|
21
|
+
attr_accessor :configuration
|
22
|
+
end
|
23
|
+
|
24
|
+
# Yield the configuration to host:
|
25
|
+
def self.configure
|
26
|
+
self.configuration ||= Configuration.new
|
27
|
+
yield(configuration)
|
28
|
+
end
|
29
|
+
|
30
|
+
DoesKeyValue.configuration = Configuration.new
|
31
|
+
|
32
|
+
end # DoesKeyValue
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# DoesKeyValue::Index -- An AR model used for updating indexes
|
5
|
+
|
6
|
+
module DoesKeyValue
|
7
|
+
|
8
|
+
class Index < ActiveRecord::Base
|
9
|
+
|
10
|
+
# The default index table (overidden individually for custom tables):
|
11
|
+
self.table_name = "key_value_index"
|
12
|
+
|
13
|
+
# All attributes of an index row are mass-editable:
|
14
|
+
attr_accessible :obj_type, :obj_id, :key_name, :key_type,
|
15
|
+
:value_string, :value_integer, :value_decimal, :value_boolean, :value_datetime
|
16
|
+
|
17
|
+
|
18
|
+
# Search the database for objects matching the given query for the given key:
|
19
|
+
def self.find_objects(klass, key_name, value)
|
20
|
+
object_type = klass.to_s
|
21
|
+
key_type = klass.key_options(key_name)[:type]
|
22
|
+
|
23
|
+
condition_set = {obj_type: object_type, key_name: key_name, key_type: key_type, "value_#{key_type}"=>value}
|
24
|
+
DoesKeyValue.log("Condition Set for index find: #{condition_set.inspect}")
|
25
|
+
table_agnostic_exec(klass) do
|
26
|
+
index_rows = DoesKeyValue::Index.where(condition_set)
|
27
|
+
object_ids = index_rows.blank? ? [] : index_rows.collect {|i| i.obj_id }
|
28
|
+
klass.where(:id=>object_ids)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Read the appropriate index values for the given object/key combination:
|
34
|
+
def self.read_index(object, key_name)
|
35
|
+
object_type = object.class.to_s
|
36
|
+
object_id = object.id
|
37
|
+
key_type = object.class.key_options(key_name)[:type]
|
38
|
+
|
39
|
+
# Prepare the query conditions:
|
40
|
+
condition_set = {obj_type: object_type, obj_id: object_id, key_name: key_name}
|
41
|
+
|
42
|
+
# Access the appropriate value column of the returned index:
|
43
|
+
table_agnostic_exec(object.class) do
|
44
|
+
DoesKeyValue::Index.where(condition_set).first().try(:send, "value_#{key_type}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Update the appropriate index with new/changed information for the given
|
50
|
+
# object/key/value combination:
|
51
|
+
def self.update_index(object, key_name, value)
|
52
|
+
# Log our index column values:
|
53
|
+
object_type = object.class.to_s
|
54
|
+
object_id = object.id
|
55
|
+
key_type = object.class.key_options(key_name)[:type]
|
56
|
+
|
57
|
+
# Prepare update conditions and manipulators:
|
58
|
+
update_set = {key_type: key_type}
|
59
|
+
condition_set = {obj_type: object_type, obj_id: object_id, key_name: key_name}
|
60
|
+
create_set = {obj_type: object_type, obj_id: object_id, key_name: key_name, key_type: key_type}
|
61
|
+
|
62
|
+
# Insert the new value of the correct type:
|
63
|
+
update_set["value_#{key_type}"] = value
|
64
|
+
create_set["value_#{key_type}"] = value
|
65
|
+
|
66
|
+
DoesKeyValue.log("Updating Index for class:#{object_type} key:#{key_name}:")
|
67
|
+
DoesKeyValue.log("update_set: #{update_set.inspect}")
|
68
|
+
DoesKeyValue.log("condition_set: #{condition_set.inspect}")
|
69
|
+
DoesKeyValue.log("create_set: #{create_set.inspect}")
|
70
|
+
|
71
|
+
# Update an index in a table-agnostic way to support table-based key-value storage in
|
72
|
+
# database tables other than the universal table:
|
73
|
+
table_agnostic_exec(object.class) do
|
74
|
+
updated_count = DoesKeyValue::Index.update_all( update_set, condition_set )
|
75
|
+
if !value.nil? && updated_count == 0
|
76
|
+
DoesKeyValue::Index.create( create_set )
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
# Delete an index record for a given object/key combination:
|
84
|
+
def self.delete_index(object, key_name)
|
85
|
+
object_type = object.class.to_s
|
86
|
+
object_id = object.id
|
87
|
+
|
88
|
+
deleted_count = table_agnostic_exec(object.class) do
|
89
|
+
DoesKeyValue::Index.delete_all(
|
90
|
+
obj_type: object_type, obj_id: object_id, key_name: key_name
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# Return true only if the storage method for the given class is table:
|
100
|
+
def self.table_storage_for_class?(klass)
|
101
|
+
storage_options = DoesKeyValue::State.instance.options_for_class(klass)
|
102
|
+
storage_options[:table].nil? ? false : true
|
103
|
+
end
|
104
|
+
|
105
|
+
# Perform a block action inside of table-altered query:
|
106
|
+
def self.table_agnostic_exec(klass)
|
107
|
+
begin
|
108
|
+
original_table_name = DoesKeyValue::Index.table_name
|
109
|
+
if DoesKeyValue::Index.table_storage_for_class?(klass)
|
110
|
+
class_storage_options = DoesKeyValue::State.instance.options_for_class(klass)
|
111
|
+
if class_storage_options[:table]
|
112
|
+
DoesKeyValue::Index.table_name = class_storage_options[:table]
|
113
|
+
DoesKeyValue.log("Storage table for index changed to '#{DoesKeyValue::Index.table_name}'")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
exec_result = yield
|
118
|
+
|
119
|
+
if DoesKeyValue::Index.table_storage_for_class?(klass)
|
120
|
+
DoesKeyValue::Index.table_name = original_table_name
|
121
|
+
DoesKeyValue.log("Storage table for index changed back to original '#{DoesKeyValue::Index.table_name}")
|
122
|
+
end
|
123
|
+
|
124
|
+
return exec_result
|
125
|
+
|
126
|
+
rescue ActiveRecord::StatementInvalid => e
|
127
|
+
DoesKeyValue.log("Database query statement invalid: #{e.message}")
|
128
|
+
if (e.message =~ /doesn't exist/)
|
129
|
+
DoesKeyValue.log("It appears the index table `#{DoesKeyValue::Index.table_name}` expected has not been generated.")
|
130
|
+
DoesKeyValue.log("To generate the necessary table run: rails generate doeskeyvalue #{DoesKeyValue::Index.table_name}")
|
131
|
+
end
|
132
|
+
raise e
|
133
|
+
|
134
|
+
ensure
|
135
|
+
# Ensure that the table name is always restored
|
136
|
+
DoesKeyValue::Index.table_name = original_table_name
|
137
|
+
DoesKeyValue.log("After table-agnostic execution, table name restored to '#{DoesKeyValue::Index.table_name}'")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
end # Index
|
144
|
+
|
145
|
+
end # DoesKeyValue
|