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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 542197f677322bd49dc623661de6f72adae6e259
4
- data.tar.gz: 8612b393a7cbc50203a8848c9928dfade4776ef2
3
+ metadata.gz: 4c3d4b1b4f103d05dc3371d1e8ebeb7f666ac227
4
+ data.tar.gz: 33f47581329169894a7c7c9f3b79cec592b62eb4
5
5
  SHA512:
6
- metadata.gz: a06189e792adec4ac11a2bc6020439135051a1d7194f21d43008d9077fcef511ddcf9f8d252f5f25478aa9220425b38bc03655462a6138b04e9e941bcfdcc566
7
- data.tar.gz: dfc9469b4dd7c9e1dbb611db6db1e150ac643d6f52f890f58665308f384457cf5a0b2f6a7e885af51ff7db48f16d996971a3b12229bf8e226bd53a9c188c0bcb
6
+ metadata.gz: 6e35186b4dfbc9b54984d4e5a350035ecc8c64136f827ab3762586aeb14477699d2035fcce317d113402c393c068103acdddd23415ec6d69f879040d1831c111
7
+ data.tar.gz: b32badf3a634ba1aa2b572129622e9a4a2650bfefef3fbf470ee37f891c0080225d1fdb054aa0089b299dd6d0e051c26b62cb02e061e415e5b9fb3877b6ac228
data/Rakefile CHANGED
@@ -1,7 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
- Bundler::GemHelper.install_tasks
5
4
  RSpec::Core::RakeTask.new(:spec)
6
5
 
7
6
  task :default => :spec
@@ -10,9 +10,8 @@ module ActiveRecord::TypedStore
10
10
 
11
11
  module ClassMethods
12
12
 
13
- def typed_store(store_attribute, options={}, &block)
14
- dsl = super
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, columns)
39
- _ar_32_fallback_initializer(store_attribute, columns)
40
- columns.each do |column|
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 _ar_32_fallback_initializer(store_attribute, columns)
46
- define_method(store_attribute) do
47
- initialized = "@_#{store_attribute}_initialized"
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
- end
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
- def _ar_32_fallback_reader(store_attribute, column)
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
- store.has_key?(column.name) ? store[column.name] : column.default
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 reload(*)
84
- _ar_32_reload_stores!
85
- super
61
+ def read_store_attribute(store_attribute, key)
62
+ store = initialize_store_attribute(store_attribute)
63
+ store[key]
86
64
  end
87
65
 
88
- private
89
-
90
- def _ar_32_reload_stores!
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
- ActiveRecord::Base.send(:include, AR32Fallbacks)
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, attribute)
27
- prepare_without_initialization(object, attribute)
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
- casted_value = value
45
- if store_definition = self.class.stored_typed_attributes[store_attribute]
46
- if column_definition = store_definition[key]
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 = IS_AR_4_0 ? super : send(store_attribute)
59
- if columns = self.class.stored_typed_attributes[store_attribute]
60
- store = initialize_store(store, columns.values)
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
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module TypedStore
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  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 'active_record/typed_store'
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
 
@@ -13,6 +13,7 @@ def define_columns(t)
13
13
 
14
14
  t.string :name, default: '', null: false
15
15
  t.string :email
16
+ t.string :cell_phone, default: nil
16
17
 
17
18
  t.boolean :public, default: false, null: false
18
19
  t.boolean :enabled, default: true
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.2.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-08 00:00:00.000000000 Z
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