changeling 0.0.3 → 0.0.4
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/README.md +12 -1
- data/lib/changeling/models/logling.rb +38 -15
- data/lib/changeling/probeling.rb +4 -0
- data/lib/changeling/trackling.rb +1 -1
- data/lib/changeling/version.rb +1 -1
- data/spec/lib/changeling/models/logling_spec.rb +31 -9
- data/spec/lib/changeling/probeling_spec.rb +86 -0
- data/spec/lib/changeling/trackling_spec.rb +15 -14
- metadata +26 -20
data/README.md
CHANGED
@@ -93,6 +93,17 @@ Access all of an objects history:
|
|
93
93
|
@post.all_history
|
94
94
|
```
|
95
95
|
|
96
|
+
Access all of an objects history where a specific field was changed:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
@post.history_for_field(:title)
|
100
|
+
# Or if you prefer stringified fields:
|
101
|
+
@post.history_for_field('title')
|
102
|
+
|
103
|
+
# You can also pass in a number to limit your results
|
104
|
+
@post.history_for_field(:title, 10)
|
105
|
+
```
|
106
|
+
|
96
107
|
Properties of Loglings (history objects):
|
97
108
|
|
98
109
|
```ruby
|
@@ -102,7 +113,7 @@ log.klass # class of the object that the Logling is tracking.
|
|
102
113
|
=> Post
|
103
114
|
|
104
115
|
log.oid # the ID of the object that the Logling is tracking.
|
105
|
-
# Note: integer type IDs will be integers. Non-integer types (such as Mongo's IDs) will be represented as a string
|
116
|
+
# Note: integer type IDs will be integers. Non-integer types (such as Mongo's IDs) will be represented as a string.
|
106
117
|
=> 1
|
107
118
|
|
108
119
|
log.before # what the before state of the object was.
|
@@ -2,7 +2,7 @@ module Changeling
|
|
2
2
|
module Models
|
3
3
|
class Logling
|
4
4
|
extend ActiveModel::Naming
|
5
|
-
attr_accessor :klass, :oid, :modifications, :before, :after, :modified_at
|
5
|
+
attr_accessor :klass, :oid, :modifications, :before, :after, :modified_at, :modified_fields
|
6
6
|
|
7
7
|
include Tire::Model::Search
|
8
8
|
include Tire::Model::Persistence
|
@@ -10,12 +10,14 @@ module Changeling
|
|
10
10
|
property :klass, :type => 'string'
|
11
11
|
property :oid, :type => 'string'
|
12
12
|
property :modifications, :type => 'string'
|
13
|
+
property :modified_fields, :type => 'string', :analyzer => 'keyword'
|
13
14
|
property :modified_at, :type => 'date'
|
14
15
|
|
15
16
|
mapping do
|
16
17
|
indexes :klass, :type => "string"
|
17
18
|
indexes :oid, :type => "string"
|
18
19
|
indexes :modifications, :type => 'string'
|
20
|
+
indexes :modified_fields, :type => 'string', :analyzer => 'keyword'
|
19
21
|
indexes :modified_at, :type => 'date'
|
20
22
|
end
|
21
23
|
|
@@ -41,19 +43,36 @@ module Changeling
|
|
41
43
|
object.class
|
42
44
|
end
|
43
45
|
|
44
|
-
|
46
|
+
# TODO: Refactor me! More specs!
|
47
|
+
def records_for(object, length = nil, field = nil)
|
45
48
|
self.tire.index.refresh
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
|
50
|
+
if field
|
51
|
+
results = self.search do
|
52
|
+
query do
|
53
|
+
filtered do
|
54
|
+
query { all }
|
55
|
+
filter :terms, :klass => [Logling.klassify(object).to_s.underscore]
|
56
|
+
filter :terms, :oid => [object.id.to_s]
|
57
|
+
filter :terms, :modified_fields => [field]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
sort { by :modified_at, "desc" }
|
62
|
+
end.results
|
63
|
+
else
|
64
|
+
results = self.search do
|
65
|
+
query do
|
66
|
+
filtered do
|
67
|
+
query { all }
|
68
|
+
filter :terms, :klass => [Logling.klassify(object).to_s.underscore]
|
69
|
+
filter :terms, :oid => [object.id.to_s]
|
70
|
+
end
|
52
71
|
end
|
53
|
-
end
|
54
72
|
|
55
|
-
|
56
|
-
|
73
|
+
sort { by :modified_at, "desc" }
|
74
|
+
end.results
|
75
|
+
end
|
57
76
|
|
58
77
|
if length
|
59
78
|
results.take(length)
|
@@ -68,7 +87,8 @@ module Changeling
|
|
68
87
|
:klass => self.klass.to_s.underscore,
|
69
88
|
:oid => self.oid.to_s,
|
70
89
|
:modifications => self.modifications.to_json,
|
71
|
-
:modified_at => self.modified_at
|
90
|
+
:modified_at => self.modified_at,
|
91
|
+
:modified_fields => self.modified_fields
|
72
92
|
}.to_json
|
73
93
|
end
|
74
94
|
|
@@ -87,19 +107,20 @@ module Changeling
|
|
87
107
|
self.klass = object['klass'].camelize.constantize
|
88
108
|
self.oid = object['oid'].to_i.to_s == object['oid'] ? object['oid'].to_i : object['oid']
|
89
109
|
self.modifications = changes
|
110
|
+
self.modified_fields = self.modifications.keys
|
90
111
|
|
91
112
|
self.before, self.after = Logling.parse_changes(changes)
|
92
113
|
|
93
114
|
self.modified_at = DateTime.parse(object['modified_at'])
|
94
115
|
else
|
95
|
-
changes = object.changes
|
96
|
-
|
116
|
+
changes = object.changes.reject { |k, v| v.nil? }
|
97
117
|
# Remove updated_at field.
|
98
118
|
changes.delete("updated_at")
|
99
119
|
|
100
120
|
self.klass = Logling.klassify(object)
|
101
121
|
self.oid = object.id
|
102
122
|
self.modifications = changes
|
123
|
+
self.modified_fields = self.modifications.keys
|
103
124
|
|
104
125
|
self.before, self.after = Logling.parse_changes(changes)
|
105
126
|
|
@@ -112,7 +133,9 @@ module Changeling
|
|
112
133
|
end
|
113
134
|
|
114
135
|
def save
|
115
|
-
|
136
|
+
unless self.modifications.empty?
|
137
|
+
_run_save_callbacks {}
|
138
|
+
end
|
116
139
|
end
|
117
140
|
end
|
118
141
|
end
|
data/lib/changeling/probeling.rb
CHANGED
@@ -7,5 +7,9 @@ module Changeling
|
|
7
7
|
def history(records = 10)
|
8
8
|
Changeling::Models::Logling.records_for(self, records.to_i)
|
9
9
|
end
|
10
|
+
|
11
|
+
def history_for_field(field_name, records = nil)
|
12
|
+
Changeling::Models::Logling.records_for(self, records ? records.to_i : nil, field_name.to_s)
|
13
|
+
end
|
10
14
|
end
|
11
15
|
end
|
data/lib/changeling/trackling.rb
CHANGED
data/lib/changeling/version.rb
CHANGED
@@ -38,15 +38,7 @@ describe Changeling::Models::Logling do
|
|
38
38
|
"klass"=>"BlogPost",
|
39
39
|
"oid"=>"50b8355f7a93d04908000001",
|
40
40
|
"modifications"=>"{\"public\":[true,false]}",
|
41
|
-
"modified_at"=>"2012-11-29T20:26:07-08:00"
|
42
|
-
"id"=>"UU2YGr1WSbilNUlyM1ja8g",
|
43
|
-
"_score"=>nil,
|
44
|
-
"_type"=>"changeling/models/logling",
|
45
|
-
"_index"=>"changeling_test_changeling_models_loglings",
|
46
|
-
"_version"=>1,
|
47
|
-
"sort"=>[1354249567000],
|
48
|
-
"highlight"=>nil,
|
49
|
-
"_explanation"=>nil
|
41
|
+
"modified_at"=>"2012-11-29T20:26:07-08:00"
|
50
42
|
}
|
51
43
|
|
52
44
|
@logling = @klass.new(@object)
|
@@ -72,6 +64,10 @@ describe Changeling::Models::Logling do
|
|
72
64
|
@logling.modifications.should == @changes
|
73
65
|
end
|
74
66
|
|
67
|
+
it "should set the modified_fields as the keys of the modifications" do
|
68
|
+
@logling.modified_fields.should == @changes.keys
|
69
|
+
end
|
70
|
+
|
75
71
|
it "should set before and after based on .parse_changes" do
|
76
72
|
@logling.before.should == @before
|
77
73
|
@logling.after.should == @after
|
@@ -104,6 +100,22 @@ describe Changeling::Models::Logling do
|
|
104
100
|
@logling.after.should == @after
|
105
101
|
end
|
106
102
|
|
103
|
+
it "should set the modified_fields as the keys of the modifications" do
|
104
|
+
@logling.modified_fields.should == @changes.keys
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should ignore changes that are nil" do
|
108
|
+
changes = {}
|
109
|
+
|
110
|
+
@changes.keys.each do |key|
|
111
|
+
changes[key] = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
@object.stub(:changes).and_return(changes)
|
115
|
+
|
116
|
+
@klass.new(@object).modifications.should be_empty
|
117
|
+
end
|
118
|
+
|
107
119
|
it "should set modified_at to the object's time of update if the object responds to the updated_at method" do
|
108
120
|
@object.should_receive(:respond_to?).with(:updated_at).and_return(true)
|
109
121
|
|
@@ -154,6 +166,7 @@ describe Changeling::Models::Logling do
|
|
154
166
|
end
|
155
167
|
end
|
156
168
|
|
169
|
+
# TODO: More specs!
|
157
170
|
describe ".records_for" do
|
158
171
|
before(:each) do
|
159
172
|
@index = @klass.tire.index
|
@@ -204,6 +217,10 @@ describe Changeling::Models::Logling do
|
|
204
217
|
@logling.should_receive(:modified_at)
|
205
218
|
end
|
206
219
|
|
220
|
+
it "should include an array of the object's modified fields" do
|
221
|
+
@logling.should_receive(:modified_fields)
|
222
|
+
end
|
223
|
+
|
207
224
|
after(:each) do
|
208
225
|
@logling.to_indexed_json
|
209
226
|
end
|
@@ -240,6 +257,11 @@ describe Changeling::Models::Logling do
|
|
240
257
|
@logling.should_receive(:_run_save_callbacks)
|
241
258
|
end
|
242
259
|
|
260
|
+
it "should not update the index if there are no changes" do
|
261
|
+
@logling.stub(:modifications).and_return({})
|
262
|
+
@logling.should_not_receive(:_run_save_callbacks)
|
263
|
+
end
|
264
|
+
|
243
265
|
after(:each) do
|
244
266
|
@logling.save
|
245
267
|
end
|
@@ -57,4 +57,90 @@ describe Changeling::Probeling do
|
|
57
57
|
@object.history(20).count.should == 4
|
58
58
|
end
|
59
59
|
end
|
60
|
+
|
61
|
+
describe ".history_for_field" do
|
62
|
+
it "should query Logling with it's class name, and it's own ID, and a field name" do
|
63
|
+
@klass.should_receive(:records_for).with(@object, nil, "field")
|
64
|
+
@object.history_for_field("field")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be able to take a length to specify amount of loglings to return" do
|
68
|
+
@klass.should_receive(:records_for).with(@object, 5, "field")
|
69
|
+
@object.history_for_field("field", 5)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should handle non-integer arguments for length" do
|
73
|
+
@klass.should_receive(:records_for).with(@object, 5, "field")
|
74
|
+
@object.history_for_field("field", "5")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should handle symbol arguments for field" do
|
78
|
+
@klass.should_receive(:records_for).with(@object, nil, "field")
|
79
|
+
@object.history_for_field(:field)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should only return loglings where the specified field has changed" do
|
83
|
+
models.values.each do |value|
|
84
|
+
value[:changes].keys.each do |field|
|
85
|
+
@object.history_for_field(field).each do |logling|
|
86
|
+
logling.modifications.keys.should include(field)
|
87
|
+
end
|
88
|
+
|
89
|
+
@object.history_for_field(field).count.should == 2
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should be able to find loglings even if there was more than one change logged in the logling" do
|
95
|
+
models.each_pair do |model, args|
|
96
|
+
@object = model.new(args[:options])
|
97
|
+
@object.save!
|
98
|
+
|
99
|
+
args[:changes].each do |field, values|
|
100
|
+
values.reverse.each do |value|
|
101
|
+
@object.send("#{field}=", value)
|
102
|
+
@object.save!
|
103
|
+
# Sleep to guarantee saves are not within the same second.
|
104
|
+
sleep 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# This should make 2 changes at the same time then save it
|
109
|
+
args[:changes].each do |field, values|
|
110
|
+
@object.send("#{field}=", values.last)
|
111
|
+
end
|
112
|
+
|
113
|
+
@object.save!
|
114
|
+
end
|
115
|
+
|
116
|
+
models.values.each do |value|
|
117
|
+
value[:changes].keys.each do |field|
|
118
|
+
# Reverse chronological order: first object is the last inserted, which should have 2 changes.
|
119
|
+
# The last object should have 1 change.
|
120
|
+
@object.history_for_field(field).last.modifications.keys.count.should == 1
|
121
|
+
@object.history_for_field(field).first.modifications.keys.count.should == 2
|
122
|
+
|
123
|
+
@object.history_for_field(field).count.should == 3
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should only return the specified amount of loglings" do
|
129
|
+
models.values.each do |value|
|
130
|
+
value[:changes].keys.each do |field|
|
131
|
+
@object.history_for_field(field).count.should == 2
|
132
|
+
@object.history_for_field(field, 1).count.should == 1
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should not error out if the record count desired is more than the total number of loglings" do
|
138
|
+
models.values.each do |value|
|
139
|
+
value[:changes].keys.each do |field|
|
140
|
+
@object.history_for_field(field).count.should == 2
|
141
|
+
@object.history_for_field(field, 10).count.should == 2
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
60
146
|
end
|
@@ -11,26 +11,27 @@ describe Changeling::Trackling do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "callbacks" do
|
14
|
-
before(:each) do
|
15
|
-
@changes = args[:changes]
|
16
|
-
end
|
17
|
-
|
18
14
|
it "should not create a logling when doing the initial save of a new object" do
|
19
15
|
@klass.should_not_receive(:create)
|
20
|
-
@object.
|
16
|
+
@object.run_after_callbacks(:create)
|
21
17
|
end
|
22
18
|
|
23
|
-
it "should create a logling
|
24
|
-
|
25
|
-
@object.
|
26
|
-
|
27
|
-
|
19
|
+
it "should create a logling when updating an object and changes are made" do
|
20
|
+
@klass.should_receive(:create)
|
21
|
+
@object.stub(:changes).and_return({ :field => 'value' })
|
22
|
+
@object.run_after_callbacks(:update)
|
23
|
+
end
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
it "should create a logling when updating an object and changes are empty" do
|
26
|
+
@klass.should_not_receive(:create)
|
27
|
+
@object.stub(:changes).and_return({})
|
28
|
+
@object.run_after_callbacks(:update)
|
29
|
+
end
|
32
30
|
|
33
|
-
|
31
|
+
it "should not create a logling when updating an object and no changes have been made" do
|
32
|
+
@klass.should_not_receive(:create)
|
33
|
+
@object.stub(:changes).and_return(nil)
|
34
|
+
@object.run_after_callbacks(:update)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: changeling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-12-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: tire
|
16
|
-
requirement: &
|
16
|
+
requirement: &70120925957500 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70120925957500
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activemodel
|
27
|
-
requirement: &
|
27
|
+
requirement: &70120925956900 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70120925956900
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mongoid
|
38
|
-
requirement: &
|
38
|
+
requirement: &70120925956080 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - =
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 3.0.3
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70120925956080
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: activerecord
|
49
|
-
requirement: &
|
49
|
+
requirement: &70120925955460 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - =
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 3.2.7
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70120925955460
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: debugger
|
60
|
-
requirement: &
|
60
|
+
requirement: &70120925954960 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70120925954960
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
|
-
requirement: &
|
71
|
+
requirement: &70120925954180 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70120925954180
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: rspec
|
82
|
-
requirement: &
|
82
|
+
requirement: &70120925953640 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70120925953640
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: bson_ext
|
93
|
-
requirement: &
|
93
|
+
requirement: &70120925953060 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,10 +98,10 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *70120925953060
|
102
102
|
- !ruby/object:Gem::Dependency
|
103
103
|
name: database_cleaner
|
104
|
-
requirement: &
|
104
|
+
requirement: &70120925952440 !ruby/object:Gem::Requirement
|
105
105
|
none: false
|
106
106
|
requirements:
|
107
107
|
- - ! '>='
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
version: '0'
|
110
110
|
type: :development
|
111
111
|
prerelease: false
|
112
|
-
version_requirements: *
|
112
|
+
version_requirements: *70120925952440
|
113
113
|
description: A simple, yet flexible solution to tracking changes made to objects in
|
114
114
|
your database.
|
115
115
|
email:
|
@@ -151,12 +151,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
151
151
|
- - ! '>='
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
hash: -473586463949581456
|
154
157
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
158
|
none: false
|
156
159
|
requirements:
|
157
160
|
- - ! '>='
|
158
161
|
- !ruby/object:Gem::Version
|
159
162
|
version: '0'
|
163
|
+
segments:
|
164
|
+
- 0
|
165
|
+
hash: -473586463949581456
|
160
166
|
requirements: []
|
161
167
|
rubyforge_project:
|
162
168
|
rubygems_version: 1.8.15
|