acts_as_diffable 0.0.1
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/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
|
+
|