activerecord-typedstore 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|