activerecord-typedstore 1.2.2 → 1.4.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 +5 -5
- data/.github/workflows/ruby.yml +47 -0
- data/activerecord-typedstore.gemspec +2 -2
- data/gemfiles/{Gemfile.ar-5.1 → Gemfile.ar-6.0} +1 -1
- data/gemfiles/Gemfile.ar-6.1 +6 -0
- data/gemfiles/Gemfile.ar-master +1 -0
- data/lib/active_record/typed_store/behavior.rb +75 -0
- data/lib/active_record/typed_store/dsl.rb +2 -0
- data/lib/active_record/typed_store/extension.rb +27 -99
- data/lib/active_record/typed_store/field.rb +2 -0
- data/lib/active_record/typed_store/identity_coder.rb +2 -0
- data/lib/active_record/typed_store/type.rb +4 -2
- data/lib/active_record/typed_store/typed_hash.rb +2 -0
- data/lib/active_record/typed_store/version.rb +3 -1
- data/lib/active_record/typed_store.rb +3 -1
- data/lib/activerecord-typedstore.rb +2 -0
- data/spec/active_record/typed_store_spec.rb +59 -109
- data/spec/support/models.rb +8 -47
- metadata +12 -12
- data/.travis.yml +0 -40
- data/gemfiles/Gemfile.ar-4.2 +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 17f01f8701f6af429d7fb15bfbcf9cf202f1d82cbe0f6878aa4a503ed69fc0e8
|
|
4
|
+
data.tar.gz: e5e1dd4100a034b074fe2682e485ae1365c8bcbe9c4bb7bc96f697a5042c0f62
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
@@ -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', '>=
|
|
21
|
+
spec.add_dependency 'activerecord', '>= 5.2'
|
|
22
22
|
|
|
23
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
|
data/gemfiles/Gemfile.ar-master
CHANGED
|
@@ -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,123 +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'
|
|
4
7
|
require 'active_record/typed_store/identity_coder'
|
|
5
8
|
|
|
6
9
|
module ActiveRecord::TypedStore
|
|
7
10
|
module Extension
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
self.typed_stores = {}
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
module ClassMethods
|
|
16
|
-
def store_accessors
|
|
17
|
-
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
|
|
18
15
|
end
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
typed_klass = TypedHash.create(dsl.fields.values)
|
|
22
|
+
const_set("#{store_attribute}_hash".camelize, typed_klass)
|
|
26
23
|
|
|
27
|
-
|
|
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)
|
|
28
27
|
Type.new(typed_klass, dsl.coder, subtype)
|
|
29
28
|
end
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
define_method("#{accessor_name}_changed?") do
|
|
34
|
-
send("#{store_attribute}_changed?") &&
|
|
35
|
-
send(store_attribute)[accessor_name] != send("#{store_attribute}_was")[accessor_name]
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
define_method("#{accessor_name}_was") do
|
|
39
|
-
send("#{store_attribute}_was")[accessor_name]
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
define_method("restore_#{accessor_name}!") do
|
|
43
|
-
send("#{accessor_name}=", send("#{accessor_name}_was"))
|
|
44
|
-
end
|
|
29
|
+
else
|
|
30
|
+
decorate_attribute_type(store_attribute, :typed_store) do |subtype|
|
|
31
|
+
Type.new(typed_klass, dsl.coder, subtype)
|
|
45
32
|
end
|
|
46
33
|
end
|
|
34
|
+
store_accessor(store_attribute, dsl.accessors)
|
|
47
35
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def undefine_attribute_methods # :nodoc:
|
|
54
|
-
super if @typed_store_attribute_methods_generated
|
|
55
|
-
@typed_store_attribute_methods_generated = false
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def define_typed_store_attribute_methods
|
|
59
|
-
return if @typed_store_attribute_methods_generated
|
|
60
|
-
store_accessors.each do |attribute|
|
|
61
|
-
define_attribute_method(attribute.to_s)
|
|
62
|
-
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]
|
|
63
40
|
end
|
|
64
|
-
@typed_store_attribute_methods_generated = true
|
|
65
|
-
end
|
|
66
41
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
method = "#{attribute}_before_type_cast"
|
|
70
|
-
undef_method(method) if method_defined?(method)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def changes
|
|
75
|
-
changes = super
|
|
76
|
-
self.class.store_accessors.each do |attr|
|
|
77
|
-
if send("#{attr}_changed?")
|
|
78
|
-
changes[attr] = [send("#{attr}_was"), send(attr)]
|
|
42
|
+
define_method("#{accessor_name}_was") do
|
|
43
|
+
send("#{store_attribute}_was")[accessor_name]
|
|
79
44
|
end
|
|
80
|
-
end
|
|
81
|
-
changes
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def clear_attribute_change(attr_name)
|
|
85
|
-
return if self.class.store_accessors.include?(normalize_attribute(attr_name))
|
|
86
|
-
super
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def read_attribute(attr_name)
|
|
90
|
-
if self.class.store_accessors.include?(normalize_attribute(attr_name))
|
|
91
|
-
return public_send(attr_name)
|
|
92
|
-
end
|
|
93
|
-
super
|
|
94
|
-
end
|
|
95
45
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
value = public_send(attr_name)
|
|
99
|
-
|
|
100
|
-
case value
|
|
101
|
-
when true then true
|
|
102
|
-
when false, nil then false
|
|
103
|
-
else
|
|
104
|
-
if value.respond_to?(:zero?)
|
|
105
|
-
!value.zero?
|
|
106
|
-
else
|
|
107
|
-
!value.blank?
|
|
108
|
-
end
|
|
46
|
+
define_method("restore_#{accessor_name}!") do
|
|
47
|
+
send("#{accessor_name}=", send("#{accessor_name}_was"))
|
|
109
48
|
end
|
|
110
|
-
else
|
|
111
|
-
super
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def normalize_attribute(attr)
|
|
116
|
-
case attr
|
|
117
|
-
when Symbol
|
|
118
|
-
attr
|
|
119
|
-
else
|
|
120
|
-
attr.to_s.to_sym
|
|
121
49
|
end
|
|
122
50
|
end
|
|
123
51
|
end
|
|
@@ -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?(:
|
|
25
|
-
super(value.
|
|
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
|
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.
|
|
12
|
+
::ActiveRecord::Base.extend(ActiveRecord::TypedStore::Extension)
|
|
11
13
|
end
|
|
@@ -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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
516
|
-
model.
|
|
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
|
data/spec/support/models.rb
CHANGED
|
@@ -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' => '
|
|
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 =
|
|
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
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
|
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.
|
|
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:
|
|
11
|
+
date: 2021-09-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '5.2'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '5.2'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: bundler
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -143,20 +143,21 @@ executables: []
|
|
|
143
143
|
extensions: []
|
|
144
144
|
extra_rdoc_files: []
|
|
145
145
|
files:
|
|
146
|
+
- ".github/workflows/ruby.yml"
|
|
146
147
|
- ".gitignore"
|
|
147
148
|
- ".rspec"
|
|
148
|
-
- ".travis.yml"
|
|
149
149
|
- Gemfile
|
|
150
150
|
- LICENSE.txt
|
|
151
151
|
- README.md
|
|
152
152
|
- Rakefile
|
|
153
153
|
- activerecord-typedstore.gemspec
|
|
154
|
-
- gemfiles/Gemfile.ar-4.2
|
|
155
154
|
- gemfiles/Gemfile.ar-5.0
|
|
156
|
-
- gemfiles/Gemfile.ar-5.1
|
|
157
155
|
- gemfiles/Gemfile.ar-5.2
|
|
156
|
+
- gemfiles/Gemfile.ar-6.0
|
|
157
|
+
- gemfiles/Gemfile.ar-6.1
|
|
158
158
|
- gemfiles/Gemfile.ar-master
|
|
159
159
|
- lib/active_record/typed_store.rb
|
|
160
|
+
- lib/active_record/typed_store/behavior.rb
|
|
160
161
|
- lib/active_record/typed_store/dsl.rb
|
|
161
162
|
- lib/active_record/typed_store/extension.rb
|
|
162
163
|
- lib/active_record/typed_store/field.rb
|
|
@@ -174,7 +175,7 @@ homepage: https://github.com/byroot/activerecord-typedstore
|
|
|
174
175
|
licenses:
|
|
175
176
|
- MIT
|
|
176
177
|
metadata: {}
|
|
177
|
-
post_install_message:
|
|
178
|
+
post_install_message:
|
|
178
179
|
rdoc_options: []
|
|
179
180
|
require_paths:
|
|
180
181
|
- lib
|
|
@@ -189,9 +190,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
189
190
|
- !ruby/object:Gem::Version
|
|
190
191
|
version: '0'
|
|
191
192
|
requirements: []
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
signing_key:
|
|
193
|
+
rubygems_version: 3.1.2
|
|
194
|
+
signing_key:
|
|
195
195
|
specification_version: 4
|
|
196
196
|
summary: Add type casting and full method attributes support to АctiveRecord store
|
|
197
197
|
test_files:
|
data/.travis.yml
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
language: ruby
|
|
2
|
-
|
|
3
|
-
before_install:
|
|
4
|
-
- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
|
|
5
|
-
- gem install bundler -v '< 2'
|
|
6
|
-
|
|
7
|
-
rvm:
|
|
8
|
-
- 2.3.8
|
|
9
|
-
- 2.4.6
|
|
10
|
-
- 2.5.5
|
|
11
|
-
- 2.6.2
|
|
12
|
-
gemfile:
|
|
13
|
-
- gemfiles/Gemfile.ar-4.2
|
|
14
|
-
- gemfiles/Gemfile.ar-5.0
|
|
15
|
-
- gemfiles/Gemfile.ar-5.1
|
|
16
|
-
- gemfiles/Gemfile.ar-5.2
|
|
17
|
-
- gemfiles/Gemfile.ar-master
|
|
18
|
-
|
|
19
|
-
env:
|
|
20
|
-
- TIMEZONE_AWARE=1 POSTGRES=1 MYSQL=1
|
|
21
|
-
- TIMEZONE_AWARE=0 POSTGRES=1 MYSQL=1
|
|
22
|
-
- TIMEZONE_AWARE=1 POSTGRES=1 POSTGRES_JSON=1
|
|
23
|
-
- TIMEZONE_AWARE=0 POSTGRES=1 POSTGRES_JSON=1
|
|
24
|
-
|
|
25
|
-
matrix:
|
|
26
|
-
exclude:
|
|
27
|
-
- rvm: 2.3.8
|
|
28
|
-
gemfile: 'gemfiles/Gemfile.ar-master'
|
|
29
|
-
- rvm: 2.4.6
|
|
30
|
-
gemfile: 'gemfiles/Gemfile.ar-master'
|
|
31
|
-
|
|
32
|
-
addons:
|
|
33
|
-
postgresql: 9.3
|
|
34
|
-
|
|
35
|
-
sudo: false
|
|
36
|
-
cache: bundler
|
|
37
|
-
|
|
38
|
-
before_script:
|
|
39
|
-
- mysql -e 'create database typed_store_test;'
|
|
40
|
-
- psql -c 'create database typed_store_test;' -U postgres
|