amoeba 1.2.0 → 1.2.1

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-p0@amoeba
1
+ rvm use --create 1.9.3-p125@amoeba
data/README.md CHANGED
@@ -4,29 +4,19 @@ Easy copying of rails associations such as `has_many`.
4
4
 
5
5
  ![amoebalogo](http://rocksolidwebdesign.com/wp_cms/wp-content/uploads/2012/02/amoeba_logo.jpg)
6
6
 
7
- ## Installation
8
-
9
- is hopefully as you would expect:
10
-
11
- gem install amoeba
12
-
13
- or just add it to your Gemfile:
14
-
15
- gem 'amoeba'
16
-
17
7
  ## What?
18
8
 
19
9
  The goal was to be able to easily and quickly reproduce ActiveRecord objects including their children, for example copying a blog post maintaining its associated tags or categories.
20
10
 
21
11
  This gem is named "Amoeba" because amoebas are (small life forms that are) good at reproducing. Their children and grandchildren also reproduce themselves quickly and easily.
22
12
 
23
- ## Details
13
+ ### Technical Details
24
14
 
25
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.
26
16
 
27
17
  Rails 3.2 compatible.
28
18
 
29
- ## Features
19
+ ### Features
30
20
 
31
21
  - Supports the following association types
32
22
  - `has_many`
@@ -50,6 +40,16 @@ Rails 3.2 compatible.
50
40
 
51
41
  ## Usage
52
42
 
43
+ ### Installation
44
+
45
+ is hopefully as you would expect:
46
+
47
+ gem install amoeba
48
+
49
+ or just add it to your Gemfile:
50
+
51
+ gem 'amoeba'
52
+
53
53
  Configure your models with one of the styles below and then just run the `dup` method on your model as you normally would:
54
54
 
55
55
  p = Post.create(:title => "Hello World!", :content => "Lorum ipsum dolor")
@@ -67,11 +67,11 @@ By default, when enabled, amoeba will copy any and all associated child records
67
67
 
68
68
  You can configure the behavior to only include fields that you list or to only include fields that you don't exclude. Of the three, the most performant will be the indiscriminate style, followed by the inclusive style, and the exclusive style will be the slowest because of the need for an extra explicit check on each field. This performance difference is likely negligible enough that you can choose the style to use based on which is easiest to read and write, however, if your data tree is large enough and you need control over what fields get copied, inclusive style is probably a better choice than exclusive style.
69
69
 
70
- ## Configuration
70
+ ### Configuration
71
71
 
72
72
  Please note that these examples are only loose approximations of real world scenarios and may not be particularly realistic, they are only for the purpose of demonstrating feature usage.
73
73
 
74
- ### Indiscriminate Style
74
+ #### Indiscriminate Style
75
75
 
76
76
  This is the most basic usage case and will simply enable the copying of any known associations.
77
77
 
@@ -101,7 +101,7 @@ simply add the amoeba configuration block to your model and call the enable meth
101
101
 
102
102
  Child records will be automatically copied when you run the dup method.
103
103
 
104
- ### Inclusive Style
104
+ #### Inclusive Style
105
105
 
106
106
  If you only want some of the associations copied but not others, you may use the inclusive style:
107
107
 
@@ -158,7 +158,7 @@ These examples will copy the post's tags and authors but not its comments.
158
158
 
159
159
  The inclusive style, when used, will automatically disable any ther style that was previously selected.
160
160
 
161
- ### Exclusive Style
161
+ #### Exclusive Style
162
162
 
163
163
  If you have more fields to include than to exclude, you may wish to shorten the amount of typing and reading you need to do by using the exclusive style. All fields that are not explicitly excluded will be copied:
164
164
 
@@ -180,7 +180,7 @@ This example does the same thing as the inclusive style example, it will copy th
180
180
 
181
181
  The exclusive style, when used, will automatically disable any other style that was previously selected, so if you selected include fields, and then you choose some exclude fields, the `exclude_field` method will disable the previously slected inclusive style and wipe out any corresponding include fields.
182
182
 
183
- ### Cloning
183
+ #### Cloning
184
184
 
185
185
  If you are using a Many-to-Many relationship, you may tell amoeba to actually make duplicates of the original related records rather than merely maintaining association with the original records. Cloning is easy, merely tell amoeba which fields to clone in the same way you tell it which fields to include or exclude.
186
186
 
@@ -212,217 +212,119 @@ If you are using a Many-to-Many relationship, you may tell amoeba to actually ma
212
212
 
213
213
  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
214
 
215
- ## Inheritance
216
-
217
- If you are using the Single Table Inheritance provided by ActiveRecord, you may cause amoeba to automatically process child classes in the same way as their parents. All you need to do is call the `propagate` method within the amoeba block of the parent class and all child classes should copy in a similar manner.
218
-
219
- create_table :products, :force => true do |t|
220
- t.string :type # this is the STI column
215
+ ### Limiting Association Types
221
216
 
222
- # these belong to all products
223
- t.string :title
224
- t.decimal :price
217
+ By default, amoeba recognizes and attemps to copy any children of the following association types:
225
218
 
226
- # these are for shirts only
227
- t.decimal :sleeve_length
228
- t.decimal :collar_size
219
+ - has one
220
+ - has many
221
+ - has and belongs to many
229
222
 
230
- # these are for computers only
231
- t.integer :ram_size
232
- t.integer :hard_drive_size
233
- end
223
+ You may control which association types amoeba applies itself to by using the `recognize` method within the amoeba configuration block.
234
224
 
235
- class Product < ActiveRecord::Base
236
- has_many :images
237
- has_and_belongs_to_many :categories
225
+ class Post < ActiveRecord::Base
226
+ has_one :config
227
+ has_many :comments
228
+ has_and_belongs_to_many :tags
238
229
 
239
230
  amoeba do
240
- enable
241
- propagate
231
+ recognize [:has_one, :has_and_belongs_to_many]
242
232
  end
243
233
  end
244
234
 
245
- class Shirt < Product
246
- end
247
-
248
- class Computer < Product
235
+ class Comment < ActiveRecord::Base
236
+ belongs_to :post
249
237
  end
250
238
 
251
- class ProductsController
252
- def some_method
253
- my_shirt = Shirt.find(1)
254
- my_shirt.dup
255
- my_shirt.save
256
-
257
- # this shirt should now:
258
- # - have its own copy of all parent images
259
- # - be in the same categories as the parent
260
- end
239
+ class Tag < ActiveRecord::Base
240
+ has_and_belongs_to_many :posts
261
241
  end
262
242
 
263
- This example should duplicate all the images and sections associated with this Shirt, which is a child of Product
264
-
265
- ### Parenting Style
266
-
267
- By default, propagation uses submissive parenting, meaning the config settings on the parent will be applied, but any child settings, if present, will either add to or overwrite the parent settings depending on how you call the DSL methods.
243
+ 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.
268
244
 
269
- You may change this behavior, the so called "parenting style", to give preference to the parent settings or to ignore any and all child settings.
245
+ ### Nested Association Validation
270
246
 
271
- #### Relaxed Parenting
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.
272
248
 
273
- The `:relaxed` parenting style will prefer parent settings.
249
+ For example this will throw a validation error saying that your posts are invalid:
274
250
 
275
- class Product < ActiveRecord::Base
276
- has_many :images
277
- has_and_belongs_to_many :sections
251
+ class Author < ActiveRecord::Base
252
+ has_many :posts
278
253
 
279
254
  amoeba do
280
- exclude_field :images
281
- propagate :relaxed
255
+ enable
282
256
  end
283
257
  end
284
258
 
285
- class Shirt < Product
286
- include_field :images
287
- include_field :sections
288
- prepend :title => "Copy of "
289
- end
290
-
291
- In this example, the conflicting `include_field` settings on the child will be ignored and the parent `exclude_field` setting will be used, while the `prepend` setting on the child will be honored because it doesn't conflict with the parent.
292
-
293
- #### Strict Parenting
294
-
295
- The `:strict` style will ignore child settings altogether and inherit any parent settings.
296
-
297
- class Product < ActiveRecord::Base
298
- has_many :images
299
- has_and_belongs_to_many :sections
259
+ class Post < ActiveRecord::Base
260
+ belongs_to :author
261
+ validates_presence_of :author
300
262
 
301
263
  amoeba do
302
- exclude_field :images
303
- propagate :strict
264
+ enable
304
265
  end
305
266
  end
306
267
 
307
- class Shirt < Product
308
- include_field :images
309
- include_field :sections
310
- prepend :title => "Copy of "
311
- end
312
-
313
- In this example, the only processing that will happen when a Shirt is duplicated is whatever processing is allowed by the parent. So in this case the parent's `exclude_field` directive takes precedence over the child's `include_field` settings, and not only that, but none of the other settings for the child are used either. The `prepend` setting of the child is completely ignored.
314
-
315
- #### Parenting and Precedence
316
-
317
- Because of the two general forms of DSL config parameter usage, you may wish to make yourself mindful of how your coding style will affect the outcome of duplicating an object.
318
-
319
- Just remember that:
320
-
321
- * If you pass an array you will wipe all previous settings
322
- * If you pass single values, you will add to currently existing settings
323
-
324
- This means that, for example:
325
-
326
- * When using the submissive parenting style, you can child take full precedence on a per field basis by passing an array of config values. This will cause the setting from the parent to be overridden instead of added to.
327
- * When using the relaxed parenting style, you can still let the parent take precedence on a per field basis by passing an array of config values. This will cause the setting for that child to be overridden instead of added to.
268
+ author = Author.find(1)
269
+ author.dup
328
270
 
329
- #### A Submissive Override Example
271
+ author.save # this will fail validation
330
272
 
331
- This version will use both the parent and child settings, so both the images and sections will be copied.
273
+ Wheras this will work fine:
332
274
 
333
- class Product < ActiveRecord::Base
334
- has_many :images
335
- has_and_belongs_to_many :sections
275
+ class Author < ActiveRecord::Base
276
+ has_many :posts, :inverse_of => :author
336
277
 
337
278
  amoeba do
338
- include_field :images
339
- propagate
279
+ enable
340
280
  end
341
281
  end
342
282
 
343
- class Shirt < Product
344
- include_field :sections
345
- end
346
-
347
- The next version will use only the child settings because passing an array will override any previous settings rather than adding to them and the child config takes precedence in the `submissive` parenting style. So in this case only the sections will be copied.
348
-
349
- class Product < ActiveRecord::Base
350
- has_many :images
351
- has_and_belongs_to_many :sections
283
+ class Post < ActiveRecord::Base
284
+ belongs_to :author, :inverse_of => :posts
285
+ validates_presence_of :author
352
286
 
353
287
  amoeba do
354
- include_field :images
355
- propagate
288
+ enable
356
289
  end
357
290
  end
358
291
 
359
- class Shirt < Product
360
- include_field [:sections]
361
- end
292
+ author = Author.find(1)
293
+ author.dup
362
294
 
363
- #### A Relaxed Override Example
295
+ author.save # this will pass validation
364
296
 
365
- This version will use both the parent and child settings, so both the images and sections will be copied.
366
-
367
- class Product < ActiveRecord::Base
368
- has_many :images
369
- has_and_belongs_to_many :sections
297
+ This issue is not amoeba specific and also occurs when creating new objects using `accepts_nested_attributes_for`, like this:
370
298
 
371
- amoeba do
372
- include_field :images
373
- propagate :relaxed
374
- end
299
+ class Author < ActiveRecord::Base
300
+ has_many :posts
301
+ accepts_nested_attributes_for :posts
375
302
  end
376
303
 
377
- class Shirt < Product
378
- include_field :sections
304
+ class Post < ActiveRecord::Base
305
+ belongs_to :author
306
+ validates_presence_of :author
379
307
  end
380
308
 
381
- The next version will use only the parent settings because passing an array will override any previous settings rather than adding to them and the parent config takes precedence in the `relaxed` parenting style. So in this case only the images will be copied.
382
-
383
- class Product < ActiveRecord::Base
384
- has_many :images
385
- has_and_belongs_to_many :sections
309
+ # this will fail validation
310
+ author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
386
311
 
387
- amoeba do
388
- include_field [:images]
389
- propagate
390
- end
391
- end
312
+ This issue with `accepts_nested_attributes_for` can also be solved by using `:inverse_of`, like this:
392
313
 
393
- class Shirt < Product
394
- include_field :sections
314
+ class Author < ActiveRecord::Base
315
+ has_many :posts, :inverse_of => :author
316
+ accepts_nested_attributes_for :posts
395
317
  end
396
318
 
397
- ### Limiting Association Types
398
-
399
- By default, amoeba recognizes and attemps to copy any children of the following association types:
400
-
401
- - has one
402
- - has many
403
- - has and belongs to many
404
-
405
- You may control which association types amoeba applies itself to by using the `recognize` method within the amoeba configuration block.
406
-
407
319
  class Post < ActiveRecord::Base
408
- has_one :config
409
- has_many :comments
410
- has_and_belongs_to_many :tags
411
-
412
- amoeba do
413
- recognize [:has_one, :has_and_belongs_to_many]
414
- end
415
- end
416
-
417
- class Comment < ActiveRecord::Base
418
- belongs_to :post
320
+ belongs_to :author, :inverse_of => :posts
321
+ validates_presence_of :author
419
322
  end
420
323
 
421
- class Tag < ActiveRecord::Base
422
- has_and_belongs_to_many :posts
423
- end
324
+ # this will pass validation
325
+ author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
424
326
 
425
- 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.
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.
426
328
 
427
329
  ### Field Preprocessors
428
330
 
@@ -500,6 +402,8 @@ You may run a search and replace query on a copied object's field during the cop
500
402
 
501
403
  #### Custom Methods
502
404
 
405
+ ##### Customize
406
+
503
407
  You may run a custom method or methods to do basically anything you like, simply pass a lambda block, or an array of lambda blocks to the `customize` directive. Each block must have the same form, meaning that each block must accept two parameters, the original object and the newly copied object. You may then do whatever you wish, like this:
504
408
 
505
409
  class Post < ActiveRecord::Base
@@ -536,9 +440,27 @@ or this, using an array:
536
440
  end
537
441
  end
538
442
 
443
+ ##### Override
444
+
445
+ Lambda blocks passed to customize run, by default, after all copying and field pre-processing. If you wish to run a method before any customization or field pre-processing, you may use `override` the cousin of `customize`. Usage is the same as above.
446
+
447
+ class Post < ActiveRecord::Base
448
+ amoeba do
449
+ prepend :title => "Hello world! "
450
+
451
+ override(lambda { |original_post,new_post|
452
+ if original_post.foo == "bar"
453
+ new_post.baz = "qux"
454
+ end
455
+ })
456
+
457
+ append :comments => "... know what I'm sayin?"
458
+ end
459
+ end
460
+
539
461
  #### Chaining
540
462
 
541
- You may apply a preprocessor to multiple fields at once.
463
+ You may apply a single preprocessor to multiple fields at once.
542
464
 
543
465
  class Post < ActiveRecord::Base
544
466
  amoeba do
@@ -739,29 +661,211 @@ You may control how amoeba copies your object, on the fly, by passing a configur
739
661
  end
740
662
  end
741
663
 
742
- ### Configuration Reference
664
+ ### Inheritance
665
+
666
+ If you are using the Single Table Inheritance provided by ActiveRecord, you may cause amoeba to automatically process child classes in the same way as their parents. All you need to do is call the `propagate` method within the amoeba block of the parent class and all child classes should copy in a similar manner.
667
+
668
+ create_table :products, :force => true do |t|
669
+ t.string :type # this is the STI column
670
+
671
+ # these belong to all products
672
+ t.string :title
673
+ t.decimal :price
674
+
675
+ # these are for shirts only
676
+ t.decimal :sleeve_length
677
+ t.decimal :collar_size
678
+
679
+ # these are for computers only
680
+ t.integer :ram_size
681
+ t.integer :hard_drive_size
682
+ end
683
+
684
+ class Product < ActiveRecord::Base
685
+ has_many :images
686
+ has_and_belongs_to_many :categories
687
+
688
+ amoeba do
689
+ enable
690
+ propagate
691
+ end
692
+ end
693
+
694
+ class Shirt < Product
695
+ end
696
+
697
+ class Computer < Product
698
+ end
699
+
700
+ class ProductsController
701
+ def some_method
702
+ my_shirt = Shirt.find(1)
703
+ my_shirt.dup
704
+ my_shirt.save
705
+
706
+ # this shirt should now:
707
+ # - have its own copy of all parent images
708
+ # - be in the same categories as the parent
709
+ end
710
+ end
711
+
712
+ This example should duplicate all the images and sections associated with this Shirt, which is a child of Product
713
+
714
+ #### Parenting Style
715
+
716
+ By default, propagation uses submissive parenting, meaning the config settings on the parent will be applied, but any child settings, if present, will either add to or overwrite the parent settings depending on how you call the DSL methods.
717
+
718
+ You may change this behavior, the so called "parenting style", to give preference to the parent settings or to ignore any and all child settings.
719
+
720
+ ##### Relaxed Parenting
721
+
722
+ The `:relaxed` parenting style will prefer parent settings.
723
+
724
+ class Product < ActiveRecord::Base
725
+ has_many :images
726
+ has_and_belongs_to_many :sections
727
+
728
+ amoeba do
729
+ exclude_field :images
730
+ propagate :relaxed
731
+ end
732
+ end
733
+
734
+ class Shirt < Product
735
+ include_field :images
736
+ include_field :sections
737
+ prepend :title => "Copy of "
738
+ end
739
+
740
+ In this example, the conflicting `include_field` settings on the child will be ignored and the parent `exclude_field` setting will be used, while the `prepend` setting on the child will be honored because it doesn't conflict with the parent.
741
+
742
+ ##### Strict Parenting
743
+
744
+ The `:strict` style will ignore child settings altogether and inherit any parent settings.
745
+
746
+ class Product < ActiveRecord::Base
747
+ has_many :images
748
+ has_and_belongs_to_many :sections
749
+
750
+ amoeba do
751
+ exclude_field :images
752
+ propagate :strict
753
+ end
754
+ end
755
+
756
+ class Shirt < Product
757
+ include_field :images
758
+ include_field :sections
759
+ prepend :title => "Copy of "
760
+ end
761
+
762
+ In this example, the only processing that will happen when a Shirt is duplicated is whatever processing is allowed by the parent. So in this case the parent's `exclude_field` directive takes precedence over the child's `include_field` settings, and not only that, but none of the other settings for the child are used either. The `prepend` setting of the child is completely ignored.
763
+
764
+ ##### Parenting and Precedence
765
+
766
+ Because of the two general forms of DSL config parameter usage, you may wish to make yourself mindful of how your coding style will affect the outcome of duplicating an object.
767
+
768
+ Just remember that:
769
+
770
+ * If you pass an array you will wipe all previous settings
771
+ * If you pass single values, you will add to currently existing settings
772
+
773
+ This means that, for example:
774
+
775
+ * When using the submissive parenting style, you can child take full precedence on a per field basis by passing an array of config values. This will cause the setting from the parent to be overridden instead of added to.
776
+ * When using the relaxed parenting style, you can still let the parent take precedence on a per field basis by passing an array of config values. This will cause the setting for that child to be overridden instead of added to.
777
+
778
+ ##### A Submissive Override Example
779
+
780
+ This version will use both the parent and child settings, so both the images and sections will be copied.
781
+
782
+ class Product < ActiveRecord::Base
783
+ has_many :images
784
+ has_and_belongs_to_many :sections
785
+
786
+ amoeba do
787
+ include_field :images
788
+ propagate
789
+ end
790
+ end
791
+
792
+ class Shirt < Product
793
+ include_field :sections
794
+ end
795
+
796
+ The next version will use only the child settings because passing an array will override any previous settings rather than adding to them and the child config takes precedence in the `submissive` parenting style. So in this case only the sections will be copied.
797
+
798
+ class Product < ActiveRecord::Base
799
+ has_many :images
800
+ has_and_belongs_to_many :sections
801
+
802
+ amoeba do
803
+ include_field :images
804
+ propagate
805
+ end
806
+ end
807
+
808
+ class Shirt < Product
809
+ include_field [:sections]
810
+ end
811
+
812
+ ##### A Relaxed Override Example
813
+
814
+ This version will use both the parent and child settings, so both the images and sections will be copied.
815
+
816
+ class Product < ActiveRecord::Base
817
+ has_many :images
818
+ has_and_belongs_to_many :sections
819
+
820
+ amoeba do
821
+ include_field :images
822
+ propagate :relaxed
823
+ end
824
+ end
825
+
826
+ class Shirt < Product
827
+ include_field :sections
828
+ end
829
+
830
+ The next version will use only the parent settings because passing an array will override any previous settings rather than adding to them and the parent config takes precedence in the `relaxed` parenting style. So in this case only the images will be copied.
831
+
832
+ class Product < ActiveRecord::Base
833
+ has_many :images
834
+ has_and_belongs_to_many :sections
835
+
836
+ amoeba do
837
+ include_field [:images]
838
+ propagate
839
+ end
840
+ end
841
+
842
+ class Shirt < Product
843
+ include_field :sections
844
+ end
845
+
846
+ ## Configuration Reference
743
847
 
744
848
  Here is a static reference to the available configuration methods, usable within the amoeba block on your rails models.
745
849
 
746
- #### Controlling Associations
850
+ ### Controlling Associations
747
851
 
748
- * `enable`
852
+ #### enable
749
853
 
750
854
  Enables amoeba in the default style of copying all known associated child records. Using the enable method is only required if you wish to enable amoeba but you are not using either the `include_field` or `exclude_field` directives. If you use either inclusive or exclusive style, amoeba is automatically enabled for you, so calling `enable` would be redundant, though it won't hurt.
751
855
 
752
- * `include_field`
856
+ #### include_field
753
857
 
754
858
  Adds a field to the list of fields which should be copied. All associations not in this list will not be copied. This method may be called multiple times, once per desired field, or you may pass an array of field names. Passing a single symbol will add to the list of included fields. Passing an array will empty the list and replace it with the array you pass.
755
859
 
756
- * `exclude_field`
860
+ #### exclude_field
757
861
 
758
862
  Adds a field to the list of fields which should not be copied. Only the associations that are not in this list will be copied. This method may be called multiple times, once per desired field, or you may pass an array of field names. Passing a single symbol will add to the list of excluded fields. Passing an array will empty the list and replace it with the array you pass.
759
863
 
760
- * `clone`
864
+ #### clone
761
865
 
762
866
  Adds a field to the list of associations which should have their associated children actually cloned. This means for example, that instead of just maintaining original associations with previously existing tags, a copy will be made of each tag, and the new record will be associated with these new tag copies rather than the old tag copies. This method may be called multiple times, once per desired field, or you may pass an array of field names. Passing a single symbol will add to the list of excluded fields. Passing an array will empty the list and replace it with the array you pass.
763
867
 
764
- * `propagate`
868
+ #### propagate
765
869
 
766
870
  This causes any inherited child models to take the same config settings when copied. This method may take up to one argument to control the so called "parenting style". The argument should be one of `strict`, `relaxed` or `submissive`.
767
871
 
@@ -775,7 +879,7 @@ Here is a static reference to the available configuration methods, usable within
775
879
 
776
880
  will choose the strict parenting style of inherited settings.
777
881
 
778
- * `raised`
882
+ #### raised
779
883
 
780
884
  This causes any child to behave with a (potentially) different "parenting style" than its actual parent. This method takes up to a single parameter for which there are three options, `strict`, `relaxed` and `submissive`.
781
885
 
@@ -789,29 +893,29 @@ Here is a static reference to the available configuration methods, usable within
789
893
 
790
894
  will choose the relaxed parenting style of inherited settings for this child. A parenting style set via the `raised` method takes precedence over the parenting style set using the `propagate` method.
791
895
 
792
- #### Pre-Processing Fields
896
+ ### Pre-Processing Fields
793
897
 
794
- * `nullify`
898
+ #### nullify
795
899
 
796
900
  Adds a field to the list of non-association based fields which should be set to nil during copy. All fields in this list will be set to `nil` - note that any nullified field will be given its default value if a default value exists on this model's migration. This method may be called multiple times, once per desired field, or you may pass an array of field names. Passing a single symbol will add to the list of null fields. Passing an array will empty the list and replace it with the array you pass.
797
901
 
798
- * `prepend`
902
+ #### prepend
799
903
 
800
904
  Prefix a field with some text. This only works for string fields. Accepts a hash of fields to prepend. The keys are the field names and the values are the prefix strings. An example scenario would be to add a string such as "Copy of " to your title field. Don't forget to include extra space to the right if you want it. Passing a hash will add each key value pair to the list of prepend directives. If you wish to empty the list of directives, you may pass the hash inside of an array like this `[{:title => "Copy of "}]`.
801
905
 
802
- * `append`
906
+ #### append
803
907
 
804
908
  Append some text to a field. This only works for string fields. Accepts a hash of fields to prepend. The keys are the field names and the values are the prefix strings. An example would be to add " (copied version)" to your description field. Don't forget to add a leading space if you want it. Passing a hash will add each key value pair to the list of append directives. If you wish to empty the list of directives, you may pass the hash inside of an array like this `[{:contents => " (copied version)"}]`.
805
909
 
806
- * `set`
910
+ #### set
807
911
 
808
912
  Set a field to a given value. This sould work for almost any type of field. Accepts a hash of fields and the values you want them set to.. The keys are the field names and the values are the prefix strings. An example would be to add " (copied version)" to your description field. Don't forget to add a leading space if you want it. Passing a hash will add each key value pair to the list of append directives. If you wish to empty the list of directives, you may pass the hash inside of an array like this `[{:approval_state => "open_for_editing"}]`.
809
913
 
810
- * `regex`
914
+ #### regex
811
915
 
812
916
  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"}]`.
813
917
 
814
- * `customize`
918
+ #### customize
815
919
 
816
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
817
921
 
@@ -825,12 +929,17 @@ The behavior when copying nested hierarchical models is undefined. Copying a cat
825
929
 
826
930
  The behavior when copying polymorphic `has_many` associations is also undefined. Support for these types of associations is planned for a future release.
827
931
 
828
- ### For Developers
932
+ ## For Developers
829
933
 
830
934
  You may run the rspec tests like this:
831
935
 
832
936
  bundle exec rspec spec
833
937
 
938
+ ### TODO
939
+
940
+ * add ability to cancel further processing from within an override block
941
+ * write some spec for the override method
942
+
834
943
  ## License
835
944
 
836
945
  [The BSD License](http://www.opensource.org/licenses/bsd-license.php)
@@ -28,7 +28,7 @@ EOF
28
28
  s.add_development_dependency "bundler", ">= 1.0.0"
29
29
  s.add_development_dependency "rspec", "~> 2.3"
30
30
 
31
- s.add_development_dependency "sqlite3-ruby"
31
+ s.add_development_dependency "sqlite3"
32
32
 
33
33
  s.add_dependency "activerecord", ">= 3.0"
34
34
  end
@@ -59,6 +59,11 @@ module Amoeba
59
59
  @customizations
60
60
  end
61
61
 
62
+ def overrides
63
+ @overrides ||= []
64
+ @overrides
65
+ end
66
+
62
67
  def null_fields
63
68
  @null_fields ||= []
64
69
  @null_fields
@@ -138,6 +143,17 @@ module Amoeba
138
143
  @clones
139
144
  end
140
145
 
146
+ def override(value=nil)
147
+ @do_preproc ||= true
148
+ @overrides ||= []
149
+ if value.is_a?(Array)
150
+ @overrides = value
151
+ else
152
+ @overrides << value if value
153
+ end
154
+ @overrides
155
+ end
156
+
141
157
  def customize(value=nil)
142
158
  @do_preproc ||= true
143
159
  @customizations ||= []
@@ -310,6 +326,7 @@ module Amoeba
310
326
  def dup(options={})
311
327
  @result = super()
312
328
 
329
+ # Inherit Parent Settings {{{
313
330
  if !amoeba_conf.enabled && parent_amoeba_conf.inherit
314
331
  if amoeba_conf.upbringing
315
332
  parenting_style = amoeba_conf.upbringing
@@ -332,8 +349,16 @@ module Amoeba
332
349
  self.class.amoeba(&child_settings)
333
350
  end
334
351
  end
352
+ # }}}
335
353
 
336
354
  # Run Amoeba {{{
355
+ # pramoeba_conf.overridesepend any extra strings to indicate uniqueness of the new record(s)
356
+ if amoeba_conf.overrides.length > 0
357
+ amoeba_conf.overrides.each do |block|
358
+ block.call(self, @result)
359
+ end
360
+ end
361
+
337
362
  if amoeba_conf.enabled
338
363
  # Deep Clone Settings {{{
339
364
  amoeba_conf.clones.each do |clone_field|
@@ -464,11 +489,6 @@ module Amoeba
464
489
  @result[n] = nil
465
490
  end
466
491
 
467
- # prepend any extra strings to indicate uniqueness of the new record(s)
468
- amoeba_conf.customizations.each do |block|
469
- block.call(self, @result)
470
- end
471
-
472
492
  # prepend any extra strings to indicate uniqueness of the new record(s)
473
493
  amoeba_conf.coercions.each do |field,coercion|
474
494
  @result[field] = "#{coercion}"
@@ -488,6 +508,11 @@ module Amoeba
488
508
  amoeba_conf.regexes.each do |field,action|
489
509
  @result[field].gsub!(action[:replace], action[:with])
490
510
  end
511
+
512
+ # prepend any extra strings to indicate uniqueness of the new record(s)
513
+ amoeba_conf.customizations.each do |block|
514
+ block.call(self, @result)
515
+ end
491
516
  end
492
517
  # }}}
493
518
  end
@@ -1,3 +1,3 @@
1
1
  module Amoeba
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -33,6 +33,7 @@ describe "amoeba" do
33
33
  start_postnote_count = rs["note_count"]
34
34
 
35
35
  new_post.save
36
+ new_post.errors.messages.length.should == 0
36
37
 
37
38
  end_account_count = Account.all.count
38
39
  end_history_count = History.all.count
@@ -83,6 +84,12 @@ describe "amoeba" do
83
84
  old_post.widgets.map(&:id).include?(id).should_not be true
84
85
  end
85
86
  # }}}
87
+ # Author {{{
88
+ old_author = Author.find(1)
89
+ new_author = old_author.dup
90
+ new_author.save
91
+ new_author.errors.messages.length.should == 0
92
+ # }}}
86
93
  # Products {{{
87
94
  # Base Class {{{
88
95
  old_product = Product.find(1)
@@ -94,6 +101,7 @@ describe "amoeba" do
94
101
 
95
102
  new_product = old_product.dup
96
103
  new_product.save
104
+ new_product.errors.messages.length.should == 0
97
105
 
98
106
  end_image_count = Image.where(:product_id => old_product.id).count
99
107
  end_newimage_count = Image.where(:product_id => new_product.id).count
@@ -121,6 +129,7 @@ describe "amoeba" do
121
129
 
122
130
  new_product = old_product.dup
123
131
  new_product.save
132
+ new_product.errors.messages.length.should == 0
124
133
 
125
134
  end_image_count = Image.where(:product_id => old_product.id).count
126
135
  end_newimage_count = Image.where(:product_id => new_product.id).count
@@ -147,6 +156,7 @@ describe "amoeba" do
147
156
 
148
157
  new_product = old_product.dup
149
158
  new_product.save
159
+ new_product.errors.messages.length.should == 0
150
160
 
151
161
  end_image_count = Image.where(:product_id => old_product.id).count
152
162
  end_newimage_count = Image.where(:product_id => new_product.id).count
@@ -1,10 +1,13 @@
1
1
  u1 = User.create(:name => "Robert Johnson", :email => "bob@crossroads.com")
2
2
  u2 = User.create(:name => "Miles Davis", :email => "miles@kindofblue.com")
3
3
 
4
+ a1 = Author.create(:full_name => "Kermit The Vonnegut", :nickname => "kvsoitgoes")
5
+ a2 = Author.create(:full_name => "Arthur Sees Clarck", :nickname => "strangewater")
6
+
4
7
  t = Topic.create(:title => "Ponies", :description => "Lets talk about my ponies.")
5
8
 
6
9
  # First Post {{{
7
- p1 = t.posts.create(:author => u1, :title => "My little pony", :contents => "Lorum ipsum dolor rainbow bright. I like dogs, dogs are awesome.")
10
+ p1 = t.posts.create(:owner => u1, :author => a1, :title => "My little pony", :contents => "Lorum ipsum dolor rainbow bright. I like dogs, dogs are awesome.")
8
11
  f1 = p1.create_post_config(:is_visible => true, :is_open => false, :password => 'abcdefg123')
9
12
  a1 = p1.create_account(:title => "Foo")
10
13
  h1 = p1.account.create_history(:some_stuff => "Bar")
@@ -2,9 +2,21 @@ class Topic < ActiveRecord::Base
2
2
  has_many :posts
3
3
  end
4
4
 
5
+ class User < ActiveRecord::Base
6
+ has_many :posts
7
+ end
8
+
9
+ class Author < ActiveRecord::Base
10
+ has_many :posts, :inverse_of => :author
11
+ amoeba do
12
+ enable
13
+ end
14
+ end
15
+
5
16
  class Post < ActiveRecord::Base
6
17
  belongs_to :topic
7
- belongs_to :author, :class_name => 'User'
18
+ belongs_to :owner, :class_name => 'User'
19
+ belongs_to :author, :inverse_of => :posts
8
20
  has_one :post_config
9
21
  has_one :account
10
22
  has_one :history, :through => :account
@@ -15,7 +27,9 @@ class Post < ActiveRecord::Base
15
27
  has_many :widgets, :through => :post_widgets
16
28
  has_and_belongs_to_many :tags
17
29
  has_and_belongs_to_many :notes
18
- #has_and_belongs_to_many :tags
30
+
31
+ validates_presence_of :topic
32
+ validates_presence_of :author
19
33
 
20
34
  amoeba do
21
35
  enable
@@ -141,10 +155,6 @@ class Note < ActiveRecord::Base
141
155
  has_and_belongs_to_many :posts
142
156
  end
143
157
 
144
- class User < ActiveRecord::Base
145
- has_many :posts
146
- end
147
-
148
158
  # Inheritance
149
159
  class Product < ActiveRecord::Base
150
160
  has_many :images
@@ -9,6 +9,7 @@ ActiveRecord::Schema.define do
9
9
 
10
10
  create_table :posts, :force => true do |t|
11
11
  t.integer :topic_id
12
+ t.integer :owner_id
12
13
  t.integer :author_id
13
14
  t.string :title
14
15
  t.string :contents
@@ -110,6 +111,12 @@ ActiveRecord::Schema.define do
110
111
  t.timestamps
111
112
  end
112
113
 
114
+ create_table :authors, :force => true do |t|
115
+ t.string :full_name
116
+ t.string :nickname
117
+ t.timestamps
118
+ end
119
+
113
120
  create_table :posts_tags, :force => true do |t|
114
121
  t.integer :post_id
115
122
  t.integer :tag_id
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.0
4
+ version: 1.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-25 00:00:00.000000000 Z
12
+ date: 2012-04-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &11153980 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *11153980
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.0
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rspec
27
- requirement: &11153480 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '2.3'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *11153480
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.3'
36
46
  - !ruby/object:Gem::Dependency
37
- name: sqlite3-ruby
38
- requirement: &11153080 !ruby/object:Gem::Requirement
47
+ name: sqlite3
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: '0'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *11153080
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: activerecord
49
- requirement: &11152540 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,7 +69,12 @@ dependencies:
54
69
  version: '3.0'
55
70
  type: :runtime
56
71
  prerelease: false
57
- version_requirements: *11152540
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
58
78
  description: ! 'An extension to ActiveRecord to allow the duplication method to also
59
79
  copy associated children, with recursive support for nested of grandchildren. The
60
80
  behavior is controllable with a simple DSL both on your rails models and on the
@@ -123,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
143
  version: '0'
124
144
  requirements: []
125
145
  rubyforge_project: amoeba
126
- rubygems_version: 1.8.10
146
+ rubygems_version: 1.8.21
127
147
  signing_key:
128
148
  specification_version: 3
129
149
  summary: Easy copying of rails models and their child associations.