changeling 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/README.md +24 -3
- data/changeling.gemspec +3 -0
- data/lib/changeling.rb +20 -0
- data/lib/changeling/blameling.rb +17 -0
- data/lib/changeling/models/logling.rb +34 -44
- data/lib/changeling/support/search.rb +40 -0
- data/lib/changeling/version.rb +1 -1
- data/spec/controllers/blameling_controller_spec.rb +20 -0
- data/spec/controllers/blog_posts_controller_spec.rb +20 -0
- data/spec/controllers/current_account_controller_spec.rb +20 -0
- data/spec/controllers/no_current_user_controller_spec.rb +20 -0
- data/spec/fixtures/app/application.rb +27 -0
- data/spec/fixtures/app/controllers/blameling_controller.rb +7 -0
- data/spec/fixtures/app/controllers/blog_posts_controller.rb +15 -0
- data/spec/fixtures/app/controllers/current_account_controller.rb +19 -0
- data/spec/fixtures/app/controllers/no_current_user_controller.rb +7 -0
- data/spec/fixtures/{models/mongoid → app/models}/blog_post.rb +0 -0
- data/spec/fixtures/app/models/blog_post_active_record.rb +5 -0
- data/spec/fixtures/{models/mongoid → app/models}/blog_post_no_timestamp.rb +0 -0
- data/spec/lib/changeling/models/logling_spec.rb +54 -20
- data/spec/lib/changeling/probeling_spec.rb +1 -1
- data/spec/lib/changeling/support/search_spec.rb +81 -0
- data/spec/lib/changeling/trackling_spec.rb +1 -1
- data/spec/spec_helper.rb +31 -22
- metadata +85 -28
data/.rvmrc
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Changeling [![Build Status][travis-image]][travis-link]
|
1
|
+
# Changeling [![Build Status][travis-image]][travis-link] [![Gem Version](https://badge.fury.io/rb/changeling.png)](http://badge.fury.io/rb/changeling)
|
2
2
|
|
3
3
|
[travis-image]: https://secure.travis-ci.org/hahuang65/Changeling.png?branch=master
|
4
4
|
[travis-link]: http://travis-ci.org/hahuang65/Changeling
|
@@ -49,7 +49,23 @@ class Post
|
|
49
49
|
end
|
50
50
|
```
|
51
51
|
|
52
|
-
|
52
|
+
If you want to keep track of the user who made the changes:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Doesn't have to be ApplicationController, perhaps you only want it in controllers for certain resources.
|
56
|
+
class ApplicationController < ActionController::Base
|
57
|
+
include Changeling::Blameling
|
58
|
+
|
59
|
+
# Changeling assumes your user is current_user, but if not, override the changeling_blame_user method like so:
|
60
|
+
def changeling_blame_user
|
61
|
+
current_account
|
62
|
+
end
|
63
|
+
|
64
|
+
# Controller logic here...
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
That's it! Including the module(s) will silently keep track of any changes made to objects of this class.
|
53
69
|
For example:
|
54
70
|
|
55
71
|
```ruby
|
@@ -122,6 +138,11 @@ log.before # what the before state of the object was.
|
|
122
138
|
log.after # what the after state of the object is.
|
123
139
|
=> {"title" => "New Title"}
|
124
140
|
|
141
|
+
log.modified_by # ID of the user who made the changes to the object
|
142
|
+
# Note: this could be nil if the Blameling module was not set up in you controller, or if changes were made from a place without a user object, such as the Rails console.
|
143
|
+
# Note: integer type IDs will be integers. Non-integer types (such as Mongo's IDs) will be represented as a string.
|
144
|
+
=> 33
|
145
|
+
|
125
146
|
log.modifications # what changes were made to the object that this Logling recorded. Basically a roll up of the .before and .after methods.
|
126
147
|
=> {"title" => ["Old Title", "New Title"]}
|
127
148
|
|
@@ -129,7 +150,7 @@ log.modified_at # what time these changes were made.
|
|
129
150
|
=> Sat, 08 Sep 2012 10:21:46 UTC +00:00
|
130
151
|
|
131
152
|
log.as_json # JSON representation of the changes.
|
132
|
-
=> {:class => Post, :oid => 1, :modifications=> { "title" => ["Old Title", "New Title"] }, :modified_at => Sat, 08 Sep 2012 10:21:46 UTC +00:00}
|
153
|
+
=> {:class => Post, :oid => 1, :modified_by => 33, :modifications=> { "title" => ["Old Title", "New Title"] }, :modified_at => Sat, 08 Sep 2012 10:21:46 UTC +00:00}
|
133
154
|
```
|
134
155
|
|
135
156
|
## Testing
|
data/changeling.gemspec
CHANGED
@@ -32,6 +32,9 @@ Gem::Specification.new do |gem|
|
|
32
32
|
end
|
33
33
|
gem.add_development_dependency "rake"
|
34
34
|
gem.add_development_dependency "rspec"
|
35
|
+
gem.add_development_dependency "rspec-rails"
|
35
36
|
gem.add_development_dependency "bson_ext"
|
36
37
|
gem.add_development_dependency "database_cleaner"
|
38
|
+
gem.add_development_dependency "sqlite3"
|
39
|
+
gem.add_development_dependency "rails"
|
37
40
|
end
|
data/lib/changeling.rb
CHANGED
@@ -7,8 +7,28 @@ module Changeling
|
|
7
7
|
|
8
8
|
autoload :Trackling, 'changeling/trackling'
|
9
9
|
autoload :Probeling, 'changeling/probeling'
|
10
|
+
autoload :Blameling, 'changeling/blameling'
|
10
11
|
|
11
12
|
module Models
|
12
13
|
autoload :Logling, 'changeling/models/logling'
|
13
14
|
end
|
15
|
+
|
16
|
+
module Support
|
17
|
+
autoload :Search, 'changeling/support/search'
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.blame_user
|
21
|
+
self.changeling_store[:blame_user]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.blame_user=(user)
|
25
|
+
self.changeling_store[:blame_user] = user
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def self.changeling_store
|
30
|
+
Thread.current[:changeling] ||= {
|
31
|
+
:blame_user => nil
|
32
|
+
}
|
33
|
+
end
|
14
34
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Changeling
|
2
|
+
module Blameling
|
3
|
+
def self.included(base)
|
4
|
+
base.before_filter :set_changeling_blame_user
|
5
|
+
end
|
6
|
+
|
7
|
+
protected
|
8
|
+
def changeling_blame_user
|
9
|
+
current_user rescue nil
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def set_changeling_blame_user
|
14
|
+
Changeling.blame_user = changeling_blame_user
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -2,13 +2,15 @@ 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, :modified_fields
|
5
|
+
attr_accessor :klass, :oid, :modified_by, :modifications, :before, :after, :modified_at, :modified_fields
|
6
6
|
|
7
7
|
include Tire::Model::Search
|
8
|
+
include Tire::Model::Callbacks
|
8
9
|
include Tire::Model::Persistence
|
9
10
|
|
10
11
|
property :klass, :type => 'string'
|
11
12
|
property :oid, :type => 'string'
|
13
|
+
property :modified_by, :type => 'string'
|
12
14
|
property :modifications, :type => 'string'
|
13
15
|
property :modified_fields, :type => 'string', :analyzer => 'keyword'
|
14
16
|
property :modified_at, :type => 'date'
|
@@ -16,6 +18,7 @@ module Changeling
|
|
16
18
|
mapping do
|
17
19
|
indexes :klass, :type => "string"
|
18
20
|
indexes :oid, :type => "string"
|
21
|
+
indexes :modified_by, :type => "string"
|
19
22
|
indexes :modifications, :type => 'string'
|
20
23
|
indexes :modified_fields, :type => 'string', :analyzer => 'keyword'
|
21
24
|
indexes :modified_at, :type => 'date'
|
@@ -40,59 +43,38 @@ module Changeling
|
|
40
43
|
end
|
41
44
|
|
42
45
|
def klassify(object)
|
43
|
-
object.class
|
46
|
+
object.class.to_s.underscore
|
44
47
|
end
|
45
48
|
|
46
|
-
# TODO: Refactor me! More specs!
|
47
49
|
def records_for(object, length = nil, field = nil)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
71
|
-
end
|
72
|
-
|
73
|
-
sort { by :modified_at, "desc" }
|
74
|
-
end.results
|
75
|
-
end
|
50
|
+
filters = [
|
51
|
+
{ :klass => Logling.klassify(object) },
|
52
|
+
{ :oid => object.id.to_s }
|
53
|
+
]
|
54
|
+
|
55
|
+
filters << { :modified_fields => field } if field
|
76
56
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
results.map { |result|
|
81
|
-
if result.class == Changeling::Models::Logling
|
82
|
-
result
|
83
|
-
elsif result.class == Tire::Results::Item
|
84
|
-
Logling.new(JSON.parse(result.to_json))
|
85
|
-
elsif result.class == Hash
|
86
|
-
Logling.new(result)
|
87
|
-
end
|
57
|
+
sort = {
|
58
|
+
:field => :modified_at,
|
59
|
+
:direction => :desc
|
88
60
|
}
|
61
|
+
|
62
|
+
results = Changeling::Support::Search.find_by(:filters => filters, :sort => sort)
|
63
|
+
|
64
|
+
if length
|
65
|
+
results.take(length)
|
66
|
+
else
|
67
|
+
results
|
68
|
+
end
|
89
69
|
end
|
90
70
|
end
|
91
71
|
|
92
72
|
def to_indexed_json
|
93
73
|
{
|
74
|
+
:id => self.id,
|
94
75
|
:klass => self.klass.to_s.underscore,
|
95
76
|
:oid => self.oid.to_s,
|
77
|
+
:modified_by => self.modified_by,
|
96
78
|
:modifications => self.modifications.to_json,
|
97
79
|
:modified_at => self.modified_at,
|
98
80
|
:modified_fields => self.modified_fields
|
@@ -103,16 +85,23 @@ module Changeling
|
|
103
85
|
{
|
104
86
|
:class => self.klass,
|
105
87
|
:oid => self.oid,
|
88
|
+
:modified_by => self.modified_by,
|
106
89
|
:modifications => self.modifications,
|
107
90
|
:modified_at => self.modified_at
|
108
91
|
}
|
109
92
|
end
|
110
93
|
|
94
|
+
def id
|
95
|
+
# Make sure ElasticSearch creates new entries rather than update old entries.
|
96
|
+
Digest::MD5.hexdigest("#{self.klass}:#{self.oid}:#{self.modifications}")
|
97
|
+
end
|
98
|
+
|
111
99
|
def initialize(object)
|
112
100
|
if object.class == Hash
|
113
101
|
changes = JSON.parse(object['modifications'])
|
114
102
|
self.klass = object['klass'].camelize.constantize
|
115
103
|
self.oid = object['oid'].to_i.to_s == object['oid'] ? object['oid'].to_i : object['oid']
|
104
|
+
self.modified_by = object['modified_by'].to_i.to_s == object['modified_by'] ? object['modified_by'].to_i : object['modified_by']
|
116
105
|
self.modifications = changes
|
117
106
|
self.modified_fields = self.modifications.keys
|
118
107
|
|
@@ -124,8 +113,9 @@ module Changeling
|
|
124
113
|
# Remove updated_at field.
|
125
114
|
changes.delete("updated_at")
|
126
115
|
|
127
|
-
self.klass =
|
116
|
+
self.klass = object.class
|
128
117
|
self.oid = object.id
|
118
|
+
self.modified_by = Changeling.blame_user.try(:id) || nil
|
129
119
|
self.modifications = changes
|
130
120
|
self.modified_fields = self.modifications.keys
|
131
121
|
|
@@ -141,7 +131,7 @@ module Changeling
|
|
141
131
|
|
142
132
|
def save
|
143
133
|
unless self.modifications.empty?
|
144
|
-
|
134
|
+
self.update_index
|
145
135
|
end
|
146
136
|
end
|
147
137
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Changeling
|
2
|
+
module Support
|
3
|
+
class Search
|
4
|
+
def self.find_by(args)
|
5
|
+
return [] unless args.kind_of?(Hash)
|
6
|
+
|
7
|
+
filters = args[:filters]
|
8
|
+
sort = args[:sort]
|
9
|
+
return [] unless filters || sort
|
10
|
+
|
11
|
+
@class = Changeling::Models::Logling
|
12
|
+
@class.tire.index.refresh
|
13
|
+
|
14
|
+
results = @class.search do
|
15
|
+
query do
|
16
|
+
filtered do
|
17
|
+
query { all }
|
18
|
+
filters.each do |f|
|
19
|
+
filter :terms, { f.first[0].to_sym => [f.first[1].to_s] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sort { by sort[:field], sort[:direction].to_s }
|
25
|
+
end.results
|
26
|
+
|
27
|
+
# Some apps may return Tire::Results::Item objects in results instead of Changeling objects.
|
28
|
+
results.map { |result|
|
29
|
+
if result.class == @class
|
30
|
+
result
|
31
|
+
elsif result.class == Tire::Results::Item
|
32
|
+
@class.new(JSON.parse(result.to_json))
|
33
|
+
elsif result.class == Hash
|
34
|
+
@class.new(result)
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/changeling/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RailsApp do
|
4
|
+
controller(RailsApp::BlamelingController) do
|
5
|
+
extend(RSpec::Rails::ControllerExampleGroup::BypassRescue)
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
# Request needs to be setup to avoid path setting error
|
10
|
+
@request = ActionController::TestRequest.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set current_user if Blameling is included" do
|
14
|
+
Thread.new {
|
15
|
+
post :create
|
16
|
+
# Look in application.rb for the User class and it's id method.
|
17
|
+
BlogPost.last.all_history.last.modified_by.should == 33
|
18
|
+
}.join
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RailsApp do
|
4
|
+
controller(RailsApp::BlogPostsController) do
|
5
|
+
extend(RSpec::Rails::ControllerExampleGroup::BypassRescue)
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
# Request needs to be setup to avoid path setting error
|
10
|
+
@request = ActionController::TestRequest.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not set current_user if Blameling is not included" do
|
14
|
+
Thread.new {
|
15
|
+
post :create
|
16
|
+
# Look in application.rb for the User class and it's id method.
|
17
|
+
BlogPost.last.all_history.last.modified_by.should == nil
|
18
|
+
}.join
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RailsApp do
|
4
|
+
controller(RailsApp::CurrentAccountController) do
|
5
|
+
extend(RSpec::Rails::ControllerExampleGroup::BypassRescue)
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
# Request needs to be setup to avoid path setting error
|
10
|
+
@request = ActionController::TestRequest.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not set current_user if current_user is not defined" do
|
14
|
+
Thread.new {
|
15
|
+
post :create
|
16
|
+
# Look in application.rb for the User class and it's id method.
|
17
|
+
BlogPost.last.all_history.last.modified_by.should == 88
|
18
|
+
}.join
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RailsApp do
|
4
|
+
controller(RailsApp::NoCurrentUserController) do
|
5
|
+
extend(RSpec::Rails::ControllerExampleGroup::BypassRescue)
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
# Request needs to be setup to avoid path setting error
|
10
|
+
@request = ActionController::TestRequest.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not set current_user if current_user is not defined" do
|
14
|
+
Thread.new {
|
15
|
+
post :create
|
16
|
+
# Look in application.rb for the User class and it's id method.
|
17
|
+
BlogPost.last.all_history.last.modified_by.should == nil
|
18
|
+
}.join
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RailsApp
|
4
|
+
class Application < Rails::Application
|
5
|
+
# app config here
|
6
|
+
# config.secret_token = '572c86f5ede338bd8aba8dae0fd3a326aabababc98d1e6ce34b9f5'
|
7
|
+
# routes.draw do
|
8
|
+
# resources :blog_posts
|
9
|
+
# end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ApplicationController < ActionController::Base
|
13
|
+
def render(*attributes)
|
14
|
+
# Override render so we don't have to deal with rendering in tests.
|
15
|
+
end
|
16
|
+
|
17
|
+
def current_user
|
18
|
+
User.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class User
|
23
|
+
def id
|
24
|
+
33
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RailsApp
|
2
|
+
class BlogPostsController < ApplicationController
|
3
|
+
def create
|
4
|
+
@object = BlogPost.new(models[BlogPost][:options])
|
5
|
+
@object.save!
|
6
|
+
|
7
|
+
models[BlogPost][:changes].each do |field, values|
|
8
|
+
values.reverse.each do |value|
|
9
|
+
@object.send("#{field}=", value)
|
10
|
+
@object.save!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require (File.expand_path('../no_current_user_controller', __FILE__))
|
2
|
+
|
3
|
+
module RailsApp
|
4
|
+
class CurrentAccountController < NoCurrentUserController
|
5
|
+
def changeling_blame_user
|
6
|
+
current_account
|
7
|
+
end
|
8
|
+
|
9
|
+
def current_account
|
10
|
+
Account.new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Account
|
15
|
+
def id
|
16
|
+
88
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
File without changes
|
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Changeling::Models::Logling do
|
4
4
|
before(:all) do
|
@@ -83,8 +83,8 @@ describe Changeling::Models::Logling do
|
|
83
83
|
@before, @after = @klass.parse_changes(@changes)
|
84
84
|
end
|
85
85
|
|
86
|
-
it "should set klass as the
|
87
|
-
@logling.klass.should == @
|
86
|
+
it "should set klass as the object's class" do
|
87
|
+
@logling.klass.should == @object.class
|
88
88
|
end
|
89
89
|
|
90
90
|
it "should set oid as the object's ID" do
|
@@ -161,39 +161,73 @@ describe Changeling::Models::Logling do
|
|
161
161
|
end
|
162
162
|
|
163
163
|
describe ".klassify" do
|
164
|
-
it "should return the object's class" do
|
165
|
-
@klass.klassify(@object).should == @object.class
|
164
|
+
it "should return the object's class as an underscored string" do
|
165
|
+
@klass.klassify(@object).should == @object.class.to_s.underscore
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
-
# TODO: More specs!
|
170
169
|
describe ".records_for" do
|
171
170
|
before(:each) do
|
172
|
-
@
|
171
|
+
@search = Changeling::Support::Search
|
173
172
|
@results = []
|
174
|
-
@klass.stub_chain(:tire, :index).and_return(@index)
|
175
|
-
@klass.stub_chain(:search, :results).and_return(@results)
|
176
173
|
end
|
177
174
|
|
178
|
-
|
179
|
-
|
175
|
+
context "length parameter" do
|
176
|
+
before(:each) do
|
177
|
+
@search.stub(:find_by).and_return(@results)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should only return the amount specified" do
|
181
|
+
num = 5
|
182
|
+
@results.should_receive(:take).with(num).and_return([])
|
183
|
+
@klass.records_for(@object, 5)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should return all if no amount is specified" do
|
187
|
+
@results.should_not_receive(:take)
|
188
|
+
@klass.records_for(@object)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should search with filters on the klass and oid" do
|
193
|
+
@search.should_receive(:find_by).with(hash_including({
|
194
|
+
:filters => [
|
195
|
+
{ :klass => @klass.klassify(@object) },
|
196
|
+
{ :oid => @object.id.to_s }
|
197
|
+
]
|
198
|
+
})).and_return(@results)
|
180
199
|
@klass.records_for(@object)
|
181
200
|
end
|
182
201
|
|
183
|
-
it "should
|
184
|
-
|
185
|
-
|
186
|
-
|
202
|
+
it "should search with a filter on the field if one is passed in" do
|
203
|
+
@search.should_receive(:find_by).with(hash_including(
|
204
|
+
:filters => [
|
205
|
+
{ :klass => @klass.klassify(@object) },
|
206
|
+
{ :oid => @object.id.to_s },
|
207
|
+
{ :modified_fields => "field" }
|
208
|
+
]
|
209
|
+
)).and_return(@results)
|
210
|
+
@klass.records_for(@object, nil, "field")
|
187
211
|
end
|
188
212
|
|
189
|
-
it "should
|
190
|
-
@
|
191
|
-
|
213
|
+
it "should sort by descending modified_at" do
|
214
|
+
@search.should_receive(:find_by).with(hash_including({
|
215
|
+
:sort => {
|
216
|
+
:field => :modified_at,
|
217
|
+
:direction => :desc
|
218
|
+
}
|
219
|
+
})).and_return(@results)
|
220
|
+
@klass.records_for(@object, nil)
|
192
221
|
end
|
193
222
|
end
|
194
223
|
end
|
195
224
|
|
196
225
|
context "Instance Methods" do
|
226
|
+
before(:each) do
|
227
|
+
# Stub :id so that it doesn't screw up these test's expectations.
|
228
|
+
@logling.stub(:id).and_return(1)
|
229
|
+
end
|
230
|
+
|
197
231
|
describe ".to_indexed_json" do
|
198
232
|
it "should include the object's klass attribute" do
|
199
233
|
@logling.should_receive(:klass)
|
@@ -232,7 +266,7 @@ describe Changeling::Models::Logling do
|
|
232
266
|
end
|
233
267
|
|
234
268
|
it "should include the object's klass attribute" do
|
235
|
-
@json[:class].should == @
|
269
|
+
@json[:class].should == @object.class
|
236
270
|
end
|
237
271
|
|
238
272
|
it "should include the object's oid attribute" do
|
@@ -254,7 +288,7 @@ describe Changeling::Models::Logling do
|
|
254
288
|
|
255
289
|
describe ".save" do
|
256
290
|
it "should update the ElasticSearch index" do
|
257
|
-
@logling.should_receive(:
|
291
|
+
@logling.should_receive(:update_index)
|
258
292
|
end
|
259
293
|
|
260
294
|
it "should not update the index if there are no changes" do
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Changeling::Support::Search do
|
4
|
+
before(:all) do
|
5
|
+
@klass = Changeling::Models::Logling
|
6
|
+
@search = Changeling::Support::Search
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".find_by" do
|
10
|
+
before(:each) do
|
11
|
+
@index = @klass.tire.index
|
12
|
+
@klass.stub_chain(:tire, :index).and_return(@index)
|
13
|
+
|
14
|
+
filters = [
|
15
|
+
{ :klass => "blog_post" },
|
16
|
+
{ :oid => "1" }
|
17
|
+
]
|
18
|
+
|
19
|
+
sort = {
|
20
|
+
:field => :modified_at,
|
21
|
+
:direction => :desc
|
22
|
+
}
|
23
|
+
|
24
|
+
@options = { :filters => filters, :sort => sort }
|
25
|
+
|
26
|
+
@results = []
|
27
|
+
|
28
|
+
models.each_pair do |model, args|
|
29
|
+
object = model.new(args[:options])
|
30
|
+
changes = args[:changes]
|
31
|
+
|
32
|
+
object.stub(:changes).and_return(changes)
|
33
|
+
@results << @klass.new(object)
|
34
|
+
end
|
35
|
+
|
36
|
+
@klass.stub_chain(:search, :results).and_return(@results)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return an empty array if options are not a hash" do
|
40
|
+
@search.find_by(nil).should == []
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should return an empty array if options do not include filters or sort keys" do
|
44
|
+
@search.find_by({}).should == []
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should refresh the ElasticSearch index" do
|
48
|
+
@index.should_receive(:refresh)
|
49
|
+
@search.find_by(@options)
|
50
|
+
end
|
51
|
+
|
52
|
+
context "results processing" do
|
53
|
+
it "should return objects as is if they are Logling objects" do
|
54
|
+
@search.find_by(@options).should == @results
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should parse them and convert them into Logling objects if they are returned as Tire::Results::Item objects" do
|
58
|
+
@tire_object = Tire::Results::Item.new
|
59
|
+
@tire_json = "{}"
|
60
|
+
@hash = {}
|
61
|
+
|
62
|
+
@results = [@tire_object]
|
63
|
+
@klass.stub_chain(:search, :results).and_return(@results)
|
64
|
+
|
65
|
+
@tire_object.should_receive(:to_json).and_return(@tire_json)
|
66
|
+
JSON.should_receive(:parse).with(@tire_json).and_return(@hash)
|
67
|
+
@klass.should_receive(:new).with(@hash)
|
68
|
+
|
69
|
+
@search.find_by(@options)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should convert them into Logling objects if they are returned as Hash objects" do
|
73
|
+
@results = [{}, {}]
|
74
|
+
@klass.stub_chain(:search, :results).and_return(@results)
|
75
|
+
|
76
|
+
@results.each { |r| @klass.should_receive(:new).with(r) }
|
77
|
+
@search.find_by(@options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require(File.expand_path('../../lib/changeling', __FILE__))
|
2
2
|
require 'mongoid'
|
3
3
|
require 'database_cleaner'
|
4
|
+
require 'rails/all'
|
5
|
+
require 'rspec/rails'
|
6
|
+
require 'action_controller/railtie' # allows ActionController::Base
|
4
7
|
|
5
8
|
# Fixtures
|
6
9
|
Dir[File.dirname(__FILE__) + "/fixtures/**/*.rb"].each { |file| require file }
|
@@ -15,10 +18,24 @@ else
|
|
15
18
|
Mongoid.database = Mongo::Connection.new('localhost','27017').db('changeling_test')
|
16
19
|
end
|
17
20
|
|
21
|
+
# ActiveRecord setup
|
22
|
+
ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
|
23
|
+
|
24
|
+
ActiveRecord::Migration.verbose = false
|
25
|
+
ActiveRecord::Schema.define(:version => 1) do
|
26
|
+
# See the activerecord folder under spec/fixtures/models
|
27
|
+
create_table :blog_post_active_records do |t|
|
28
|
+
t.string :title
|
29
|
+
t.string :content
|
30
|
+
t.boolean :public
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
18
34
|
RSpec.configure do |config|
|
19
35
|
config.mock_with :rspec
|
20
36
|
|
21
37
|
config.before(:suite) do
|
38
|
+
DatabaseCleaner[:active_record].strategy = :truncation
|
22
39
|
DatabaseCleaner[:mongoid].strategy = :truncation
|
23
40
|
Tire::Model::Search.index_prefix "changeling_test"
|
24
41
|
end
|
@@ -48,29 +65,21 @@ def clear_tire_indexes
|
|
48
65
|
end
|
49
66
|
|
50
67
|
def models
|
51
|
-
|
52
|
-
|
53
|
-
:
|
54
|
-
|
55
|
-
|
56
|
-
:public => false
|
57
|
-
},
|
58
|
-
:changes => {
|
59
|
-
"public" => [false, true],
|
60
|
-
"content" => ["Something about Changeling", "Content about Changeling"]
|
61
|
-
}
|
68
|
+
hash = {
|
69
|
+
:options => {
|
70
|
+
:title => "Changeling",
|
71
|
+
:content => "Something about Changeling",
|
72
|
+
:public => false
|
62
73
|
},
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
:title => "Changeling",
|
67
|
-
:content => "Something about Changeling",
|
68
|
-
:public => false
|
69
|
-
},
|
70
|
-
:changes => {
|
71
|
-
"public" => [false, true],
|
72
|
-
"content" => ["Something about Changeling", "Content about Changeling"]
|
73
|
-
}
|
74
|
+
:changes => {
|
75
|
+
"public" => [false, true],
|
76
|
+
"content" => ["Something about Changeling", "Content about Changeling"]
|
74
77
|
}
|
75
78
|
}
|
79
|
+
|
80
|
+
@models = {
|
81
|
+
BlogPost => hash,
|
82
|
+
BlogPostNoTimestamp => hash,
|
83
|
+
BlogPostActiveRecord => hash
|
84
|
+
}
|
76
85
|
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.6
|
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:
|
12
|
+
date: 2013-02-09 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: tire
|
16
|
-
requirement: &
|
16
|
+
requirement: &70261420742240 !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: *70261420742240
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: activemodel
|
27
|
-
requirement: &
|
27
|
+
requirement: &70261420741820 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,32 +32,32 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70261420741820
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: mongoid
|
38
|
-
requirement: &
|
38
|
+
requirement: &70261420741320 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - =
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
43
|
+
version: 2.4.1
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70261420741320
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: activerecord
|
49
|
-
requirement: &
|
49
|
+
requirement: &70261420740820 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - =
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 3.
|
54
|
+
version: 3.1.3
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70261420740820
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
|
-
name:
|
60
|
-
requirement: &
|
59
|
+
name: ruby-debug19
|
60
|
+
requirement: &70261420740440 !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: *70261420740440
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
|
-
requirement: &
|
71
|
+
requirement: &70261420739980 !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: *70261420739980
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: rspec
|
82
|
-
requirement: &
|
82
|
+
requirement: &70261404212700 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,21 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70261404212700
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rspec-rails
|
93
|
+
requirement: &70261404212280 !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *70261404212280
|
91
102
|
- !ruby/object:Gem::Dependency
|
92
103
|
name: bson_ext
|
93
|
-
requirement: &
|
104
|
+
requirement: &70261404211860 !ruby/object:Gem::Requirement
|
94
105
|
none: false
|
95
106
|
requirements:
|
96
107
|
- - ! '>='
|
@@ -98,10 +109,32 @@ dependencies:
|
|
98
109
|
version: '0'
|
99
110
|
type: :development
|
100
111
|
prerelease: false
|
101
|
-
version_requirements: *
|
112
|
+
version_requirements: *70261404211860
|
102
113
|
- !ruby/object:Gem::Dependency
|
103
114
|
name: database_cleaner
|
104
|
-
requirement: &
|
115
|
+
requirement: &70261404211440 !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: *70261404211440
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: sqlite3
|
126
|
+
requirement: &70261404211020 !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: *70261404211020
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rails
|
137
|
+
requirement: &70261404210600 !ruby/object:Gem::Requirement
|
105
138
|
none: false
|
106
139
|
requirements:
|
107
140
|
- - ! '>='
|
@@ -109,7 +142,7 @@ dependencies:
|
|
109
142
|
version: '0'
|
110
143
|
type: :development
|
111
144
|
prerelease: false
|
112
|
-
version_requirements: *
|
145
|
+
version_requirements: *70261404210600
|
113
146
|
description: A simple, yet flexible solution to tracking changes made to objects in
|
114
147
|
your database.
|
115
148
|
email:
|
@@ -128,15 +161,28 @@ files:
|
|
128
161
|
- Rakefile
|
129
162
|
- changeling.gemspec
|
130
163
|
- lib/changeling.rb
|
164
|
+
- lib/changeling/blameling.rb
|
131
165
|
- lib/changeling/models/logling.rb
|
132
166
|
- lib/changeling/probeling.rb
|
167
|
+
- lib/changeling/support/search.rb
|
133
168
|
- lib/changeling/trackling.rb
|
134
169
|
- lib/changeling/version.rb
|
135
170
|
- spec/config/mongoid.yml
|
136
|
-
- spec/
|
137
|
-
- spec/
|
171
|
+
- spec/controllers/blameling_controller_spec.rb
|
172
|
+
- spec/controllers/blog_posts_controller_spec.rb
|
173
|
+
- spec/controllers/current_account_controller_spec.rb
|
174
|
+
- spec/controllers/no_current_user_controller_spec.rb
|
175
|
+
- spec/fixtures/app/application.rb
|
176
|
+
- spec/fixtures/app/controllers/blameling_controller.rb
|
177
|
+
- spec/fixtures/app/controllers/blog_posts_controller.rb
|
178
|
+
- spec/fixtures/app/controllers/current_account_controller.rb
|
179
|
+
- spec/fixtures/app/controllers/no_current_user_controller.rb
|
180
|
+
- spec/fixtures/app/models/blog_post.rb
|
181
|
+
- spec/fixtures/app/models/blog_post_active_record.rb
|
182
|
+
- spec/fixtures/app/models/blog_post_no_timestamp.rb
|
138
183
|
- spec/lib/changeling/models/logling_spec.rb
|
139
184
|
- spec/lib/changeling/probeling_spec.rb
|
185
|
+
- spec/lib/changeling/support/search_spec.rb
|
140
186
|
- spec/lib/changeling/trackling_spec.rb
|
141
187
|
- spec/spec_helper.rb
|
142
188
|
homepage: ''
|
@@ -159,15 +205,26 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
205
|
version: '0'
|
160
206
|
requirements: []
|
161
207
|
rubyforge_project:
|
162
|
-
rubygems_version: 1.8.
|
208
|
+
rubygems_version: 1.8.15
|
163
209
|
signing_key:
|
164
210
|
specification_version: 3
|
165
211
|
summary: Object change-logger
|
166
212
|
test_files:
|
167
213
|
- spec/config/mongoid.yml
|
168
|
-
- spec/
|
169
|
-
- spec/
|
214
|
+
- spec/controllers/blameling_controller_spec.rb
|
215
|
+
- spec/controllers/blog_posts_controller_spec.rb
|
216
|
+
- spec/controllers/current_account_controller_spec.rb
|
217
|
+
- spec/controllers/no_current_user_controller_spec.rb
|
218
|
+
- spec/fixtures/app/application.rb
|
219
|
+
- spec/fixtures/app/controllers/blameling_controller.rb
|
220
|
+
- spec/fixtures/app/controllers/blog_posts_controller.rb
|
221
|
+
- spec/fixtures/app/controllers/current_account_controller.rb
|
222
|
+
- spec/fixtures/app/controllers/no_current_user_controller.rb
|
223
|
+
- spec/fixtures/app/models/blog_post.rb
|
224
|
+
- spec/fixtures/app/models/blog_post_active_record.rb
|
225
|
+
- spec/fixtures/app/models/blog_post_no_timestamp.rb
|
170
226
|
- spec/lib/changeling/models/logling_spec.rb
|
171
227
|
- spec/lib/changeling/probeling_spec.rb
|
228
|
+
- spec/lib/changeling/support/search_spec.rb
|
172
229
|
- spec/lib/changeling/trackling_spec.rb
|
173
230
|
- spec/spec_helper.rb
|