changeling 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/.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] [](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
|