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 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