ar_merge 0.1.2

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/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ - Merges associations/attributes you want
2
+ - Can merge duplicates
3
+ - Protects from self-merges
4
+ - Keeps counters valid
5
+ - Removes merged record
6
+
7
+
8
+ INSTALL
9
+ =======
10
+
11
+ Rails plugin
12
+
13
+ script/plugin install git://github.com/grosser/ar_merge.git
14
+
15
+ OR Gem
16
+
17
+ sudo gem install ar_merge
18
+
19
+
20
+ USAGE
21
+ =====
22
+ Merge from outside the model:
23
+
24
+ user.merge!(other,:attributes=>user.attributes.keys,:associations=>%w[movies friends])`
25
+
26
+ Merge from inside the model
27
+
28
+ User < ActiveRecord::Base
29
+ def merge!(other)
30
+ super(other,:attributes=>%w[email website])
31
+ end
32
+ end
33
+
34
+ Merge duplicates
35
+
36
+ #merge all new users, that have the same email
37
+ User.merge_duplicates!(User.find_all_by_status('new')) , :compare=>:email)
38
+
39
+ AUTHOR
40
+ ======
41
+ [Michael Grosser](http://pragmatig.wordpress.com)
42
+ grosser.michael@gmail.com
43
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ task :default => :spec
2
+ require 'spec/rake/spectask'
3
+ Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
4
+
5
+ begin
6
+ require 'jeweler'
7
+ project_name = 'ar_merge'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = project_name
10
+ gem.summary = "Merge 2 ActiveRecords, preserving associations and attributes"
11
+ gem.email = "grosser.michael@gmail.com"
12
+ gem.homepage = "http://github.com/grosser/#{project_name}"
13
+ gem.authors = ["Michael Grosser"]
14
+ gem.add_dependency ['activerecord']
15
+ end
16
+
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.2
data/ar_merge.gemspec ADDED
@@ -0,0 +1,52 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{ar_merge}
8
+ s.version = "0.1.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2009-11-22}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "README.markdown"
16
+ ]
17
+ s.files = [
18
+ "README.markdown",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "ar_merge.gemspec",
22
+ "init.rb",
23
+ "lib/ar_merge.rb",
24
+ "spec/ar_merge_spec.rb",
25
+ "spec/setup_test_model.rb",
26
+ "spec/spec_helper.rb"
27
+ ]
28
+ s.homepage = %q{http://github.com/grosser/ar_merge}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubygems_version = %q{1.3.5}
32
+ s.summary = %q{Merge 2 ActiveRecords, preserving associations and attributes}
33
+ s.test_files = [
34
+ "spec/spec_helper.rb",
35
+ "spec/ar_merge_spec.rb",
36
+ "spec/setup_test_model.rb"
37
+ ]
38
+
39
+ if s.respond_to? :specification_version then
40
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
44
+ s.add_runtime_dependency(%q<activerecord>, [">= 0"])
45
+ else
46
+ s.add_dependency(%q<activerecord>, [">= 0"])
47
+ end
48
+ else
49
+ s.add_dependency(%q<activerecord>, [">= 0"])
50
+ end
51
+ end
52
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'ar_merge'
data/lib/ar_merge.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'activerecord'
2
+
3
+ module ARMerge
4
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.send(:include, InstanceMethods)
9
+ end
10
+
11
+ module InstanceMethods
12
+ def merge!(other, options={})
13
+ raise "cannot merge wit a new record" if other.new_record?
14
+ raise "cannot merge with myself" if other == self
15
+
16
+ #merge associations
17
+ (options[:associations]||[]).each do |association_name|
18
+ other.send(association_name).each do |associated|
19
+ send(association_name) << associated
20
+ end
21
+
22
+ #update counters, this is very basic/hacky/not secure for customized counters...
23
+ counter = "#{association_name}_count"
24
+ next unless other.respond_to?(counter)
25
+ self.class.update_counters(id, counter => other.send(counter))
26
+ end
27
+
28
+ #merge attributes
29
+ (options[:attributes]||[]).each do |attr|
30
+ send("#{attr}=", other.send(attr)) if send(attr).blank?
31
+ end
32
+
33
+ #cleanup
34
+ other.reload.destroy
35
+ save!
36
+ end
37
+ end
38
+
39
+ module ClassMethods
40
+ def merge_duplicates!(records,options)
41
+ records.each do |record|
42
+ next if record.nil?
43
+ records.each do |other|
44
+ next if other.nil?
45
+ next if other == record
46
+ is_comparable = other.send(options[:compare]) == record.send(options[:compare])
47
+ next unless is_comparable
48
+
49
+ #merge and remove the other
50
+ records[records.index(other)]=nil
51
+ record.merge!(other)
52
+ end
53
+ end.compact
54
+ end
55
+ end
56
+ end
57
+
58
+ ActiveRecord::Base.send(:include, ARMerge)
@@ -0,0 +1,102 @@
1
+ require "spec/spec_helper"
2
+
3
+ describe ARMerge do
4
+ describe :merge! do
5
+ before do
6
+ @user = User.create!(:name=>'x')
7
+ @u2 = User.create!(:name=>'y')
8
+ end
9
+
10
+ describe :removal do
11
+ it "removes the merged user" do
12
+ @user.merge!(@u2)
13
+ lambda{@u2.reload}.should raise_error(ActiveRecord::RecordNotFound)
14
+ end
15
+
16
+ it "saves the merging user" do
17
+ @user.should_receive(:save!)
18
+ @user.merge!(@u2)
19
+ end
20
+ end
21
+
22
+ describe :attributes do
23
+ it "merges and overtakes attributes" do
24
+ @user.name = ''
25
+ @user.merge!(@u2,:attributes=>['name'])
26
+ @user.name.should == 'y'
27
+ end
28
+
29
+ it "does not overtake attributes when current is not blank" do
30
+ @user.merge!(@u2,:attributes=>['name'])
31
+ @user.name.should == 'x'
32
+ end
33
+ end
34
+
35
+ describe :associations do
36
+ before do
37
+ Movie.delete_all
38
+ @user.movies << Movie.new
39
+ @user.save!
40
+ @user.movies.size.should == Movie.count
41
+ end
42
+
43
+ it "overtakes asociated objects" do
44
+ @u2.merge!(@user,:associations=>[:movies])
45
+ @u2.reload.should have(Movie.count).movies
46
+ end
47
+
48
+ it "does not create new objects" do
49
+ lambda{@u2.merge!(@user,:associations=>['movies'])}.should_not change(Movie,:count)
50
+ end
51
+
52
+ it "keeps counters in sync" do
53
+ user, merged_user = CountingUser.create!, CountingUser.create!
54
+ user.movies_count.should == 0
55
+ merged_user.movies << Movie.new
56
+ merged_user.save!
57
+ merged_user.reload.movies_count.should == 1
58
+
59
+ user.merge!(merged_user,:associations=>[:movies])
60
+ user.reload.movies_count.should == 1
61
+ end
62
+ end
63
+
64
+ it "does no merge with new" do
65
+ lambda{@user.merge!(User.new)}.should raise_error
66
+ end
67
+
68
+ it "does no merge with self" do
69
+ lambda{@user.merge!(User.find(@user.id))}.should raise_error
70
+ end
71
+ end
72
+
73
+ describe :merge_duplicates! do
74
+ before do
75
+ @u1 = User.create!(:name=>'a')
76
+ @u2 = User.create!(:name=>'b')
77
+ @u3 = User.create!(:name=>'a')
78
+ @u4 = User.create!(:name=>'a')
79
+ @users=[@u1,@u2,@u3]
80
+ end
81
+
82
+ it "merges all records that have the same attributes" do
83
+ User.merge_duplicates!(@users,:compare=>:name).should == [@u1,@u2]
84
+ end
85
+
86
+ it "destroys the duplicates" do
87
+ User.merge_duplicates!(@users,:compare=>:name)
88
+ lambda{@u3.reload}.should raise_error(ActiveRecord::RecordNotFound)
89
+ end
90
+
91
+ it "merges the first with each of its duplicates" do
92
+ @users = [@u2,@u3,@u1,@u4]
93
+ @u3.should_receive(:merge!).with(@u1)
94
+ @u3.should_receive(:merge!).with(@u4)
95
+ User.merge_duplicates!(@users,:compare=>:name).should == [@u2,@u3]
96
+ end
97
+ end
98
+
99
+ it "has a VERSION" do
100
+ ARMerge::VERSION.should =~ /^\d+\.\d+\.\d+$/
101
+ end
102
+ end
@@ -0,0 +1,41 @@
1
+ # connect
2
+ ActiveRecord::Base.configurations = {"test" => {
3
+ :adapter => "sqlite3",
4
+ :database => ":memory:",
5
+ }.with_indifferent_access}
6
+
7
+ ActiveRecord::Base.establish_connection(:test)
8
+
9
+ # create tables
10
+ ActiveRecord::Schema.define(:version => 1) do
11
+ create_table :users do |t|
12
+ t.string :name
13
+ t.timestamps
14
+ end
15
+
16
+ create_table :counting_users do |t|
17
+ t.integer :movies_count, :default=>0, :null=>false
18
+ t.string :name
19
+ t.timestamps
20
+ end
21
+
22
+ create_table :movies do |t|
23
+ t.integer :user_id
24
+ t.integer :counting_user_id
25
+ t.timestamps
26
+ end
27
+ end
28
+
29
+ # create models
30
+ class User < ActiveRecord::Base
31
+ has_many :movies
32
+ end
33
+
34
+ class CountingUser < ActiveRecord::Base
35
+ has_many :movies
36
+ end
37
+
38
+ class Movie < ActiveRecord::Base
39
+ belongs_to :user
40
+ belongs_to :counting_user, :counter_cache => true
41
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ $LOAD_PATH << "lib"
3
+ require "init"
4
+ require "spec/setup_test_model"
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ar_merge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Michael Grosser
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-22 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
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
+ description:
26
+ email: grosser.michael@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.markdown
33
+ files:
34
+ - README.markdown
35
+ - Rakefile
36
+ - VERSION
37
+ - ar_merge.gemspec
38
+ - init.rb
39
+ - lib/ar_merge.rb
40
+ - spec/ar_merge_spec.rb
41
+ - spec/setup_test_model.rb
42
+ - spec/spec_helper.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/grosser/ar_merge
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Merge 2 ActiveRecords, preserving associations and attributes
71
+ test_files:
72
+ - spec/spec_helper.rb
73
+ - spec/ar_merge_spec.rb
74
+ - spec/setup_test_model.rb