amoeba 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -25,14 +25,16 @@ Rails 3.2 compatible.
25
25
  - `has_and_belongs_to_many`
26
26
  - A simple DSL for configuration of which fields to copy. The DSL can be applied to your rails models or used on the fly.
27
27
  - Multiple configuration styles such as inclusive, exclusive and indiscriminate (aka copy everything).
28
- - Supports cloning of the children of Many-to-Many records as well as not cloning the child records but merely maintaining original associations
28
+ - Supports cloning of the children of Many-to-Many records or merely maintaining original associations
29
29
  - Supports recursive copying of child and grandchild records.
30
30
  - Supports preprocessing of fields to help indicate uniqueness and ensure the integrity of your data depending on your business logic needs, e.g. prepending "Copy of " or similar text.
31
+ - Supports preprocessing of fields with custom lambda blocks so you can basically whatever you want, for example if you need some custom logic while making copies.
31
32
  - Amoeba can perform the following preprocessing operations on fields of copied records
32
33
  - set
33
34
  - prepend
34
35
  - append
35
36
  - nullify
37
+ - customize
36
38
  - regex
37
39
 
38
40
  ## Installation
@@ -309,6 +311,44 @@ You may run a search and replace query on a copied object's field during the cop
309
311
  end
310
312
  end
311
313
 
314
+ #### Custom Methods
315
+
316
+ 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:
317
+
318
+ class Post < ActiveRecord::Base
319
+ amoeba do
320
+ prepend :title => "Hello world! "
321
+
322
+ customize(lambda { |original_post,new_post|
323
+ if original_post.foo == "bar"
324
+ new_post.baz = "qux"
325
+ end
326
+ })
327
+
328
+ append :comments => "... know what I'm sayin?"
329
+ end
330
+ end
331
+
332
+ or this, using an array:
333
+
334
+ class Post < ActiveRecord::Base
335
+ has_and_belongs_to_many :tags
336
+
337
+ amoeba do
338
+ include_field :tags
339
+
340
+ customize([
341
+ lambda do |orig_obj,copy_of_obj|
342
+ # good stuff goes here
343
+ end,
344
+
345
+ lambda do |orig_obj,copy_of_obj|
346
+ # more good stuff goes here
347
+ end
348
+ ])
349
+ end
350
+ end
351
+
312
352
  #### Chaining
313
353
 
314
354
  You may apply a preprocessor to multiple fields at once.
@@ -516,6 +556,8 @@ You may control how amoeba copies your object, on the fly, by passing a configur
516
556
 
517
557
  Here is a static reference to the available configuration methods, usable within the amoeba block on your rails models.
518
558
 
559
+ #### Controlling Associations
560
+
519
561
  `enable`
520
562
 
521
563
  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.
@@ -528,6 +570,12 @@ Adds a field to the list of fields which should be copied. All associations not
528
570
 
529
571
  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.
530
572
 
573
+ `clone`
574
+
575
+ 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.
576
+
577
+ #### Pre-Processing Fields
578
+
531
579
  `nullify`
532
580
 
533
581
  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.
@@ -540,17 +588,27 @@ Prefix a field with some text. This only works for string fields. Accepts a hash
540
588
 
541
589
  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)"}]`.
542
590
 
591
+ `set`
592
+
593
+ 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"}]`.
594
+
543
595
  `regex`
544
596
 
545
597
  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"}]`.
546
598
 
547
- ## Known Limitations and Issues
599
+ `customize`
600
+
601
+ 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
602
+
603
+ 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.
548
604
 
549
- Amoeba does not copy the actual HABMT child records but rather simply adds records to the M:M breakout table to associate the new parent copy with the same records that the original parent were associated with. In other words, it doesn't duplicate your tags or categories, but merely reassociates your parent copy with the same tags or categories that the old parent had.
605
+ ## Known Limitations and Issues
550
606
 
551
607
  The regular expression preprocessor uses case-sensitive `String#gsub`. Given the performance decreases inherrent in using regular expressions already, the fact that character classes can essentially account for case-insensitive searches, the desire to keep the DSL simple and the general use cases for this gem, I don't see a good reason to add yet more decision based conditional syntax to accommodate using case-insensitive searches or singular replacements with `String#sub`. If you find yourself wanting either of these features, by all means fork the code base and if you like your changes, submit a pull request.
552
608
 
