activerecord-typedstore 1.1.3 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab5f09b64721a41a7eae679a4ae848813504913d1d87815c035352da6b3134eb
4
- data.tar.gz: bc9d48e95b48f9ad9d5b7e02f1edd9a1ce1004e8c93bfb6d18fc4d944d63c161
3
+ metadata.gz: 922ee9aa28151d4d9682563fb960a01e3f0b483d1c9d1e9168c095391b87344e
4
+ data.tar.gz: 1116257100c7775ec75c3742666c9ab35454ceca71bef07781fe8581edaeeeec
5
5
  SHA512:
6
- metadata.gz: bbd034206f96e1063bdb078417b685148346edeeb36968068bd54a2a8e95321ec56d5aa69b50851238607a862a01cba1a1770ac541e283eba12735c11dd84f5a
7
- data.tar.gz: f04cdc9a39338b8b0fcc164da82f4b472acbf7e59543e6f8ebf5b51300e5bf55f4cb01d602a7dfc083833fef38069ed468049a49be3b6ff45b84dac7e0f86952
6
+ metadata.gz: 0c01c5370bc89c17b38a7f9a928e96064cececa214eb9e43a90a10b0ee8e990d18fbea8d18f1c45e1b1f3a9446d17174e79fe5483b1ae5a89ac5f5e994e618a8
7
+ data.tar.gz: 99fc46ff54dd8ba238ef3bda41d2861d200fb8f11f41a5b07d0c5846d747435c3424a5f550c168c6f73a36c78638f6affbf95636f4a75c56af40ad8d42685268
@@ -1,22 +1,33 @@
1
+ sudo: false
1
2
  language: ruby
3
+ cache: bundler
4
+
5
+ before_install:
6
+ - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
7
+ - gem install bundler -v '< 2'
2
8
 
3
9
  rvm:
4
- - 2.3.5
5
- - 2.4.2
10
+ - "2.5"
11
+ - "2.6"
6
12
  gemfile:
7
- - gemfiles/Gemfile.ar-4.2
8
- - gemfiles/Gemfile.ar-5.0
9
- - gemfiles/Gemfile.ar-5.1
13
+ - gemfiles/Gemfile.ar-5.2
14
+ - gemfiles/Gemfile.ar-6.0
15
+ - gemfiles/Gemfile.ar-master
10
16
 
11
17
  env:
12
- - TIMEZONE_AWARE=1 POSTGRES=1 MYSQL=1
13
- - TIMEZONE_AWARE=0 POSTGRES=1 MYSQL=1
18
+ - TIMEZONE_AWARE=1 POSTGRES=1 MYSQL=1 POSTGRES_JSON=1
19
+ - TIMEZONE_AWARE=0 POSTGRES=1 MYSQL=1 POSTGRES_JSON=1
14
20
 
15
- addons:
16
- postgresql: 9.3
21
+ matrix:
22
+ exclude:
23
+ - rvm: "2.6"
24
+ gemfile: 'gemfiles/Gemfile.6-0'
25
+ - rvm: "2.6"
26
+ gemfile: 'gemfiles/Gemfile.ar-master'
17
27
 
18
- sudo: false
19
- cache: bundler
28
+ services:
29
+ - mysql
30
+ - postgresql
20
31
 
21
32
  before_script:
22
33
  - mysql -e 'create database typed_store_test;'
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 Postgres JSON or 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
 
@@ -123,21 +123,7 @@ typed_store :settings, coder: Base64MarshalCoder do |s|
123
123
  end
