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 +62 -8
- data/lib/amoeba.rb +22 -0
- data/lib/amoeba/version.rb +1 -1
- data/spec/lib/amoeba_spec.rb +6 -1
- data/spec/support/data.rb +3 -3
- data/spec/support/models.rb +35 -0
- data/spec/support/schema.rb +6 -0
- metadata +10 -10
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
|
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
|
-
|
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
|
-
|
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.
|
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)
|
data/lib/amoeba.rb
CHANGED
@@ -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}"
|
data/lib/amoeba/version.rb
CHANGED
data/spec/lib/amoeba_spec.rb
CHANGED
@@ -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
|
data/spec/support/data.rb
CHANGED
@@ -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)
|
data/spec/support/models.rb
CHANGED
@@ -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
|
data/spec/support/schema.rb
CHANGED
@@ -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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *8806100
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
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: *
|
35
|
+
version_requirements: *8805580
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: sqlite3-ruby
|
38
|
-
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: *
|
46
|
+
version_requirements: *8805180
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: activerecord
|
49
|
-
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: *
|
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
|