acts_as_diffable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +56 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/acts_as_diffable.gemspec +68 -0
- data/init.rb +1 -0
- data/lib/acts_as_diffable.rb +213 -0
- data/test/app_root/app/controllers/application_controller.rb +2 -0
- data/test/app_root/app/models/group.rb +4 -0
- data/test/app_root/app/models/tag.rb +4 -0
- data/test/app_root/app/models/user.rb +11 -0
- data/test/app_root/config/boot.rb +115 -0
- data/test/app_root/config/database.yml +31 -0
- data/test/app_root/config/environment.rb +14 -0
- data/test/app_root/config/environments/in_memory.rb +0 -0
- data/test/app_root/config/environments/mysql.rb +0 -0
- data/test/app_root/config/environments/postgresql.rb +0 -0
- data/test/app_root/config/environments/sqlite.rb +0 -0
- data/test/app_root/config/environments/sqlite3.rb +0 -0
- data/test/app_root/config/routes.rb +4 -0
- data/test/app_root/db/migrate/20101117081517_create_users.rb +19 -0
- data/test/app_root/db/migrate/20101117081553_create_groups.rb +13 -0
- data/test/app_root/db/migrate/20101117081632_create_tags.rb +14 -0
- data/test/app_root/lib/console_with_fixtures.rb +4 -0
- data/test/app_root/log/.gitignore +0 -0
- data/test/app_root/script/console +7 -0
- data/test/diff_test.rb +40 -0
- data/test/fixtures/groups.yml +7 -0
- data/test/fixtures/tags.yml +33 -0
- data/test/fixtures/users.yml +54 -0
- data/test/test_helper.rb +21 -0
- metadata +110 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 James Mason & The SUSE Studio Team @ NOVELL/SUSE
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
ActsAsDiffable provides a dead-simple way to compare two instances of a class,
|
2
|
+
including any or all associations, or more complex relationships.
|
3
|
+
|
4
|
+
The return is a hash*, suitable for digestion by case-based textualizers,
|
5
|
+
JSON processors, etc., in the form { 'attribute' => [from, to] }
|
6
|
+
* In the instance of no changes, a nil is returned
|
7
|
+
|
8
|
+
== Usage
|
9
|
+
|
10
|
+
class Foo < ActiveRecord::Base
|
11
|
+
acts_as_diffable
|
12
|
+
end
|
13
|
+
> Foo.first.diff(Foo.last) => { 'bar' => ['foo', nil] }
|
14
|
+
|
15
|
+
=== Associations
|
16
|
+
|
17
|
+
For plural associations, a :diff_key option needs to be added to the association,
|
18
|
+
defining how to relate disparate instances within each parent's collections.
|
19
|
+
This can be a single field or a collection of fields in an array, and will be
|
20
|
+
expressed as the key side of a hash with the value being the hash of attribute
|
21
|
+
differences.
|
22
|
+
|
23
|
+
For singular association, there is no need to specify a way to organize and
|
24
|
+
compare, so we only need to express which associations to include in the diff,
|
25
|
+
by adding a :diff option to the association that evaluates to true.
|
26
|
+
|
27
|
+
class Foo < ActiveRecord::Base
|
28
|
+
acts_as_diffable
|
29
|
+
|
30
|
+
has_one :bar, :diff => true
|
31
|
+
has_many :fish, :diff_keypattern => :name
|
32
|
+
has_and_belongs_to_many :users, :diff_keypattern => [:firstname, :lastname]
|
33
|
+
end
|
34
|
+
> Foo.first.diff(Foo.last)
|
35
|
+
=> { 'bar' => { 'attr1' => ['a', 'b'],
|
36
|
+
'attr2' => [14, nil] },
|
37
|
+
'fish => { 'nemo' => {'fish_attr1' => [nil, 'zip'] },
|
38
|
+
'goldie' => {'fish_attr1' => ['zap', 'zop'] } },
|
39
|
+
'users' => { ['Jane', 'Doe'] => { 'firstname' => 'Jane',
|
40
|
+
'lastname' => 'Doe' },
|
41
|
+
['John', 'Doe'] => { '_deleted' => true } } }
|
42
|
+
|
43
|
+
=== More complex relationships
|
44
|
+
|
45
|
+
In addition to the marked associations, any method on the class that returns an
|
46
|
+
ActiveRecord-ish object can be included in the diff by adding a
|
47
|
+
manual_diff_definiton. For comparing collections of ActiveRecord objects,
|
48
|
+
use the form:
|
49
|
+
|
50
|
+
manual_diff_definition :name, :eval => 'instance_eval_code',
|
51
|
+
:diff_key => [:key, :pattern]
|
52
|
+
|
53
|
+
For simpler singular comparisons, omit the diff_key option.
|
54
|
+
|
55
|
+
|
56
|
+
Have a lot of fun!
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gem|
|
4
|
+
gem.name = "acts_as_diffable"
|
5
|
+
gem.summary = "Compare two instances of an ActiveRecord::Base class."
|
6
|
+
gem.description = "ActsAsDiffable provides a dead-simple way to compare two instances of a class, including any or all associations, or more complex relationships."
|
7
|
+
gem.email = "jmason@suse.com"
|
8
|
+
gem.homepage = "https://github.com/bear454/ActsAsDiffable"
|
9
|
+
gem.authors = ["James Mason 'bear454'"]
|
10
|
+
gem.add_dependency "activerecord", ">=2.3.14"
|
11
|
+
end
|
12
|
+
Jeweler::RubygemsDotOrgTasks.new
|
13
|
+
rescue LoadError
|
14
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
15
|
+
end
|
16
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "acts_as_diffable"
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["James Mason 'bear454'"]
|
12
|
+
s.date = "2012-04-19"
|
13
|
+
s.description = "ActsAsDiffable provides a dead-simple way to compare two instances of a class, including any or all associations, or more complex relationships."
|
14
|
+
s.email = "jmason@suse.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
"MIT-LICENSE",
|
20
|
+
"README",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"acts_as_diffable.gemspec",
|
24
|
+
"init.rb",
|
25
|
+
"lib/acts_as_diffable.rb",
|
26
|
+
"test/app_root/app/controllers/application_controller.rb",
|
27
|
+
"test/app_root/app/models/group.rb",
|
28
|
+
"test/app_root/app/models/tag.rb",
|
29
|
+
"test/app_root/app/models/user.rb",
|
30
|
+
"test/app_root/config/boot.rb",
|
31
|
+
"test/app_root/config/database.yml",
|
32
|
+
"test/app_root/config/environment.rb",
|
33
|
+
"test/app_root/config/environments/in_memory.rb",
|
34
|
+
"test/app_root/config/environments/mysql.rb",
|
35
|
+
"test/app_root/config/environments/postgresql.rb",
|
36
|
+
"test/app_root/config/environments/sqlite.rb",
|
37
|
+
"test/app_root/config/environments/sqlite3.rb",
|
38
|
+
"test/app_root/config/routes.rb",
|
39
|
+
"test/app_root/db/migrate/20101117081517_create_users.rb",
|
40
|
+
"test/app_root/db/migrate/20101117081553_create_groups.rb",
|
41
|
+
"test/app_root/db/migrate/20101117081632_create_tags.rb",
|
42
|
+
"test/app_root/lib/console_with_fixtures.rb",
|
43
|
+
"test/app_root/log/.gitignore",
|
44
|
+
"test/app_root/script/console",
|
45
|
+
"test/diff_test.rb",
|
46
|
+
"test/fixtures/groups.yml",
|
47
|
+
"test/fixtures/tags.yml",
|
48
|
+
"test/fixtures/users.yml",
|
49
|
+
"test/test_helper.rb"
|
50
|
+
]
|
51
|
+
s.homepage = "https://github.com/bear454/ActsAsDiffable"
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubygems_version = "1.8.22"
|
54
|
+
s.summary = "Compare two instances of an ActiveRecord::Base class."
|
55
|
+
|
56
|
+
if s.respond_to? :specification_version then
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 2.3.14"])
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<activerecord>, [">= 2.3.14"])
|
63
|
+
end
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<activerecord>, [">= 2.3.14"])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts_as_diffable'
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module ActiveRecord #:nodoc:
|
4
|
+
module Acts #:nodoc:
|
5
|
+
module Diffable #:nodoc:
|
6
|
+
|
7
|
+
SINGULAR_MACROS = [:has_one, :belongs_to]
|
8
|
+
PLURAL_MACROS = [:has_many, :has_and_belongs_to_many]
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def acts_as_diffable
|
17
|
+
class_variable_set :@@manual_diff_definitions, {}
|
18
|
+
extend ActiveRecord::Acts::Diffable::SingletonMethods
|
19
|
+
include ActiveRecord::Acts::Diffable::InstanceMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
module SingletonMethods
|
25
|
+
def manual_diff_definition(name, options = {})
|
26
|
+
definitions = class_variable_get :@@manual_diff_definitions
|
27
|
+
definitions[name.to_s] = options if options[:eval] # otherwise just ignore it
|
28
|
+
class_variable_set :@@manual_diff_definitions, definitions
|
29
|
+
end
|
30
|
+
|
31
|
+
def manual_diff_definitions
|
32
|
+
class_variable_get :@@manual_diff_definitions
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Adds instance methods.
|
37
|
+
module InstanceMethods
|
38
|
+
# Return a hash of the different attributes between two hashes, such as
|
39
|
+
# attributes of an ActiveRecord class.
|
40
|
+
def diff(other)
|
41
|
+
# is other an instance or just an id?
|
42
|
+
case other.class
|
43
|
+
when self.class
|
44
|
+
other
|
45
|
+
else
|
46
|
+
other = self.class.find(other)
|
47
|
+
end
|
48
|
+
# diff the top-level attributes
|
49
|
+
differences = attributes_diff(self, other) || {}
|
50
|
+
# has_one and belongs_to associations
|
51
|
+
ActiveRecord::Acts::Diffable::SINGULAR_MACROS.each do |macro|
|
52
|
+
self.class.reflect_on_all_associations(macro).each do |a|
|
53
|
+
differences[a.name.to_s] = singular_association_diff(self, other, a.name) if a.options[:diff]
|
54
|
+
differences.delete(a.options[:foreign_key] || "#{a.name}_id")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# has_many and habtm associations
|
58
|
+
ActiveRecord::Acts::Diffable::PLURAL_MACROS.each do |macro|
|
59
|
+
self.class.reflect_on_all_associations(macro).each do |a|
|
60
|
+
differences[a.name.to_s] = plural_association_diff(self, other, a.name, a.options[:diff_key]) if a.options[:diff_key]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
# manually defined diffs
|
64
|
+
self.class.manual_diff_definitions.each{|d_name, d_props|
|
65
|
+
if d_props[:diff_key]
|
66
|
+
differences[d_name] = plural_association_diff(self, other, d_props[:eval], d_props[:diff_key])
|
67
|
+
else
|
68
|
+
differences[d_name] = singular_association_diff(self, other, d_props[:eval])
|
69
|
+
end
|
70
|
+
}
|
71
|
+
remove_unchanged_entries differences
|
72
|
+
end
|
73
|
+
|
74
|
+
def timed_log(start_time, msg)
|
75
|
+
puts "%04.2fs %s" % [(Time.now - start_time), msg]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Helper for processing a single associated object,
|
79
|
+
# such as has_one (or belongs_to) associations.
|
80
|
+
def singular_association_diff(left_parent, right_parent, association)
|
81
|
+
association = association.to_s #instance_eval doesn't like symbols. What'ev.
|
82
|
+
attributes_diff(
|
83
|
+
left_parent.instance_eval(association),
|
84
|
+
right_parent.instance_eval(association),
|
85
|
+
association_ids(left_parent) )
|
86
|
+
end
|
87
|
+
|
88
|
+
# Helper for processing a collection of associated objects,
|
89
|
+
# such as has_many (or habtm) association.
|
90
|
+
def plural_association_diff(left_parent, right_parent, association, key_pattern)
|
91
|
+
key_pattern = Array(key_pattern)
|
92
|
+
association = association.to_s #instance_eval doesn't like symbols. What'ev.
|
93
|
+
left_association_set = Array(left_parent.instance_eval(association))
|
94
|
+
right_associaton_set = Array(right_parent.instance_eval(association))
|
95
|
+
# construct a set of values (key_set) from the attributes defined in key_pattern
|
96
|
+
key_sets = (
|
97
|
+
left_association_set.collect{|i| key_pattern.collect{|k| i.send(k) } } +
|
98
|
+
right_associaton_set.collect{|i| key_pattern.collect{|k| i.send(k) } } ).uniq
|
99
|
+
# for each key_set, compare instances in each collection
|
100
|
+
diff_set = {}
|
101
|
+
key_sets.each do |key_set|
|
102
|
+
conditions = {}
|
103
|
+
key_pattern.each_with_index{|k, i| conditions[k] = key_set[i] }
|
104
|
+
left_instance = left_association_set.find{|i|
|
105
|
+
conditions.collect{|cf,cv| i.send(cf) == cv}.all?
|
106
|
+
}
|
107
|
+
right_instance = right_associaton_set.find{|i|
|
108
|
+
conditions.collect{|cf,cv| i.send(cf) == cv}.all?
|
109
|
+
}
|
110
|
+
diff_set[keyify(key_set)] = attributes_diff(left_instance, right_instance, association_ids(left_parent) )
|
111
|
+
end
|
112
|
+
|
113
|
+
# clean up unchanged pairs
|
114
|
+
remove_unchanged_entries diff_set
|
115
|
+
end
|
116
|
+
|
117
|
+
# reduce the key out of an array to a single string if only one element
|
118
|
+
def keyify(keyset)
|
119
|
+
case keyset.size
|
120
|
+
when 1
|
121
|
+
keyset[0]
|
122
|
+
else
|
123
|
+
keyset
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Helper for collecting ids to ignore.
|
128
|
+
def association_ids(*instances)
|
129
|
+
['id'] + instances.collect{|i| i.class.to_s.underscore + '_id'}
|
130
|
+
end
|
131
|
+
|
132
|
+
# Helper for handing objects with an attributes hash (a la ARec).
|
133
|
+
def attributes_diff(left, right, ignore = [:id])
|
134
|
+
left_attributes = case
|
135
|
+
when left.is_a?(Hash) then
|
136
|
+
left
|
137
|
+
when left.respond_to?(:attributes) then
|
138
|
+
left.attributes
|
139
|
+
else
|
140
|
+
left.instance_values
|
141
|
+
end
|
142
|
+
right_attributes = case
|
143
|
+
when right.is_a?(Hash) then
|
144
|
+
right
|
145
|
+
when right.respond_to?(:attributes) then
|
146
|
+
right.attributes
|
147
|
+
else
|
148
|
+
right.instance_values
|
149
|
+
end
|
150
|
+
|
151
|
+
if left_attributes == right_attributes
|
152
|
+
return nil
|
153
|
+
else
|
154
|
+
generate_diff_hash(left_attributes, right_attributes, *ignore)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Helper for thinning the herd
|
159
|
+
def remove_unchanged_entries(diff_hash)
|
160
|
+
return nil if !diff_hash
|
161
|
+
diff_hash.delete_if{|k,v| v.nil? }
|
162
|
+
if diff_hash.empty?
|
163
|
+
return nil
|
164
|
+
else
|
165
|
+
return diff_hash
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Accepts a left & right hash, and an array of keys to ignore,
|
170
|
+
# returns a hash of the differences.
|
171
|
+
#
|
172
|
+
# This here is the meat & potatoes!
|
173
|
+
def generate_diff_hash(left, right, *ignore)
|
174
|
+
case [left.blank?, right.blank?]
|
175
|
+
when [false, true] # the represented object was deleted
|
176
|
+
{ '_delete' => true } # inspired by nested_attributes
|
177
|
+
when [true, false] # the represented object was added
|
178
|
+
(ignore + %w(created_at updated_at)).each{|k| right.delete(k.to_s) }
|
179
|
+
return right # just return the attributes to add
|
180
|
+
when [false, false] # the represented object changed
|
181
|
+
# generate the attribute diffs from each side and
|
182
|
+
# merge them together as attribute => [left_value, right_value]
|
183
|
+
if left == right
|
184
|
+
return nil
|
185
|
+
else
|
186
|
+
diff_hash = left.diff(right).merge(right.diff(left)){|k, lv, rv| [lv, rv] }
|
187
|
+
# remove any ignored attributes
|
188
|
+
ignore.each {|k| diff_hash.delete(k.to_s) }
|
189
|
+
# compress created_at/updated_at duplication
|
190
|
+
diff_hash.delete('updated_at') if diff_hash['created_at'] == diff_hash['updated_at']
|
191
|
+
remove_unchanged_entries diff_hash
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# reopen ActiveRecord and include all the above to make
|
203
|
+
# them available to all our models if they want it
|
204
|
+
|
205
|
+
ActiveRecord::Base.class_eval do
|
206
|
+
include ActiveRecord::Acts::Diffable
|
207
|
+
end
|
208
|
+
|
209
|
+
# monkeypatch the diff keys onto the association proxies
|
210
|
+
ActiveRecord::Associations::Builder::BelongsTo.class_eval("self.valid_options += [:diff]")
|
211
|
+
ActiveRecord::Associations::Builder::HasOne.class_eval("self.valid_options += [:diff]")
|
212
|
+
ActiveRecord::Associations::Builder::HasMany.class_eval("self.valid_options += [:diff_key]")
|
213
|
+
ActiveRecord::Associations::Builder::HasAndBelongsToMany.class_eval("self.valid_options += [:diff_key]")
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# top-level class
|
2
|
+
class User < ActiveRecord::Base
|
3
|
+
acts_as_diffable
|
4
|
+
|
5
|
+
belongs_to :group, :diff => true
|
6
|
+
has_many :tags, :diff_key => :name
|
7
|
+
|
8
|
+
manual_diff_definition :group_name, :eval => 'group.name'
|
9
|
+
manual_diff_definition :m_tags, :eval => 'tags', :diff_key => 'name'
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# Allow customization of the rails framework path
|
2
|
+
RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT)
|
3
|
+
|
4
|
+
# Don't change this file!
|
5
|
+
# Configure your app in config/environment.rb and config/environments/*.rb
|
6
|
+
|
7
|
+
Rails.root.to_s = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
class << self
|
11
|
+
def boot!
|
12
|
+
unless booted?
|
13
|
+
preinitialize
|
14
|
+
pick_boot.run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def booted?
|
19
|
+
defined? Rails::Initializer
|
20
|
+
end
|
21
|
+
|
22
|
+
def pick_boot
|
23
|
+
(vendor_rails? ? VendorBoot : GemBoot).new
|
24
|
+
end
|
25
|
+
|
26
|
+
def vendor_rails?
|
27
|
+
File.exist?(RAILS_FRAMEWORK_ROOT)
|
28
|
+
end
|
29
|
+
|
30
|
+
def preinitialize
|
31
|
+
load(preinitializer_path) if File.exist?(preinitializer_path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def preinitializer_path
|
35
|
+
"#{Rails.root.to_s}/config/preinitializer.rb"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Boot
|
40
|
+
def run
|
41
|
+
load_initializer
|
42
|
+
Rails::Initializer.run(:set_load_path)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class VendorBoot < Boot
|
47
|
+
def load_initializer
|
48
|
+
require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer"
|
49
|
+
Rails::Initializer.run(:install_gem_spec_stubs)
|
50
|
+
Rails::GemDependency.add_frozen_gem_path
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class GemBoot < Boot
|
55
|
+
def load_initializer
|
56
|
+
self.class.load_rubygems
|
57
|
+
load_rails_gem
|
58
|
+
require 'initializer'
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_rails_gem
|
62
|
+
if version = self.class.gem_version
|
63
|
+
gem 'rails', version
|
64
|
+
else
|
65
|
+
gem 'rails'
|
66
|
+
end
|
67
|
+
rescue Gem::LoadError => load_error
|
68
|
+
$stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
def rubygems_version
|
74
|
+
Gem::RubyGemsVersion rescue nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def gem_version
|
78
|
+
if defined? RAILS_GEM_VERSION
|
79
|
+
RAILS_GEM_VERSION
|
80
|
+
elsif ENV.include?('RAILS_GEM_VERSION')
|
81
|
+
ENV['RAILS_GEM_VERSION']
|
82
|
+
else
|
83
|
+
parse_gem_version(read_environment_rb)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_rubygems
|
88
|
+
require 'rubygems'
|
89
|
+
min_version = '1.3.1'
|
90
|
+
unless rubygems_version >= min_version
|
91
|
+
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
|
95
|
+
rescue LoadError
|
96
|
+
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
97
|
+
exit 1
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_gem_version(text)
|
101
|
+
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def read_environment_rb
|
106
|
+
environment_rb = "#{Rails.root.to_s}/config/environment.rb"
|
107
|
+
environment_rb = "#{HELPER_Rails.root.to_s}/config/environment.rb" unless File.exists?(environment_rb)
|
108
|
+
File.read(environment_rb)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# All that for this:
|
115
|
+
Rails.boot!
|
@@ -0,0 +1,31 @@
|
|
1
|
+
in_memory:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ":memory:"
|
4
|
+
verbosity: quiet
|
5
|
+
pool: 5
|
6
|
+
timeout: 5000
|
7
|
+
sqlite:
|
8
|
+
adapter: sqlite
|
9
|
+
dbfile: plugin_test.sqlite.db
|
10
|
+
pool: 5
|
11
|
+
timeout: 5000
|
12
|
+
sqlite3:
|
13
|
+
adapter: sqlite3
|
14
|
+
dbfile: plugin_test.sqlite3.db
|
15
|
+
pool: 5
|
16
|
+
timeout: 5000
|
17
|
+
postgresql:
|
18
|
+
adapter: postgresql
|
19
|
+
username: postgres
|
20
|
+
password: postgres
|
21
|
+
database: plugin_test
|
22
|
+
pool: 5
|
23
|
+
timeout: 5000
|
24
|
+
mysql:
|
25
|
+
adapter: mysql
|
26
|
+
host: localhost
|
27
|
+
username: root
|
28
|
+
password:
|
29
|
+
database: plugin_test
|
30
|
+
pool: 5
|
31
|
+
timeout: 5000
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
2
|
+
|
3
|
+
Rails::Initializer.run do |config|
|
4
|
+
config.cache_classes = false
|
5
|
+
config.whiny_nils = true
|
6
|
+
config.action_controller.session = {:key => 'rails_session', :secret => 'd229e4d22437432705ab3985d4d246'}
|
7
|
+
config.plugin_locators.unshift(
|
8
|
+
Class.new(Rails::Plugin::Locator) do
|
9
|
+
def plugins
|
10
|
+
[Rails::Plugin.new(File.expand_path('.'))]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
) unless defined?(PluginTestHelper::PluginLocator)
|
14
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class CreateUsers < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :users do |t|
|
4
|
+
t.string :name
|
5
|
+
t.integer :rank
|
6
|
+
t.float :factor
|
7
|
+
t.date :activated_on
|
8
|
+
t.text :biography
|
9
|
+
t.boolean :admin
|
10
|
+
t.integer :group_id
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.down
|
17
|
+
drop_table :users
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
# Loads fixtures into the database when running the test app via the console
|
2
|
+
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(Rails.root, '../fixtures/*.{yml,csv}'))).each do |fixture_file|
|
3
|
+
Fixtures.create_fixtures(File.join(Rails.root, '../fixtures'), File.basename(fixture_file, '.*'))
|
4
|
+
end
|
File without changes
|
data/test/diff_test.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class DiffTest < ActiveSupport::TestCase
|
4
|
+
def test_instance_is_loadable
|
5
|
+
assert users(:john)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_instances_with_identical_attributes_should_return_a_nill_for_diff
|
9
|
+
assert_equal nil, users(:john).diff(users(:john))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_only_different_attributes_should_be_returned
|
13
|
+
diff_expectation = {'name' => ['John Doe', 'Jane Doe'],
|
14
|
+
'rank' => [1, 2],
|
15
|
+
'factor' => [2.718281828459045, 3.141592653589793],
|
16
|
+
'activated_on' => [ Date.new(2010,11,17), Date.new(2005,01,01) ],
|
17
|
+
'admin' => [false, true] }
|
18
|
+
diff_result = users(:john).diff(users(:jane))
|
19
|
+
|
20
|
+
assert_equal diff_expectation.inspect, diff_result.inspect
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_singular_associations
|
24
|
+
diff_expectation = {'group' => { 'name' => ['FirstGroup', 'SecondGroup'] } }
|
25
|
+
diff_result = users(:jane).diff(users(:jane_in_a_different_group))
|
26
|
+
|
27
|
+
assert_equal diff_expectation.inspect, diff_result.inspect
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_plural_associations
|
31
|
+
diff_expectation = {'tags' => { 'foo' => {'name' => 'foo' },
|
32
|
+
'bar' => {'_delete' => true } },
|
33
|
+
'm_tags' => { 'foo' => {'name' => 'foo' },
|
34
|
+
'bar' => {'_delete' => true } } }
|
35
|
+
diff_result = users(:john).diff(users(:john_with_different_tags))
|
36
|
+
|
37
|
+
assert_equal diff_expectation.inspect, diff_result.inspect
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
2
|
+
|
3
|
+
foo:
|
4
|
+
name: foo
|
5
|
+
user: john_with_different_tags
|
6
|
+
|
7
|
+
de:
|
8
|
+
name: de
|
9
|
+
user: john
|
10
|
+
|
11
|
+
de2:
|
12
|
+
name: de
|
13
|
+
user: john_with_different_tags
|
14
|
+
|
15
|
+
de3:
|
16
|
+
name: de
|
17
|
+
user: jane
|
18
|
+
|
19
|
+
de4:
|
20
|
+
name: de
|
21
|
+
user: jane_in_a_different_group
|
22
|
+
|
23
|
+
bar:
|
24
|
+
name: bar
|
25
|
+
user: john
|
26
|
+
|
27
|
+
bar2:
|
28
|
+
name: bar
|
29
|
+
user: jane
|
30
|
+
|
31
|
+
bar3:
|
32
|
+
name: bar
|
33
|
+
user: jane_in_a_different_group
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
2
|
+
|
3
|
+
john:
|
4
|
+
name: John Doe
|
5
|
+
rank: 1
|
6
|
+
factor: 2.718281828459045
|
7
|
+
activated_on: 2010-11-17
|
8
|
+
biography: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a
|
9
|
+
justo sed mauris rutrum rutrum nec vitae ante. Mauris tincidunt, mi sed
|
10
|
+
commodo fermentum, velit magna pulvinar enim, nec fringilla elit risus in
|
11
|
+
dolor. Aenean a lacus nec sem iaculis lacinia eget sed leo. Quisque ipsum
|
12
|
+
velit, sodales non dapibus."
|
13
|
+
admin: false
|
14
|
+
group: one
|
15
|
+
|
16
|
+
john_with_different_tags:
|
17
|
+
name: John Doe
|
18
|
+
rank: 1
|
19
|
+
factor: 2.718281828459045
|
20
|
+
activated_on: 2010-11-17
|
21
|
+
biography: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a
|
22
|
+
justo sed mauris rutrum rutrum nec vitae ante. Mauris tincidunt, mi sed
|
23
|
+
commodo fermentum, velit magna pulvinar enim, nec fringilla elit risus in
|
24
|
+
dolor. Aenean a lacus nec sem iaculis lacinia eget sed leo. Quisque ipsum
|
25
|
+
velit, sodales non dapibus."
|
26
|
+
admin: false
|
27
|
+
group: one
|
28
|
+
|
29
|
+
jane:
|
30
|
+
name: Jane Doe
|
31
|
+
rank: 2
|
32
|
+
factor: 3.141592653589793
|
33
|
+
activated_on: 2005-01-01
|
34
|
+
biography: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a
|
35
|
+
justo sed mauris rutrum rutrum nec vitae ante. Mauris tincidunt, mi sed
|
36
|
+
commodo fermentum, velit magna pulvinar enim, nec fringilla elit risus in
|
37
|
+
dolor. Aenean a lacus nec sem iaculis lacinia eget sed leo. Quisque ipsum
|
38
|
+
velit, sodales non dapibus."
|
39
|
+
admin: true
|
40
|
+
group: one
|
41
|
+
|
42
|
+
jane_in_a_different_group:
|
43
|
+
name: Jane Doe
|
44
|
+
rank: 2
|
45
|
+
factor: 3.141592653589793
|
46
|
+
activated_on: 2005-01-01
|
47
|
+
biography: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a
|
48
|
+
justo sed mauris rutrum rutrum nec vitae ante. Mauris tincidunt, mi sed
|
49
|
+
commodo fermentum, velit magna pulvinar enim, nec fringilla elit risus in
|
50
|
+
dolor. Aenean a lacus nec sem iaculis lacinia eget sed leo. Quisque ipsum
|
51
|
+
velit, sodales non dapibus."
|
52
|
+
admin: true
|
53
|
+
group: two
|
54
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Set the default environment to sqlite3's in_memory database
|
2
|
+
ENV['Rails.env.to_s'] ||= 'in_memory'
|
3
|
+
|
4
|
+
# Load the Rails environment and testing framework
|
5
|
+
require "#{File.dirname(__FILE__)}/app_root/config/environment"
|
6
|
+
require 'test_help'
|
7
|
+
|
8
|
+
# Undo changes to Rails.env.to_s
|
9
|
+
silence_warnings {Rails.env.to_s = ENV['RAILS_ENV']}
|
10
|
+
|
11
|
+
# Run the migrations
|
12
|
+
ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate")
|
13
|
+
|
14
|
+
# Set default fixture loading properties
|
15
|
+
ActiveSupport::TestCase.class_eval do
|
16
|
+
self.use_transactional_fixtures = true
|
17
|
+
self.use_instantiated_fixtures = false
|
18
|
+
self.fixture_path = "#{File.dirname(__FILE__)}/fixtures"
|
19
|
+
|
20
|
+
fixtures :all
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_diffable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- James Mason 'bear454'
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-19 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: activerecord
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 31
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 3
|
32
|
+
- 14
|
33
|
+
version: 2.3.14
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
description: ActsAsDiffable provides a dead-simple way to compare two instances of a class, including any or all associations, or more complex relationships.
|
37
|
+
email: jmason@suse.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- README
|
44
|
+
files:
|
45
|
+
- MIT-LICENSE
|
46
|
+
- README
|
47
|
+
- Rakefile
|
48
|
+
- VERSION
|
49
|
+
- acts_as_diffable.gemspec
|
50
|
+
- init.rb
|
51
|
+
- lib/acts_as_diffable.rb
|
52
|
+
- test/app_root/app/controllers/application_controller.rb
|
53
|
+
- test/app_root/app/models/group.rb
|
54
|
+
- test/app_root/app/models/tag.rb
|
55
|
+
- test/app_root/app/models/user.rb
|
56
|
+
- test/app_root/config/boot.rb
|
57
|
+
- test/app_root/config/database.yml
|
58
|
+
- test/app_root/config/environment.rb
|
59
|
+
- test/app_root/config/environments/in_memory.rb
|
60
|
+
- test/app_root/config/environments/mysql.rb
|
61
|
+
- test/app_root/config/environments/postgresql.rb
|
62
|
+
- test/app_root/config/environments/sqlite.rb
|
63
|
+
- test/app_root/config/environments/sqlite3.rb
|
64
|
+
- test/app_root/config/routes.rb
|
65
|
+
- test/app_root/db/migrate/20101117081517_create_users.rb
|
66
|
+
- test/app_root/db/migrate/20101117081553_create_groups.rb
|
67
|
+
- test/app_root/db/migrate/20101117081632_create_tags.rb
|
68
|
+
- test/app_root/lib/console_with_fixtures.rb
|
69
|
+
- test/app_root/log/.gitignore
|
70
|
+
- test/app_root/script/console
|
71
|
+
- test/diff_test.rb
|
72
|
+
- test/fixtures/groups.yml
|
73
|
+
- test/fixtures/tags.yml
|
74
|
+
- test/fixtures/users.yml
|
75
|
+
- test/test_helper.rb
|
76
|
+
homepage: https://github.com/bear454/ActsAsDiffable
|
77
|
+
licenses: []
|
78
|
+
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
hash: 3
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
hash: 3
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project:
|
105
|
+
rubygems_version: 1.8.22
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Compare two instances of an ActiveRecord::Base class.
|
109
|
+
test_files: []
|
110
|
+
|