activerecord-typedstore 1.2.0 → 1.4.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
- SHA1:
3
- metadata.gz: aa72e9f075af5501961f90eac06098f00a6c2f6a
4
- data.tar.gz: a32a4b9b052cd8174756e9adf3062e5fd002da7b
2
+ SHA256:
3
+ metadata.gz: 17f01f8701f6af429d7fb15bfbcf9cf202f1d82cbe0f6878aa4a503ed69fc0e8
4
+ data.tar.gz: e5e1dd4100a034b074fe2682e485ae1365c8bcbe9c4bb7bc96f697a5042c0f62
5
5
  SHA512:
6
- metadata.gz: 131bed9f1753d59907ad67582491a8b3dec85b597b15a3b458eecdf1ee33292bb75be81657e8778b2b49d3b7462aeff5e3671d411f6ea9def139cf1444738ee6
7
- data.tar.gz: d3ec0ae6531e13aa6a7217ee202f8d1342425a259217fe7d3b5e7c190987786becd6fcfea9e087a93442476918e5fe93010a6d9fc5aa7f3fd7c988529c6fb543
6
+ metadata.gz: f1703f9023ab91ac749d74335276a0824d3072060eac8a68270f2b52e41198dfeebecffd54738cd4b097fe97948371479e2db1c1fa8ebc30ca07eeeaf63bac91
7
+ data.tar.gz: 8e48b1f137a374053092ccf52f3411759fcf421d1e4d8cf8b7f47b3c25d935aaf958f568ee35423381171aca879bf68a7b943493cf4947a8d3af19f66d54616f
@@ -0,0 +1,47 @@
1
+ name: Ruby
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ name: Ruby ${{ matrix.ruby }}
9
+ strategy:
10
+ matrix:
11
+ ruby: [2.6, 2.7]
12
+ gemfile: [Gemfile.ar-5.2, Gemfile.ar-6.0, Gemfile.ar-6.1, Gemfile.ar-master]
13
+ exclude:
14
+ - ruby: 2.6
15
+ gemfile: Gemfile.ar-6.0
16
+ - ruby: 2.6
17
+ gemfile: Gemfile.ar-6.1
18
+ - ruby: 2.6
19
+ gemfile: Gemfile.ar-master
20
+ timezone_aware: [0, 1]
21
+ env:
22
+ BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}
23
+ TIMEZONE_AWARE: ${{ matrix.timezone_aware }}
24
+ POSTGRES: 1
25
+ MYSQL: 1
26
+ POSTGRES_JSON: 1
27
+ steps:
28
+ - name: Check out code
29
+ uses: actions/checkout@v2
30
+ - name: Set up Ruby ${{ matrix.ruby }}
31
+ uses: ruby/setup-ruby@v1
32
+ with:
33
+ ruby-version: ${{ matrix.ruby }}
34
+ bundler-cache: true
35
+ - name: Start MySQL and create DB
36
+ run: |
37
+ sudo systemctl start mysql.service
38
+ mysql -uroot -proot -e 'create database typed_store_test;'
39
+ - name: Start PostgresQL and create DB
40
+ run: |
41
+ sudo sed -i s/md5/trust/g /etc/postgresql/*/main/pg_hba.conf
42
+ sudo systemctl start postgresql.service
43
+ sudo -u postgres createuser --createdb --superuser ${USER}
44
+ createdb typed_store_test
45
+ echo "POSTGRES_URL=postgres://localhost/typed_store_test" >> $GITHUB_ENV
46
+ - name: Ruby Tests
47
+ run: bundle exec rake
data/README.md CHANGED
@@ -94,10 +94,10 @@ end
94
94
 
95
95
  ```
96
96
 
97
- Type casting rules and attribute behavior are exactly the same as a for real database columns.
98
- Actually the only difference is that you wont be able to query on these attributes (unless you use JSON or Postgres HStore types) and that you don't need to do a migration to add / remove an attribute.
97
+ Type casting rules and attribute behavior are exactly the same as for real database columns.
98
+ Actually the only difference is that you won't be able to query on these attributes (unless you use JSON or Postgres HStore types) and that you don't need to do a migration to add / remove an attribute.
99
99
 
100
- If not, please fill an issue.
100
+ If not, then please fill in an issue.
101
101
 
102
102
  ## Serialization methods
103
103
 
@@ -134,7 +134,7 @@ Since HStore can only store strings:
134
134
  - `any` attributes will be converted to string
135
135
 
136
136
  If you use HStore because you need to be able to query the store from SQL, and any of these limitations are an issue for you,
137
- than you could probably use the JSON column type, which do not suffer from these limitations and is also queriable.
137
+ then you could probably use the JSON column type, which do not suffer from these limitations and is also queriable.
138
138
 
139
139
  ## Contributing
140
140
 
@@ -18,14 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'activerecord', '>= 4.2', '< 5.3'
21
+ spec.add_dependency 'activerecord', '>= 5.2'
22
22
 
23
- spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'bundler'
24
24
  spec.add_development_dependency 'rake', '~> 10'
25
25
  spec.add_development_dependency 'rspec', '~> 3'
26
26
  spec.add_development_dependency 'coveralls', '~> 0'
27
27
  spec.add_development_dependency 'sqlite3', '~> 1'
28
- spec.add_development_dependency 'pg', '~> 0.18'
28
+ spec.add_development_dependency 'pg', ENV.fetch('PG_VERSION', '~> 0.18')
29
29
  spec.add_development_dependency 'mysql2', '> 0.3'
30
30
  spec.add_development_dependency 'database_cleaner', '~> 1'
31
31
  end
@@ -1,11 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gemspec path: '..'
4
+
5
+ gem 'sqlite3', '~> 1.3.0'
3
6
  gem 'activerecord', '~> 5.0.0'
4
- gem 'bundler', '~> 1.3'
5
- gem 'rake'
6
- gem 'rspec'
7
- gem 'sqlite3'
8
- gem 'pg', '~> 0.11'
9
- gem 'mysql2', ['>= 0.3.13', '< 0.5']
10
- gem 'database_cleaner'
11
- gem 'coveralls', require: false
@@ -1,11 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'activerecord', ['>= 5.2.0.beta1', '< 5.3']
4
- gem 'bundler', '~> 1.3'
5
- gem 'rake'
6
- gem 'rspec'
7
- gem 'sqlite3'
8
- gem 'pg', '~> 0.11'
9
- gem 'mysql2', ['>= 0.3.13', '< 0.5']
10
- gem 'database_cleaner'
11
- gem 'coveralls', require: false
3
+ gemspec path: '..'
4
+
5
+ gem 'activerecord', '~> 5.2.0'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem 'activerecord', '~> 6.0.0'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ENV['PG_VERSION'] = '~> 1.1'
4
+ gemspec path: '..'
5
+
6
+ gem 'activerecord', '~> 6.1.0'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ENV['PG_VERSION'] = '~> 1.1'
4
+ gemspec path: '..'
5
+
6
+ gem 'activerecord', github: 'rails/rails'
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::TypedStore
4
+ module Behavior
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def define_attribute_methods
9
+ super
10
+ define_typed_store_attribute_methods
11
+ end
12
+
13
+ def undefine_attribute_methods # :nodoc:
14
+ super if @typed_store_attribute_methods_generated
15
+ @typed_store_attribute_methods_generated = false
16
+ end
17
+
18
+ def define_typed_store_attribute_methods
19
+ return if @typed_store_attribute_methods_generated
20
+ store_accessors.each do |attribute|
21
+ define_attribute_method(attribute)
22
+ undefine_before_type_cast_method(attribute)
23
+ end
24
+ @typed_store_attribute_methods_generated = true
25
+ end
26
+
27
+ def undefine_before_type_cast_method(attribute)
28
+ # because it mess with ActionView forms, see #14.
29
+ method = "#{attribute}_before_type_cast"
30
+ undef_method(method) if method_defined?(method)
31
+ end
32
+ end
33
+
34
+ def changes
35
+ changes = super
36
+ self.class.store_accessors.each do |attr|
37
+ if send("#{attr}_changed?")
38
+ changes[attr] = [send("#{attr}_was"), send(attr)]
39
+ end
40
+ end
41
+ changes
42
+ end
43
+
44
+ def clear_attribute_change(attr_name)
45
+ return if self.class.store_accessors.include?(attr_name.to_s)
46
+ super
47
+ end
48
+
49
+ def read_attribute(attr_name)
50
+ if self.class.store_accessors.include?(attr_name.to_s)
51
+ return public_send(attr_name)
52
+ end
53
+ super
54
+ end
55
+
56
+ def attribute?(attr_name)
57
+ if self.class.store_accessors.include?(attr_name.to_s)
58
+ value = public_send(attr_name)
59
+
60
+ case value
61
+ when true then true
62
+ when false, nil then false
63
+ else
64
+ if value.respond_to?(:zero?)
65
+ !value.zero?
66
+ else
67
+ !value.blank?
68
+ end
69
+ end
70
+ else
71
+ super
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/typed_store/field'
2
4
 
3
5
  module ActiveRecord::TypedStore
@@ -1,122 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/typed_store/dsl'
4
+ require 'active_record/typed_store/behavior'
2
5
  require 'active_record/typed_store/type'
3
6
  require 'active_record/typed_store/typed_hash'
7
+ require 'active_record/typed_store/identity_coder'
4
8
 
5
9
  module ActiveRecord::TypedStore
6
10
  module Extension
7
- extend ActiveSupport::Concern
8
-
9
- included do
10
- class_attribute :typed_stores
11
- self.typed_stores = {}
12
- end
13
-
14
- module ClassMethods
15
- def store_accessors
16
- typed_stores.each_value.flat_map(&:accessors)
11
+ def typed_store(store_attribute, options={}, &block)
12
+ unless self < Behavior
13
+ include Behavior
14
+ class_attribute :typed_stores, :store_accessors, instance_accessor: false
17
15
  end
18
16
 
19
- def typed_store(store_attribute, options={}, &block)
20
- dsl = DSL.new(store_attribute, options, &block)
21
- self.typed_stores = self.typed_stores.merge(store_attribute => dsl)
17
+ dsl = DSL.new(store_attribute, options, &block)
18
+ self.typed_stores = (self.typed_stores || {}).merge(store_attribute => dsl)
19
+ self.store_accessors = typed_stores.each_value.flat_map(&:accessors).map { |a| -a.to_s }.to_set
22
20
 
23
- typed_klass = TypedHash.create(dsl.fields.values)
24
- const_set("#{store_attribute}_hash".camelize, typed_klass)
21
+ typed_klass = TypedHash.create(dsl.fields.values)
22
+ const_set("#{store_attribute}_hash".camelize, typed_klass)
25
23
 
26
- decorate_attribute_type(store_attribute, :typed_store) do |subtype|
24
+ if ActiveRecord.version >= Gem::Version.new('6.1.0.alpha')
25
+ attribute(store_attribute) do |subtype|
26
+ subtype = subtype.subtype if subtype.is_a?(Type)
27
27
  Type.new(typed_klass, dsl.coder, subtype)
28
28
  end
29
- store_accessor(store_attribute, dsl.accessors)
30
-
31
- dsl.accessors.each do |accessor_name|
32
- define_method("#{accessor_name}_changed?") do
33
- send("#{store_attribute}_changed?") &&
34
- send(store_attribute)[accessor_name] != send("#{store_attribute}_was")[accessor_name]
35
- end
36
-
37
- define_method("#{accessor_name}_was") do
38
- send("#{store_attribute}_was")[accessor_name]
39
- end
40
-
41
- define_method("restore_#{accessor_name}!") do
42
- send("#{accessor_name}=", send("#{accessor_name}_was"))
43
- end
29
+ else
30
+ decorate_attribute_type(store_attribute, :typed_store) do |subtype|
31
+ Type.new(typed_klass, dsl.coder, subtype)
44
32
  end
45
33
  end
34
+ store_accessor(store_attribute, dsl.accessors)
46
35
 
47
- def define_attribute_methods
48
- super
49
- define_typed_store_attribute_methods
50
- end
51
-
52
- def undefine_attribute_methods # :nodoc:
53
- super if @typed_store_attribute_methods_generated
54
- @typed_store_attribute_methods_generated = false
55
- end
56
-
57
- def define_typed_store_attribute_methods
58
- return if @typed_store_attribute_methods_generated
59
- store_accessors.each do |attribute|
60
- define_attribute_method(attribute.to_s)
61
- undefine_before_type_cast_method(attribute)
36
+ dsl.accessors.each do |accessor_name|
37
+ define_method("#{accessor_name}_changed?") do
38
+ send("#{store_attribute}_changed?") &&
39
+ send(store_attribute)[accessor_name] != send("#{store_attribute}_was")[accessor_name]
62
40
  end
63
- @typed_store_attribute_methods_generated = true
64
- end
65
41
 
66
- def undefine_before_type_cast_method(attribute)
67
- # because it mess with ActionView forms, see #14.
68
- method = "#{attribute}_before_type_cast"
69
- undef_method(method) if method_defined?(method)
70
- end
71
- end
72
-
73
- def changes
74
- changes = super
75
- self.class.store_accessors.each do |attr|
76
- if send("#{attr}_changed?")
77
- changes[attr] = [send("#{attr}_was"), send(attr)]
42
+ define_method("#{accessor_name}_was") do
43
+ send("#{store_attribute}_was")[accessor_name]
78
44
  end
79
- end
80
- changes
81
- end
82
-
83
- def clear_attribute_change(attr_name)
84
- return if self.class.store_accessors.include?(normalize_attribute(attr_name))
85
- super
86
- end
87
-
88
- def read_attribute(attr_name)
89
- if self.class.store_accessors.include?(normalize_attribute(attr_name))
90
- return public_send(attr_name)
91
- end
92
- super
93
- end
94
45
 
95
- def query_attribute(attr_name)
96
- if self.class.store_accessors.include?(attr_name.to_sym)
97
- value = public_send(attr_name)
98
-
99
- case value
100
- when true then true
101
- when false, nil then false
102
- else
103
- if value.respond_to?(:zero?)
104
- !value.zero?
105
- else
106
- !value.blank?
107
- end
46
+ define_method("restore_#{accessor_name}!") do
47
+ send("#{accessor_name}=", send("#{accessor_name}_was"))
108
48
  end
109
- else
110
- super
111
- end
112
- end
113
-
114
- def normalize_attribute(attr)
115
- case attr
116
- when Symbol
117
- attr
118
- else
119
- attr.to_s.to_sym
120
49
  end
121
50
  end
122
51
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::TypedStore
2
4
  class Field
3
5
  attr_reader :array, :blank, :name, :default, :type, :null, :accessor, :type_sym
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::TypedStore
2
4
  module IdentityCoder
3
5
  extend self
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::TypedStore
2
4
  class Type < ActiveRecord::Type::Serialized
3
5
  def initialize(typed_hash_klass, coder, subtype)
@@ -21,8 +23,8 @@ module ActiveRecord::TypedStore
21
23
  define_method(method) do |value|
22
24
  return if value.nil?
23
25
 
24
- if value.respond_to?(:to_h)
25
- super(value.to_h)
26
+ if value.respond_to?(:to_hash)
27
+ super(value.to_hash)
26
28
  else
27
29
  raise ArgumentError, "ActiveRecord::TypedStore expects a hash as a column value, #{value.class} received"
28
30
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord::TypedStore
2
4
  class TypedHash < HashWithIndifferentAccess
3
5
 
@@ -15,6 +17,9 @@ module ActiveRecord::TypedStore
15
17
  end
16
18
  end
17
19
 
20
+ delegate :with_indifferent_access, to: :to_h
21
+ delegate :slice, :except, :without, to: :with_indifferent_access
22
+
18
23
  def initialize(constructor={})
19
24
  super()
20
25
  update(defaults_hash)
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module TypedStore
3
- VERSION = '1.2.0'
5
+ VERSION = '1.4.0'
4
6
  end
5
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
 
3
5
  module ActiveRecord
@@ -7,5 +9,5 @@ end
7
9
 
8
10
  ActiveSupport.on_load(:active_record) do
9
11
  require 'active_record/typed_store/extension'
10
- ::ActiveRecord::Base.send :include, ActiveRecord::TypedStore::Extension
12
+ ::ActiveRecord::Base.extend(ActiveRecord::TypedStore::Extension)
11
13
  end
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/typed_store'
@@ -201,7 +201,27 @@ describe ActiveRecord::TypedStore::TypedHash do
201
201
  hash.merge!(source: '')
202
202
  expect(hash[:source]).to be == 'web'
203
203
  end
204
+
205
+ end
206
+
207
+ describe '#except' do
208
+
209
+ it 'does not set the default for ignored keys' do
210
+ hash = hash_class.new(source: 'foo')
211
+ expect(hash.except(:source)).to_not have_key(:source)
212
+ end
213
+
204
214
  end
215
+
216
+ describe '#slice' do
217
+
218
+ it 'does not set the default for ignored keys' do
219
+ hash = hash_class.new(source: 'foo')
220
+ expect(hash.slice(:not_source)).to_not have_key(:source)
221
+ end
222
+
223
+ end
224
+
205
225
  end
206
226
 
207
227
  context 'unknown columns' do
@@ -70,20 +70,11 @@ shared_examples 'any model' do
70
70
  }.to change { !!model.age_changed? }.from(true).to(false)
71
71
  end
72
72
 
73
- if AR_VERSION >= AR_4_2
74
- it 'can be restored individually' do
75
- model.age = 24
76
- expect {
77
- model.restore_age!
78
- }.to change { model.age }.from(24).to(12)
79
- end
80
- else
81
- it 'can be reset individually' do
82
- model.age = 24
83
- expect {
84
- model.reset_age!
85
- }.to change { model.age }.from(24).to(12)
86
- end
73
+ it 'can be restored individually' do
74
+ model.age = 24
75
+ expect {
76
+ model.restore_age!
77
+ }.to change { model.age }.from(24).to(12)
87
78
  end
88
79
 
89
80
  it 'does not dirty track assigning the same boolean' do
@@ -339,13 +330,13 @@ shared_examples 'any model' do
339
330
  describe 'decimal attributes' do
340
331
 
341
332
  it 'has the defined default as initial value' do
342
- expect(model.total_price).to be == BigDecimal.new('4.2')
333
+ expect(model.total_price).to be == BigDecimal('4.2')
343
334
  expect(model.total_price).to be_a BigDecimal
344
335
  end
345
336
 
346
337
  it 'properly cast assigned value to decimal' do
347
338
  model.shipping_cost = 4.2
348
- expect(model.shipping_cost).to be == BigDecimal.new('4.2')
339
+ expect(model.shipping_cost).to be == BigDecimal('4.2')
349
340
  expect(model.shipping_cost).to be_a BigDecimal
350
341
  end
351
342
 
@@ -357,7 +348,7 @@ shared_examples 'any model' do
357
348
 
358
349
  it 'retreive a BigDecimal instance' do
359
350
  model.update(shipping_cost: 4.2)
360
- expect(model.reload.shipping_cost).to be == BigDecimal.new('4.2')
351
+ expect(model.reload.shipping_cost).to be == BigDecimal('4.2')
361
352
  expect(model.reload.shipping_cost).to be_a BigDecimal
362
353
  end
363
354
 
@@ -367,17 +358,17 @@ shared_examples 'any model' do
367
358
  end
368
359
 
369
360
  it 'positive values are considered present' do
370
- model.shipping_cost = BigDecimal.new('4.2')
361
+ model.shipping_cost = BigDecimal('4.2')
371
362
  expect(model.shipping_cost?).to be true
372
363
  end
373
364
 
374
365
  it 'negative values are considered present' do
375
- model.shipping_cost = BigDecimal.new('-4.2')
366
+ model.shipping_cost = BigDecimal('-4.2')
376
367
  expect(model.shipping_cost?).to be true
377
368
  end
378
369
 
379
370
  it '0 is not considered present' do
380
- model.shipping_cost = BigDecimal.new('0')
371
+ model.shipping_cost = BigDecimal('0')
381
372
  expect(model.shipping_cost?).to be false
382
373
  end
383
374
 
@@ -432,48 +423,26 @@ shared_examples 'any model' do
432
423
  let(:time) { time_string.respond_to?(:in_time_zone) ? time_string.in_time_zone : Time.parse(time_string) }
433
424
 
434
425
  context "with ActiveRecord #{ActiveRecord::VERSION::STRING}" do
435
- if AR_VERSION < AR_4_0
436
- it 'has the defined default as initial value' do
437
- model.save
438
- expect(model.published_at).to be == time
439
- end
426
+ it 'has the defined default as initial value' do
427
+ model.save
428
+ expect(model.reload.published_at).to be == time
429
+ end
430
+
431
+ it 'retreive a time instance' do
432
+ model.update(published_at: time)
433
+ expect(model.reload.published_at).to be == time
434
+ end
440
435
 
436
+ if ActiveRecord::Base.time_zone_aware_attributes
441
437
  it 'properly cast assigned value to time' do
442
438
  model.remind_at = time_string
443
439
  expect(model.remind_at).to be == time
444
440
  end
445
- it 'properly cast assigned value to time on save' do
441
+ else
442
+ it 'properly cast assigned value to time' do
446
443
  model.remind_at = time_string
447
- model.save
448
- model.reload
449
444
  expect(model.remind_at).to be == time
450
445
  end
451
- it 'retreive a Time instance' do
452
- model.update(published_at: time)
453
- expect(model.reload.published_at).to be == time
454
- end
455
- else
456
- it 'has the defined default as initial value' do
457
- model.save
458
- expect(model.reload.published_at).to be == time
459
- end
460
-
461
- it 'retreive a time instance' do
462
- model.update(published_at: time)
463
- expect(model.reload.published_at).to be == time
464
- end
465
-
466
- if ActiveRecord::Base.time_zone_aware_attributes
467
- it 'properly cast assigned value to time' do
468
- model.remind_at = time_string
469
- expect(model.remind_at).to be == time
470
- end
471
- else
472
- it 'properly cast assigned value to time' do
473
- model.remind_at = time_string
474
- expect(model.remind_at).to be == time
475
- end
476
- end
477
446
  end
478
447
  end
479
448
  end
@@ -485,61 +454,27 @@ shared_examples 'any model' do
485
454
  let(:time) { datetime_string.respond_to?(:in_time_zone) ? datetime_string.in_time_zone : Time.parse(datetime_string) }
486
455
 
487
456
  context "with ActiveRecord #{ActiveRecord::VERSION::STRING}" do
457
+ it 'has the defined default as initial value' do
458
+ model.save
459
+ expect(model.reload.published_at).to be == datetime
460
+ end
488
461
 
489
- if AR_VERSION < AR_4_0
490
-
491
- it 'has the defined default as initial value' do
492
- model.save
493
- expect(model.published_at).to be == time
494
- end
462
+ it 'retreive a DateTime instance' do
463
+ model.update(published_at: datetime)
464
+ expect(model.reload.published_at).to be == datetime
465
+ end
495
466
 
467
+ if ActiveRecord::Base.time_zone_aware_attributes
496
468
  it 'properly cast assigned value to time' do
497
469
  model.remind_at = datetime_string
498
470
  expect(model.remind_at).to be == time
499
471
  end
500
-
501
- it 'properly cast assigned value to time on save' do
502
- model.remind_at = datetime_string
503
- model.save
504
- model.reload
505
- expect(model.remind_at).to be == time
506
- end
507
-
508
- it 'retreive a Time instance' do
509
- model.update(published_at: datetime)
510
- expect(model.reload.published_at).to be == time
511
- end
512
-
513
472
  else
514
-
515
- it 'has the defined default as initial value' do
516
- model.save
517
- expect(model.reload.published_at).to be == datetime
518
- end
519
-
520
- it 'retreive a DateTime instance' do
521
- model.update(published_at: datetime)
522
- expect(model.reload.published_at).to be == datetime
523
- end
524
-
525
- if ActiveRecord::Base.time_zone_aware_attributes
526
-
527
- it 'properly cast assigned value to time' do
528
- model.remind_at = datetime_string
529
- expect(model.remind_at).to be == time
530
- end
531
-
532
- else
533
-
534
- it 'properly cast assigned value to datetime' do
535
- model.remind_at = datetime_string
536
- expect(model.remind_at).to be == datetime
537
- end
538
-
473
+ it 'properly cast assigned value to datetime' do
474
+ model.remind_at = datetime_string
475
+ expect(model.remind_at).to be == datetime
539
476
  end
540
-
541
477
  end
542
-
543
478
  end
544
479
 
545
480
  it 'nillify unparsable datetimes' do
@@ -788,6 +723,11 @@ shared_examples 'a store' do |retain_type = true, settings_type = :text|
788
723
  expect(model.explicit_settings[:signup][:counter]).to be_nil
789
724
  end
790
725
 
726
+ it 'coerce hashes to HashWithIndifferentAccess' do # this is actually Rails behavior
727
+ model.signup[:metadata] = { "signed_up_at" => Time.now }
728
+ expect(model.signup[:metadata]).to be_a ActiveSupport::HashWithIndifferentAccess
729
+ end
730
+
791
731
  end
792
732
  end
793
733
 
@@ -896,15 +836,6 @@ describe PostgresqlRegularARModel do
896
836
  it_should_behave_like 'a model supporting arrays', true
897
837
  end if defined?(PostgresqlRegularARModel)
898
838
 
899
- describe PostgresHstoreTypedStoreModel do
900
- if AR_VERSION >= AR_4_1
901
- pending('TODO: Rails edge HStore compatibiliy')
902
- else
903
- it_should_behave_like 'any model'
904
- it_should_behave_like 'a store', false
905
- end
906
- end if defined?(PostgresHstoreTypedStoreModel)
907
-
908
839
  describe PostgresJsonTypedStoreModel do
909
840
  it_should_behave_like 'any model'
910
841
  it_should_behave_like 'a store', true, :json
@@ -915,6 +846,11 @@ describe YamlTypedStoreModel do
915
846
  it_should_behave_like 'any model'
916
847
  it_should_behave_like 'a store'
917
848
  it_should_behave_like 'a model supporting arrays'
849
+
850
+ it 'nested hashes are not serialized as HashWithIndifferentAccess' do
851
+ model = described_class.create!
852
+ expect(model.settings_before_type_cast).not_to include('HashWithIndifferentAccess')
853
+ end
918
854
  end
919
855
 
920
856
  describe JsonTypedStoreModel do
@@ -928,3 +864,17 @@ describe MarshalTypedStoreModel do
928
864
  it_should_behave_like 'a store'
929
865
  it_should_behave_like 'a model supporting arrays'
930
866
  end
867
+
868
+ describe InheritedTypedStoreModel do
869
+ let(:model) { described_class.new }
870
+
871
+ it 'can be serialized' do
872
+ model.update(new_attribute: "foobar")
873
+ expect(model.reload.new_attribute).to be == "foobar"
874
+ end
875
+
876
+ it 'is casted' do
877
+ model.update(new_attribute: 42)
878
+ expect(model.settings[:new_attribute]).to be == '42'
879
+ end
880
+ end
@@ -2,17 +2,11 @@ require 'active_record'
2
2
  require 'json'
3
3
  require 'yaml'
4
4
 
5
- AR_VERSION = Gem::Version.new(ActiveRecord::VERSION::STRING)
6
- AR_4_0 = Gem::Version.new('4.0')
7
- AR_4_1 = Gem::Version.new('4.1.0.beta')
8
- AR_4_2 = Gem::Version.new('4.2.0-rc1')
9
- AR_5_0 = Gem::Version.new('5.0.0')
10
-
11
5
  ActiveRecord::Base.time_zone_aware_attributes = ENV['TIMEZONE_AWARE'] != '0'
12
6
  ActiveRecord::Base.configurations = {
13
7
  'test_sqlite3' => { 'adapter' => 'sqlite3', 'database' => '/tmp/typed_store.db' },
14
8
  'test_postgresql' => { 'adapter' => 'postgresql', 'database' => 'typed_store_test', 'username' => 'postgres' },
15
- 'test_mysql' => { 'adapter' => 'mysql2', 'database' => 'typed_store_test', 'username' => 'travis' },
9
+ 'test_mysql' => { 'adapter' => 'mysql2', 'database' => 'typed_store_test', 'username' => 'root', 'password' => 'root' },
16
10
  }
17
11
 
18
12
  def define_columns(t)
@@ -80,7 +74,7 @@ def define_store_with_attributes(**options)
80
74
  end
81
75
  end
82
76
 
83
- MigrationClass = AR_VERSION >= AR_5_0 ? ActiveRecord::Migration["5.0"] : ActiveRecord::Migration
77
+ MigrationClass = ActiveRecord::Migration["5.0"]
84
78
  class CreateAllTables < MigrationClass
85
79
 
86
80
  def self.recreate_table(name, *args, &block)
@@ -98,13 +92,11 @@ class CreateAllTables < MigrationClass
98
92
  ActiveRecord::Base.establish_connection(ENV['POSTGRES_URL'] || :test_postgresql)
99
93
  recreate_table(:postgresql_regular_ar_models) { |t| define_columns(t); t.text :untyped_settings }
100
94
 
101
- if AR_VERSION >= AR_4_0
102
- execute "create extension if not exists hstore"
103
- recreate_table(:postgres_hstore_typed_store_models) { |t| t.hstore :settings; t.text :untyped_settings }
95
+ execute "create extension if not exists hstore"
96
+ recreate_table(:postgres_hstore_typed_store_models) { |t| t.hstore :settings; t.text :untyped_settings }
104
97
 
105
- if ENV['POSTGRES_JSON']
106
- recreate_table(:postgres_json_typed_store_models) { |t| t.json :settings; t.text :explicit_settings; t.text :partial_settings; t.text :untyped_settings }
107
- end
98
+ if ENV['POSTGRES_JSON']
99
+ recreate_table(:postgres_json_typed_store_models) { |t| t.json :settings; t.text :explicit_settings; t.text :partial_settings; t.text :untyped_settings }
108
100
  end
109
101
  end
110
102
 
@@ -163,9 +155,8 @@ if ENV['POSTGRES']
163
155
  store :untyped_settings, accessors: [:title]
164
156
  end
165
157
 
166
- if AR_VERSION >= AR_4_0
167
-
168
- class PostgresHstoreTypedStoreModel < ActiveRecord::Base
158
+ if ENV['POSTGRES_JSON']
159
+ class PostgresJsonTypedStoreModel < ActiveRecord::Base
169
160
  establish_connection ENV['POSTGRES_URL'] || :test_postgresql
170
161
  store :untyped_settings, accessors: [:title]
171
162
 
@@ -173,35 +164,6 @@ if ENV['POSTGRES']
173
164
  define_store_with_no_attributes(coder: ColumnCoder.new(AsJson))
174
165
  define_store_with_partial_attributes(coder: ColumnCoder.new(AsJson))
175
166
  end
176
-
177
- if ENV['POSTGRES_JSON']
178
-
179
- if AR_VERSION >= AR_4_2
180
-
181
- class PostgresJsonTypedStoreModel < ActiveRecord::Base
182
- establish_connection ENV['POSTGRES_URL'] || :test_postgresql
183
- store :untyped_settings, accessors: [:title]
184
-
185
- define_store_with_attributes(coder: ColumnCoder.new(AsJson))
186
- define_store_with_no_attributes(coder: ColumnCoder.new(AsJson))
187
- define_store_with_partial_attributes(coder: ColumnCoder.new(AsJson))
188
- end
189
-
190
- else
191
-
192
- class PostgresJsonTypedStoreModel < ActiveRecord::Base
193
- establish_connection ENV['POSTGRES_URL'] || :test_postgresql
194
- store :untyped_settings, accessors: [:title]
195
-
196
- define_store_with_attributes(coder: ColumnCoder.new(AsJson))
197
- define_store_with_no_attributes(coder: ColumnCoder.new(AsJson))
198
- define_store_with_partial_attributes(coder: ColumnCoder.new(AsJson))
199
- end
200
-
201
- end
202
-
203
- end
204
-
205
167
  end
206
168
  end
207
169
 
@@ -267,5 +229,4 @@ Models = [
267
229
  ]
268
230
  Models << MysqlRegularARModel if defined?(MysqlRegularARModel)
269
231
  Models << PostgresqlRegularARModel if defined?(PostgresqlRegularARModel)
270
- Models << PostgresHstoreTypedStoreModel if defined?(PostgresHstoreTypedStoreModel)
271
232
  Models << PostgresJsonTypedStoreModel if defined?(PostgresJsonTypedStoreModel)
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: 1.2.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-02 00:00:00.000000000 Z
11
+ date: 2021-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,34 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '5.3'
19
+ version: '5.2'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
- version: '4.2'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '5.3'
26
+ version: '5.2'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: bundler
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
- - - "~>"
31
+ - - ">="
38
32
  - !ruby/object:Gem::Version
39
- version: '1.3'
33
+ version: '0'
40
34
  type: :development
41
35
  prerelease: false
42
36
  version_requirements: !ruby/object:Gem::Requirement
43
37
  requirements:
44
- - - "~>"
38
+ - - ">="
45
39
  - !ruby/object:Gem::Version
46
- version: '1.3'
40
+ version: '0'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: rake
49
43
  requirement: !ruby/object:Gem::Requirement
@@ -149,19 +143,21 @@ executables: []
149
143
  extensions: []
150
144
  extra_rdoc_files: []
151
145
  files:
146
+ - ".github/workflows/ruby.yml"
152
147
  - ".gitignore"
153
148
  - ".rspec"
154
- - ".travis.yml"
155
149
  - Gemfile
156
150
  - LICENSE.txt
157
151
  - README.md
158
152
  - Rakefile
159
153
  - activerecord-typedstore.gemspec
160
- - gemfiles/Gemfile.ar-4.2
161
154
  - gemfiles/Gemfile.ar-5.0
162
- - gemfiles/Gemfile.ar-5.1
163
155
  - gemfiles/Gemfile.ar-5.2
156
+ - gemfiles/Gemfile.ar-6.0
157
+ - gemfiles/Gemfile.ar-6.1
158
+ - gemfiles/Gemfile.ar-master
164
159
  - lib/active_record/typed_store.rb
160
+ - lib/active_record/typed_store/behavior.rb
165
161
  - lib/active_record/typed_store/dsl.rb
166
162
  - lib/active_record/typed_store/extension.rb
167
163
  - lib/active_record/typed_store/field.rb
@@ -179,7 +175,7 @@ homepage: https://github.com/byroot/activerecord-typedstore
179
175
  licenses:
180
176
  - MIT
181
177
  metadata: {}
182
- post_install_message:
178
+ post_install_message:
183
179
  rdoc_options: []
184
180
  require_paths:
185
181
  - lib
@@ -194,9 +190,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
190
  - !ruby/object:Gem::Version
195
191
  version: '0'
196
192
  requirements: []
197
- rubyforge_project:
198
- rubygems_version: 2.5.1
199
- signing_key:
193
+ rubygems_version: 3.1.2
194
+ signing_key:
200
195
  specification_version: 4
201
196
  summary: Add type casting and full method attributes support to АctiveRecord store
202
197
  test_files:
data/.travis.yml DELETED
@@ -1,25 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.3.5
5
- - 2.4.2
6
- gemfile:
7
- - gemfiles/Gemfile.ar-4.2
8
- - gemfiles/Gemfile.ar-5.0
9
- - gemfiles/Gemfile.ar-5.1
10
-
11
- env:
12
- - TIMEZONE_AWARE=1 POSTGRES=1 MYSQL=1
13
- - TIMEZONE_AWARE=0 POSTGRES=1 MYSQL=1
14
- - TIMEZONE_AWARE=1 POSTGRES=1 POSTGRES_JSON=1
15
- - TIMEZONE_AWARE=0 POSTGRES=1 POSTGRES_JSON=1
16
-
17
- addons:
18
- postgresql: 9.3
19
-
20
- sudo: false
21
- cache: bundler
22
-
23
- before_script:
24
- - mysql -e 'create database typed_store_test;'
25
- - psql -c 'create database typed_store_test;' -U postgres
@@ -1,11 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 4.2.1'
4
- gem 'bundler', '~> 1.3'
5
- gem 'rake'
6
- gem 'rspec'
7
- gem 'sqlite3'
8
- gem 'pg', '~> 0.11'
9
- gem 'mysql2', ['>= 0.3.13', '< 0.5']
10
- gem 'database_cleaner'
11
- gem 'coveralls', require: false
@@ -1,11 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 5.1.0'
4
- gem 'bundler', '~> 1.3'
5
- gem 'rake'
6
- gem 'rspec'
7
- gem 'sqlite3'
8
- gem 'pg', '~> 0.11'
9
- gem 'mysql2', ['>= 0.3.13', '< 0.5']
10
- gem 'database_cleaner'
11
- gem 'coveralls', require: false