store_attribute 0.5.3 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26bfe00fc2e06215356a2e050700f0d257e56401249c97487e26f58133f4ea0e
4
- data.tar.gz: 5e9ba6def76cf0f0aa77130db602929da9725f1483508e244557e90e8bd22892
3
+ metadata.gz: 44ef41ba6670ce7ce109d0a856948ba3ae113328037e9b52025b7a7229b6580d
4
+ data.tar.gz: e25ade087cdad2fe69394462b227a0c1b580b4a0d977a8f505c6e28af916e143
5
5
  SHA512:
6
- metadata.gz: 3de11ae80f51e606826b3dbbdd92afa4a7443ec24ef595c616908adb23f2588908052f5db52d963164843aa0868c30747b54921e9fdac2b7deaede4b770413b3
7
- data.tar.gz: 6032541f1dd51712f9755b9ca8ccfa25b145c670d319a3dde909917fb0b7447069600a3fdd7c8fce40e131733fa6495e7be420f7a77aa5f60a88c36c28301f3a
6
+ metadata.gz: 29f7c336831838593456099308e7549e41a6d879585c1fb6f92dc2e48ba5150c4bba6856b2b9cb6e680c0b02882d8015f76d16bfcabab2ab73476d7e7dc4bc47
7
+ data.tar.gz: 0ca99dc3856dfef871cf57729bef285118e2a775b9dbb4b64038d39c2f6480aa9d6c162e97d54ffa7ea5d0d100cd3fccc016658268e76dcba6bed3d2ef7e0fa5
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Change log
2
+
3
+ ## master
4
+
5
+ ## 0.6.0 (2019-07-24)
6
+
7
+ - Added default values support. ([@dreikanter][], [@SumLare][])
8
+
9
+ See [PR #7](https://github.com/palkan/store_attribute/pull/7).
10
+
11
+ - Start keeping changelog. ([@palkan][])
12
+
13
+ [@palkan]: https://github.com/palkan
14
+ [@dreikanter]: https://github.com/dreikanter
15
+ [@SumLare]: https://github.com/SumLare
data/README.md CHANGED
@@ -6,8 +6,7 @@ ActiveRecord extension which adds typecasting to store accessors.
6
6
 
7
7
  Compatible with Rails 4.2 and Rails 5+.
8
8
 
9
- <a href="https://evilmartians.com/">
10
- <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
9
+ Extracted from not merged PR to Rails: [rails/rails#18942](https://github.com/rails/rails/pull/18942).
11
10
 
12
11
  ### Install
13
12
 
@@ -33,7 +32,7 @@ Where:
33
32
  - `store_name` The name of the store.
34
33
  - `name` The name of the accessor to the store.
35
34
  - `type` A symbol such as `:string` or `:integer`, or a type object to be used for the accessor.
36
- - `options` (optional) A hash of cast type options such as `precision`, `limit`, `scale`.
35
+ - `options` (optional) A hash of cast type options such as `precision`, `limit`, `scale`, `default`.
37
36
 
38
37
  Type casting occurs every time you write data through accessor or update store itself
39
38
  and when object is loaded from database.
@@ -47,6 +46,8 @@ class MegaUser < User
47
46
  store_attribute :settings, :ratio, :integer, limit: 1
48
47
  store_attribute :settings, :login_at, :datetime
49
48
  store_attribute :settings, :active, :boolean
49
+ store_attribute :settings, :color, :string, default: "red"
50
+ store_attribute :settings, :data, :datetime, default: -> { Time.now }
50
51
  end
51
52
 
52
53
  u = MegaUser.new(active: false, login_at: "2015-01-01 00:01", ratio: "63.4608")
@@ -55,6 +56,10 @@ u.login_at.is_a?(DateTime) # => true
55
56
  u.login_at = DateTime.new(2015, 1, 1, 11, 0, 0)
56
57
  u.ratio # => 63
57
58
  u.active # => false
59
+ # Default value is set
60
+ u.color # => red
61
+ # A dynamic default can also be provided
62
+ u.data # => Current time
58
63
  # And we also have a predicate method
59
64
  u.active? # => false
60
65
  u.reload
@@ -15,43 +15,57 @@ module ActiveRecord
15
15
 
16
16
  def initialize(subtype)
17
17
  @accessor_types = {}
18
+ @defaults = {}
18
19
  @store_accessor = subtype.accessor
19
20
  super(subtype)
20
21
  end
21
22
 
22
- def add_typed_key(key, type, **options)
23
+ UNDEFINED = Object.new
24
+ private_constant :UNDEFINED
25
+
26
+ def add_typed_key(key, type, default: UNDEFINED, **options)
23
27
  type = ActiveRecord::Type.lookup(type, options) if type.is_a?(Symbol)
24
- @accessor_types[key.to_s] = type
28
+ safe_key = key.to_s
29
+ @accessor_types[safe_key] = type
30
+ @defaults[safe_key] = default unless default == UNDEFINED
25
31
  end
26
32
 
27
33
  def deserialize(value)
28
34
  hash = super
29
- if hash
30
- accessor_types.each do |key, type|
31
- hash[key] = type.deserialize(hash[key]) if hash.key?(key)
35
+ return hash unless hash
36
+ accessor_types.each do |key, type|
37
+ if hash.key?(key)
38
+ hash[key] = type.deserialize(hash[key])
39
+ elsif defaults.key?(key)
40
+ hash[key] = get_default(key)
32
41
  end
33
42
  end
34
43
  hash
35
44
  end
36
45
 
37
46
  def serialize(value)
38
- if value.is_a?(Hash)
39
- typed_casted = {}
40
- accessor_types.each do |key, type|
41
- k = key_to_cast(value, key)
42
- typed_casted[k] = type.serialize(value[k]) unless k.nil?
47
+ return super(value) unless value.is_a?(Hash)
48
+ typed_casted = {}
49
+ accessor_types.each do |str_key, type|
50
+ key = key_to_cast(value, str_key)
51
+ next unless key
52
+ if value.key?(key)
53
+ typed_casted[key] = type.serialize(value[key])
54
+ elsif defaults.key?(str_key)
55
+ typed_casted[key] = type.serialize(get_default(str_key))
43
56
  end
44
- super(value.merge(typed_casted))
45
- else
46
- super(value)
47
57
  end
58
+ super(value.merge(typed_casted))
48
59
  end
49
60
 
50
61
  def cast(value)
51
62
  hash = super
52
- if hash
53
- accessor_types.each do |key, type|
54
- hash[key] = type.cast(hash[key]) if hash.key?(key)
63
+ return hash unless hash
64
+ accessor_types.each do |key, type|
65
+ if hash.key?(key)
66
+ hash[key] = type.cast(hash[key])
67
+ elsif defaults.key?(key)
68
+ hash[key] = get_default(key)
55
69
  end
56
70
  end
57
71
  hash
@@ -74,6 +88,7 @@ module ActiveRecord
74
88
  def key_to_cast(val, key)
75
89
  return key if val.key?(key)
76
90
  return key.to_sym if val.key?(key.to_sym)
91
+ return key if defaults.key?(key)
77
92
  end
78
93
 
79
94
  def typed?(key)
@@ -84,7 +99,12 @@ module ActiveRecord
84
99
  accessor_types.fetch(key.to_s)
85
100
  end
86
101
 
87
- attr_reader :accessor_types, :store_accessor
102
+ def get_default(key)
103
+ value = defaults.fetch(key)
104
+ value.is_a?(Proc) ? value.call : value
105
+ end
106
+
107
+ attr_reader :accessor_types, :defaults, :store_accessor
88
108
  end
89
109
  end
90
110
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StoreAttribute # :nodoc:
4
- VERSION = "0.5.3"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -21,6 +21,9 @@ describe StoreAttribute do
21
21
  @connection.drop_table "users", if_exists: true
22
22
  end
23
23
 
24
+ let(:date) { Date.new(2019, 7, 17) }
25
+ let(:default_date) { User::DEFAULT_DATE }
26
+ let(:dynamic_date) { User::TODAY_DATE }
24
27
  let(:time) { DateTime.new(2015, 2, 14, 17, 0, 0) }
25
28
  let(:time_str) { "2015-02-14 17:00" }
26
29
  let(:time_str_utc) { "2015-02-14 17:00:00 UTC" }
@@ -120,7 +123,12 @@ describe StoreAttribute do
120
123
 
121
124
  it "re-typecast old data" do
122
125
  jamie = User.create!
123
- User.update_all('jparams = \'{"active":"1", "salary":"12.02"}\'::jsonb')
126
+ User.update_all(
127
+ "jparams = '{"\
128
+ '"active":"1",'\
129
+ '"salary":"12.02"'\
130
+ "}'::jsonb"
131
+ )
124
132
 
125
133
  jamie = User.find(jamie.id)
126
134
  expect(jamie).to be_active
@@ -170,4 +178,39 @@ describe StoreAttribute do
170
178
  expect(jamie.inner_json).to eq("x" => 1)
171
179
  end
172
180
  end
181
+
182
+ context "default option" do
183
+ it "should init the field after an object is created" do
184
+ jamie = User.new
185
+ expect(jamie.static_date).to eq(default_date)
186
+ end
187
+
188
+ it "should not affect explicit initialization" do
189
+ jamie = User.new(static_date: date)
190
+ expect(jamie.static_date).to eq(date)
191
+ end
192
+
193
+ it "should not affect explicit nil initialization" do
194
+ jamie = User.new(static_date: nil)
195
+ expect(jamie.static_date).to be_nil
196
+ end
197
+
198
+ it "should handle a static value" do
199
+ jamie = User.create!
200
+ jamie = User.find(jamie.id)
201
+ expect(jamie.static_date).to eq(default_date)
202
+ end
203
+
204
+ it "should handle a lambda" do
205
+ jamie = User.create!
206
+ jamie = User.find(jamie.id)
207
+ expect(jamie.dynamic_date).to eq(dynamic_date)
208
+ end
209
+
210
+ it "should handle nil" do
211
+ jamie = User.create!
212
+ jamie = User.find(jamie.id)
213
+ expect(jamie.empty_date).to be_nil
214
+ end
215
+ end
173
216
  end
@@ -20,6 +20,37 @@ describe ActiveRecord::Type::TypedStore do
20
20
  date = ::Date.new(2016, 6, 22)
21
21
  expect(subject.cast(date: "2016-06-22")).to eq("date" => date)
22
22
  end
23
+
24
+ it "with default" do
25
+ date = ::Date.new(2016, 6, 22)
26
+ subject.add_typed_key("date", :date, default: date)
27
+
28
+ expect(subject.cast({})).to eq("date" => date)
29
+ end
30
+
31
+ it "with dynamic default" do
32
+ date = ::Date.new(2016, 6, 22)
33
+ subject.add_typed_key("date", :date, default: -> { date })
34
+
35
+ expect(subject.cast({})).to eq("date" => date)
36
+ end
37
+
38
+ it "with default and explicit nil" do
39
+ date = ::Date.new(2016, 6, 22)
40
+ subject.add_typed_key("date", :date, default: date)
41
+ nil_date = {"date" => nil}
42
+
43
+ expect(subject.cast(nil_date)).to eq(nil_date)
44
+ end
45
+
46
+ it "with default and existing value" do
47
+ default = ::Date.new(2016, 6, 22)
48
+ expected = ::Date.new(2019, 7, 17)
49
+ subject.add_typed_key("date", :date, default: default)
50
+ date = {"date" => expected}
51
+
52
+ expect(subject.cast("date" => expected.to_s)).to eq(date)
53
+ end
23
54
  end
24
55
 
25
56
  describe "#deserialize" do
@@ -34,6 +65,37 @@ describe ActiveRecord::Type::TypedStore do
34
65
  date = ::Date.new(2016, 6, 22)
35
66
  expect(subject.deserialize('{"date":"2016-06-22"}')).to eq("date" => date)
36
67
  end
68
+
69
+ it "with default" do
70
+ date = ::Date.new(2016, 6, 22)
71
+ subject.add_typed_key("date", :date, default: date)
72
+
73
+ expect(subject.deserialize("{}")).to eq("date" => date)
74
+ end
75
+
76
+ it "with dynamic default" do
77
+ date = ::Date.new(2016, 6, 22)
78
+ subject.add_typed_key("date", :date, default: -> { date })
79
+
80
+ expect(subject.deserialize("{}")).to eq("date" => date)
81
+ end
82
+
83
+ it "with default and explicit nil" do
84
+ date = ::Date.new(2016, 6, 22)
85
+ subject.add_typed_key("date", :date, default: date)
86
+ s11n = {"date" => nil}.to_json
87
+
88
+ expect(subject.deserialize(s11n)).to eq("date" => nil)
89
+ end
90
+
91
+ it "with default and existing value" do
92
+ default = ::Date.new(2016, 6, 22)
93
+ expected = ::Date.new(2019, 7, 17)
94
+ subject.add_typed_key("date", :date, default: default)
95
+ s11n = {"date" => expected}.to_json
96
+
97
+ expect(subject.deserialize(s11n)).to eq("date" => expected)
98
+ end
37
99
  end
38
100
 
39
101
  describe "#serialize" do
@@ -54,6 +116,25 @@ describe ActiveRecord::Type::TypedStore do
54
116
 
55
117
  expect { subject.serialize(val: 1024) }.to raise_error(RangeError)
56
118
  end
119
+
120
+ it "with type key and a default" do
121
+ date = ::Date.new(2016, 6, 22)
122
+ subject.add_typed_key("date", :date, default: date)
123
+ expect(subject.serialize({})).to eq({date: date}.to_json)
124
+ end
125
+
126
+ it "with type key and a dynamic default" do
127
+ date = ::Date.today
128
+ default = -> { date }
129
+ subject.add_typed_key("date", :date, default: default)
130
+ expect(subject.serialize({})).to eq({date: date}.to_json)
131
+ end
132
+
133
+ it "with a symbolic type key" do
134
+ date = ::Date.new(2016, 6, 22)
135
+ subject.add_typed_key(:date, :date, default: date)
136
+ expect(subject.serialize({})).to eq({date: date}.to_json)
137
+ end
57
138
  end
58
139
 
59
140
  describe ".create_from_type" do
@@ -66,6 +147,20 @@ describe ActiveRecord::Type::TypedStore do
66
147
  expect(type.cast(date: "2016-06-22", val: "1.2")).to eq("date" => date, "val" => "1.2")
67
148
  expect(new_type.cast(date: "2016-06-22", val: "1.2")).to eq("date" => date, "val" => 1)
68
149
  end
150
+
151
+ it "accepts default", :aggregate_failures do
152
+ default = 1
153
+ type = described_class.create_from_type(json_type, "val", :date, default: default)
154
+
155
+ expect(type.cast({})).to eq("val" => default)
156
+ end
157
+
158
+ it "accepts dynamic default", :aggregate_failures do
159
+ default = 1
160
+ type = described_class.create_from_type(json_type, "val", :date, default: -> { default })
161
+
162
+ expect(type.cast({})).to eq("val" => default)
163
+ end
69
164
  end
70
165
  end
71
166
 
@@ -105,5 +200,41 @@ describe ActiveRecord::Type::TypedStore do
105
200
  expect(subject.serialize(date: date)).to eq "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2016-06-22\n"
106
201
  expect(subject.serialize("date" => date)).to eq "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2016-06-22\n"
107
202
  end
203
+
204
+ it "uses default" do
205
+ default = ::Date.new(2019, 7, 17)
206
+ subject.add_typed_key("date", :date, default: default)
207
+
208
+ expect(subject.deserialize("---\n")).to eq("date" => default)
209
+ end
210
+
211
+ it "uses dynamic default" do
212
+ default = -> { ::Date.new(2019, 7, 17) }
213
+ subject.add_typed_key("date", :date, default: default)
214
+
215
+ expect(subject.deserialize("---\n")).to eq("date" => default.call)
216
+ end
217
+
218
+ it "keeps explicit nil" do
219
+ default = ::Date.new(2019, 7, 17)
220
+ subject.add_typed_key("date", :date, default: default)
221
+
222
+ expect(subject.deserialize("---\ndate: null\n")).to eq("date" => nil)
223
+ end
224
+
225
+ it "keeps existing value" do
226
+ default = ::Date.new(2019, 7, 17)
227
+ date = ::Date.new(2016, 6, 22)
228
+ subject.add_typed_key("date", :date, default: default)
229
+
230
+ expect(subject.deserialize("---\ndate: #{date}\n")).to eq("date" => date)
231
+ end
232
+
233
+ it "with a default" do
234
+ default = ::Date.new(2019, 7, 17)
235
+ subject.add_typed_key("date", :date, default: default)
236
+ expected = "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ndate: 2019-07-17\n"
237
+ expect(subject.serialize({})).to eq(expected)
238
+ end
108
239
  end
109
240
  end
data/spec/support/user.rb CHANGED
@@ -5,9 +5,14 @@ class RawUser < ActiveRecord::Base
5
5
  end
6
6
 
7
7
  class User < ActiveRecord::Base
8
+ DEFAULT_DATE = ::Date.new(2019, 7, 17)
9
+ TODAY_DATE = ::Date.today
10
+
8
11
  store_accessor :jparams, :version, active: :boolean, salary: :integer
9
12
  store_attribute :jparams, :birthday, :date
10
-
13
+ store_attribute :jparams, :static_date, :date, default: DEFAULT_DATE
14
+ store_attribute :jparams, :dynamic_date, :date, default: -> { TODAY_DATE }
15
+ store_attribute :jparams, :empty_date, :date, default: nil
11
16
  store_attribute :jparams, :inner_json, :json
12
17
 
13
18
  store :custom, accessors: [price: :money_type]
@@ -21,6 +21,14 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.required_ruby_version = ">= 2.4.0"
23
23
 
24
+ s.metadata = {
25
+ "bug_tracker_uri" => "http://github.com/palkan/store_attribute/issues",
26
+ "changelog_uri" => "https://github.com/palkan/store_attribute/blob/master/CHANGELOG.md",
27
+ "documentation_uri" => "http://github.com/palkan/store_attribute",
28
+ "homepage_uri" => "http://github.com/palkan/store_attribute",
29
+ "source_code_uri" => "http://github.com/palkan/store_attribute"
30
+ }
31
+
24
32
  s.add_runtime_dependency "activerecord", ">= 5.0"
25
33
 
26
34
  s.add_development_dependency "pg", ">= 0.18"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: store_attribute
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-10 00:00:00.000000000 Z
11
+ date: 2019-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -147,6 +147,7 @@ files:
147
147
  - ".rspec"
148
148
  - ".rubocop.yml"
149
149
  - ".travis.yml"
150
+ - CHANGELOG.md
150
151
  - Gemfile
151
152
  - MIT-LICENSE
152
153
  - README.md
@@ -172,7 +173,12 @@ files:
172
173
  homepage: http://github.com/palkan/store_attribute
173
174
  licenses:
174
175
  - MIT
175
- metadata: {}
176
+ metadata:
177
+ bug_tracker_uri: http://github.com/palkan/store_attribute/issues
178
+ changelog_uri: https://github.com/palkan/store_attribute/blob/master/CHANGELOG.md
179
+ documentation_uri: http://github.com/palkan/store_attribute
180
+ homepage_uri: http://github.com/palkan/store_attribute
181
+ source_code_uri: http://github.com/palkan/store_attribute
176
182
  post_install_message:
177
183
  rdoc_options: []
178
184
  require_paths:
@@ -188,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
194
  - !ruby/object:Gem::Version
189
195
  version: '0'
190
196
  requirements: []
191
- rubygems_version: 3.0.2
197
+ rubygems_version: 3.0.3
192
198
  signing_key:
193
199
  specification_version: 4
194
200
  summary: ActiveRecord extension which adds typecasting to store accessors