amoeba 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use --create 1.9.3-p125@amoeba
1
+ rvm use --create 1.9.3-p194@amoeba
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Amoeba
1
+ # IMPORTANT NEWS
2
+
3
+ * As of December 11th, 2012 Amoeba no longer overrides the built in `ActiveRecord::Base#dup` method and instead implements its own method called `amoeba_dup`. Make sure to update your code if you do a bundle update and or double check that your gemfile has a version restriction bound to v1.x e.g. '~> 1.2' listed for amoeba.
4
+
5
+ ## Amoeba
2
6
 
3
7
  Easy copying of rails associations such as `has_many`.
4
8
 
@@ -12,7 +16,7 @@ This gem is named "Amoeba" because amoebas are (small life forms that are) good
12
16
 
13
17
  ### Technical Details
14
18
 
15
- An ActiveRecord extension gem to allow the duplication of associated child record objects when duplicating an active record model. This gem overrides and adds to the built in `ActiveRecord::Base#dup` method.
19
+ An ActiveRecord extension gem to allow the duplication of associated child record objects when duplicating an active record model.
16
20
 
17
21
  Rails 3.2 compatible.
18
22
 
@@ -50,7 +54,7 @@ or just add it to your Gemfile:
50
54
 
51
55
  gem 'amoeba'
52
56
 
53
- Configure your models with one of the styles below and then just run the `dup` method on your model as you normally would:
57
+ Configure your models with one of the styles below and then just run the `amoeba_dup` method on your model where you would run the `dup` method normally:
54
58
 
55
59
  p = Post.create(:title => "Hello World!", :content => "Lorum ipsum dolor")
56
60
  p.comments.create(:content => "I love it!")
@@ -58,7 +62,7 @@ Configure your models with one of the styles below and then just run the `dup` m
58
62
 
59
63
  puts Comment.all.count # should be 2
60
64
 
61
- my_copy = p.dup
65
+ my_copy = p.amoeba_dup
62
66
  my_copy.save
63
67
 
64
68
  puts Comment.all.count # should be 4
@@ -99,7 +103,7 @@ simply add the amoeba configuration block to your model and call the enable meth
99
103
  belongs_to :post
100
104
  end
101
105
 
102
- Child records will be automatically copied when you run the dup method.
106
+ Child records will be automatically copied when you run the `amoeba_dup` method.
103
107
 
104
108
  #### Inclusive Style
105
109
 
@@ -212,7 +216,7 @@ If you are using a Many-to-Many relationship, you may tell amoeba to actually ma
212
216
 
213
217
  This example will actually duplicate the warnings and widgets in the database. If there were originally 3 warnings in the database then, upon duplicating a post, you will end up with 6 warnings in the database. This is in contrast to the default behavior where your new post would merely be re-associated with any previously existing warnings and those warnings themselves would not be duplicated.
214
218
 
215
- ### Limiting Association Types
219
+ #### Limiting Association Types
216
220
 
217
221
  By default, amoeba recognizes and attemps to copy any children of the following association types:
218
222
 
