doeskeyvalue 0.2.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|