activerecord_cloneable 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c7356c885224c919a9ff09103fa118b69857ddfc
4
+ data.tar.gz: 9be80f2671b2464ea0202688d62979d755452df9
5
+ SHA512:
6
+ metadata.gz: fdbf35075828f2cc9be75cddd0c5925109e2bb50a4063e15450728afb956677168cb3e77dafd2cc5e0f2078c44f4165176ecc48eddf16b3fbe0407300f226547
7
+ data.tar.gz: 8d24200b3d7cdc87dba52bac2371f34ad1c7821683947ac1db68dfec8909e3761f3b7488e6e2fd7a6ba25fb22cfc52e7101e8c7438e6fc5d91ef0c626365c6ee
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 [name of plugin creator]
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,70 @@
1
+ Activerecord-cloneable
2
+ ======================
3
+
4
+ This is a quick tool to help clone active records.
5
+
6
+ Let's say you have a great rails app, and you want to quickly add clone
7
+ functionality to an object. Use this.
8
+
9
+ INSTALL
10
+ =======
11
+ Bundler:
12
+
13
+ Into your Gemfile put:
14
+ gem 'activerecord_cloneable', :git => "git://github.com/apalmblad/activerecord_cloneable.git",
15
+ :require => 'active_record/cloneable'
16
+
17
+ In initialize code, do:
18
+
19
+ ActiveRecord::Base.send( :include, ActiveRecord::Cloneable )
20
+
21
+ In each of the models that you want to clone, add in:
22
+ class Record < ActiveRecord::Base
23
+ cloneable
24
+ end
25
+
26
+
27
+ Example
28
+ =======
29
+
30
+ Basic:
31
+
32
+ cloned_record = record.clone_record
33
+
34
+ By default, we'll look at belongs to relations, and clone those to. Often,
35
+ that's not desirable. We might want to share some of the parents with our
36
+ clone.
37
+
38
+ Then,
39
+ cloned_record = record.clone_record( :shared_parents => [:belongs_to_association_name]
40
+
41
+ We might want to set up some attributes on our cloned record.
42
+
43
+ cloned_record = record.clone_record( :attributes => { :name => "Ima Clone" } )
44
+
45
+ Sometimes there is child data that you might want to ignore. Lets say logs of
46
+ activity, or other things that are not desired to be attached to the clone.
47
+
48
+ cloned_record = record.clone_record( :skipped_child_relations => [:logs] )
49
+
50
+ Gotta go deep? Want to clone something from a parent but not it's children?
51
+
52
+ cloned_record = record.clone_record( :skipped_child_relations => [{ :child_1 => :logs} ]] )
53
+
54
+ Finally, everyone knows that clones you cannot tell apart are a plot device for
55
+ a formulaic scifi movie. Therefore, by default, if a name or title attribute is present,
56
+ it's marked to indicate that the cloned object is a copy. If you have another
57
+ string field that you want to indicate as such, pass them in:
58
+
59
+ cloned_record = record.clone_record( :name_fields => [ :name_title] )
60
+
61
+
62
+
63
+ Disclaimer
64
+ ==========
65
+
66
+ This scratches an itch for me. I don't know if it will scratch an itch for you.
67
+ It might ruin your day -- or worse. I haven't tested this nearly as well as I
68
+ should. If you send in problem reports, I'll try and fix them. No warranties.
69
+
70
+ Copyright (c) 2011 Adam Palmblad, apalmblad@gmail.com, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the activerecord_cloneable plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the activerecord_cloneable plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'Activerecord-cloneable'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'activerecord_cloneable'
3
+ s.version = '0.1.3'
4
+ s.summary = "A library to help clone active records - that is, generate active new record objects with the data copied from an existing record."
5
+ s.authors = ["Adam Palmblad"]
6
+ s.email = 'apalmblad@gmail.com'
7
+ s.date = %q{2013-08-16}
8
+ s.description = %q{A tool to help with cloning of active record objects.}
9
+ s.files= ['MIT-LICENSE', 'README', 'Rakefile', 'activerecord_cloneable.gemspec',
10
+ 'test/activerecord_cloneable_test.rb', 'test/test_helper.rb',
11
+ 'lib/active_record/cloneable.rb']
12
+ end
@@ -0,0 +1,147 @@
1
+ module ActiveRecord::Cloneable
2
+ # ------------------------------------------------------------------- included
3
+ def self.included( base )
4
+ base.extend( ClassMethods )
5
+ end
6
+ module ClassMethods
7
+ # ---------------------------------------------------------------- cloneable
8
+ def cloneable
9
+ # HACK: check that this best way
10
+ class_eval <<-EOV
11
+ include ActiveRecord::Cloneable::InstanceMethods
12
+ EOV
13
+ end
14
+ end
15
+ module InstanceMethods
16
+ # ------------------------------------------------------ clone_basic_details
17
+ def clone_basic_details(cloned_record, belongs_to_keys, args )
18
+ # assign the attributes, minus any assigned or any from a belongs_to relation
19
+ self.class.content_columns.each do |column|
20
+ if !args[:attributes].has_key?( column.name.to_sym ) && !belongs_to_keys.include?( column.name.to_sym )
21
+ m = "#{column.name}=".to_sym
22
+ if cloned_record.respond_to?( m )
23
+ cloned_record.send( m, read_attribute( column.name ) )
24
+ else
25
+ cloned_record.write_attribute( column.name, read_attribute( column.name ) )
26
+ end
27
+ end
28
+ end
29
+ args[:attributes].each_pair do |k,v|
30
+ cloned_record.send( "#{k}=", v )
31
+ end
32
+ # Set the name or title field to be the existing value + (copy)
33
+ ( args[:name_fields] || [:name,:title] ).each do |name_attr|
34
+ if has_attribute?( name_attr ) && read_attribute( :name_attr )
35
+ cloned_form.write_attribute( name_attr, read_attribute( name_attr ) + ' (copy)' )
36
+ break if args[:name_fields].nil?
37
+ end
38
+ end
39
+ #cloned_record.save
40
+ end
41
+ # ----------------------------------------------- clone_bleongs_to_relations
42
+ def clone_belongs_to_relations( relations, cloned_record, args )
43
+ return if relations.nil?
44
+ relations.each do |parent_relation|
45
+ obj = send( parent_relation.name )
46
+ next if obj.nil?
47
+ if args[:shared_parent_relations].include?( parent_relation.name.to_sym )
48
+ cloned_record.send( "#{parent_relation.name}=", obj )
49
+ elsif !args[:cloned_parents].include?( obj )
50
+ # We don't know what the parent calls this child.
51
+ begin
52
+ rec = obj.clone_record( :skipped_children => args[:skipped_children] + [self],
53
+ :skipped_child_relations => find_applicable_clone_args( parent_relation.name, args[:skipped_child_relations] ),
54
+ :skipped_parent_relations => find_applicable_clone_args( parent_relation.name, args[:skipped_parent_relations] ),
55
+ :shared_parent_relations => find_applicable_clone_args( parent_relation.name, args[:shared_parent_relations] )
56
+ )
57
+ rescue NoMethodError => e
58
+ raise "#{obj.class.name} objects do not know how to clone themselves; they should be marked as cloneable or skipped."
59
+ end
60
+ cloned_record.send( "#{parent_relation.name}=", rec )
61
+ end
62
+ end
63
+ end
64
+ # ----------------------------------------------- find_applicable_clone_args
65
+ def find_applicable_clone_args( relation_name, args )
66
+ relation_name = relation_name.to_sym
67
+ return nil if args.nil?
68
+ r_val = args.map do |x|
69
+ if x.is_a?( Hash )
70
+ x[relation_name]
71
+ else
72
+ nil
73
+ end
74
+ end
75
+ r_val.flatten
76
+ end
77
+ # ---------------------------------------------------- clone_child_relation?
78
+ def clone_child_relation?( relation_name, skipped_child_relations )
79
+ relation_name = relation_name.to_sym
80
+ skipped_child_relations.each do |relation|
81
+ unless relation.is_a?( Hash )
82
+ return false if relation == relation_name
83
+ end
84
+ end
85
+ return true
86
+ end
87
+ # ---------------------------------------------------------------------- clone
88
+ def clone_record( args = {} )
89
+ args[:shared_parent_relations] ||= []
90
+ args[:skipped_child_relations] ||= []
91
+ args[:cloned_parents] ||= []
92
+ args[:skipped_children] ||= []
93
+ args[:attributes] ||={}
94
+ cloned_record = args[:object] || self.class.new
95
+ data = {}
96
+ self.class.reflections.each do |k,v|
97
+ data[v.macro] ||= []
98
+ data[v.macro] << v
99
+ end
100
+ belongs_to_keys = ( data[:belongs_to] || [] ).map{ |x| x.primary_key_name.to_sym }
101
+
102
+ clone_belongs_to_relations( data[:belongs_to], cloned_record, args )
103
+ clone_basic_details( cloned_record, belongs_to_keys, args )
104
+
105
+ ((data[:has_many] || []) + (data[:has_and_belongs_to_many]||[]) ).each do |child_relation|
106
+ next if child_relation.through_reflection
107
+ next if !clone_child_relation?( child_relation.name, args[:skipped_child_relations] )
108
+ kids = send( child_relation.name )
109
+ next if kids.nil?
110
+ records = kids.find( :all )
111
+ records.each do |child_record|
112
+ next if args[:skipped_children].include?( child_record )
113
+ cloned_child_record = kids.build
114
+ child_args = { :cloned_parents => args[:cloned_parents] + [self], :attributes => {}, :object => cloned_child_record,
115
+ :skipped_child_relations => find_applicable_clone_args( child_relation.name, args[:skipped_child_relations] ) }
116
+ #if child_relation.macro == :has_many ||child_relation.macro == :has_one
117
+ # child_args[:attributes][child_relation.primary_key_name.to_sym] = nil
118
+ #end
119
+ begin
120
+ cloned_child_record = child_record.clone_record( child_args )
121
+ cloned_record.send( child_relation.name ) << cloned_child_record
122
+ rescue NoMethodError => e
123
+ raise "#{child_record.class.name} objects do not know how to clone themselves; they should be marked as cloneable or skipped. (#{self.class.name} / #{child_relation.name}"
124
+ end
125
+ end
126
+ end
127
+ ( data[:has_one] || []).each do |child_relation|
128
+ next if child_relation.through_reflection
129
+ next if !clone_child_relation?( child_relation.name, args[:skipped_child_relations] )
130
+ kid = send( child_relation.name )
131
+ next if kid.nil?
132
+ next if args[:skipped_children].include?( kid )
133
+ cloned_child_record = kid.build
134
+ child_args = { :cloned_parents => args[:cloned_parents] + [self],
135
+ :attributes => {}, :object => cloned_child_record,
136
+ :skipped_child_relations => args[:skipped_child_relations].find_all{ |x| x.is_a?( Hash ) && x[child_relation.name.to_sym] }.map{ |x| x.values }.flatten }
137
+ begin
138
+ cloned_child_record = kid.clone_record( child_args )
139
+ cloned_record.send( "#{child_relation.name}=", cloned_child_record )
140
+ rescue NoMethodError => e
141
+ raise "#{kid.class.name} objects do not know how to clone themselves; they should be marked as cloneable or skipped. (#{self.class.name} / #{child_relation.name}"
142
+ end
143
+ end
144
+ return cloned_record
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,54 @@
1
+ require 'test_helper'
2
+
3
+ class ActiveRecordCloneableTest < ActiveSupport::TestCase
4
+ # Replace this with your real tests.
5
+ class Apple < ActiveRecord::Base
6
+ cloneable
7
+ belongs_to :banana
8
+ validates_presence_of :banana
9
+ validates_associated :banana
10
+ end
11
+ class Banana < ActiveRecord::Base
12
+ cloneable
13
+ has_many :apples
14
+ has_one :bread
15
+ end
16
+ class NonCloneableThing < ActiveRecord::Base
17
+ belongs_to :apple
18
+ end
19
+ class Bread < ActiveRecord::Base
20
+ cloneable
21
+ belongs_to :banana
22
+ end
23
+ test "Basic test" do
24
+ original = Banana.new
25
+ original.save
26
+ a = original.apples.build
27
+ apple =original.apples.new
28
+ original.save!
29
+ original.reload
30
+ assert( original.apples.any? )
31
+ clone = original.clone_record
32
+ clone.save!
33
+ assert( clone.apples.any? )
34
+ end
35
+ def test_with_non_cloneable_things
36
+ Apple.has_many( :non_cloneable_things )
37
+ original = Banana.new
38
+ original.save
39
+ a = original.apples.create
40
+ a.non_cloneable_things.create
41
+ original.save
42
+ clone = original.clone_record( :skipped_child_relations => [{ :apples => :non_cloneable_things}] )
43
+ assert( clone.apples.any? )
44
+ end
45
+ def test_with_have_one
46
+ b = Bread.new
47
+ b.banana = Banana.new
48
+ b.save!
49
+ new_bread = b.clone_record( :skipped_parent_relations => [ { :bread => :banana }] )
50
+ new_bread.save!
51
+ assert( new_bread.banana )
52
+ assert_equal( new_bread, new_bread.banana.bread )
53
+ end
54
+ end
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'active_support'
4
+ require 'active_support/test_case'
5
+ require 'active_record'
6
+ require 'active_record/cloneable'
7
+ ActiveRecord::Base.send( :include, ActiveRecord::Cloneable )
8
+ ActiveRecord::Base.establish_connection(:adapter => 'mysql',
9
+ :database => 'cloneable_test',
10
+ :username => 'root' )
11
+ class DoSetup < ActiveRecord::Migration
12
+ def self.up
13
+ create_table :breads do |t|
14
+ t.column :banana_id, :integer
15
+ end
16
+ drop_table :non_cloneable_things
17
+ create_table :non_cloneable_things do |t|
18
+ t.column :apple_id,:integer
19
+ end
20
+ create_table :apples do |t|
21
+ t.column :banana_id, :integer
22
+ end
23
+ create_table :bananas do |t|
24
+ end
25
+ end
26
+
27
+ def self.down
28
+ drop_table :bananas
29
+ drop_table :apples
30
+ end
31
+ end
32
+ begin
33
+ DoSetup.up
34
+ rescue ActiveRecord::StatementInvalid => e
35
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord_cloneable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Adam Palmblad
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-16 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A tool to help with cloning of active record objects.
14
+ email: apalmblad@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - MIT-LICENSE
20
+ - README
21
+ - Rakefile
22
+ - activerecord_cloneable.gemspec
23
+ - lib/active_record/cloneable.rb
24
+ - test/activerecord_cloneable_test.rb
25
+ - test/test_helper.rb
26
+ homepage:
27
+ licenses: []
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.2.1
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: A library to help clone active records - that is, generate active new record
49
+ objects with the data copied from an existing record.
50
+ test_files: []