553
- The behavior when copying nested hierarchical models is undefined. Copying a category model which has a `parent_id` field pointing to the parent category, for example, is currently undefined. The behavior when copying polymorphic `has_many` associations is also undefined. Support for these types of associations is planned for a future release.
609
+ The behavior when copying nested hierarchical models is undefined. Copying a category model which has a `parent_id` field pointing to the parent category, for example, is currently undefined.
610
+
611
+ The behavior when copying polymorphic `has_many` associations is also undefined. Support for these types of associations is planned for a future release.
554
612
 
555
613
  ### For Developers
556
614
 
@@ -558,10 +616,6 @@ You may run the rspec tests like this:
558
616
 
559
617
  bundle exec rspec spec
560
618
 
561
- ## TODO
562
-
563
- Write more tests.... anyone?
564
-
565
619
  ## License
566
620
 
567
621
  [The BSD License](http://www.opensource.org/licenses/bsd-license.php)
@@ -41,6 +41,11 @@ module Amoeba
41
41
  @clones
42
42
  end
43
43
 
44
+ def customizations
45
+ @customizations ||= []
46
+ @customizations
47
+ end
48
+
44
49
  def null_fields
45
50
  @null_fields ||= []
46
51
  @null_fields
@@ -109,6 +114,17 @@ module Amoeba
109
114
  @clones
110
115
  end
111
116
 
117
+ def customize(value=nil)
118
+ @do_preproc ||= true
119
+ @customizations ||= []
120
+ if value.is_a?(Array)
121
+ @customizations = value
122
+ else
123
+ @customizations << value if value
124
+ end
125
+ @customizations
126
+ end
127
+
112
128
  def recognize(value=nil)
113
129
  @enabled ||= true
114
130
  @known_macros ||= []
@@ -245,6 +261,7 @@ module Amoeba
245
261
  # actually copy and reassociate the new children
246
262
  # rather than only maintaining the associations
247
263
  self.send(relation_name).each do |old_obj|
264
+
248
265
  copy_of_obj = old_obj.dup
249
266
 
250
267
  # associate this new child to the new parent object
@@ -334,6 +351,11 @@ module Amoeba
334
351
  @result[n] = nil
335
352
  end
336
353
 
354
+ # prepend any extra strings to indicate uniqueness of the new record(s)
355
+ amoeba_conf.customizations.each do |block|
356
+ block.call(self, @result)
357
+ end
358
+
337
359
  # prepend any extra strings to indicate uniqueness of the new record(s)
338
360
  amoeba_conf.coercions.each do |field,coercion|
339
361
  @result[field] = "#{coercion}"
@@ -1,3 +1,3 @@
1
1
  module Amoeba
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -57,7 +57,7 @@ describe "amoeba" do
57
57
  end_history_count.should == start_history_count * 2
58
58
  end_supercat_count.should == start_supercat_count * 2
59
59
  end_post_count.should == start_post_count * 2
60
- end_comment_count.should == start_comment_count * 2
60
+ end_comment_count.should == (start_comment_count * 2) + 2
61
61
  end_rating_count.should == start_rating_count * 2
62
62
  end_postconfig_count.should == start_postconfig_count * 2
63
63
  end_posttag_count.should == start_posttag_count * 2
@@ -72,6 +72,11 @@ describe "amoeba" do
72
72
  new_post.supercats.map(&:other_ramblings).uniq.include?("La la la").should be true
73
73
  new_post.title.should == "Copy of #{old_post.title}"
74
74
  new_post.contents.should == "Here's a copy: #{old_post.contents.gsub(/dog/, 'cat')} (copied version)"
75
+ new_post.comments.length.should == 5
76
+ new_post.comments.select{ |c| c.nerf == 'ratatat' && c.contents.nil? }.length.should == 1
77
+ new_post.comments.select{ |c| c.nerf == 'ratatat' }.length.should == 2
78
+ new_post.comments.select{ |c| c.nerf == 'bonk' }.length.should == 1
79
+ new_post.comments.select{ |c| c.nerf == 'bonkers' && c.contents.nil? }.length.should == 1
75
80
 
76
81
  new_post.widgets.map(&:id).each do |id|
77
82
  old_post.widgets.map(&:id).include?(id).should_not be true
@@ -8,7 +8,7 @@ p1 = t.posts.create(:author => u1, :title => "My little pony", :contents => "Lor
8
8
  f1 = p1.create_post_config(:is_visible => true, :is_open => false, :password => 'abcdefg123')
9
9
  a1 = p1.create_account(:title => "Foo")
10
10
  h1 = p1.account.create_history(:some_stuff => "Bar")
11
- c1 = p1.comments.create(:contents => "I love it!")
11
+ c1 = p1.comments.create(:contents => "I love it!", :nerf => "ratatat")
12
12
  c1.ratings.create(:num_stars => 5)
13
13
  c1.ratings.create(:num_stars => 5)
14
14
  c1.ratings.create(:num_stars => 4)
@@ -16,7 +16,7 @@ c1.ratings.create(:num_stars => 3)
16
16
  c1.ratings.create(:num_stars => 5)
17
17
  c1.ratings.create(:num_stars => 5)
18
18
 
19
- c2 = p1.comments.create(:contents => "I hate it!")
19
+ c2 = p1.comments.create(:contents => "I hate it!", :nerf => "whapaow")
20
20
  c2.ratings.create(:num_stars => 3)
21
21
  c2.ratings.create(:num_stars => 1)
22
22
  c2.ratings.create(:num_stars => 4)
@@ -24,7 +24,7 @@ c2.ratings.create(:num_stars => 1)
24
24
  c2.ratings.create(:num_stars => 1)
25
25
  c2.ratings.create(:num_stars => 2)
26
26
 
27
- c3 = p1.comments.create(:contents => "kthxbbq!!11!!!1!eleven!!")
27
+ c3 = p1.comments.create(:contents => "kthxbbq!!11!!!1!eleven!!", :nerf => "bonk")
28
28
  c3.ratings.create(:num_stars => 0)
29
29
  c3.ratings.create(:num_stars => 0)
30
30
  c3.ratings.create(:num_stars => 1)
@@ -23,9 +23,44 @@ class Post < ActiveRecord::Base
23
23
  prepend :title => "Copy of "
24
24
  append :contents => " (copied version)"
25
25
  regex :contents => {:replace => /dog/, :with => 'cat'}
26
+ customize([
27
+ lambda do |orig_obj,copy_of_obj|
28
+ orig_obj.comments.each do |oc|
29
+ if oc.nerf == "ratatat"
30
+ hash = oc.attributes
31
+ hash[:id] = nil
32
+ hash[:post_id] = nil
33
+ hash[:contents] = nil
34
+
35
+ cc = Comment.new(hash)
36
+
37
+ copy_of_obj.comments << cc
38
+ end
39
+ end
40
+ end,
41
+ lambda do |orig_obj,copy_of_obj|
42
+ orig_obj.comments.each do |oc|
43
+ if oc.nerf == "bonk"
44
+ hash = oc.attributes
45
+ hash[:id] = nil
46
+ hash[:post_id] = nil
47
+ hash[:contents] = nil
48
+ hash[:nerf] = "bonkers"
49
+
50
+ cc = Comment.new(hash)
51
+
52
+ copy_of_obj.comments << cc
53
+ end
54
+ end
55
+ end
56
+ ])
26
57
  end
27
58
  end
28
59
 
60
+ class CustomThing < ActiveRecord::Base
61
+ belongs_to :post
62
+ end
63
+
29
64
  class Account < ActiveRecord::Base
30
65
  belongs_to :post
31
66
  has_one :history
@@ -29,9 +29,15 @@ ActiveRecord::Schema.define do
29
29
  t.timestamps
30
30
  end
31
31
 
32
+ create_table :custom_things, :force => true do |t|
33
+ t.string :value
34
+ t.timestamps
35
+ end
36
+
32
37
  create_table :comments, :force => true do |t|
33
38
  t.integer :post_id
34
39
  t.string :contents
40
+ t.string :nerf
35
41
  t.timestamps
36
42
  end
37
43
 
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.0.0
4
+ version: 1.1.0
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-21 00:00:00.000000000 Z
12
+ date: 2012-03-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &12789100 !ruby/object:Gem::Requirement
16
+ requirement: &8806100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.0.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *12789100
24
+ version_requirements: *8806100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &12788540 !ruby/object:Gem::Requirement
27
+ requirement: &8805580 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '2.3'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *12788540
35
+ version_requirements: *8805580
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: sqlite3-ruby
38
- requirement: &12788080 !ruby/object:Gem::Requirement
38
+ requirement: &8805180 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *12788080
46
+ version_requirements: *8805180
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: activerecord
49
- requirement: &12787380 !ruby/object:Gem::Requirement
49
+ requirement: &8804640 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '3.0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *12787380
57
+ version_requirements: *8804640
58
58
  description: ! 'An extension to ActiveRecord to allow the duplication method to also
59
59
  copy associated children, with recursive support for nested of grandchildren. The
60
60
  behavior is controllable with a simple DSL both on your rails models and on the