ar_merge 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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