acts_as_diffable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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!
@@ -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,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,4 @@
1
+ # for testing single associations
2
+ class Group < ActiveRecord::Base
3
+ has_many :users
4
+ end
@@ -0,0 +1,4 @@
1
+ # for testing plural associations
2
+ class Tag < ActiveRecord::Base
3
+ belongs_to :user
4
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.connect ':controller/:action/:id'
3
+ map.connect ':controller/:action/:id.:format'
4
+ end
@@ -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,13 @@
1
+ class CreateGroups < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :groups do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :groups
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ class CreateTags < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tags do |t|
4
+ t.string :name
5
+ t.integer :user_id
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :tags
13
+ end
14
+ 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
@@ -0,0 +1,7 @@
1
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
2
+ libs = " -r irb/completion"
3
+ libs << " -r test/test_helper"
4
+ libs << " -r console_app"
5
+ libs << " -r console_with_helpers"
6
+ libs << " -r console_with_fixtures"
7
+ exec "#{irb} #{libs} --simple-prompt"
@@ -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,7 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+
3
+ one:
4
+ name: FirstGroup
5
+
6
+ two:
7
+ name: SecondGroup
@@ -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
+
@@ -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
+