plyushkin 0.0.5 → 0.0.6

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