plyushkin 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,5 @@
3
3
  *.swp
4
4
  *.swo
5
5
  plyushkin*.gem
6
+ vendor/bundle
7
+ tags
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plyushkin (0.0.5)
4
+ plyushkin (0.0.6)
5
5
  activerecord (~> 3.2.12)
6
6
 
7
7
  GEM
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
- ##### Specifiying a callback after a value is stored
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) }
@@ -74,7 +74,7 @@ class Plyushkin::BaseValue
74
74
  end
75
75
 
76
76
  def to_i(value)
77
- value =~ /\A\d+\Z/ ? value.to_i : value
77
+ value =~ /\A\d+(\.\d+)?\Z/ ? value.to_i : value
78
78
  end
79
79
 
80
80
  #TODO: Maybe this can be nicer.
@@ -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
 
@@ -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] = type
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
- :callbacks => @callbacks[name.to_sym],
30
- :ignore_unchanged_values => @model.ignore_unchanged_values[name.to_sym] )
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 => @model.ignore_unchanged_values[name])
69
+ :ignore_unchanged_values => model.ignore_unchanged_values[name],
70
+ :filter => @filters[name])
64
71
  @properties[name] = property
65
72
  end
66
73
  end
@@ -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 = opts[:type]
17
- @callbacks = opts[: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 = name.to_s
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
@@ -1,3 +1,3 @@
1
1
  module Plyushkin
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -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
@@ -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.6
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-07-28 00:00:00.000000000 Z
13
+ date: 2014-11-12 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord