plyushkin 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  .bundle
2
2
  .DS_Store
3
3
  *.swp
4
+ *.swo
4
5
  plyushkin*.gem
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: 1.9.3
3
+ script: bundle exec rake spec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plyushkin (0.0.4)
4
+ plyushkin (0.0.5)
5
5
  activerecord (~> 3.2.12)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Plyushkin
2
2
 
3
- TODO: Write a gem description
3
+ [![Code Climate](https://codeclimate.com/github/OnlifeHealth/plyushkin.png)](https://codeclimate.com/github/OnlifeHealth/plyushkin) [![Gem Version](https://badge.fury.io/rb/plyushkin.svg)](http://badge.fury.io/rb/plyushkin)
4
+ [![Build Status](https://travis-ci.org/OnlifeHealth/plyushkin.png)](https://travis-ci.org/OnlifeHealth/plyushkin)
5
+
6
+ Plyushkin provides a way to capture historical data in an ActiveRecord class.
4
7
 
5
8
  ## Installation
6
9
 
@@ -18,7 +21,7 @@ Or install it yourself as:
18
21
 
19
22
  ## Usage
20
23
 
21
- Configure the backend service that plyushkin will use in your environments/<environment>.rb file.
24
+ Configure the backend service that plyushkin will use in your environments/&lt;environment&gt;.rb file.
22
25
  For example, to configure the stub service for running specs, the following code would go in your
23
26
  config/environments/test.rb file of a Rails application.
24
27
 
@@ -27,20 +30,216 @@ config/environments/test.rb file of a Rails application.
27
30
  end
28
31
 
29
32
  To use plyushkin against a live web service,
30
- assign an instance of ``Plyushkin::Service::Web.new(:url => 'http://yourservice.com')``
33
+
34
+ config.before_initialize do |c|
35
+ Plyushkin::Service.service =
36
+ Plyushkin::Service::Web.new(:url => 'http://yourservice.com')
37
+ end
38
+
39
+ ## Quick start
40
+ To add a property, use the `hoards` class macro on a class that inherits from ActiveRecord::Base.
41
+
42
+ class Vehicle < ActiveRecord::Base
43
+ hoards :mechanic
44
+ end
45
+
46
+ This will add a mechanic attribute to the Vehicle class. To set the mechanic for the vehicle
47
+
48
+ v = Vehicle.new
49
+ v.mechanic.create(:value => 'Mike')
50
+
51
+ This will update the in-memory version of the vehicle, but has not yet persisted the change. To
52
+ save the change to the plyushkin-service
53
+
54
+ v.save
55
+
56
+ To get the value of the vehicle's mechanic
57
+
58
+ v.mechanic.last.value # => 'Mike'
59
+
60
+ To get the date that the mechanic was last set
61
+
62
+ v.mechanic.last.date # => Wed, 09 Jul 2014 11:52:12 -0500
63
+
64
+ Every Plyushkin value has a `date` attribute.
65
+
66
+ Once we change the mechanic on the vehicle, we will now have historical data and can view all the
67
+ mechanics that worked on the vehicle.
68
+
69
+ v.mechanic.create(:value => 'Joe')
70
+ v.mechanic.all.map(&:value) # => ['Mike', 'Joe']
71
+
72
+ #### Setting past values
73
+
74
+ To store a property using a DateTime other than the current time
75
+
76
+ v.mechanic.create(:value => 'Sally', :date => 3.days.ago)
77
+ v.mechanic.last.date # => Sun, 06 Jul 2014 11:52:12 -0500
78
+
79
+ #### Value types
80
+
81
+ In the example above, we needed to use `last` and `all` methods to see the mechanic. This is
82
+ because the property getter returns a `Plyushkin::Property`. A property consists of
83
+ instances of classes that derive from `Plyushkin::BaseValue`.
84
+
85
+ `Plyushkin::BaseValue` provides basic value functionality. It provides a `date` attribute, that is
86
+ required for all Plyushkin values. And, it provides four formatters, `to_i`, `to_f`,
87
+ `to_date` and `to_bool`.
88
+
89
+ When not specifying a value type when calling `hoards`, Plyushkin will use a
90
+ `Plyushkin::StringValue` as the value type. In addition to the base implementation, this basic
91
+ implementation includes one attribute, `value`, which uses no formatter.
92
+
93
+ In most applications, you will not want to use `Plyushkin::StringValue`, and instead would want
94
+ to create your own value type implementation that derives from `Plyushkin::BaseValue` and use custom
95
+ value types.
96
+
97
+ ##### Creating a custom value type
98
+
99
+ In our vehicle example, if we wanted to capture oil change history, we would start by creating a
100
+ custom value type.
101
+
102
+ class OilChangeValue < Plyushkin::BaseValue
103
+ persisted_attr :mileage, :formatter => :to_i
104
+ persisted_attr :oil_type
105
+ end
106
+
107
+ The `OilChangeValue` will have a mileage attribute that uses the `to_i` formatter. Formatters attempt to
108
+ convert the attribute assignment to a specified format. If no formatter is provided, the attribute will be
109
+ stored as a string.
110
+
111
+ We can now add this to our vehicle
112
+
113
+ class Vehicle < ActiveRecord::Base
114
+ hoards :mechanic
115
+ hoards :oil_change, :type => OilChangeValue
116
+ end
117
+
118
+ To set the oil_change
119
+
120
+ v.oil_change.create(:mileage => '1234', :oil_type => '10W30')
121
+
122
+ To get the latest oil change details
123
+
124
+ v.oil_change.last.mileage # => 1234
125
+ v.oil_change.last.oil_type # => '10W30'
126
+
127
+ ##### Specifiying a callback after a value is stored
128
+
129
+ When defining a hoards property, you can set a callback for after the value is persisted. For example,
130
+ if the vehicle table contains a `next_oil_change_mileage` column, we might want to update it whenever
131
+ an oil_change is saved.
132
+
133
+ class Vehicle < ActiveRecord::Base
134
+ hoards :oil_change, :type => OilChangeValue,
135
+ :after_create => :calculate_next_oil_change_mileage
136
+
137
+ def calculate_next_oil_change_mileage
138
+ next_oil_change_mileage = oil_change.last.mileage + 3000
139
+ end
140
+ end
141
+
142
+ ##### Ignoring unchanged values
143
+
144
+ There may be a case where we don't need to track history when a value is set, but is the same as the
145
+ previous value. For example, if the mechanic for your last maintenance is Mike and Mike again
146
+ performs the maintenance, we don't need two data points recorded.
147
+
148
+ In the current configuration, calling create twice would create two values
149
+
150
+ v.mechanic.create(:value => 'Joe')
151
+ v.mechanic.create(:value => 'Mike')
152
+ v.mechanic.create(:value => 'Mike')
153
+ v.mechanic.all.map(&:value) # => [ 'Joe', 'Mike', 'Mike' ]
154
+
155
+ By setting the `:ignore_unchanged_values` option, we can change this behavior.
156
+
157
+ class Vehicle < ActiveRecord::Base
158
+ hoards :mechanic, :ignore_unchanged_values => true
159
+ end
160
+
161
+ v.mechanic.create(:value => 'Joe')
162
+ v.mechanic.create(:value => 'Mike')
163
+ v.mechanic.create(:value => 'Mike')
164
+ v.mechanic.all.map(&:value) # => [ 'Joe', 'Mike' ]
165
+
166
+ In this example, the date of the last value will be the date that `mechanic` was first assigned 'Mike'.
167
+
168
+ ###### Validation
169
+
170
+ Validation is done with `ActiveModel::Validations`. This is included in `Plyushkin::BaseValue`.
171
+
172
+ class OilChangeValue < Plyushkin::BaseValue
173
+ persisted_attr :oil_type
174
+ persisted_attr :mileage
175
+
176
+ validates :oil_type, :inclusion => { :in => ['10W30', '5W40'] }
177
+ validates :mileage, :numericality => { :only_integer => true,
178
+ :greater_than_or_equal_to => 0 }
179
+ end
180
+
181
+ ###### Adding behavior
182
+
183
+ `Plyushkin::BaseValue` and it's subclasses are classes and additional behavior can be added to
184
+ encapsulate functionality.
185
+
186
+ class OilChange < Plyushkin::BaseValue
187
+ persisted_attr :mileage
188
+
189
+ def mileage_as_km
190
+ mileage / 0.6214
191
+ end
192
+ end
193
+
194
+ v.oil_change.create(:mileage => 10000)
195
+ v.oil_change.last.mileage # => 10000
196
+ v.oil_change.last.mileage_as_km # => 6214
197
+
198
+ ##### Accessing a property that has not had any values assigned
199
+
200
+ `Plyushkin::NilValue` is a special case used when no value has been assigned to a property yet.
201
+
202
+ v = Vehicle.new
203
+ v.mechanic.last # => Plyushkin::NilValue
204
+ v.mechanic.last.value # => nil
205
+ v.oil_change.last # => Plyushkin::NilValue
206
+ v.oil_change.last.mileage # => nil
207
+ v.oil_change.last.oil_type # => nil
208
+
209
+ This is necessary so that consumers of the property do not need to check if a value is nil before trying
210
+ to access an attribute of the value.
211
+
212
+ In addition, `Plyushkin::Property` has a property `empty?` to indicate whether any values have been assigned
213
+
214
+ v.mechanic.empty? # => true
215
+ v.oil_change.empty? # => true
31
216
 
32
217
  ## Testing
33
218
 
34
219
  Plyushkin provides RSpec matchers for testing class macros. To use these matchers,
35
- add ``config.include Plyushkin::Test::Matchers`` to your RSpec.configure in spec_helper.
220
+ add `config.include Plyushkin::Test::Matchers` to your RSpec.configure in spec_helper.
221
+
222
+ #### Testing custom value types
223
+ To test Plyushkin configuration in your custom value type:
224
+
225
+ describe OilChangeValue do
226
+ it { should persist_attribute(:mileage) }
227
+ it { should persist_attribute(:mileage).with_format(:to_i) }
228
+ it { should_not persist_attribute(:air_filter) }
229
+ it { should persist_attribute(:oil_type) }
230
+ it { should_not persist_attribute(:oil_type).with_format(:to_f) }
231
+
232
+ # RSpec Shoulda matchers also work to test validations here.
233
+ end
36
234
 
37
235
  To test Plyushkin configuration in your model:
38
236
 
39
- describe YourModel do
40
- it { should persist_attribute(:your_attribute) }
41
- it { should_not persist_attribute(:your_non_plyushkin_attribute) }
42
- it { should persist_attribute(:your_attribute).with_format(:to_date) }
43
- it { should_not persist_attribute(:your_attribute).with_format(:not_a_formatter) }
237
+ describe Vehicle do
238
+ it { should hoard(:mechanic) }
239
+ it { should hoard(:mechanic).and_ignore_unchanged_values }
240
+ it { should hoard(:oil_change).of_type(OilChangeValue) }
241
+ it { should hoard(:oil_change).of_type(OilChangeValue).
242
+ and_after_create_call(:calculate_next_oil_change_mileage) }
44
243
  end
45
244
 
46
245
  ## Contributing
data/Rakefile CHANGED
@@ -1 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+
3
+ desc "Run rspec spec for those accustomed to the rake task"
4
+ task :spec do
5
+ sh 'rspec spec'
6
+ end
@@ -93,8 +93,8 @@ class Plyushkin::BaseValue
93
93
  end
94
94
 
95
95
  def to_bool(value)
96
- return true if value == "true"
97
- return false if value == "false"
96
+ return true if [1, "1", "true"].include?(value)
97
+ return false if [0, "0", "false"].include?(value)
98
98
  return value
99
99
  end
100
100
 
@@ -11,4 +11,5 @@ module Plyushkin::Cache
11
11
  end
12
12
 
13
13
  require File.dirname(File.expand_path(__FILE__)) + "/cache/stub"
14
+ require File.dirname(File.expand_path(__FILE__)) + "/cache/null"
14
15
  require File.dirname(File.expand_path(__FILE__)) + "/cache/rails_cache"
@@ -0,0 +1,10 @@
1
+ class Plyushkin::Cache::Null
2
+ def write(key, value)
3
+ end
4
+
5
+ def read(key)
6
+ end
7
+
8
+ def clear
9
+ end
10
+ end
@@ -73,14 +73,16 @@ class Plyushkin::Property
73
73
  end
74
74
 
75
75
  def value_hashes
76
- value_hashes = {}
76
+ all_value_hashes = []
77
77
  all.each do |value|
78
+ value_hash = {}
78
79
  value_type.persisted_attributes.each do |attr|
79
- value_hashes[attr] = value.send(attr)
80
+ value_hash[attr] = value.send(attr)
80
81
  end
82
+ all_value_hashes << value_hash
81
83
  end
82
84
 
83
- value_hashes == {} ? [] : [value_hashes]
85
+ all_value_hashes
84
86
  end
85
87
 
86
88
  def nil?
@@ -26,4 +26,7 @@ module Plyushkin::Test
26
26
  persisted_attr :value, :formatter => :to_date
27
27
  end
28
28
 
29
+ class Plyushkin::Test::ComplexModel < ActiveRecord::Base
30
+ hoards :coordinate, :type => Plyushkin::Test::CoordinateValue
31
+ end
29
32
  end
@@ -1,3 +1,3 @@
1
1
  module Plyushkin
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -172,11 +172,27 @@ describe Plyushkin::BaseValue do
172
172
 
173
173
  describe '#to_bool' do
174
174
  it 'should return true if string true is passed in' do
175
- Plyushkin::BaseValue.new.to_bool("true").should be_true
175
+ Plyushkin::BaseValue.new.to_bool("true").should == true
176
+ end
177
+
178
+ it 'should return true if "1" is passed in' do
179
+ Plyushkin::BaseValue.new.to_bool("1").should == true
180
+ end
181
+
182
+ it 'should return false if "0" is passed in' do
183
+ Plyushkin::BaseValue.new.to_bool("0").should == false
184
+ end
185
+
186
+ it 'should return true if 1 is passed in' do
187
+ Plyushkin::BaseValue.new.to_bool(1).should == true
188
+ end
189
+
190
+ it 'should return false if 0 is passed in' do
191
+ Plyushkin::BaseValue.new.to_bool(0).should == false
176
192
  end
177
193
 
178
194
  it 'should return false if string false is passed in' do
179
- Plyushkin::BaseValue.new.to_bool("false").should be_false
195
+ Plyushkin::BaseValue.new.to_bool("false").should == false
180
196
  end
181
197
 
182
198
  it 'should return nil if nil is passed in' do
@@ -43,6 +43,13 @@ describe Plyushkin::Persistence do
43
43
  service.get("widget", 1)["name"].last["value"].should == "Mike"
44
44
  end
45
45
 
46
+ it 'should save multiple new values of the same property' do
47
+ persistence.properties[:name].create(:value => 'Bob')
48
+ persistence.save(1)
49
+ persistence.load(1)
50
+ persistence.properties[:name].all.length.should == 2
51
+ end
52
+
46
53
  it 'should mark all properties as persisted' do
47
54
  persistence.properties[:name].create(:value => "Mike")
48
55
  persistence.properties[:weight].create(:value => "150")
@@ -227,13 +227,46 @@ describe Plyushkin::Property do
227
227
  end
228
228
  end
229
229
 
230
- it '#insert_position' do
231
- property = Plyushkin::Property.new(:property_name)
232
- value1 = property.create(:value => 7, :date => DateTime.now - 7.days)
233
- value2 = property.create(:value => 5, :date => DateTime.now - 5.days)
234
- property.insert_position(DateTime.now).should == 2
235
- property.insert_position(DateTime.now - 6.days).should == 1
236
- property.insert_position(DateTime.now - 8.days).should == 0
230
+ describe '#insert_position' do
231
+ it 'should get position in date order' do
232
+ property = Plyushkin::Property.new(:property_name)
233
+ value1 = property.create(:value => 7, :date => DateTime.now - 7.days)
234
+ value2 = property.create(:value => 5, :date => DateTime.now - 5.days)
235
+ property.insert_position(DateTime.now).should == 2
236
+ property.insert_position(DateTime.now - 6.days).should == 1
237
+ property.insert_position(DateTime.now - 8.days).should == 0
238
+ end
239
+ end
240
+
241
+ describe '#value_hashes' do
242
+ let(:property) { Plyushkin::Property.new(:property_name) }
243
+
244
+ it 'should include all values as hashes in an array' do
245
+
246
+ p1 = property.create(:value => 1)
247
+ p2 = property.create(:value => 2)
248
+ property.value_hashes.should == [
249
+ { :date => p1.date, :value => 1 },
250
+ { :date => p2.date, :value => 2 }
251
+ ]
252
+ end
253
+
254
+ it 'should include all attributes in value hash' do
255
+ property = Plyushkin::Property.new(:property_name, :type => Plyushkin::Test::CoordinateValue)
256
+
257
+ c1 = property.create(:x => 5, :y => 10)
258
+ c2 = property.create(:x => 15, :y => 20)
259
+ property.value_hashes.should == [
260
+ { :date => c1.date, :x => 5, :y => 10 },
261
+ { :date => c2.date, :x => 15, :y => 20 }
262
+ ]
263
+
264
+ end
265
+
266
+ it 'should be an empty array if no values are set' do
267
+ property.value_hashes.should == []
268
+ end
269
+
237
270
  end
238
271
 
239
272
  end
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.5
4
+ version: 0.0.6
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-03-19 00:00:00.000000000 Z
13
+ date: 2014-07-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -70,6 +70,7 @@ extra_rdoc_files: []
70
70
  files:
71
71
  - .gitignore
72
72
  - .rspec
73
+ - .travis.yml
73
74
  - Gemfile
74
75
  - Gemfile.lock
75
76
  - LICENSE.txt
@@ -78,6 +79,7 @@ files:
78
79
  - lib/plyushkin.rb
79
80
  - lib/plyushkin/base_value.rb
80
81
  - lib/plyushkin/cache.rb
82
+ - lib/plyushkin/cache/null.rb
81
83
  - lib/plyushkin/cache/rails_cache.rb
82
84
  - lib/plyushkin/cache/stub.rb
83
85
  - lib/plyushkin/core_ext/active_record_base.rb