activerecord-typedstore 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/Rakefile +1 -2
- data/lib/active_record/typed_store/ar_32_fallbacks.rb +22 -47
- data/lib/active_record/typed_store/ar_41_fallbacks.rb +3 -22
- data/lib/active_record/typed_store/column.rb +5 -0
- data/lib/active_record/typed_store/extension.rb +81 -19
- data/lib/active_record/typed_store/version.rb +1 -1
- data/lib/activerecord-typedstore.rb +1 -0
- data/spec/active_record/typed_store_spec.rb +50 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/support/models.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c3d4b1b4f103d05dc3371d1e8ebeb7f666ac227
|
4
|
+
data.tar.gz: 33f47581329169894a7c7c9f3b79cec592b62eb4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e35186b4dfbc9b54984d4e5a350035ecc8c64136f827ab3762586aeb14477699d2035fcce317d113402c393c068103acdddd23415ec6d69f879040d1831c111
|
7
|
+
data.tar.gz: b32badf3a634ba1aa2b572129622e9a4a2650bfefef3fbf470ee37f891c0080225d1fdb054aa0089b299dd6d0e051c26b62cb02e061e415e5b9fb3877b6ac228
|
data/Rakefile
CHANGED
@@ -10,9 +10,8 @@ module ActiveRecord::TypedStore
|
|
10
10
|
|
11
11
|
module ClassMethods
|
12
12
|
|
13
|
-
def typed_store(store_attribute,
|
14
|
-
dsl
|
15
|
-
_ar_32_fallback_accessors(store_attribute, dsl.columns)
|
13
|
+
def typed_store(store_attribute, dsl)
|
14
|
+
_ar_32_fallback_accessors(store_attribute, dsl.column_names)
|
16
15
|
end
|
17
16
|
|
18
17
|
protected
|
@@ -35,65 +34,41 @@ module ActiveRecord::TypedStore
|
|
35
34
|
attribute_method_matchers_cache.clear
|
36
35
|
end
|
37
36
|
|
38
|
-
def _ar_32_fallback_accessors(store_attribute,
|
39
|
-
|
40
|
-
|
41
|
-
_ar_32_fallback_accessor(store_attribute, column)
|
37
|
+
def _ar_32_fallback_accessors(store_attribute, column_names)
|
38
|
+
column_names.each do |name|
|
39
|
+
_ar_32_fallback_accessor(store_attribute, name)
|
42
40
|
end
|
43
41
|
end
|
44
42
|
|
45
|
-
def
|
46
|
-
define_method(
|
47
|
-
|
48
|
-
unless instance_variable_get(initialized)
|
49
|
-
store = read_attribute(store_attribute)
|
50
|
-
store = initialize_store(store, columns)
|
51
|
-
write_attribute(store_attribute, store)
|
52
|
-
instance_variable_set(initialized, true)
|
53
|
-
end
|
54
|
-
read_attribute(store_attribute)
|
43
|
+
def _ar_32_fallback_accessor(store_attribute, key)
|
44
|
+
define_method("#{key}=") do |value|
|
45
|
+
write_store_attribute(store_attribute, key, value)
|
55
46
|
end
|
56
|
-
|
57
|
-
|
58
|
-
def _ar_32_fallback_accessor(store_attribute, column)
|
59
|
-
_ar_32_fallback_writer(store_attribute, column)
|
60
|
-
_ar_32_fallback_reader(store_attribute, column)
|
61
|
-
end
|
62
|
-
|
63
|
-
def _ar_32_fallback_writer(store_attribute, column)
|
64
|
-
define_method("#{column.name}_with_type_casting=") do |value|
|
65
|
-
casted_value = column.cast(value)
|
66
|
-
attribute_will_change!(column.name.to_s) if casted_value != send(column.name)
|
67
|
-
send("#{column.name}_without_type_casting=", casted_value)
|
47
|
+
define_method(key) do
|
48
|
+
read_store_attribute(store_attribute, key)
|
68
49
|
end
|
69
|
-
alias_method_chain "#{column.name}=", :type_casting
|
70
50
|
end
|
71
51
|
|
72
|
-
|
73
|
-
define_method(column.name) do
|
74
|
-
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
|
75
|
-
store = send(store_attribute)
|
52
|
+
end
|
76
53
|
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
54
|
+
private
|
80
55
|
|
56
|
+
def initialize_store_attribute(store_attribute)
|
57
|
+
send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
|
58
|
+
send(store_attribute)
|
81
59
|
end
|
82
60
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
61
|
+
def read_store_attribute(store_attribute, key)
|
62
|
+
store = initialize_store_attribute(store_attribute)
|
63
|
+
store[key]
|
86
64
|
end
|
87
65
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
self.class.stored_typed_attributes.keys.each do |store_attribute|
|
92
|
-
instance_variable_set("@_#{store_attribute}_initialized", false)
|
93
|
-
end
|
66
|
+
def write_store_attribute(store_attribute, key, value)
|
67
|
+
attribute_will_change!(store_attribute.to_s)
|
68
|
+
send(store_attribute)[key] = value
|
94
69
|
end
|
95
70
|
|
96
71
|
end
|
97
72
|
|
98
|
-
|
73
|
+
Extension.send(:include, AR32Fallbacks)
|
99
74
|
end
|
@@ -2,19 +2,8 @@ module ActiveRecord::TypedStore
|
|
2
2
|
|
3
3
|
module AR41Fallbacks
|
4
4
|
|
5
|
-
def reload(*)
|
6
|
-
_ar_41_reload_stores!
|
7
|
-
super
|
8
|
-
end
|
9
|
-
|
10
5
|
private
|
11
6
|
|
12
|
-
def _ar_41_reload_stores!
|
13
|
-
self.class.stored_typed_attributes.keys.each do |store_attribute|
|
14
|
-
instance_variable_set("@_#{store_attribute}_initialized", false)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
7
|
module HashAccessorPatch
|
19
8
|
|
20
9
|
def self.extended(hash_accessor)
|
@@ -23,16 +12,9 @@ module ActiveRecord::TypedStore
|
|
23
12
|
|
24
13
|
protected
|
25
14
|
|
26
|
-
def prepare_with_initialization(object,
|
27
|
-
prepare_without_initialization(object,
|
28
|
-
|
29
|
-
initialized = "@_#{attribute}_initialized"
|
30
|
-
unless object.instance_variable_get(initialized)
|
31
|
-
store = object.send(:initialize_store_attribute, attribute)
|
32
|
-
object.send("#{attribute}=", store)
|
33
|
-
object.instance_variable_set(initialized, true)
|
34
|
-
end
|
35
|
-
|
15
|
+
def prepare_with_initialization(object, store_attribute)
|
16
|
+
prepare_without_initialization(object, store_attribute)
|
17
|
+
object.send(:initialize_store_attribute, store_attribute)
|
36
18
|
end
|
37
19
|
|
38
20
|
end
|
@@ -41,5 +23,4 @@ module ActiveRecord::TypedStore
|
|
41
23
|
ActiveRecord::Store::IndifferentHashAccessor.extend(HashAccessorPatch)
|
42
24
|
end
|
43
25
|
|
44
|
-
ActiveRecord::Base.send(:include, AR41Fallbacks)
|
45
26
|
end
|
@@ -22,6 +22,11 @@ module ActiveRecord::TypedStore
|
|
22
22
|
casted_value
|
23
23
|
end
|
24
24
|
|
25
|
+
def extract_default(value)
|
26
|
+
return value if (type == :string || type == :text) && value.nil?
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
25
30
|
def type_cast(value, map=true)
|
26
31
|
if array && (map || value.is_a?(Array))
|
27
32
|
return [] if map && !value.is_a?(Array)
|
@@ -4,21 +4,14 @@ require 'active_record/typed_store/dsl'
|
|
4
4
|
module ActiveRecord::TypedStore
|
5
5
|
AR_VERSION = Gem::Version.new(ActiveRecord::VERSION::STRING)
|
6
6
|
IS_AR_3_2 = AR_VERSION < Gem::Version.new('4.0')
|
7
|
-
IS_AR_4_0 = AR_VERSION >= Gem::Version.new('4.0') && AR_VERSION < Gem::Version.new('4.1.0.beta')
|
8
7
|
IS_AR_4_1 = AR_VERSION >= Gem::Version.new('4.1.0.beta')
|
9
8
|
|
10
|
-
unless IS_AR_3_2
|
11
|
-
ActiveModel::AttributeMethods::ClassMethods.send(:alias_method, :define_virtual_attribute_method, :define_attribute_method)
|
12
|
-
end
|
13
|
-
|
14
9
|
module Extension
|
15
10
|
extend ActiveSupport::Concern
|
16
11
|
|
17
12
|
included do
|
18
13
|
class_attribute :stored_typed_attributes, instance_accessor: false
|
19
14
|
self.stored_typed_attributes = {}
|
20
|
-
require 'active_record/typed_store/ar_32_fallbacks' if IS_AR_3_2
|
21
|
-
require 'active_record/typed_store/ar_41_fallbacks' if IS_AR_4_1
|
22
15
|
end
|
23
16
|
|
24
17
|
module ClassMethods
|
@@ -32,34 +25,72 @@ module ActiveRecord::TypedStore
|
|
32
25
|
stored_typed_attributes[store_attribute].merge!(dsl.columns.index_by(&:name))
|
33
26
|
|
34
27
|
dsl.column_names.each { |c| define_virtual_attribute_method(c.to_s) }
|
28
|
+
dsl.column_names.each { |c| define_store_attribute_queries(store_attribute, c) }
|
29
|
+
|
30
|
+
super(store_attribute, dsl) if defined?(super)
|
35
31
|
|
36
32
|
dsl
|
37
33
|
end
|
38
34
|
|
35
|
+
private
|
36
|
+
|
37
|
+
def define_store_attribute_queries(store_attribute, column_name)
|
38
|
+
define_method("#{column_name}?") do
|
39
|
+
query_store_attribute(store_attribute, column_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def reload(*)
|
46
|
+
reload_stores!
|
47
|
+
super
|
39
48
|
end
|
40
49
|
|
41
50
|
protected
|
42
51
|
|
43
52
|
def write_store_attribute(store_attribute, key, value)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
casted_value = column_definition.cast(value)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
attribute_will_change!(key.to_s) if casted_value != read_store_attribute(store_attribute, key)
|
53
|
+
previous_value = read_store_attribute(store_attribute, key)
|
54
|
+
casted_value = cast_store_attribute(store_attribute, key, value)
|
55
|
+
attribute_will_change!(key.to_s) if casted_value != previous_value
|
52
56
|
super(store_attribute, key, casted_value)
|
53
57
|
end
|
54
58
|
|
55
59
|
private
|
56
60
|
|
61
|
+
def cast_store_attribute(store_attribute, key, value)
|
62
|
+
column = store_column_definition(store_attribute, key)
|
63
|
+
column ? column.cast(value) : value
|
64
|
+
end
|
65
|
+
|
66
|
+
def store_column_definition(store_attribute, key)
|
67
|
+
store_definition = self.class.stored_typed_attributes[store_attribute]
|
68
|
+
store_definition && store_definition[key]
|
69
|
+
end
|
70
|
+
|
71
|
+
def if_store_uninitialized(store_attribute)
|
72
|
+
initialized = "@_#{store_attribute}_initialized"
|
73
|
+
unless instance_variable_get(initialized)
|
74
|
+
yield
|
75
|
+
instance_variable_set(initialized, true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def reload_stores!
|
80
|
+
self.class.stored_typed_attributes.keys.each do |store_attribute|
|
81
|
+
instance_variable_set("@_#{store_attribute}_initialized", false)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
57
85
|
def initialize_store_attribute(store_attribute)
|
58
|
-
store =
|
59
|
-
|
60
|
-
|
86
|
+
store = defined?(super) ? super : send(store_attribute)
|
87
|
+
store.tap do |store|
|
88
|
+
if_store_uninitialized(store_attribute) do
|
89
|
+
if columns = self.class.stored_typed_attributes[store_attribute]
|
90
|
+
initialize_store(store, columns.values)
|
91
|
+
end
|
92
|
+
end
|
61
93
|
end
|
62
|
-
store
|
63
94
|
end
|
64
95
|
|
65
96
|
def initialize_store(store, columns)
|
@@ -73,5 +104,36 @@ module ActiveRecord::TypedStore
|
|
73
104
|
store
|
74
105
|
end
|
75
106
|
|
107
|
+
# heavilly inspired from ActiveRecord::Base#query_attribute
|
108
|
+
def query_store_attribute(store_attribute, key)
|
109
|
+
value = read_store_attribute(store_attribute, key)
|
110
|
+
|
111
|
+
case value
|
112
|
+
when true then true
|
113
|
+
when false, nil then false
|
114
|
+
else
|
115
|
+
column = store_column_definition(store_attribute, key)
|
116
|
+
if column.nil?
|
117
|
+
if Numeric === value || value !~ /[^0-9]/
|
118
|
+
!value.to_i.zero?
|
119
|
+
else
|
120
|
+
return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
|
121
|
+
!value.blank?
|
122
|
+
end
|
123
|
+
elsif column.number?
|
124
|
+
!value.zero?
|
125
|
+
else
|
126
|
+
!value.blank?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
76
131
|
end
|
132
|
+
|
133
|
+
require 'active_record/typed_store/ar_32_fallbacks' if IS_AR_3_2
|
134
|
+
require 'active_record/typed_store/ar_41_fallbacks' if IS_AR_4_1
|
135
|
+
unless IS_AR_3_2
|
136
|
+
ActiveModel::AttributeMethods::ClassMethods.send(:alias_method, :define_virtual_attribute_method, :define_attribute_method)
|
137
|
+
end
|
138
|
+
|
77
139
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_record/typed_store'
|
@@ -26,6 +26,19 @@ shared_examples 'any model' do
|
|
26
26
|
|
27
27
|
end
|
28
28
|
|
29
|
+
describe 'attribute presence' do
|
30
|
+
|
31
|
+
it 'return true if present' do
|
32
|
+
expect(model.age?).to be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'return false if blank' do
|
36
|
+
model.age = 0
|
37
|
+
expect(model.age?).to be_false
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
29
42
|
describe 'dirty tracking' do
|
30
43
|
|
31
44
|
it 'track changed attributes' do
|
@@ -91,6 +104,10 @@ shared_examples 'any model' do
|
|
91
104
|
expect(model.name).to be == ''
|
92
105
|
end
|
93
106
|
|
107
|
+
it 'default to nil if specified explicitly' do
|
108
|
+
expect(model.cell_phone).to be_nil
|
109
|
+
end
|
110
|
+
|
94
111
|
it 'properly cast the value as string' do
|
95
112
|
model.update_attributes(name: 42)
|
96
113
|
expect(model.reload.name).to be == '42'
|
@@ -325,6 +342,39 @@ shared_examples 'a store' do
|
|
325
342
|
|
326
343
|
let(:model) { described_class.new }
|
327
344
|
|
345
|
+
describe 'initialization' do
|
346
|
+
|
347
|
+
it 'is done only once' do
|
348
|
+
model.should_receive(:initialize_store).once
|
349
|
+
3.times do
|
350
|
+
model.age = (rand * 100).to_i
|
351
|
+
model.age
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'is done again after a reload' do
|
356
|
+
model.save
|
357
|
+
|
358
|
+
model.should_receive(:initialize_store).twice
|
359
|
+
3.times do
|
360
|
+
model.age = (rand * 100).to_i
|
361
|
+
model.age
|
362
|
+
end
|
363
|
+
model.reload
|
364
|
+
3.times do
|
365
|
+
model.age = (rand * 100).to_i
|
366
|
+
model.age
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'is not performe if no store attributes are accessed' do
|
371
|
+
model.should_not_receive(:initialize_store)
|
372
|
+
model.update_attributes(untyped_settings: {foo: :bar})
|
373
|
+
model.update_attributes(untyped_settings: {})
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
|
328
378
|
describe 'attributes' do
|
329
379
|
|
330
380
|
it 'retrieve default if assigned nil and null not allowed' do
|
@@ -438,7 +488,6 @@ shared_examples 'a model supporting arrays' do |regular=false|
|
|
438
488
|
end
|
439
489
|
|
440
490
|
describe Sqlite3RegularARModel do
|
441
|
-
ActiveRecord::Base.establish_connection('test_sqlite3')
|
442
491
|
it_should_behave_like 'any model'
|
443
492
|
it_should_behave_like 'a db backed model'
|
444
493
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,7 +11,7 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
|
11
11
|
]
|
12
12
|
SimpleCov.start
|
13
13
|
|
14
|
-
require '
|
14
|
+
require 'activerecord-typedstore'
|
15
15
|
|
16
16
|
Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each { |f| require f }
|
17
17
|
|
data/spec/support/models.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-typedstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-11-
|
11
|
+
date: 2013-11-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- lib/active_record/typed_store/dsl.rb
|
162
162
|
- lib/active_record/typed_store/extension.rb
|
163
163
|
- lib/active_record/typed_store/version.rb
|
164
|
+
- lib/activerecord-typedstore.rb
|
164
165
|
- spec/active_record/typed_store_spec.rb
|
165
166
|
- spec/spec_helper.rb
|
166
167
|
- spec/support/database_cleaner.rb
|