124
124
  ```
125
125
 
126
- If you want to use Postgres HStore or JSON column types, then you need a very simple coder:
127
- ```ruby
128
- module DumbCoder
129
- extend self
130
-
131
- def load(data)
132
- data || {}
133
- end
134
-
135
- def dump(data)
136
- data || {}
137
- end
138
-
139
- end
140
- ```
126
+ If you want to use JSON column or Postgres HStore types, then you can pass in `ActiveRecord::TypedStore::IdentityCoder` as the coder.
141
127
 
142
128
  ## HStore limitations
143
129
 
@@ -148,7 +134,7 @@ Since HStore can only store strings:
148
134
  - `any` attributes will be converted to string
149
135
 
150
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,
151
- 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.
152
138
 
153
139
  ## Contributing
154
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', github: 'rails/rails'
@@ -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
@@ -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
@@ -29,7 +31,7 @@ module ActiveRecord::TypedStore
29
31
  delegate :keys, to: :@fields
30
32
 
31
33
  NO_DEFAULT_GIVEN = Object.new
32
- [:string, :text, :integer, :float, :datetime, :date, :boolean, :decimal, :any].each do |type|
34
+ [:string, :text, :integer, :float, :time, :datetime, :date, :boolean, :decimal, :any].each do |type|
33
35
  define_method(type) do |name, **options|
34
36
  @fields[name] = Field.new(name, type, options)
35
37
  end
@@ -1,122 +1,50 @@
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|
27
26
  Type.new(typed_klass, dsl.coder, subtype)
28
27
  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
28
+ else
29
+ decorate_attribute_type(store_attribute, :typed_store) do |subtype|
30
+ Type.new(typed_klass, dsl.coder, subtype)
44
31
  end
45
32
  end
33
+ store_accessor(store_attribute, dsl.accessors)
46
34
 
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)
35
+ dsl.accessors.each do |accessor_name|
36
+ define_method("#{accessor_name}_changed?") do
37
+ send("#{store_attribute}_changed?") &&
38
+ send(store_attribute)[accessor_name] != send("#{store_attribute}_was")[accessor_name]
62
39
  end
63
- @typed_store_attribute_methods_generated = true
64
- end
65
40
 
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)]
41
+ define_method("#{accessor_name}_was") do
42
+ send("#{store_attribute}_was")[accessor_name]
78
43
  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
44
 
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
45
+ define_method("restore_#{accessor_name}!") do
46
+ send("#{accessor_name}=", send("#{accessor_name}_was"))
108
47
  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
48
  end
121
49
  end
122
50
  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
@@ -39,6 +41,7 @@ module ActiveRecord::TypedStore
39
41
  string: ::ActiveRecord::Type::String,
40
42
  float: ::ActiveRecord::Type::Float,
41
43
  date: ::ActiveRecord::Type::Date,
44
+ time: ::ActiveRecord::Type::Time,
42
45
  datetime: ::ActiveRecord::Type::DateTime,
43
46
  decimal: ::ActiveRecord::Type::Decimal,
44
47
  any: ::ActiveRecord::Type::Value,
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord::TypedStore
4
+ module IdentityCoder
5
+ extend self
6
+
7
+ def load(data)
8
+ data || {}
9
+ end
10
+
11
+ def dump(data)
12
+ data || {}
13
+ end
14
+ end
15
+ 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)
@@ -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.1.3'
5
+ VERSION = '1.3.1'
4
6
  end
5
7
  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
 
@@ -426,68 +417,64 @@ shared_examples 'any model' do
426
417
 
427
418
  end
428
419
 
429
- describe 'datetime attributes' do
430
-
431
- let(:datetime) { DateTime.new(1984, 6, 8, 13, 57, 12) }
432
- let(:datetime_string) { '1984-06-08 13:57:12' }
433
- let(:time) { datetime_string.respond_to?(:in_time_zone) ? datetime_string.in_time_zone : Time.parse(datetime_string) }
420
+ describe 'time attributes' do
421
+ let(:time) { Time.new(1984, 6, 8, 13, 57, 12) }
422
+ let(:time_string) { '1984-06-08 13:57:12' }
423
+ let(:time) { time_string.respond_to?(:in_time_zone) ? time_string.in_time_zone : Time.parse(time_string) }
434
424
 
435
425
  context "with ActiveRecord #{ActiveRecord::VERSION::STRING}" do
426
+ it 'has the defined default as initial value' do
427
+ model.save
428
+ expect(model.reload.published_at).to be == time
429
+ end
436
430
 
437
- if AR_VERSION < AR_4_0
438
-
439
- it 'has the defined default as initial value' do
440
- model.save
441
- expect(model.published_at).to be == time
442
- end
431
+ it 'retreive a time instance' do
432
+ model.update(published_at: time)
433
+ expect(model.reload.published_at).to be == time
434
+ end
443
435
 
436
+ if ActiveRecord::Base.time_zone_aware_attributes
444
437
  it 'properly cast assigned value to time' do
445
- model.remind_at = datetime_string
438
+ model.remind_at = time_string
446
439
  expect(model.remind_at).to be == time
447
440
  end
448
-
449
- it 'properly cast assigned value to time on save' do
450
- model.remind_at = datetime_string
451
- model.save
452
- model.reload
453
- expect(model.remind_at).to be == time
454
- end
455
-
456
- it 'retreive a Time instance' do
457
- model.update(published_at: datetime)
458
- expect(model.reload.published_at).to be == time
459
- end
460
-
461
441
  else
462
-
463
- it 'has the defined default as initial value' do
464
- model.save
465
- expect(model.reload.published_at).to be == datetime
466
- end
467
-
468
- it 'retreive a DateTime instance' do
469
- model.update(published_at: datetime)
470
- expect(model.reload.published_at).to be == datetime
442
+ it 'properly cast assigned value to time' do
443
+ model.remind_at = time_string
444
+ expect(model.remind_at).to be == time
471
445
  end
446
+ end
447
+ end
448
+ end
472
449
 
473
- if ActiveRecord::Base.time_zone_aware_attributes
450
+ describe 'datetime attributes' do
474
451
 
475
- it 'properly cast assigned value to time' do
476
- model.remind_at = datetime_string
477
- expect(model.remind_at).to be == time
478
- end
452
+ let(:datetime) { DateTime.new(1984, 6, 8, 13, 57, 12) }
453
+ let(:datetime_string) { '1984-06-08 13:57:12' }
454
+ let(:time) { datetime_string.respond_to?(:in_time_zone) ? datetime_string.in_time_zone : Time.parse(datetime_string) }
479
455
 
480
- else
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
481
461
 
482
- it 'properly cast assigned value to datetime' do
483
- model.remind_at = datetime_string
484
- expect(model.remind_at).to be == datetime
485
- 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
486
466
 
467
+ if ActiveRecord::Base.time_zone_aware_attributes
468
+ it 'properly cast assigned value to time' do
469
+ model.remind_at = datetime_string
470
+ expect(model.remind_at).to be == time
471
+ end
472
+ else
473
+ it 'properly cast assigned value to datetime' do
474
+ model.remind_at = datetime_string
475
+ expect(model.remind_at).to be == datetime
487
476
  end
488
-
489
477
  end
490
-
491
478
  end
492
479
 
493
480
  it 'nillify unparsable datetimes' do
@@ -513,7 +500,7 @@ shared_examples 'any model' do
513
500
 
514
501
  end
515
502
 
516
- shared_examples 'a store' do |retain_type=true|
503
+ shared_examples 'a store' do |retain_type = true, settings_type = :text|
517
504
  let(:model) { described_class.new }
518
505
 
519
506
  describe "without connection" do
@@ -537,7 +524,7 @@ shared_examples 'a store' do |retain_type=true|
537
524
  describe 'model.typed_stores' do
538
525
  it "can access keys" do
539
526
  stores = model.class.typed_stores
540
- expect(stores[:settings].keys).to eq [:no_default, :name, :email, :cell_phone, :public, :enabled, :age, :max_length, :rate, :price, :published_on, :remind_on, :published_at, :remind_at, :total_price, :shipping_cost, :grades, :tags, :nickname, :author, :source, :signup, :country]
527
+ expect(stores[:settings].keys).to eq [:no_default, :name, :email, :cell_phone, :public, :enabled, :age, :max_length, :rate, :price, :published_on, :remind_on, :published_at_time, :remind_at_time, :published_at, :remind_at, :total_price, :shipping_cost, :grades, :tags, :nickname, :author, :source, :signup, :country]
541
528
  end
542
529
 
543
530
  it "can access keys even when accessors are not defined" do
@@ -617,7 +604,7 @@ shared_examples 'a store' do |retain_type=true|
617
604
  end
618
605
 
619
606
  it 'delegates internal methods to the underlying type' do
620
- expect(model.class.type_for_attribute("settings").type).to eq :text
607
+ expect(model.class.type_for_attribute("settings").type).to eq settings_type
621
608
  end
622
609
  end
623
610
 
@@ -844,18 +831,9 @@ describe PostgresqlRegularARModel do
844
831
  it_should_behave_like 'a model supporting arrays', true
845
832
  end if defined?(PostgresqlRegularARModel)
846
833
 
847
- describe PostgresHstoreTypedStoreModel do
848
- if AR_VERSION >= AR_4_1
849
- pending('TODO: Rails edge HStore compatibiliy')
850
- else
851
- it_should_behave_like 'any model'
852
- it_should_behave_like 'a store', false
853
- end
854
- end if defined?(PostgresHstoreTypedStoreModel)
855
-
856
834
  describe PostgresJsonTypedStoreModel do
857
835
  it_should_behave_like 'any model'
858
- it_should_behave_like 'a store'
836
+ it_should_behave_like 'a store', true, :json
859
837
  it_should_behave_like 'a model supporting arrays'
860
838
  end if defined?(PostgresJsonTypedStoreModel)
861
839
 
@@ -2,12 +2,6 @@ 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' },
@@ -34,6 +28,9 @@ def define_columns(t)
34
28
  t.date :published_on, default: '1984-06-08', null: false
35
29
  t.date :remind_on
36
30
 
31
+ t.time :published_at_time, default: '1984-06-08 13:57:12', null: false
32
+ t.time :remind_at_time
33
+
37
34
  t.datetime :published_at, default: '1984-06-08 13:57:12', null: false
38
35
  t.datetime :remind_at
39
36
 
@@ -77,7 +74,7 @@ def define_store_with_attributes(**options)
77
74
  end
78
75
  end
79
76
 
80
- MigrationClass = AR_VERSION >= AR_5_0 ? ActiveRecord::Migration["5.0"] : ActiveRecord::Migration
77
+ MigrationClass = ActiveRecord::Migration["5.0"]
81
78
  class CreateAllTables < MigrationClass
82
79
 
83
80
  def self.recreate_table(name, *args, &block)
@@ -95,14 +92,11 @@ class CreateAllTables < MigrationClass
95
92
  ActiveRecord::Base.establish_connection(ENV['POSTGRES_URL'] || :test_postgresql)
96
93
  recreate_table(:postgresql_regular_ar_models) { |t| define_columns(t); t.text :untyped_settings }
97
94
 
98
- if AR_VERSION >= AR_4_0
99
- execute "create extension if not exists hstore"
100
- 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 }
101
97
 
102
- if ENV['POSTGRES_JSON']
103
- execute "create extension if not exists json"
104
- recreate_table(:postgres_json_typed_store_models) { |t| t.json :settings; t.text :untyped_settings }
105
- 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 }
106
100
  end
107
101
  end
108
102
 
@@ -161,9 +155,8 @@ if ENV['POSTGRES']
161
155
  store :untyped_settings, accessors: [:title]
162
156
  end
163
157
 
164
- if AR_VERSION >= AR_4_0
165
-
166
- class PostgresHstoreTypedStoreModel < ActiveRecord::Base
158
+ if ENV['POSTGRES_JSON']
159
+ class PostgresJsonTypedStoreModel < ActiveRecord::Base
167
160
  establish_connection ENV['POSTGRES_URL'] || :test_postgresql
168
161
  store :untyped_settings, accessors: [:title]
169
162
 
@@ -171,35 +164,6 @@ if ENV['POSTGRES']
171
164
  define_store_with_no_attributes(coder: ColumnCoder.new(AsJson))
172
165
  define_store_with_partial_attributes(coder: ColumnCoder.new(AsJson))
173
166
  end
174
-
175
- if ENV['POSTGRES_JSON']
176
-
177
- if AR_VERSION >= AR_4_2
178
-
179
- class PostgresJsonTypedStoreModel < ActiveRecord::Base
180
- establish_connection ENV['POSTGRES_URL'] || :test_postgresql
181
- store :untyped_settings, accessors: [:title]
182
-
183
- define_store_with_attributes(coder: false)
184
- define_store_with_no_attributes(coder: false)
185
- define_store_with_partial_attributes(coder: false)
186
- end
187
-
188
- else
189
-
190
- class PostgresJsonTypedStoreModel < ActiveRecord::Base
191
- establish_connection ENV['POSTGRES_URL'] || :test_postgresql
192
- store :untyped_settings, accessors: [:title]
193
-
194
- define_store_with_attributes(coder: ColumnCoder.new(AsJson))
195
- define_store_with_no_attributes(coder: ColumnCoder.new(AsJson))
196
- define_store_with_partial_attributes(coder: ColumnCoder.new(AsJson))
197
- end
198
-
199
- end
200
-
201
- end
202
-
203
167
  end
204
168
  end
205
169
 
@@ -265,5 +229,4 @@ Models = [
265
229
  ]
266
230
  Models << MysqlRegularARModel if defined?(MysqlRegularARModel)
267
231
  Models << PostgresqlRegularARModel if defined?(PostgresqlRegularARModel)
268
- Models << PostgresHstoreTypedStoreModel if defined?(PostgresHstoreTypedStoreModel)
269
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.1.3
4
+ version: 1.3.1
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-02-01 00:00:00.000000000 Z
11
+ date: 2020-07-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
@@ -157,14 +151,16 @@ files:
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-master
164
158
  - lib/active_record/typed_store.rb
159
+ - lib/active_record/typed_store/behavior.rb
165
160
  - lib/active_record/typed_store/dsl.rb
166
161
  - lib/active_record/typed_store/extension.rb
167
162
  - lib/active_record/typed_store/field.rb
163
+ - lib/active_record/typed_store/identity_coder.rb
168
164
  - lib/active_record/typed_store/type.rb
169
165
  - lib/active_record/typed_store/typed_hash.rb
170
166
  - lib/active_record/typed_store/version.rb
@@ -178,7 +174,7 @@ homepage: https://github.com/byroot/activerecord-typedstore
178
174
  licenses:
179
175
  - MIT
180
176
  metadata: {}
181
- post_install_message:
177
+ post_install_message:
182
178
  rdoc_options: []
183
179
  require_paths:
184
180
  - lib
@@ -193,9 +189,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
189
  - !ruby/object:Gem::Version
194
190
  version: '0'
195
191
  requirements: []
196
- rubyforge_project:
197
- rubygems_version: 2.7.3
198
- signing_key:
192
+ rubygems_version: 3.1.2
193
+ signing_key:
199
194
  specification_version: 4
200
195
  summary: Add type casting and full method attributes support to АctiveRecord store
201
196
  test_files:
@@ -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