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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +8 -3
- data/lib/store_attribute/active_record/type/typed_store.rb +37 -17
- data/lib/store_attribute/version.rb +1 -1
- data/spec/cases/store_attribute_spec.rb +44 -1
- data/spec/store_attribute/typed_store_spec.rb +131 -0
- data/spec/support/user.rb +6 -1
- data/store_attribute.gemspec +8 -0
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44ef41ba6670ce7ce109d0a856948ba3ae113328037e9b52025b7a7229b6580d
|
4
|
+
data.tar.gz: e25ade087cdad2fe69394462b227a0c1b580b4a0d977a8f505c6e28af916e143
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
@@ -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(
|
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]
|
data/store_attribute.gemspec
CHANGED
@@ -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.
|
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-
|
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.
|
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
|