store_attribute 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
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