@@ -242,90 +246,6 @@ You may control which association types amoeba applies itself to by using the `r
242
246
 
243
247
  This example will copy the post's configuration data and keep tags associated with the new post, but will not copy the post's comments because amoeba will only recognize and copy children of `has_one` and `has_and_belongs_to_many` associations and in this example, comments are not an `has_and_belongs_to_many` association.
244
248
 
245
- ### Nested Association Validation
246
-
247
- If you end up with some validation issues when trying to validate the presence of a child's `belongs_to` association, just be sure to include the `:inverse_of` declaration on your relationships and all should be well.
248
-
249
- For example this will throw a validation error saying that your posts are invalid:
250
-
251
- class Author < ActiveRecord::Base
252
- has_many :posts
253
-
254
- amoeba do
255
- enable
256
- end
257
- end
258
-
259
- class Post < ActiveRecord::Base
260
- belongs_to :author
261
- validates_presence_of :author
262
-
263
- amoeba do
264
- enable
265
- end
266
- end
267
-
268
- author = Author.find(1)
269
- author.dup
270
-
271
- author.save # this will fail validation
272
-
273
- Wheras this will work fine:
274
-
275
- class Author < ActiveRecord::Base
276
- has_many :posts, :inverse_of => :author
277
-
278
- amoeba do
279
- enable
280
- end
281
- end
282
-
283
- class Post < ActiveRecord::Base
284
- belongs_to :author, :inverse_of => :posts
285
- validates_presence_of :author
286
-
287
- amoeba do
288
- enable
289
- end
290
- end
291
-
292
- author = Author.find(1)
293
- author.dup
294
-
295
- author.save # this will pass validation
296
-
297
- This issue is not amoeba specific and also occurs when creating new objects using `accepts_nested_attributes_for`, like this:
298
-
299
- class Author < ActiveRecord::Base
300
- has_many :posts
301
- accepts_nested_attributes_for :posts
302
- end
303
-
304
- class Post < ActiveRecord::Base
305
- belongs_to :author
306
- validates_presence_of :author
307
- end
308
-
309
- # this will fail validation
310
- author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
311
-
312
- This issue with `accepts_nested_attributes_for` can also be solved by using `:inverse_of`, like this:
313
-
314
- class Author < ActiveRecord::Base
315
- has_many :posts, :inverse_of => :author
316
- accepts_nested_attributes_for :posts
317
- end
318
-
319
- class Post < ActiveRecord::Base
320
- belongs_to :author, :inverse_of => :posts
321
- validates_presence_of :author
322
- end
323
-
324
- # this will pass validation
325
- author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
326
-
327
- The crux of the issue is that upon duplication, the new `Author` instance does not yet have an ID because it has not yet been persisted, so the `:posts` do not yet have an `:author_id` either, and thus no `:author` and thus they will fail validation. This issue may likely affect amoeba usage so if you get some validation failures, be sure to add `:inverse_of` to your models.
328
-
329
249
  ### Field Preprocessors
330
250
 
331
251
  #### Nullify
@@ -488,7 +408,7 @@ This example should result in something like this:
488
408
  :contents => "I like dogs, dogs are awesome."
489
409
  )
490
410
 
491
- new_post = post.dup
411
+ new_post = post.amoeba_dup
492
412
 
493
413
  new_post.title # "Copy of Hello world"
494
414
  new_post.contents # "Original contents: I like cats, cats are awesome. (copied version)"
@@ -612,7 +532,7 @@ Copying of `has_many :through` associations works automatically. They perform th
612
532
  belongs_to :part
613
533
 
614
534
  amoeba do
615
- prefix :notes => "Copy of "
535
+ prepend :notes => "Copy of "
616
536
  end
617
537
  end
618
538
 
@@ -653,7 +573,7 @@ You may control how amoeba copies your object, on the fly, by passing a configur
653
573
  prepend :contents => "Here's a copy: "
654
574
  end
655
575
 
656
- new_post = old_post.dup
576
+ new_post = old_post.amoeba_dup
657
577
 
658
578
  new_post.title # should be "Copy of Hello world"
659
579
  new_post.contents # should be "Here's a copy: Lorum ipsum"
@@ -700,7 +620,7 @@ If you are using the Single Table Inheritance provided by ActiveRecord, you may
700
620
  class ProductsController
701
621
  def some_method
702
622
  my_shirt = Shirt.find(1)
703
- my_shirt.dup
623
+ my_shirt.amoeba_dup
704
624
  my_shirt.save
705
625
 
706
626
  # this shirt should now:
@@ -843,6 +763,90 @@ The next version will use only the parent settings because passing an array will
843
763
  include_field :sections
844
764
  end
845
765
 
766
+ ### Validating Nested Attributes
767
+
768
+ If you end up with some validation issues when trying to validate the presence of a child's `belongs_to` association, just be sure to include the `:inverse_of` declaration on your relationships and all should be well.
769
+
770
+ For example this will throw a validation error saying that your posts are invalid:
771
+
772
+ class Author < ActiveRecord::Base
773
+ has_many :posts
774
+
775
+ amoeba do
776
+ enable
777
+ end
778
+ end
779
+
780
+ class Post < ActiveRecord::Base
781
+ belongs_to :author
782
+ validates_presence_of :author
783
+
784
+ amoeba do
785
+ enable
786
+ end
787
+ end
788
+
789
+ author = Author.find(1)
790
+ author.amoeba_dup
791
+
792
+ author.save # this will fail validation
793
+
794
+ Wheras this will work fine:
795
+
796
+ class Author < ActiveRecord::Base
797
+ has_many :posts, :inverse_of => :author
798
+
799
+ amoeba do
800
+ enable
801
+ end
802
+ end
803
+
804
+ class Post < ActiveRecord::Base
805
+ belongs_to :author, :inverse_of => :posts
806
+ validates_presence_of :author
807
+
808
+ amoeba do
809
+ enable
810
+ end
811
+ end
812
+
813
+ author = Author.find(1)
814
+ author.amoeba_dup
815
+
816
+ author.save # this will pass validation
817
+
818
+ This issue is not amoeba specific and also occurs when creating new objects using `accepts_nested_attributes_for`, like this:
819
+
820
+ class Author < ActiveRecord::Base
821
+ has_many :posts
822
+ accepts_nested_attributes_for :posts
823
+ end
824
+
825
+ class Post < ActiveRecord::Base
826
+ belongs_to :author
827
+ validates_presence_of :author
828
+ end
829
+
830
+ # this will fail validation
831
+ author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
832
+
833
+ This issue with `accepts_nested_attributes_for` can also be solved by using `:inverse_of`, like this:
834
+
835
+ class Author < ActiveRecord::Base
836
+ has_many :posts, :inverse_of => :author
837
+ accepts_nested_attributes_for :posts
838
+ end
839
+
840
+ class Post < ActiveRecord::Base
841
+ belongs_to :author, :inverse_of => :posts
842
+ validates_presence_of :author
843
+ end
844
+
845
+ # this will pass validation
846
+ author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
847
+
848
+ The crux of the issue is that upon duplication, the new `Author` instance does not yet have an ID because it has not yet been persisted, so the `:posts` do not yet have an `:author_id` either, and thus no `:author` and thus they will fail validation. This issue may likely affect amoeba usage so if you get some validation failures, be sure to add `:inverse_of` to your models.
849
+
846
850
  ## Configuration Reference
847
851
 
848
852
  Here is a static reference to the available configuration methods, usable within the amoeba block on your rails models.
@@ -915,9 +919,15 @@ Here is a static reference to the available configuration methods, usable within
915
919
 
916
920
  Globally search and replace the field for a given pattern. Accepts a hash of fields to run search and replace upon. The keys are the field names and the values are each a hash with information about what to find and what to replace it with. in the form of . An example would be to replace all occurrences of the word "dog" with the word "cat", the parameter hash would look like this `:contents => {:replace => /dog/, :with => "cat"}`. Passing a hash will add each key value pair to the list of regex directives. If you wish to empty the list of directives, you may pass the hash inside of an array like this `[{:contents => {:replace => /dog/, :with => "cat"}]`.
917
921
 
922
+ #### override
923
+
924
+ Runs a custom method so you can do basically whatever you want. All you need to do is pass a lambda block or an array of lambda blocks that take two parameters, the original object and the new object copy. These blocks will run before any other duplication or field processing.
925
+
926
+ This method may be called multiple times, once per desired customizer block, or you may pass an array of lambdas. Passing a single lambda will add to the list of processing directives. Passing an array will empty the list and replace it with the array you pass.
927
+
918
928
  #### customize
919
929
 
920
- Runs a custom method so you can do basically whatever you want. All you need to do is pass a lambda block or an array of lambda blocks that take two parameters, the original object and the new object copy
930
+ Runs a custom method so you can do basically whatever you want. All you need to do is pass a lambda block or an array of lambda blocks that take two parameters, the original object and the new object copy. These blocks will run after all copying and field processing.
921
931
 
922
932
  This method may be called multiple times, once per desired customizer block, or you may pass an array of lambdas. Passing a single lambda will add to the list of processing directives. Passing an array will empty the list and replace it with the array you pass.
923
933
 
@@ -323,8 +323,8 @@ module Amoeba
323
323
  end
324
324
  # }}}
325
325
 
326
- def dup(options={})
327
- @result = super()
326
+ def amoeba_dup(options={})
327
+ @result = self.dup()
328
328
 
329
329
  # Inherit Parent Settings {{{
330
330
  if !amoeba_conf.enabled && parent_amoeba_conf.inherit
@@ -422,7 +422,7 @@ module Amoeba
422
422
  old_obj = self.send(relation_name)
423
423
 
424
424
  if not old_obj.nil?
425
- copy_of_obj = old_obj.dup
425
+ copy_of_obj = old_obj.amoeba_dup
426
426
  copy_of_obj[:"#{settings.foreign_key}"] = nil
427
427
 
428
428
  @result.send(:"#{relation_name}=", copy_of_obj)
@@ -439,7 +439,7 @@ module Amoeba
439
439
  # rather than only maintaining the associations
440
440
  self.send(relation_name).each do |old_obj|
441
441
 
442
- copy_of_obj = old_obj.dup
442
+ copy_of_obj = old_obj.amoeba_dup
443
443
 
444
444
  # associate this new child to the new parent object
445
445
  @result.send(relation_name) << copy_of_obj
@@ -454,7 +454,7 @@ module Amoeba
454
454
  return if settings.is_a?(ActiveRecord::Reflection::ThroughReflection)
455
455
 
456
456
  self.send(relation_name).each do |old_obj|
457
- copy_of_obj = old_obj.dup
457
+ copy_of_obj = old_obj.amoeba_dup
458
458
  copy_of_obj[:"#{settings.foreign_key}"] = nil
459
459
 
460
460
  # associate this new child to the new parent object
@@ -467,7 +467,7 @@ module Amoeba
467
467
 
468
468
  if clone
469
469
  self.send(relation_name).each do |old_obj|
470
- copy_of_obj = old_obj.dup
470
+ copy_of_obj = old_obj.amoeba_dup
471
471
 
472
472
  # associate this new child to the new parent object
473
473
  @result.send(relation_name) << copy_of_obj
@@ -1,3 +1,3 @@
1
1
  module Amoeba
2
- VERSION = "1.2.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -12,7 +12,7 @@ describe "amoeba" do
12
12
  prepend :contents => "Here's a copy: "
13
13
  end
14
14
 
15
- new_post = old_post.dup
15
+ new_post = old_post.amoeba_dup
16
16
 
17
17
  start_account_count = Account.all.count
18
18
  start_history_count = History.all.count
@@ -83,12 +83,21 @@ describe "amoeba" do
83
83
  new_post.widgets.map(&:id).each do |id|
84
84
  old_post.widgets.map(&:id).include?(id).should_not be true
85
85
  end
86
+
87
+ new_post.custom_things.length.should == 3
88
+ new_post.custom_things.select{ |ct| ct.value == [] }.length.should == 1
89
+ new_post.custom_things.select{ |ct| ct.value == [1,2]}.length.should == 1
90
+ new_post.custom_things.select{ |ct| ct.value == [78]}.length.should == 1
86
91
  # }}}
87
92
  # Author {{{
88
93
  old_author = Author.find(1)
89
- new_author = old_author.dup
94
+ new_author = old_author.amoeba_dup
90
95
  new_author.save
91
96
  new_author.errors.messages.length.should == 0
97
+ new_author.posts.first.custom_things.length.should == 3
98
+ new_author.posts.first.custom_things.select{ |ct| ct.value == [] }.length.should == 1
99
+ new_author.posts.first.custom_things.select{ |ct| ct.value == [1,2]}.length.should == 1
100
+ new_author.posts.first.custom_things.select{ |ct| ct.value == [78]}.length.should == 1
92
101
  # }}}
93
102
  # Products {{{
94
103
  # Base Class {{{
@@ -99,7 +108,7 @@ describe "amoeba" do
99
108
  rs = ActiveRecord::Base.connection.select_one('SELECT COUNT(*) AS section_count FROM products_sections WHERE product_id = ?', old_product.id)
100
109
  start_prodsection_count = rs["section_count"]
101
110
 
102
- new_product = old_product.dup
111
+ new_product = old_product.amoeba_dup
103
112
  new_product.save
104
113
  new_product.errors.messages.length.should == 0
105
114
 
@@ -127,7 +136,7 @@ describe "amoeba" do
127
136
  rs = ActiveRecord::Base.connection.select_one('SELECT COUNT(*) AS section_count FROM products_sections WHERE product_id = ?', old_product.id)
128
137
  start_prodsection_count = rs["section_count"]
129
138
 
130
- new_product = old_product.dup
139
+ new_product = old_product.amoeba_dup
131
140
  new_product.save
132
141
  new_product.errors.messages.length.should == 0
133
142
 
@@ -154,7 +163,7 @@ describe "amoeba" do
154
163
  rs = ActiveRecord::Base.connection.select_one('SELECT COUNT(*) AS section_count FROM products_sections WHERE product_id = ?', old_product.id)
155
164
  start_prodsection_count = rs["section_count"]
156
165
 
157
- new_product = old_product.dup
166
+ new_product = old_product.amoeba_dup
158
167
  new_product.save
159
168
  new_product.errors.messages.length.should == 0
160
169
 
@@ -81,6 +81,8 @@ s2.superkittens.create(:value => "Spot")
81
81
  s3.superkittens.create(:value => "Dopey")
82
82
  s3.superkittens.create(:value => "Sneezy")
83
83
  s3.superkittens.create(:value => "Sleepy")
84
+
85
+ p1.custom_things.create([{:value => [1,2]}, {:value => []}, {:value => [78]}])
84
86
  # }}}
85
87
 
86
88
  # Product {{{
@@ -25,6 +25,7 @@ class Post < ActiveRecord::Base
25
25
  has_many :categories, :through => :supercats
26
26
  has_many :post_widgets
27
27
  has_many :widgets, :through => :post_widgets
28
+ has_many :custom_things
28
29
  has_and_belongs_to_many :tags
29
30
  has_and_belongs_to_many :notes
30
31
 
@@ -73,6 +74,30 @@ end
73
74
 
74
75
  class CustomThing < ActiveRecord::Base
75
76
  belongs_to :post
77
+ class ArrayPack
78
+ def self.load(str)
79
+ unless str.present? && str.length > 0
80
+ return []
81
+ end
82
+ if str.is_a?(Array)
83
+ return str
84
+ end
85
+ str.split(',').collect(&:to_i)
86
+ end
87
+ def self.dump(int_array)
88
+ unless int_array.present? && int_array.length > 0
89
+ return ""
90
+ end
91
+ int_array.join(',')
92
+ end
93
+ end
94
+
95
+ serialize :value, ArrayPack
96
+
97
+ before_create :hydrate_me
98
+ def hydrate_me
99
+ self.value = self.value
100
+ end
76
101
  end
77
102
 
78
103
  class Account < ActiveRecord::Base
@@ -82,6 +82,7 @@ ActiveRecord::Schema.define do
82
82
  end
83
83
 
84
84
  create_table :custom_things, :force => true do |t|
85
+ t.integer :post_id
85
86
  t.string :value
86
87
  t.timestamps
87
88
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amoeba
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-08 00:00:00.000000000 Z
12
+ date: 2013-01-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -143,8 +143,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
143
  version: '0'
144
144
  requirements: []
145
145
  rubyforge_project: amoeba
146
- rubygems_version: 1.8.21
146
+ rubygems_version: 1.8.24
147
147
  signing_key:
148
148
  specification_version: 3
149
149
  summary: Easy copying of rails models and their child associations.
150
- test_files: []
150
+ test_files:
151
+ - spec/lib/amoeba_spec.rb
152
+ - spec/spec_helper.rb
153
+ - spec/support/data.rb
154
+ - spec/support/models.rb
155
+ - spec/support/schema.rb