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 +1 -0
- data/.travis.yml +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +208 -9
- data/Rakefile +5 -0
- data/lib/plyushkin/base_value.rb +2 -2
- data/lib/plyushkin/cache.rb +1 -0
- data/lib/plyushkin/cache/null.rb +10 -0
- data/lib/plyushkin/property.rb +5 -3
- data/lib/plyushkin/test/value_types.rb +3 -0
- data/lib/plyushkin/version.rb +1 -1
- data/spec/lib/base_value_spec.rb +18 -2
- data/spec/lib/persistence_spec.rb +7 -0
- data/spec/lib/property_spec.rb +40 -7
- metadata +4 -2
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Plyushkin
|
2
2
|
|
3
|
-
|
3
|
+
[](https://codeclimate.com/github/OnlifeHealth/plyushkin) [](http://badge.fury.io/rb/plyushkin)
|
4
|
+
[](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
|
24
|
+
Configure the backend service that plyushkin will use in your environments/<environment>.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
|
-
|
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
|
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
|
40
|
-
it { should
|
41
|
-
it {
|
42
|
-
it { should
|
43
|
-
it {
|
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
data/lib/plyushkin/base_value.rb
CHANGED
@@ -93,8 +93,8 @@ class Plyushkin::BaseValue
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def to_bool(value)
|
96
|
-
return true if
|
97
|
-
return false if
|
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
|
|
data/lib/plyushkin/cache.rb
CHANGED
data/lib/plyushkin/property.rb
CHANGED
@@ -73,14 +73,16 @@ class Plyushkin::Property
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def value_hashes
|
76
|
-
|
76
|
+
all_value_hashes = []
|
77
77
|
all.each do |value|
|
78
|
+
value_hash = {}
|
78
79
|
value_type.persisted_attributes.each do |attr|
|
79
|
-
|
80
|
+
value_hash[attr] = value.send(attr)
|
80
81
|
end
|
82
|
+
all_value_hashes << value_hash
|
81
83
|
end
|
82
84
|
|
83
|
-
|
85
|
+
all_value_hashes
|
84
86
|
end
|
85
87
|
|
86
88
|
def nil?
|
data/lib/plyushkin/version.rb
CHANGED
data/spec/lib/base_value_spec.rb
CHANGED
@@ -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
|
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
|
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")
|
data/spec/lib/property_spec.rb
CHANGED
@@ -227,13 +227,46 @@ describe Plyushkin::Property do
|
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
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.
|
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-
|
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
|