plyushkin 0.0.6 → 0.0.7
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.
- data/.gitignore +2 -0
- data/Gemfile.lock +1 -1
- data/README.md +38 -2
- data/lib/plyushkin/base_value.rb +1 -1
- data/lib/plyushkin/core_ext/active_record_base.rb +7 -0
- data/lib/plyushkin/core_ext/plyushkin_extensions.rb +7 -0
- data/lib/plyushkin/model.rb +13 -3
- data/lib/plyushkin/persistence.rb +10 -3
- data/lib/plyushkin/property.rb +8 -7
- data/lib/plyushkin/version.rb +1 -1
- data/spec/lib/base_value_spec.rb +4 -0
- data/spec/lib/core_ext/active_record_base_spec.rb +118 -0
- data/spec/lib/property_spec.rb +14 -0
- metadata +2 -2
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -124,7 +124,7 @@ To get the latest oil change details
|
|
124
124
|
v.oil_change.last.mileage # => 1234
|
125
125
|
v.oil_change.last.oil_type # => '10W30'
|
126
126
|
|
127
|
-
#####
|
127
|
+
##### Specifying a callback after a value is stored
|
128
128
|
|
129
129
|
When defining a hoards property, you can set a callback for after the value is persisted. For example,
|
130
130
|
if the vehicle table contains a `next_oil_change_mileage` column, we might want to update it whenever
|
@@ -139,6 +139,42 @@ an oil_change is saved.
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
+
##### Filters
|
143
|
+
|
144
|
+
Sometimes it is useful to filter hoarded values. For example, we may want the ability to soft-delete
|
145
|
+
values from the historical properties (hard deletes is against the phylosophy of plyushkin),
|
146
|
+
but we can obtain similar results by:
|
147
|
+
|
148
|
+
class OilChangeValue < Plyushkin::BaseValue
|
149
|
+
persisted_attr :mileage, :formatter => :to_i
|
150
|
+
persisted_attr :oil_type
|
151
|
+
persisted_attr :is_deleted, :formatter => :to_bool
|
152
|
+
end
|
153
|
+
|
154
|
+
class Vehicle < ActiveRecord::Base
|
155
|
+
hoards :oil_change, :type => OilChangeValue, :filter => :soft_delete_filter
|
156
|
+
|
157
|
+
def soft_delete_filter(value)
|
158
|
+
value.is_deleted != true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
Now all values for the `oil_change` property will be filtered to exclude any values where `is_deleted == true`.
|
163
|
+
|
164
|
+
You can also specify a class level filter for all hoards that a class might implement by using:
|
165
|
+
|
166
|
+
class Vehicle < ActiveRecord::Base
|
167
|
+
filter_hoards_by :soft_delete_filter
|
168
|
+
end
|
169
|
+
|
170
|
+
This would cause all hoards to be filtered using the `soft_delete_filter`, unless this behavior was
|
171
|
+
overridden using a `:filter` parameter on the individual hoards.
|
172
|
+
|
173
|
+
If at anytime you wish to get unfiltered data from a property, you can still do that using the
|
174
|
+
`:unfiltered` option:
|
175
|
+
|
176
|
+
vehicle.oil_change.all(:unfiltered => true) #=> unfiltered data
|
177
|
+
|
142
178
|
##### Ignoring unchanged values
|
143
179
|
|
144
180
|
There may be a case where we don't need to track history when a value is set, but is the same as the
|
@@ -236,7 +272,7 @@ To test Plyushkin configuration in your model:
|
|
236
272
|
|
237
273
|
describe Vehicle do
|
238
274
|
it { should hoard(:mechanic) }
|
239
|
-
it { should hoard(:mechanic).and_ignore_unchanged_values }
|
275
|
+
it { should hoard(:mechanic).and_ignore_unchanged_values }
|
240
276
|
it { should hoard(:oil_change).of_type(OilChangeValue) }
|
241
277
|
it { should hoard(:oil_change).of_type(OilChangeValue).
|
242
278
|
and_after_create_call(:calculate_next_oil_change_mileage) }
|
data/lib/plyushkin/base_value.rb
CHANGED
@@ -9,6 +9,13 @@ ActiveRecord::Base.instance_eval do
|
|
9
9
|
|
10
10
|
plyushkin_model.register(name, opts[:type] || Plyushkin::StringValue, opts)
|
11
11
|
plyushkin_model.register_callback(name, :after_create, opts[:after_create]) if opts[:after_create]
|
12
|
+
plyushkin_model.register_filter(name, opts[:filter]) if opts[:filter]
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter_hoards_by(filter)
|
16
|
+
initialize_plyushkin
|
17
|
+
|
18
|
+
plyushkin_model.hoarding_filter = filter
|
12
19
|
end
|
13
20
|
|
14
21
|
def initialize_plyushkin
|
@@ -17,6 +17,13 @@ module PlyushkinExtensions
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
self.class.plyushkin_model.registered_types.each do |name, type|
|
21
|
+
filter = self.class.plyushkin_model.filters[name] || self.class.plyushkin_model.hoarding_filter
|
22
|
+
plyushkin.register_filter(name) do |value|
|
23
|
+
(filter && filter.is_a?(Symbol)) ? send(filter, value) : filter.call(value)
|
24
|
+
end if filter
|
25
|
+
end
|
26
|
+
|
20
27
|
plyushkin.load(id)
|
21
28
|
end
|
22
29
|
|
data/lib/plyushkin/model.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
class Plyushkin::Model
|
2
2
|
attr_reader :service, :name, :cache
|
3
|
+
attr_accessor :hoarding_filter
|
3
4
|
|
4
5
|
def initialize(service, name, cache)
|
5
6
|
raise Plyushkin::Error.new <<-ERROR unless service
|
@@ -10,19 +11,24 @@ class Plyushkin::Model
|
|
10
11
|
@types = {}
|
11
12
|
@ignore_unchanged_values = {}
|
12
13
|
@callbacks = {}
|
14
|
+
@filters = {}
|
13
15
|
@name = name
|
14
16
|
@cache = cache
|
15
17
|
end
|
16
18
|
|
17
|
-
def register(name, type, opts={})
|
18
|
-
@types[name]
|
19
|
-
@ignore_unchanged_values[name] = opts[:ignore_unchanged_values]
|
19
|
+
def register(name, type, opts = {})
|
20
|
+
@types[name] = type
|
21
|
+
@ignore_unchanged_values[name] = opts[:ignore_unchanged_values]
|
20
22
|
end
|
21
23
|
|
22
24
|
def register_callback(name, callback, method_sym)
|
23
25
|
@callbacks[name] = { callback => method_sym }
|
24
26
|
end
|
25
27
|
|
28
|
+
def register_filter(name, method_sym)
|
29
|
+
@filters[name] = method_sym
|
30
|
+
end
|
31
|
+
|
26
32
|
def registered_types
|
27
33
|
@types.dup
|
28
34
|
end
|
@@ -34,4 +40,8 @@ class Plyushkin::Model
|
|
34
40
|
def ignore_unchanged_values
|
35
41
|
@ignore_unchanged_values.dup
|
36
42
|
end
|
43
|
+
|
44
|
+
def filters
|
45
|
+
@filters.dup
|
46
|
+
end
|
37
47
|
end
|
@@ -3,6 +3,7 @@ class Plyushkin::Persistence
|
|
3
3
|
def initialize(model)
|
4
4
|
@model = model
|
5
5
|
@callbacks = {}
|
6
|
+
@filters = {}
|
6
7
|
end
|
7
8
|
|
8
9
|
def properties
|
@@ -26,8 +27,9 @@ class Plyushkin::Persistence
|
|
26
27
|
@properties = {}
|
27
28
|
cached(id).each do |name, values|
|
28
29
|
property = Plyushkin::Property.load(name, model.registered_types[name.to_sym], values,
|
29
|
-
|
30
|
-
|
30
|
+
:callbacks => @callbacks[name.to_sym],
|
31
|
+
:ignore_unchanged_values => model.ignore_unchanged_values[name.to_sym],
|
32
|
+
:filter => @filters[name.to_sym])
|
31
33
|
@properties[name.to_sym] = property
|
32
34
|
end if id
|
33
35
|
add_missing_properties
|
@@ -37,6 +39,10 @@ class Plyushkin::Persistence
|
|
37
39
|
@callbacks[name] = { callback => block }
|
38
40
|
end
|
39
41
|
|
42
|
+
def register_filter(name, &block)
|
43
|
+
@filters[name] = block
|
44
|
+
end
|
45
|
+
|
40
46
|
private
|
41
47
|
def model
|
42
48
|
@model
|
@@ -60,7 +66,8 @@ class Plyushkin::Persistence
|
|
60
66
|
property = Plyushkin::Property.new(name,
|
61
67
|
:type => model.registered_types[name],
|
62
68
|
:callbacks => @callbacks[name],
|
63
|
-
:ignore_unchanged_values =>
|
69
|
+
:ignore_unchanged_values => model.ignore_unchanged_values[name],
|
70
|
+
:filter => @filters[name])
|
64
71
|
@properties[name] = property
|
65
72
|
end
|
66
73
|
end
|
data/lib/plyushkin/property.rb
CHANGED
@@ -11,12 +11,13 @@ class Plyushkin::Property
|
|
11
11
|
end unless last.valid?
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(name, opts={})
|
15
|
-
@values
|
16
|
-
@value_type
|
17
|
-
@callbacks
|
14
|
+
def initialize(name, opts = {})
|
15
|
+
@values = []
|
16
|
+
@value_type = opts[:type]
|
17
|
+
@callbacks = opts[:callbacks]
|
18
|
+
@filter = opts[:filter]
|
18
19
|
@ignore_unchanged_values = opts[:ignore_unchanged_values]
|
19
|
-
@name
|
20
|
+
@name = name.to_s
|
20
21
|
end
|
21
22
|
|
22
23
|
def create(attr={})
|
@@ -42,8 +43,8 @@ class Plyushkin::Property
|
|
42
43
|
@callbacks.fetch(sym, DEFAULT_CALLBACK).call if @callbacks
|
43
44
|
end
|
44
45
|
|
45
|
-
def all
|
46
|
-
@values
|
46
|
+
def all(opts = {})
|
47
|
+
(@filter && !opts[:unfiltered]) ? @values.select(&@filter) : @values
|
47
48
|
end
|
48
49
|
|
49
50
|
def last
|
data/lib/plyushkin/version.rb
CHANGED
data/spec/lib/base_value_spec.rb
CHANGED
@@ -138,6 +138,10 @@ describe Plyushkin::BaseValue do
|
|
138
138
|
it 'should return nil when arg is nil' do
|
139
139
|
Plyushkin::BaseValue.new.to_i(nil).should be_nil
|
140
140
|
end
|
141
|
+
|
142
|
+
it 'should return an integer when arg is a float' do
|
143
|
+
Plyushkin::BaseValue.new.to_i("2.0").should == 2
|
144
|
+
end
|
141
145
|
end
|
142
146
|
|
143
147
|
describe '#to_f' do
|
@@ -104,6 +104,124 @@ describe ActiveRecord::Base do
|
|
104
104
|
|
105
105
|
end
|
106
106
|
|
107
|
+
describe 'filter option' do
|
108
|
+
it 'should filter results when a filter is specified' do
|
109
|
+
clazz = Class.new(Plyushkin::Test::Member) do
|
110
|
+
hoards :test_value, :filter => Proc.new{ |v| v.value > 5 }
|
111
|
+
end
|
112
|
+
|
113
|
+
member = clazz.new
|
114
|
+
member.test_value.create(:value => 3)
|
115
|
+
value = member.test_value.create(:value => 9)
|
116
|
+
|
117
|
+
member.test_value.all.should == [ value ]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should filter results when a filter is specified as a symbol' do
|
121
|
+
clazz = Class.new(Plyushkin::Test::Member) do
|
122
|
+
hoards :test_value, :filter => :filter_by
|
123
|
+
|
124
|
+
def filter_by(value)
|
125
|
+
value.value > 5
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
member = clazz.new
|
130
|
+
member.test_value.create(:value => 3)
|
131
|
+
value = member.test_value.create(:value => 9)
|
132
|
+
|
133
|
+
member.test_value.all.should == [ value ]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe 'class level filter for plyushkin' do
|
138
|
+
it 'should filter all hoards if no filter is specified' do
|
139
|
+
clazz = Class.new(Plyushkin::Test::Member) do
|
140
|
+
filter_hoards_by :filter_by
|
141
|
+
hoards :test_value
|
142
|
+
hoards :bacon
|
143
|
+
|
144
|
+
def filter_by(value)
|
145
|
+
value.value > 5
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
member = clazz.new
|
150
|
+
member.test_value.create(:value => 3)
|
151
|
+
value = member.test_value.create(:value => 9)
|
152
|
+
|
153
|
+
member.test_value.all.should == [ value ]
|
154
|
+
|
155
|
+
member.bacon.create(:value => 4)
|
156
|
+
member.bacon.all.should == []
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should filter all hoards if no filter is specified' do
|
160
|
+
clazz = Class.new(Plyushkin::Test::Member) do
|
161
|
+
hoards :test_value
|
162
|
+
hoards :bacon
|
163
|
+
filter_hoards_by :filter_by
|
164
|
+
|
165
|
+
def filter_by(value)
|
166
|
+
value.value > 5
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
member = clazz.new
|
171
|
+
member.test_value.create(:value => 3)
|
172
|
+
value = member.test_value.create(:value => 9)
|
173
|
+
|
174
|
+
member.test_value.all.should == [ value ]
|
175
|
+
|
176
|
+
member.bacon.create(:value => 4)
|
177
|
+
member.bacon.all.should == []
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'filter should override class level filter' do
|
181
|
+
clazz = Class.new(Plyushkin::Test::Member) do
|
182
|
+
filter_hoards_by :filter_by
|
183
|
+
hoards :test_value
|
184
|
+
hoards :bacon, :filter => Proc.new {|v| v.value == 4}
|
185
|
+
|
186
|
+
def filter_by(value)
|
187
|
+
value.value > 5
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
member = clazz.new
|
192
|
+
member.test_value.create(:value => 3)
|
193
|
+
value = member.test_value.create(:value => 9)
|
194
|
+
|
195
|
+
member.test_value.all.should == [ value ]
|
196
|
+
|
197
|
+
bacon_value = member.bacon.create(:value => 4)
|
198
|
+
member.bacon.all.should == [ bacon_value ]
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'filter should override class level filter' do
|
203
|
+
clazz = Class.new(Plyushkin::Test::Member) do
|
204
|
+
hoards :test_value
|
205
|
+
hoards :bacon, :filter => Proc.new {|v| v.value == 4}
|
206
|
+
filter_hoards_by :filter_by
|
207
|
+
|
208
|
+
def filter_by(value)
|
209
|
+
value.value > 5
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
member = clazz.new
|
214
|
+
member.test_value.create(:value => 3)
|
215
|
+
value = member.test_value.create(:value => 9)
|
216
|
+
|
217
|
+
member.test_value.all.should == [ value ]
|
218
|
+
|
219
|
+
bacon_value = member.bacon.create(:value => 4)
|
220
|
+
member.bacon.all.should == [ bacon_value ]
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
107
225
|
describe '#validates' do
|
108
226
|
it 'should not be valid if hoarding property is not valid' do
|
109
227
|
clazz = Class.new(Plyushkin::Test::Member) do
|
data/spec/lib/property_spec.rb
CHANGED
@@ -157,6 +157,20 @@ describe Plyushkin::Property do
|
|
157
157
|
value2 = property.create(:value => 7, :date => DateTime.now - 7.days)
|
158
158
|
property.all.should == [ value2, value1 ]
|
159
159
|
end
|
160
|
+
|
161
|
+
it 'should apply a default filter, if defined' do
|
162
|
+
property = Plyushkin::Property.new(:property_name, :filter => Proc.new { |v| v.value > 5 })
|
163
|
+
value1 = property.create(:value => 5, :date => DateTime.now - 5.days)
|
164
|
+
value2 = property.create(:value => 7, :date => DateTime.now - 7.days)
|
165
|
+
property.all.should == [ value2 ]
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should return all data if :unfiltered => true' do
|
169
|
+
property = Plyushkin::Property.new(:property_name, :filter => Proc.new { |v| v.value > 5 })
|
170
|
+
value1 = property.create(:value => 5, :date => DateTime.now - 5.days)
|
171
|
+
value2 = property.create(:value => 7, :date => DateTime.now - 7.days)
|
172
|
+
property.all(:unfiltered => true).should == [ value2, value1 ]
|
173
|
+
end
|
160
174
|
end
|
161
175
|
|
162
176
|
describe '#last' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plyushkin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-11-12 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|