merger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ test/debug.log
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Brandon Keepers
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,9 @@
1
+ Merger
2
+ ======
3
+
4
+ This is a Rails plugin for merging Active Record models together. It is far from complete, but at the moment will move all the associations from the newer records to the oldest record and delete the newer records.
5
+
6
+ Example
7
+ =======
8
+
9
+ @person.merge!(@dup1, @dup2)
@@ -0,0 +1,61 @@
1
+ require 'rake'
2
+ require "load_multi_rails_rake_tasks"
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "merger"
8
+ gem.summary = "A Rails plugin for merging Active Record models"
9
+ gem.email = "brandon@opensoul.org"
10
+ gem.homepage = "http://github.com/collectiveidea/merger"
11
+ gem.authors = ["Brandon Keepers"]
12
+ gem.add_dependency "active_record"
13
+ gem.add_development_dependency "mocha"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+
22
+ desc 'Default: run unit tests.'
23
+ task :default => :test
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib' << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ namespace :test do
33
+ begin
34
+ require 'rcov/rcovtask'
35
+ Rcov::RcovTask.new(:coverage) do |test|
36
+ test.libs << 'test'
37
+ test.pattern = 'test/**/test_*.rb'
38
+ test.verbose = true
39
+ test.output_dir = 'coverage'
40
+ test.rcov_opts = %w(--exclude test,/usr/lib/ruby,/Library/Ruby --sort coverage)
41
+ end
42
+ rescue LoadError
43
+ task :coverage do
44
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
45
+ end
46
+ end
47
+ end
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "merger #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ rdoc.options << '--line-numbers' << '--inline-source'
58
+ end
59
+
60
+ task :default => :test
61
+ task :test => :check_dependencies
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'merger'
2
+
3
+ ActiveRecord::Base.send(:include, Merger)
@@ -0,0 +1,13 @@
1
+ require 'merger/merge'
2
+
3
+ module Merger
4
+ def self.included(base)
5
+ base.send :include, InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ def merge!(*others)
10
+ Merger::Merge.new(others, :keep => self).merge!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ module Merger
2
+ class Merge
3
+ attr_reader :keep, :duplicates, :options
4
+
5
+ def initialize(*records)
6
+ @options = records.extract_options!
7
+ @options[:destroy] = true unless @options.has_key?(:destroy)
8
+ records = records.flatten.uniq
9
+ @keep = options[:keep] || records.sort_by(&:id).first
10
+ @duplicates = records - [@keep]
11
+ end
12
+
13
+ def ignored_associations
14
+ return @ignored if @ignored
15
+ @ignored = Array(options[:skip_association])
16
+ keep.class.reflect_on_all_associations.each do |association|
17
+ @ignored << association.through_reflection.name if association.through_reflection
18
+ end
19
+ @ignored
20
+ end
21
+
22
+ def associations!
23
+ keep.class.reflect_on_all_associations.each do |association|
24
+ duplicates.each do |record|
25
+ next if ignored_associations.include?(association.name)
26
+ case association.macro
27
+ when :has_many, :has_and_belongs_to_many
28
+ name = "#{association.name.to_s.singularize}_ids"
29
+ keep.send("#{name}=", keep.send(name) | record.send(name))
30
+ when :belongs_to, :has_one
31
+ keep.send("#{association.name}=", record.send(association.name)) if keep.send("#{association.name}").nil?
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def merge!
38
+ keep.class.transaction do
39
+ duplicates.each {|duplicate| duplicate.send(:before_merge, keep) if duplicate.respond_to?(:before_merge) }
40
+ associations!
41
+ duplicates.each {|duplicate| duplicate.send(:after_merge, keep) if duplicate.respond_to?(:after_merge) }
42
+
43
+ duplicates.each(&:destroy) if options[:destroy]
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :dbfile: acts_as_audited_plugin.sqlite.db
4
+ sqlite3mem:
5
+ :adapter: sqlite3
6
+ :dbfile: ":memory:"
7
+ sqlite3:
8
+ :adapter: sqlite3
9
+ :dbfile: acts_as_audited_plugin.sqlite3.db
10
+ postgresql:
11
+ :adapter: postgresql
12
+ :username: postgres
13
+ :password: postgres
14
+ :database: acts_as_audited_plugin_test
15
+ :min_messages: ERROR
16
+ mysql:
17
+ :adapter: mysql
18
+ :host: localhost
19
+ :username: rails
20
+ :password:
21
+ :database: acts_as_audited_plugin_test
@@ -0,0 +1,27 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :people, :force => true do |t|
3
+ t.string :given_name, :family_name
4
+ t.timestamps
5
+ end
6
+
7
+ create_table :phones, :force => true do |t|
8
+ t.string :number, :type
9
+ t.belongs_to :person
10
+ end
11
+
12
+ create_table :tags, :force => true do |t|
13
+ t.string :name
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :taggings, :force => true do |t|
18
+ t.belongs_to :person
19
+ t.belongs_to :tag
20
+ end
21
+
22
+ create_table :companies, :force => true do |t|
23
+ t.belongs_to :person
24
+ t.string :type
25
+ t.string :name
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ original_client_1:
2
+ type: Client
3
+ name: client 1
4
+ person_id: 1
5
+ original_client_2:
6
+ type: Client
7
+ name: client 2
8
+ person_id: 1
9
+ original_supplier_1:
10
+ type: Supplier
11
+ name: supplier 1
12
+ person_id: 1
13
+
14
+ duplicate_client_3:
15
+ type: Client
16
+ name: client 3
17
+ person_id: 2
18
+
19
+ duplicate_supplier_2:
20
+ type: Supplier
21
+ name: supplier 2
22
+ person_id: 2
23
+ duplicate_supplier_3:
24
+ type: Supplier
25
+ name: supplier 3
26
+ person_id: 2
@@ -0,0 +1,10 @@
1
+ original:
2
+ id: 1
3
+ given_name: Brandon
4
+ family_name: Keepers
5
+ created_at: <%= 1.year.ago.to_s(:db) %>
6
+ duplicate:
7
+ id: 2
8
+ given_name: Brandon
9
+ family_name: Keepers
10
+ created_at: <%= 1.week.ago.to_s(:db) %>
@@ -0,0 +1,12 @@
1
+ original_home:
2
+ number: 1
3
+ type: Home
4
+ person_id: 1
5
+ original_work:
6
+ number: 2
7
+ type: Work
8
+ person_id: 1
9
+ duplicate_mobile:
10
+ number: 3
11
+ type: Mobile
12
+ person_id: 2
@@ -0,0 +1,11 @@
1
+ tagging_brandon_as_friend1:
2
+ person_id: 1
3
+ tag_id: 2
4
+ tagging_brandon_as_friend2:
5
+ person_id: 1
6
+ tag_id: 3
7
+ tagging_brandon_as_developper:
8
+ person_id: 1
9
+ tag_id: 4
10
+
11
+
@@ -0,0 +1,16 @@
1
+ original_friend:
2
+ id: 1
3
+ name: Friend
4
+ dup_friend1:
5
+ id: 2
6
+ name: Friends
7
+ dup_friend2:
8
+ id: 3
9
+ name: Friends
10
+ developer:
11
+ id: 4
12
+ name: Developer
13
+
14
+
15
+
16
+
@@ -0,0 +1,83 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ class Merger::MergeTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @merge = Merger::Merge.new(people(:original), people(:duplicate))
7
+ end
8
+
9
+ def test_keeps_oldest_record
10
+ assert_equal people(:original), @merge.keep
11
+ end
12
+
13
+ def test_explicitly_specify_record_to_keep
14
+ @merge = Merger::Merge.new(Person.find(:all), :keep => people(:duplicate))
15
+ assert_equal people(:duplicate), @merge.keep
16
+ end
17
+
18
+ def test_uses_newer_as_duplicates
19
+ assert @merge.duplicates.include?(people(:duplicate))
20
+ assert !@merge.duplicates.include?(people(:original))
21
+ end
22
+
23
+ def test_moves_associations_to_original
24
+ @merge.associations!
25
+ assert_equal 3, people(:original).phones.count
26
+ assert_equal 0, people(:duplicate).phones.count
27
+ end
28
+
29
+ def test_skip_association
30
+ Merger::Merge.new(people(:original), people(:duplicate), :skip_association => :phones).merge!
31
+ assert_equal 2, people(:original).phones.count
32
+ end
33
+
34
+ def test_merge_join_model
35
+
36
+ duplicated_tags = [
37
+ tags(:original_friend),
38
+ tags(:dup_friend1),
39
+ tags(:dup_friend2)
40
+ ]
41
+
42
+ Merger::Merge.new(duplicated_tags, :keep => tags(:original_friend)).merge!
43
+
44
+ assert_equal 2, people(:original).tags.count
45
+ assert_equal true, people(:original).tags.include?(tags(:original_friend))
46
+ assert_equal true, people(:original).tags.include?(tags(:developer))
47
+
48
+ end
49
+
50
+ def test_merge_belongs_to
51
+ Merger::Merge.new( phones(:duplicate_mobile), :keep => phones(:original_work) ).merge!
52
+
53
+ assert people(:original).phones.include?( phones(:original_work) )
54
+ assert !people(:original).phones.include?( phones(:duplicate_mobile) )
55
+
56
+ assert_equal people(:original), phones(:original_work).person
57
+ assert_equal 0, people(:duplicate).phones.count, people(:duplicate).phones.inspect
58
+ end
59
+
60
+ def test_merge_model_with_single_table_inheritance
61
+ @merge.associations!
62
+ assert_equal 6, people(:original).companies.count
63
+ assert_equal 3, people(:original).clients.count
64
+ assert_equal 3, people(:original).suppliers.count
65
+ assert_equal 0, people(:duplicate).companies.count
66
+ end
67
+
68
+ def test_destroy_duplicates
69
+ @merge.merge!
70
+ assert_nil Person.find_by_id(people(:duplicate).id)
71
+ end
72
+
73
+ def test_no_destroy_option
74
+ Merger::Merge.new(people(:original), people(:duplicate), :destroy => false).merge!
75
+ assert_not_nil Person.find_by_id(people(:duplicate).id)
76
+ end
77
+
78
+ def test_callback
79
+ people(:duplicate).expects(:before_merge).with(people(:original))
80
+ people(:duplicate).expects(:after_merge).with(people(:original))
81
+ @merge.merge!
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+
3
+ class MergerTest < Test::Unit::TestCase
4
+
5
+ def test_ar_responds_to_merge!
6
+ assert Person.new.respond_to?(:merge!)
7
+ end
8
+
9
+ end
@@ -0,0 +1,55 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'mocha'
6
+ require 'multi_rails_init'
7
+ require 'active_record'
8
+ require 'active_record/fixtures'
9
+
10
+ require File.dirname(__FILE__) + '/../init.rb'
11
+
12
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
13
+
14
+ ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + "/db/database.yml"))
15
+ ActiveRecord::Base.establish_connection(ENV["DB"] || "sqlite3mem")
16
+ ActiveRecord::Migration.verbose = false
17
+ load(File.join(File.dirname(__FILE__), "db", "schema.rb"))
18
+
19
+ class Person < ActiveRecord::Base
20
+ has_many :phones
21
+ has_many :taggings, :dependent => :destroy
22
+ has_many :tags, :through => :taggings
23
+
24
+ has_many :companies
25
+ has_many :clients
26
+ has_many :suppliers
27
+ end
28
+ class Phone < ActiveRecord::Base
29
+ belongs_to :person
30
+ set_inheritance_column false
31
+ end
32
+ class Tag < ActiveRecord::Base
33
+ has_many :taggings, :dependent => :destroy
34
+ has_many :people, :through => :taggings
35
+ end
36
+
37
+ class Tagging < ActiveRecord::Base
38
+ belongs_to :person
39
+ belongs_to :tag
40
+ end
41
+
42
+ class Company < ActiveRecord::Base
43
+ belongs_to :person
44
+ end
45
+ class Client < Company
46
+ end
47
+ class Supplier < Company
48
+ end
49
+
50
+ class Test::Unit::TestCase #:nodoc:
51
+ self.fixture_path = File.dirname(__FILE__) + "/fixtures/"
52
+ self.use_transactional_fixtures = true
53
+ self.use_instantiated_fixtures = false
54
+ fixtures :all
55
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: merger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brandon Keepers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-02 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: active_record
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: mocha
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: brandon@opensoul.org
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README
44
+ files:
45
+ - .gitignore
46
+ - LICENSE
47
+ - README
48
+ - Rakefile
49
+ - VERSION
50
+ - init.rb
51
+ - lib/merger.rb
52
+ - lib/merger/merge.rb
53
+ - test/db/database.yml
54
+ - test/db/schema.rb
55
+ - test/fixtures/companies.yml
56
+ - test/fixtures/people.yml
57
+ - test/fixtures/phones.yml
58
+ - test/fixtures/taggings.yml
59
+ - test/fixtures/tags.yml
60
+ - test/merger/merge_test.rb
61
+ - test/merger_test.rb
62
+ - test/test_helper.rb
63
+ has_rdoc: true
64
+ homepage: http://github.com/collectiveidea/merger
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --charset=UTF-8
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.5
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: A Rails plugin for merging Active Record models
91
+ test_files:
92
+ - test/db/schema.rb
93
+ - test/merger/merge_test.rb
94
+ - test/merger_test.rb
95
+ - test/test_helper.rb