activerecord-typedstore 1.2.0 → 1.3.2
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/README.md +4 -4
- data/activerecord-typedstore.gemspec +3 -3
- data/gemfiles/Gemfile.ar-5.0 +3 -8
- data/gemfiles/Gemfile.ar-5.2 +3 -9
- data/gemfiles/Gemfile.ar-6.0 +5 -0
- data/gemfiles/Gemfile.ar-6.1 +6 -0
- data/gemfiles/Gemfile.ar-master +6 -0
- data/lib/active_record/typed_store.rb +3 -1
- 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 +28 -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 +2 -0
- data/lib/active_record/typed_store/typed_hash.rb +5 -0
- data/lib/active_record/typed_store/version.rb +3 -1
- data/lib/activerecord-typedstore.rb +2 -0
- data/spec/active_record/typed_store/typed_hash_spec.rb +20 -0
- data/spec/active_record/typed_store_spec.rb +49 -109
- data/spec/support/models.rb +8 -47
- metadata +17 -22
- data/.travis.yml +0 -25
- data/gemfiles/Gemfile.ar-4.2 +0 -11
- data/gemfiles/Gemfile.ar-5.1 +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: bdeb6337150bcfbc1307433960078b4ee4fa86930eea89ed1e27214df82b6d1a
|
4
|
+
data.tar.gz: 883fc7c802f2d86ca5a5c18133a75b8759907de917a938d765b3f55852338170
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 989ac79c84da4898c175da31cef578c576396987c7356093430e116089e27c377f561f05a53872bd0adfb79c4ee4deae795f8d0ef0e67bc1b4f461ef04790e1b
|
7
|
+
data.tar.gz: a7f1e9fb20c79d5acd321140aad66d345c058155f2722d2afc9b25247615428b0286c8040d5c2a432a2f2d05b5a2666d2c773dbc56dcdfba9113d39f13901593
|
@@ -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
|
98
|
-
Actually the only difference is that you
|
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
|
-
|
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', '>=
|
21
|
+
spec.add_dependency 'activerecord', '>= 5.2'
|
22
22
|
|
23
|
-
spec.add_development_dependency 'bundler'
|
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-5.0
CHANGED
@@ -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
|
data/gemfiles/Gemfile.ar-5.2
CHANGED
@@ -1,11 +1,5 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
gem '
|
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'
|
@@ -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
|
@@ -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,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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
24
|
-
|
21
|
+
typed_klass = TypedHash.create(dsl.fields.values)
|
22
|
+
const_set("#{store_attribute}_hash".camelize, typed_klass)
|
25
23
|
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
67
|
-
|
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
|
-
|
96
|
-
|
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 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)
|
@@ -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
|
-
|
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
|
@@ -896,15 +831,6 @@ describe PostgresqlRegularARModel do
|
|
896
831
|
it_should_behave_like 'a model supporting arrays', true
|
897
832
|
end if defined?(PostgresqlRegularARModel)
|
898
833
|
|
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
834
|
describe PostgresJsonTypedStoreModel do
|
909
835
|
it_should_behave_like 'any model'
|
910
836
|
it_should_behave_like 'a store', true, :json
|
@@ -928,3 +854,17 @@ describe MarshalTypedStoreModel do
|
|
928
854
|
it_should_behave_like 'a store'
|
929
855
|
it_should_behave_like 'a model supporting arrays'
|
930
856
|
end
|
857
|
+
|
858
|
+
describe InheritedTypedStoreModel do
|
859
|
+
let(:model) { described_class.new }
|
860
|
+
|
861
|
+
it 'can be serialized' do
|
862
|
+
model.update(new_attribute: "foobar")
|
863
|
+
expect(model.reload.new_attribute).to be == "foobar"
|
864
|
+
end
|
865
|
+
|
866
|
+
it 'is casted' do
|
867
|
+
model.update(new_attribute: 42)
|
868
|
+
expect(model.settings[:new_attribute]).to be == '42'
|
869
|
+
end
|
870
|
+
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.2
|
4
|
+
version: 1.3.2
|
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-02-18 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: '
|
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: '
|
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: '
|
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: '
|
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
|
-
|
198
|
-
|
199
|
-
signing_key:
|
193
|
+
rubygems_version: 3.1.4
|
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
|
data/gemfiles/Gemfile.ar-4.2
DELETED
data/gemfiles/Gemfile.ar-5.1
DELETED