amoeba 1.2.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4ae461133565a412e2338cfa8b4f2fd8cbed8fa5a4b5bd56724c8f583888312c
4
+ data.tar.gz: 127513d887e21e276b1511c0b6556d311832df82d781f9bd46bac708d4a830d0
5
+ SHA512:
6
+ metadata.gz: c5e8515376b4a6cb533630fb17980bc72ba46b916e40a8bd66e99944e31dd5603d63a5a33d4fc5aeed09ea59e4750f94a8a70ded31c2d0b596c82ca290603947
7
+ data.tar.gz: dd2e5f454c30535c68a704b1488a8de0e33bfe237130a79a57584cd5ae4bb1437ba99fef0f5e1dd1784750d64a9a8ac753c1064d35cdb4ea8a635dcdfbafec0c
data/.cane ADDED
@@ -0,0 +1,4 @@
1
+ --no-doc
2
+ --style-measure 99
3
+ --abc-max 5
4
+ --style-exclude spec/**/*
data/.gitignore CHANGED
@@ -1,5 +1,8 @@
1
1
  *.gem
2
2
  .bundle
3
- Gemfile.lock
3
+ Gemfile*.lock
4
4
  pkg/*
5
5
  spec/test.sqlite3
6
+ coverage
7
+ .idea
8
+ gemfiles/*.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rubocop.yml ADDED
@@ -0,0 +1,17 @@
1
+ AllCops:
2
+ Include:
3
+ - '**/Rakefile'
4
+ Exclude:
5
+ - 'spec/**/*'
6
+ Metrics/LineLength:
7
+ Max: 99
8
+ Style/FileName:
9
+ Enabled: false
10
+ Style/ModuleFunction:
11
+ Enabled: false
12
+ Style/Encoding:
13
+ Enabled: false
14
+ Documentation:
15
+ Enabled: false
16
+ Metrics/MethodLength:
17
+ Max: 15
data/.travis.yml ADDED
@@ -0,0 +1,110 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
4
+ - 2.3
5
+ - 2.4
6
+ - 2.5
7
+ - 2.6
8
+ - 2.7
9
+ - 3.0
10
+ - ruby-head
11
+ - jruby
12
+ gemfile:
13
+ - gemfiles/activerecord_4.2.gemfile
14
+ - gemfiles/activerecord_5.0.gemfile
15
+ - gemfiles/activerecord_5.1.gemfile
16
+ - gemfiles/activerecord_5.2.gemfile
17
+ - gemfiles/activerecord_6.0.gemfile
18
+ - gemfiles/activerecord_6.1.gemfile
19
+ - gemfiles/jruby_activerecord_6.1.gemfile
20
+ - gemfiles/activerecord_head.gemfile
21
+ - gemfiles/jruby_activerecord_head.gemfile
22
+ matrix:
23
+ allow_failures:
24
+ - rvm: ruby-head
25
+ - rvm: jruby
26
+ # These may work if there is a new version of sqlite3 1.3.x supporting
27
+ # Ruby 3.0
28
+ - rvm: 3.0
29
+ gemfile: gemfiles/activerecord_5.0.gemfile
30
+ - rvm: 3.0
31
+ gemfile: gemfiles/activerecord_5.1.gemfile
32
+ - rvm: 3.0
33
+ gemfile: gemfiles/activerecord_5.2.gemfile
34
+ exclude:
35
+ - rvm: 2.2
36
+ gemfile: gemfiles/activerecord_6.0.gemfile
37
+ - rvm: 2.2
38
+ gemfile: gemfiles/activerecord_6.1.gemfile
39
+ - rvm: 2.2
40
+ gemfile: gemfiles/activerecord_head.gemfile
41
+ - rvm: 2.2
42
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
43
+ - rvm: 2.2
44
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
45
+ - rvm: 2.3
46
+ gemfile: gemfiles/activerecord_6.0.gemfile
47
+ - rvm: 2.3
48
+ gemfile: gemfiles/activerecord_6.1.gemfile
49
+ - rvm: 2.3
50
+ gemfile: gemfiles/activerecord_head.gemfile
51
+ - rvm: 2.3
52
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
53
+ - rvm: 2.3
54
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
55
+ - rvm: 2.4
56
+ gemfile: gemfiles/activerecord_6.0.gemfile
57
+ - rvm: 2.4
58
+ gemfile: gemfiles/activerecord_6.1.gemfile
59
+ - rvm: 2.4
60
+ gemfile: gemfiles/activerecord_head.gemfile
61
+ - rvm: 2.4
62
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
63
+ - rvm: 2.4
64
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
65
+ - rvm: 2.5
66
+ gemfile: gemfiles/activerecord_head.gemfile
67
+ - rvm: 2.5
68
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
69
+ - rvm: 2.5
70
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
71
+ - rvm: 2.6
72
+ gemfile: gemfiles/activerecord_head.gemfile
73
+ - rvm: 2.6
74
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
75
+ - rvm: 2.6
76
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
77
+ - rvm: 2.7
78
+ gemfile: gemfiles/activerecord_4.2.gemfile
79
+ - rvm: 2.7
80
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
81
+ - rvm: 2.7
82
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
83
+ - rvm: 3.0
84
+ gemfile: gemfiles/activerecord_4.2.gemfile
85
+ - rvm: 3.0
86
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
87
+ - rvm: 3.0
88
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
89
+ - rvm: ruby-head
90
+ gemfile: gemfiles/activerecord_4.2.gemfile
91
+ - rvm: ruby-head
92
+ gemfile: gemfiles/jruby_activerecord_6.1.gemfile
93
+ - rvm: ruby-head
94
+ gemfile: gemfiles/jruby_activerecord_head.gemfile
95
+ - rvm: jruby
96
+ gemfile: gemfiles/activerecord_4.2.gemfile
97
+ - rvm: jruby
98
+ gemfile: gemfiles/activerecord_5.0.gemfile
99
+ - rvm: jruby
100
+ gemfile: gemfiles/activerecord_5.1.gemfile
101
+ - rvm: jruby
102
+ gemfile: gemfiles/activerecord_5.2.gemfile
103
+ - rvm: jruby
104
+ gemfile: gemfiles/activerecord_6.0.gemfile
105
+ - rvm: jruby
106
+ gemfile: gemfiles/activerecord_6.1.gemfile
107
+ - rvm: jruby
108
+ gemfile: gemfiles/activerecord_head.gemfile
109
+ bundler_args: --without local_development
110
+ before_install: gem install bundler -v 1.17.3
data/Appraisals ADDED
@@ -0,0 +1,76 @@
1
+ appraise 'activerecord-4.2' do
2
+ gem 'activerecord', '~> 4.2.0'
3
+ group :development, :test do
4
+ gem "sqlite3", "~> 1.3.0"
5
+ end
6
+ end
7
+
8
+ appraise 'activerecord-5.0' do
9
+ gem 'activerecord', '~> 5.0.0'
10
+ group :development, :test do
11
+ gem "sqlite3", "~> 1.3.0"
12
+ end
13
+ end
14
+
15
+ appraise 'activerecord-5.1' do
16
+ gem 'activerecord', '~> 5.1.0'
17
+ group :development, :test do
18
+ gem "sqlite3", "~> 1.3.0"
19
+ end
20
+ end
21
+
22
+ appraise 'activerecord-5.2' do
23
+ gem 'activerecord', '~> 5.2.0'
24
+ group :development, :test do
25
+ gem "sqlite3", "~> 1.3.0"
26
+ end
27
+ end
28
+
29
+ appraise 'activerecord-6.0' do
30
+ gem 'activerecord', '~> 6.0.0'
31
+ group :development, :test do
32
+ gem "sqlite3", "~> 1.4.0"
33
+ end
34
+ end
35
+
36
+ appraise 'activerecord-6.1' do
37
+ gem 'activerecord', '~> 6.1.0'
38
+ group :development, :test do
39
+ gem "sqlite3", "~> 1.4.0"
40
+ end
41
+ end
42
+
43
+ appraise 'jruby-activerecord-6.1' do
44
+ gem 'activerecord', '~> 6.1.0'
45
+ group :development, :test do
46
+ gem 'activerecord-jdbc-adapter', '~> 61.0'
47
+ gem 'activerecord-jdbcsqlite3-adapter', '~> 61.0'
48
+ end
49
+ end
50
+
51
+ appraise 'activerecord-head' do
52
+ git 'git://github.com/rails/arel.git' do
53
+ gem 'arel'
54
+ end
55
+ git 'git://github.com/rails/rails.git', branch: 'main' do
56
+ gem 'activerecord'
57
+ end
58
+ group :development, :test do
59
+ gem "sqlite3", "~> 1.4.0"
60
+ end
61
+ end
62
+
63
+ appraise 'jruby-activerecord-head' do
64
+ git 'git://github.com/rails/arel.git' do
65
+ gem 'arel'
66
+ end
67
+ git 'git://github.com/rails/rails.git', branch: 'main' do
68
+ gem 'activerecord'
69
+ end
70
+ group :development, :test do
71
+ git 'git://github.com/jruby/activerecord-jdbc-adapter' do
72
+ gem 'activerecord-jdbc-adapter'
73
+ gem 'activerecord-jdbcsqlite3-adapter', glob: 'activerecord-jdbcsqlite3-adapter/activerecord-jdbcsqlite3-adapter.gemspec'
74
+ end
75
+ end
76
+ end
data/Gemfile CHANGED
@@ -1,4 +1,12 @@
1
- source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in power_dup.gemspec
1
+ source 'https://rubygems.org'
4
2
  gemspec
3
+
4
+ group :development, :test do
5
+ gem 'rake'
6
+ gem 'coveralls', require: false
7
+ end
8
+
9
+ group :local_development do
10
+ gem 'pry'
11
+ gem 'appraisal'
12
+ end
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # Amoeba
2
2
 
3
- Easy copying of rails associations such as `has_many`.
3
+ Easy cloning of active_record objects including associations and several operations under associations and attributes.
4
4
 
5
- ![amoebalogo](http://rocksolidwebdesign.com/wp_cms/wp-content/uploads/2012/02/amoeba_logo.jpg)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/d4809ae57ca999fff022/maintainability)](https://codeclimate.com/github/amoeba-rb/amoeba/maintainability)
6
+ [![Gem Version](https://badge.fury.io/rb/amoeba.svg)](http://badge.fury.io/rb/amoeba)
7
+ [![Build Status](https://travis-ci.org/amoeba-rb/amoeba.svg?branch=master)](https://travis-ci.org/amoeba-rb/amoeba)
6
8
 
7
9
  ## What?
8
10
 
@@ -12,9 +14,9 @@ This gem is named "Amoeba" because amoebas are (small life forms that are) good
12
14
 
13
15
  ### Technical Details
14
16
 
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.
17
+ An ActiveRecord extension gem to allow the duplication of associated child record objects when duplicating an active record model.
16
18
 
17
- Rails 3.2 compatible.
19
+ Rails 4.x, 5.0, 5.1, 5.2, 6.0, 6.1 compatible.
18
20
 
19
21
  ### Features
20
22
 
@@ -44,26 +46,32 @@ Rails 3.2 compatible.
44
46
 
45
47
  is hopefully as you would expect:
46
48
 
47
- gem install amoeba
49
+ ```sh
50
+ gem install amoeba
51
+ ```
48
52
 
49
53
  or just add it to your Gemfile:
50
54
 
51
- gem 'amoeba'
55
+ ```sh
56
+ gem 'amoeba'
57
+ ```
52
58
 
53
- Configure your models with one of the styles below and then just run the `dup` method on your model as you normally would:
59
+ 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
60
 
55
- p = Post.create(:title => "Hello World!", :content => "Lorum ipsum dolor")
56
- p.comments.create(:content => "I love it!")
57
- p.comments.create(:content => "This sucks!")
61
+ ```ruby
62
+ p = Post.create(:title => "Hello World!", :content => "Lorum ipsum dolor")
63
+ p.comments.create(:content => "I love it!")
64
+ p.comments.create(:content => "This sucks!")
58
65
 
59
- puts Comment.all.count # should be 2
66
+ puts Comment.all.count # should be 2
60
67
 
61
- my_copy = p.dup
62
- my_copy.save
68
+ my_copy = p.amoeba_dup
69
+ my_copy.save
63
70
 
64
- puts Comment.all.count # should be 4
71
+ puts Comment.all.count # should be 4
72
+ ```
65
73
 
66
- By default, when enabled, amoeba will copy any and all associated child records automatically and associated them with the new parent record.
74
+ By default, when enabled, amoeba will copy any and all associated child records automatically and associate them with the new parent record.
67
75
 
68
76
  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
77
 
@@ -77,144 +85,197 @@ This is the most basic usage case and will simply enable the copying of any know
77
85
 
78
86
  If you have some models for a blog about like this:
79
87
 
80
- class Post < ActiveRecord::Base
81
- has_many :comments
82
- end
88
+ ```ruby
89
+ class Post < ActiveRecord::Base
90
+ has_many :comments
91
+ end
83
92
 
84
- class Comment < ActiveRecord::Base
85
- belongs_to :post
86
- end
93
+ class Comment < ActiveRecord::Base
94
+ belongs_to :post
95
+ end
96
+ ```
87
97
 
88
98
  simply add the amoeba configuration block to your model and call the enable method to enable the copying of child records, like this:
89
99
 
90
- class Post < ActiveRecord::Base
91
- has_many :comments
100
+ ```ruby
101
+ class Post < ActiveRecord::Base
102
+ has_many :comments
92
103
 
93
- amoeba do
94
- enable
95
- end
96
- end
104
+ amoeba do
105
+ enable
106
+ end
107
+ end
97
108
 
98
- class Comment < ActiveRecord::Base
99
- belongs_to :post
100
- end
109
+ class Comment < ActiveRecord::Base
110
+ belongs_to :post
111
+ end
112
+ ```
101
113
 
102
- Child records will be automatically copied when you run the dup method.
114
+ Child records will be automatically copied when you run the `amoeba_dup` method.
103
115
 
104
116
  #### Inclusive Style
105
117
 
106
118
  If you only want some of the associations copied but not others, you may use the inclusive style:
107
119
 
108
- class Post < ActiveRecord::Base
109
- has_many :comments
110
- has_many :tags
111
- has_many :authors
120
+ ```ruby
121
+ class Post < ActiveRecord::Base
122
+ has_many :comments
123
+ has_many :tags
124
+ has_many :authors
112
125
 
113
- amoeba do
114
- enable
115
- include_field :tags
116
- include_field :authors
117
- end
118
- end
126
+ amoeba do
127
+ enable
128
+ include_association :tags
129
+ include_association :authors
130
+ end
131
+ end
119
132
 
120
- class Comment < ActiveRecord::Base
121
- belongs_to :post
122
- end
133
+ class Comment < ActiveRecord::Base
134
+ belongs_to :post
135
+ end
136
+ ```
123
137
 
124
138
  Using the inclusive style within the amoeba block actually implies that you wish to enable amoeba, so there is no need to run the enable method, though it won't hurt either:
125
139
 
126
- class Post < ActiveRecord::Base
127
- has_many :comments
128
- has_many :tags
129
- has_many :authors
130
-
131
- amoeba do
132
- include_field :tags
133
- include_field :authors
134
- end
135
- end
136
-
137
- class Comment < ActiveRecord::Base
138
- belongs_to :post
139
- end
140
-
141
- You may also specify fields to be copied by passing an array. If you call the `include_field` with a single value, it will be appended to the list of already included fields. If you pass an array, your array will overwrite the original values.
142
-
143
- class Post < ActiveRecord::Base
144
- has_many :comments
145
- has_many :tags
146
- has_many :authors
147
-
148
- amoeba do
149
- include_field [:tags, :authors]
150
- end
151
- end
152
-
153
- class Comment < ActiveRecord::Base
154
- belongs_to :post
155
- end
140
+ ```ruby
141
+ class Post < ActiveRecord::Base
142
+ has_many :comments
143
+ has_many :tags
144
+ has_many :authors
145
+
146
+ amoeba do
147
+ include_association :tags
148
+ include_association :authors
149
+ end
150
+ end
151
+
152
+ class Comment < ActiveRecord::Base
153
+ belongs_to :post
154
+ end
155
+ ```
156
+
157
+ You may also specify fields to be copied by passing an array. If you call the `include_association` with a single value, it will be appended to the list of already included fields. If you pass an array, your array will overwrite the original values.
158
+
159
+ ```ruby
160
+ class Post < ActiveRecord::Base
161
+ has_many :comments
162
+ has_many :tags
163
+ has_many :authors
164
+
165
+ amoeba do
166
+ include_association [:tags, :authors]
167
+ end
168
+ end
169
+
170
+ class Comment < ActiveRecord::Base
171
+ belongs_to :post
172
+ end
173
+ ```
156
174
 
157
175
  These examples will copy the post's tags and authors but not its comments.
158
176
 
159
- The inclusive style, when used, will automatically disable any ther style that was previously selected.
177
+ The inclusive style, when used, will automatically disable any other style that was previously selected.
160
178
 
161
179
  #### Exclusive Style
162
180
 
163
181
  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
182
 
165
- class Post < ActiveRecord::Base
166
- has_many :comments
167
- has_many :tags
168
- has_many :authors
183
+ ```ruby
184
+ class Post < ActiveRecord::Base
185
+ has_many :comments
186
+ has_many :tags
187
+ has_many :authors
169
188
 
170
- amoeba do
171
- exclude_field :comments
172
- end
173
- end
189
+ amoeba do
190
+ exclude_association :comments
191
+ end
192
+ end
174
193
 
175
- class Comment < ActiveRecord::Base
176
- belongs_to :post
177
- end
194
+ class Comment < ActiveRecord::Base
195
+ belongs_to :post
196
+ end
197
+ ```
178
198
 
179
199
  This example does the same thing as the inclusive style example, it will copy the post's tags and authors but not its comments. As with inclusive style, there is no need to explicitly enable amoeba when specifying fields to exclude.
180
200
 
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.
201
+ 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_association` method will disable the previously selected inclusive style and wipe out any corresponding include fields.
202
+
203
+ #### Conditions
204
+
205
+ Also if you need to path extra condition for include or exclude relationship you can path method name to `:if` option.
206
+
207
+ ```ruby
208
+ class Post < ActiveRecord::Base
209
+ has_many :comments
210
+ has_many :tags
211
+
212
+ amoeba do
213
+ include_association :comments, if: :popular?
214
+ end
215
+
216
+ def popular?
217
+ likes > 15
218
+ end
219
+ end
220
+ ```
221
+
222
+ After call `Post.first.amoeba_dup` if `likes` is larger 15 than all comments will be duplicated too, but in another situation - no relations will be cloned. Same behavior will be for `exclude_association`.
223
+
224
+ **Be aware**! If you wrote:
225
+ ```ruby
226
+ class Post < ActiveRecord::Base
227
+ has_many :comments
228
+ has_many :tags
229
+
230
+ amoeba do
231
+ exclude_association :tags
232
+ include_association :comments, if: :popular?
233
+ end
234
+
235
+ def popular?
236
+ likes > 15
237
+ end
238
+ end
239
+ ```
240
+ inclusion strategy will be chosen regardless of the result of `popular?` method call (the same for reverse situation).
182
241
 
183
242
  #### Cloning
184
243
 
185
244
  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
245
 
187
- class Post < ActiveRecord::Base
188
- has_and_belongs_to_many :warnings
246
+ ```ruby
247
+ class Post < ActiveRecord::Base
248
+ has_and_belongs_to_many :warnings
189
249
 
190
- has_many :post_widgets
191
- has_many :widgets, :through => :post_widgets
250
+ has_many :post_widgets
251
+ has_many :widgets, :through => :post_widgets
192
252
 
193
- amoeba do
194
- enable
195
- clone [:widgets, :tags]
196
- end
197
- end
253
+ amoeba do
254
+ enable
255
+ clone [:widgets, :warnings]
256
+ end
257
+ end
198
258
 
199
- class Warning < ActiveRecord::Base
200
- has_and_belongs_to_many :posts
201
- end
259
+ class Warning < ActiveRecord::Base
260
+ has_and_belongs_to_many :posts
261
+ end
202
262
 
203
- class PostWidget < ActiveRecord::Base
204
- belongs_to :widget
205
- belongs_to :post
206
- end
263
+ class PostWidget < ActiveRecord::Base
264
+ belongs_to :widget
265
+ belongs_to :post
266
+ end
207
267
 
208
- class Widget < ActiveRecord::Base
209
- has_many :post_widgets
210
- has_many :posts, :through => :post_widgets
211
- end
268
+ class Widget < ActiveRecord::Base
269
+ has_many :post_widgets
270
+ has_many :posts, :through => :post_widgets
271
+ end
272
+ ```
212
273
 
213
274
  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
275
 
215
- ### Limiting Association Types
276
+ #### Limiting Association Types
216
277
 
217
- By default, amoeba recognizes and attemps to copy any children of the following association types:
278
+ By default, amoeba recognizes and attempts to copy any children of the following association types:
218
279
 
219
280
  - has one
220
281
  - has many
@@ -222,134 +283,54 @@ By default, amoeba recognizes and attemps to copy any children of the following
222
283
 
223
284
  You may control which association types amoeba applies itself to by using the `recognize` method within the amoeba configuration block.
224
285
 
225
- class Post < ActiveRecord::Base
226
- has_one :config
227
- has_many :comments
228
- has_and_belongs_to_many :tags
286
+ ```ruby
287
+ class Post < ActiveRecord::Base
288
+ has_one :config
289
+ has_many :comments
290
+ has_and_belongs_to_many :tags
229
291
 
230
- amoeba do
231
- recognize [:has_one, :has_and_belongs_to_many]
232
- end
233
- end
292
+ amoeba do
293
+ recognize [:has_one, :has_and_belongs_to_many]
294
+ end
295
+ end
234
296
 
235
- class Comment < ActiveRecord::Base
236
- belongs_to :post
237
- end
297
+ class Comment < ActiveRecord::Base
298
+ belongs_to :post
299
+ end
238
300
 
239
- class Tag < ActiveRecord::Base
240
- has_and_belongs_to_many :posts
241
- end
301
+ class Tag < ActiveRecord::Base
302
+ has_and_belongs_to_many :posts
303
+ end
304
+ ```
242
305
 
243
306
  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
307
 
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
308
  ### Field Preprocessors
330
309
 
331
310
  #### Nullify
332
311
 
333
312
  If you wish to prevent a regular (non `has_*` association based) field from retaining it's value when copied, you may "zero out" or "nullify" the field, like this:
334
313
 
335
- class Topic < ActiveRecord::Base
336
- has_many :posts
337
- end
314
+ ```ruby
315
+ class Topic < ActiveRecord::Base
316
+ has_many :posts
317
+ end
338
318
 
339
- class Post < ActiveRecord::Base
340
- belongs_to :topic
341
- has_many :comments
319
+ class Post < ActiveRecord::Base
320
+ belongs_to :topic
321
+ has_many :comments
342
322
 
343
- amoeba do
344
- enable
345
- nullify :date_published
346
- nullify :topic_id
347
- end
348
- end
323
+ amoeba do
324
+ enable
325
+ nullify :date_published
326
+ nullify :topic_id
327
+ end
328
+ end
349
329
 
350
- class Comment < ActiveRecord::Base
351
- belongs_to :post
352
- end
330
+ class Comment < ActiveRecord::Base
331
+ belongs_to :post
332
+ end
333
+ ```
353
334
 
354
335
  This example will copy all of a post's comments. It will also nullify the publishing date and dissociate the post from its original topic.
355
336
 
@@ -357,13 +338,15 @@ Unlike inclusive and exclusive styles, specifying null fields will not automatic
357
338
 
358
339
  #### Set
359
340
 
360
- If you wish to just set a field to an aribrary value on all duplicated objects you may use the `set` directive. For example, if you wanted to copy an object that has some kind of approval process associated with it, you likely may wish to set the new object's state to be open or "in progress" again.
341
+ If you wish to just set a field to an arbitrary value on all duplicated objects you may use the `set` directive. For example, if you wanted to copy an object that has some kind of approval process associated with it, you likely may wish to set the new object's state to be open or "in progress" again.
361
342
 
362
- class Post < ActiveRecord::Base
363
- amoeba do
364
- set :state_tracker => "open_for_editing"
365
- end
366
- end
343
+ ```ruby
344
+ class Post < ActiveRecord::Base
345
+ amoeba do
346
+ set :state_tracker => "open_for_editing"
347
+ end
348
+ end
349
+ ```
367
350
 
368
351
  In this example, when a post is duplicated, it's `state_tracker` field will always be given a value of `open_for_editing` to start.
369
352
 
@@ -371,34 +354,40 @@ In this example, when a post is duplicated, it's `state_tracker` field will alwa
371
354
 
372
355
  You may add a string to the beginning of a copied object's field during the copy phase:
373
356
 
374
- class Post < ActiveRecord::Base
375
- amoeba do
376
- enable
377
- prepend :title => "Copy of "
378
- end
379
- end
357
+ ```ruby
358
+ class Post < ActiveRecord::Base
359
+ amoeba do
360
+ enable
361
+ prepend :title => "Copy of "
362
+ end
363
+ end
364
+ ```
380
365
 
381
366
  #### Append
382
367
 
383
368
  You may add a string to the end of a copied object's field during the copy phase:
384
369
 
385
- class Post < ActiveRecord::Base
386
- amoeba do
387
- enable
388
- append :title => "Copy of "
389
- end
390
- end
370
+ ```ruby
371
+ class Post < ActiveRecord::Base
372
+ amoeba do
373
+ enable
374
+ append :title => "Copy of "
375
+ end
376
+ end
377
+ ```
391
378
 
392
379
  #### Regex
393
380
 
394
381
  You may run a search and replace query on a copied object's field during the copy phase:
395
382
 
396
- class Post < ActiveRecord::Base
397
- amoeba do
398
- enable
399
- regex :contents => {:replace => /dog/, :with => 'cat'}
400
- end
401
- end
383
+ ```ruby
384
+ class Post < ActiveRecord::Base
385
+ amoeba do
386
+ enable
387
+ regex :contents => {:replace => /dog/, :with => 'cat'}
388
+ end
389
+ end
390
+ ```
402
391
 
403
392
  #### Custom Methods
404
393
 
@@ -406,133 +395,147 @@ You may run a search and replace query on a copied object's field during the cop
406
395
 
407
396
  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:
408
397
 
409
- class Post < ActiveRecord::Base
410
- amoeba do
411
- prepend :title => "Hello world! "
398
+ ```ruby
399
+ class Post < ActiveRecord::Base
400
+ amoeba do
401
+ prepend :title => "Hello world! "
412
402
 
413
- customize(lambda { |original_post,new_post|
414
- if original_post.foo == "bar"
415
- new_post.baz = "qux"
416
- end
417
- })
418
-
419
- append :comments => "... know what I'm sayin?"
403
+ customize(lambda { |original_post,new_post|
404
+ if original_post.foo == "bar"
405
+ new_post.baz = "qux"
420
406
  end
421
- end
407
+ })
408
+
409
+ append :comments => "... know what I'm sayin?"
410
+ end
411
+ end
412
+ ```
422
413
 
423
414
  or this, using an array:
424
415
 
425
- class Post < ActiveRecord::Base
426
- has_and_belongs_to_many :tags
416
+ ```ruby
417
+ class Post < ActiveRecord::Base
418
+ has_and_belongs_to_many :tags
427
419
 
428
- amoeba do
429
- include_field :tags
420
+ amoeba do
421
+ include_association :tags
430
422
 
431
- customize([
432
- lambda do |orig_obj,copy_of_obj|
433
- # good stuff goes here
434
- end,
423
+ customize([
424
+ lambda do |orig_obj,copy_of_obj|
425
+ # good stuff goes here
426
+ end,
435
427
 
436
- lambda do |orig_obj,copy_of_obj|
437
- # more good stuff goes here
438
- end
439
- ])
428
+ lambda do |orig_obj,copy_of_obj|
429
+ # more good stuff goes here
440
430
  end
441
- end
431
+ ])
432
+ end
433
+ end
434
+ ```
442
435
 
443
436
  ##### Override
444
437
 
445
438
  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
439
 
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
- })
440
+ ```ruby
441
+ class Post < ActiveRecord::Base
442
+ amoeba do
443
+ prepend :title => "Hello world! "
456
444
 
457
- append :comments => "... know what I'm sayin?"
445
+ override(lambda { |original_post,new_post|
446
+ if original_post.foo == "bar"
447
+ new_post.baz = "qux"
458
448
  end
459
- end
449
+ })
450
+
451
+ append :comments => "... know what I'm sayin?"
452
+ end
453
+ end
454
+ ```
460
455
 
461
456
  #### Chaining
462
457
 
463
458
  You may apply a single preprocessor to multiple fields at once.
464
459
 
465
- class Post < ActiveRecord::Base
466
- amoeba do
467
- enable
468
- prepend :title => "Copy of ", :contents => "Copied contents: "
469
- end
470
- end
460
+ ```ruby
461
+ class Post < ActiveRecord::Base
462
+ amoeba do
463
+ enable
464
+ prepend :title => "Copy of ", :contents => "Copied contents: "
465
+ end
466
+ end
467
+ ```
471
468
 
472
469
  #### Stacking
473
470
 
474
- You may apply multiple preproccessing directives to a single model at once.
471
+ You may apply multiple preprocessing directives to a single model at once.
475
472
 
476
- class Post < ActiveRecord::Base
477
- amoeba do
478
- prepend :title => "Copy of ", :contents => "Original contents: "
479
- append :contents => " (copied version)"
480
- regex :contents => {:replace => /dog/, :with => 'cat'}
481
- end
482
- end
473
+ ```ruby
474
+ class Post < ActiveRecord::Base
475
+ amoeba do
476
+ prepend :title => "Copy of ", :contents => "Original contents: "
477
+ append :contents => " (copied version)"
478
+ regex :contents => {:replace => /dog/, :with => 'cat'}
479
+ end
480
+ end
481
+ ```
483
482
 
484
483
  This example should result in something like this:
485
484
 
486
- post = Post.create(
487
- :title => "Hello world",
488
- :contents => "I like dogs, dogs are awesome."
489
- )
485
+ ```ruby
486
+ post = Post.create(
487
+ :title => "Hello world",
488
+ :contents => "I like dogs, dogs are awesome."
489
+ )
490
490
 
491
- new_post = post.dup
491
+ new_post = post.amoeba_dup
492
492
 
493
- new_post.title # "Copy of Hello world"
494
- new_post.contents # "Original contents: I like cats, cats are awesome. (copied version)"
493
+ new_post.title # "Copy of Hello world"
494
+ new_post.contents # "Original contents: I like cats, cats are awesome. (copied version)"
495
+ ```
495
496
 
496
- Like `nullify`, the preprocessing directives do not automatically enable the copying of associated child records. If only preprocessing directives are used and you do want to copy child records and no `include_field` or `exclude_field` list is provided, you must still explicitly enable the copying of child records by calling the enable method from within the amoeba block on your model.
497
+ Like `nullify`, the preprocessing directives do not automatically enable the copying of associated child records. If only preprocessing directives are used and you do want to copy child records and no `include_association` or `exclude_association` list is provided, you must still explicitly enable the copying of child records by calling the enable method from within the amoeba block on your model.
497
498
 
498
499
  ### Precedence
499
500
 
500
- You may use a combination of configuration methods within each model's amoeba block. Recognized association types take precedence over inclusion or exclusion lists. Inclusive style takes precedence over exclusive style, and these two explicit styles take precedence over the indiscriminate style. In other words, if you list fields to copy, amoeba will only copy the fields you list, or only copy the fields you don't exclude as the case may be. Additionally, if a field type is not recognized it will not be copied, regardless of whether it appears in an inclusion list. If you want amoeba to automatically copy all of your child records, do not list any fields using either `include_field` or `exclude_field`.
501
+ You may use a combination of configuration methods within each model's amoeba block. Recognized association types take precedence over inclusion or exclusion lists. Inclusive style takes precedence over exclusive style, and these two explicit styles take precedence over the indiscriminate style. In other words, if you list fields to copy, amoeba will only copy the fields you list, or only copy the fields you don't exclude as the case may be. Additionally, if a field type is not recognized it will not be copied, regardless of whether it appears in an inclusion list. If you want amoeba to automatically copy all of your child records, do not list any fields using either `include_association` or `exclude_association`.
501
502
 
502
503
  The following example syntax is perfectly valid, and will result in the usage of inclusive style. The order in which you call the configuration methods within the amoeba block does not matter:
503
504
 
504
- class Topic < ActiveRecord::Base
505
- has_many :posts
506
- end
507
-
508
- class Post < ActiveRecord::Base
509
- belongs_to :topic
510
- has_many :comments
511
- has_many :tags
512
- has_many :authors
513
-
514
- amoeba do
515
- exclude_field :authors
516
- include_field :tags
517
- nullify :date_published
518
- prepend :title => "Copy of "
519
- append :contents => " (copied version)"
520
- regex :contents => {:replace => /dog/, :with => 'cat'}
521
- include_field :authors
522
- enable
523
- nullify :topic_id
524
- end
525
- end
526
-
527
- class Comment < ActiveRecord::Base
528
- belongs_to :post
529
- end
505
+ ```ruby
506
+ class Topic < ActiveRecord::Base
507
+ has_many :posts
508
+ end
509
+
510
+ class Post < ActiveRecord::Base
511
+ belongs_to :topic
512
+ has_many :comments
513
+ has_many :tags
514
+ has_many :authors
515
+
516
+ amoeba do
517
+ exclude_association :authors
518
+ include_association :tags
519
+ nullify :date_published
520
+ prepend :title => "Copy of "
521
+ append :contents => " (copied version)"
522
+ regex :contents => {:replace => /dog/, :with => 'cat'}
523
+ include_association :authors
524
+ enable
525
+ nullify :topic_id
526
+ end
527
+ end
528
+
529
+ class Comment < ActiveRecord::Base
530
+ belongs_to :post
531
+ end
532
+ ```
530
533
 
531
534
  This example will copy all of a post's tags and authors, but not its comments. It will also nullify the publishing date and dissociate the post from its original topic. It will also preprocess the post's fields as in the previous preprocessing example.
532
535
 
533
- Note that, because of precedence, inclusive style is used and the list of exclude fields is never consulted. Additionally, the `enable` method is redundant because amoeba is automatically enabled when using `include_field`.
536
+ Note that, because of precedence, inclusive style is used and the list of exclude fields is never consulted. Additionally, the `enable` method is redundant because amoeba is automatically enabled when using `include_association`.
534
537
 
535
- The preprocessing directives are run after child records are copied andare run in this order.
538
+ The preprocessing directives are run after child records are copied and are run in this order.
536
539
 
537
540
  1. Null fields
538
541
  2. Prepends
@@ -545,26 +548,28 @@ Preprocessing directives do not affect inclusion and exclusion lists.
545
548
 
546
549
  You may cause amoeba to keep copying down the chain as far as you like, simply add amoeba blocks to each model you wish to have copy its children. Amoeba will automatically recurse into any enabled grandchildren and copy them as well.
547
550
 
548
- class Post < ActiveRecord::Base
549
- has_many :comments
551
+ ```ruby
552
+ class Post < ActiveRecord::Base
553
+ has_many :comments
550
554
 
551
- amoeba do
552
- enable
553
- end
554
- end
555
+ amoeba do
556
+ enable
557
+ end
558
+ end
555
559
 
556
- class Comment < ActiveRecord::Base
557
- belongs_to :post
558
- has_many :ratings
560
+ class Comment < ActiveRecord::Base
561
+ belongs_to :post
562
+ has_many :ratings
559
563
 
560
- amoeba do
561
- enable
562
- end
563
- end
564
+ amoeba do
565
+ enable
566
+ end
567
+ end
564
568
 
565
- class Rating < ActiveRecord::Base
566
- belongs_to :comment
567
- end
569
+ class Rating < ActiveRecord::Base
570
+ belongs_to :comment
571
+ end
572
+ ```
568
573
 
569
574
  In this example, when a post is copied, amoeba will copy each all of a post's comments and will also copy each comment's ratings.
570
575
 
@@ -572,142 +577,150 @@ In this example, when a post is copied, amoeba will copy each all of a post's co
572
577
 
573
578
  Using the `has_one :through` association is simple, just be sure to enable amoeba on the each model with a `has_one` association and amoeba will automatically and recursively drill down, like so:
574
579
 
575
- class Supplier < ActiveRecord::Base
576
- has_one :account
577
- has_one :history, :through => :account
580
+ ```ruby
581
+ class Supplier < ActiveRecord::Base
582
+ has_one :account
583
+ has_one :history, :through => :account
578
584
 
579
- amoeba do
580
- enable
581
- end
582
- end
585
+ amoeba do
586
+ enable
587
+ end
588
+ end
583
589
 
584
- class Account < ActiveRecord::Base
585
- belongs_to :supplier
586
- has_one :history
590
+ class Account < ActiveRecord::Base
591
+ belongs_to :supplier
592
+ has_one :history
587
593
 
588
- amoeba do
589
- enable
590
- end
591
- end
594
+ amoeba do
595
+ enable
596
+ end
597
+ end
592
598
 
593
- class History < ActiveRecord::Base
594
- belongs_to :account
595
- end
599
+ class History < ActiveRecord::Base
600
+ belongs_to :account
601
+ end
602
+ ```
596
603
 
597
604
  ### Has Many Through
598
605
 
599
606
  Copying of `has_many :through` associations works automatically. They perform the copy in the same way as the `has_and_belongs_to_many` association, meaning the actual child records are not copied, but rather the associations are simply maintained. You can add some field preprocessors to the middle model if you like but this is not strictly necessary:
600
607
 
601
- class Assembly < ActiveRecord::Base
602
- has_many :manifests
603
- has_many :parts, :through => :manifests
608
+ ```ruby
609
+ class Assembly < ActiveRecord::Base
610
+ has_many :manifests
611
+ has_many :parts, :through => :manifests
604
612
 
605
- amoeba do
606
- enable
607
- end
608
- end
613
+ amoeba do
614
+ enable
615
+ end
616
+ end
609
617
 
610
- class Manifest < ActiveRecord::Base
611
- belongs_to :assembly
612
- belongs_to :part
618
+ class Manifest < ActiveRecord::Base
619
+ belongs_to :assembly
620
+ belongs_to :part
613
621
 
614
- amoeba do
615
- prefix :notes => "Copy of "
616
- end
617
- end
622
+ amoeba do
623
+ prepend :notes => "Copy of "
624
+ end
625
+ end
618
626
 
619
- class Part < ActiveRecord::Base
620
- has_many :manifests
621
- has_many :assemblies, :through => :manifests
627
+ class Part < ActiveRecord::Base
628
+ has_many :manifests
629
+ has_many :assemblies, :through => :manifests
622
630
 
623
- amoeba do
624
- enable
625
- end
626
- end
631
+ amoeba do
632
+ enable
633
+ end
634
+ end
635
+ ```
627
636
 
628
637
  ### On The Fly Configuration
629
638
 
630
639
  You may control how amoeba copies your object, on the fly, by passing a configuration block to the model's amoeba method. The configuration method is static but the configuration is applied on a per instance basis.
631
640
 
632
- class Post < ActiveRecord::Base
633
- has_many :comments
641
+ ```ruby
642
+ class Post < ActiveRecord::Base
643
+ has_many :comments
634
644
 
635
- amoeba do
636
- enable
637
- prepend :title => "Copy of "
638
- end
639
- end
645
+ amoeba do
646
+ enable
647
+ prepend :title => "Copy of "
648
+ end
649
+ end
640
650
 
641
- class Comment < ActiveRecord::Base
642
- belongs_to :post
643
- end
651
+ class Comment < ActiveRecord::Base
652
+ belongs_to :post
653
+ end
644
654
 
645
- class PostsController < ActionController
646
- def duplicate_a_post
647
- old_post = Post.create(
648
- :title => "Hello world",
649
- :contents => "Lorum ipsum"
650
- )
655
+ class PostsController < ActionController
656
+ def duplicate_a_post
657
+ old_post = Post.create(
658
+ :title => "Hello world",
659
+ :contents => "Lorum ipsum"
660
+ )
651
661
 
652
- old_post.class.amoeba do
653
- prepend :contents => "Here's a copy: "
654
- end
662
+ old_post.class.amoeba do
663
+ prepend :contents => "Here's a copy: "
664
+ end
655
665
 
656
- new_post = old_post.dup
666
+ new_post = old_post.amoeba_dup
657
667
 
658
- new_post.title # should be "Copy of Hello world"
659
- new_post.contents # should be "Here's a copy: Lorum ipsum"
660
- new_post.save
661
- end
662
- end
668
+ new_post.title # should be "Copy of Hello world"
669
+ new_post.contents # should be "Here's a copy: Lorum ipsum"
670
+ new_post.save
671
+ end
672
+ end
673
+ ```
663
674
 
664
675
  ### Inheritance
665
676
 
666
677
  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
678
 
668
- create_table :products, :force => true do |t|
669
- t.string :type # this is the STI column
679
+ ```ruby
680
+ create_table :products, :force => true do |t|
681
+ t.string :type # this is the STI column
670
682
 
671
- # these belong to all products
672
- t.string :title
673
- t.decimal :price
683
+ # these belong to all products
684
+ t.string :title
685
+ t.decimal :price
674
686
 
675
- # these are for shirts only
676
- t.decimal :sleeve_length
677
- t.decimal :collar_size
687
+ # these are for shirts only
688
+ t.decimal :sleeve_length
689
+ t.decimal :collar_size
678
690
 
679
- # these are for computers only
680
- t.integer :ram_size
681
- t.integer :hard_drive_size
682
- end
691
+ # these are for computers only
692
+ t.integer :ram_size
693
+ t.integer :hard_drive_size
694
+ end
683
695
 
684
- class Product < ActiveRecord::Base
685
- has_many :images
686
- has_and_belongs_to_many :categories
696
+ class Product < ActiveRecord::Base
697
+ has_many :images
698
+ has_and_belongs_to_many :categories
687
699
 
688
- amoeba do
689
- enable
690
- propagate
691
- end
692
- end
700
+ amoeba do
701
+ enable
702
+ propagate
703
+ end
704
+ end
693
705
 
694
- class Shirt < Product
695
- end
706
+ class Shirt < Product
707
+ end
696
708
 
697
- class Computer < Product
698
- end
709
+ class Computer < Product
710
+ end
699
711
 
700
- class ProductsController
701
- def some_method
702
- my_shirt = Shirt.find(1)
703
- my_shirt.dup
704
- my_shirt.save
712
+ class ProductsController
713
+ def some_method
714
+ my_shirt = Shirt.find(1)
715
+ my_shirt.amoeba_dup
716
+ my_shirt.save
705
717
 
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
718
+ # this shirt should now:
719
+ # - have its own copy of all parent images
720
+ # - be in the same categories as the parent
721
+ end
722
+ end
723
+ ```
711
724
 
712
725
  This example should duplicate all the images and sections associated with this Shirt, which is a child of Product
713
726
 
@@ -721,45 +734,49 @@ You may change this behavior, the so called "parenting style", to give preferenc
721
734
 
722
735
  The `:relaxed` parenting style will prefer parent settings.
723
736
 
724
- class Product < ActiveRecord::Base
725
- has_many :images
726
- has_and_belongs_to_many :sections
737
+ ```ruby
738
+ class Product < ActiveRecord::Base
739
+ has_many :images
740
+ has_and_belongs_to_many :sections
727
741
 
728
- amoeba do
729
- exclude_field :images
730
- propagate :relaxed
731
- end
732
- end
742
+ amoeba do
743
+ exclude_association :images
744
+ propagate :relaxed
745
+ end
746
+ end
733
747
 
734
- class Shirt < Product
735
- include_field :images
736
- include_field :sections
737
- prepend :title => "Copy of "
738
- end
748
+ class Shirt < Product
749
+ include_association :images
750
+ include_association :sections
751
+ prepend :title => "Copy of "
752
+ end
753
+ ```
739
754
 
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.
755
+ In this example, the conflicting `include_association` settings on the child will be ignored and the parent `exclude_association` setting will be used, while the `prepend` setting on the child will be honored because it doesn't conflict with the parent.
741
756
 
742
757
  ##### Strict Parenting
743
758
 
744
759
  The `:strict` style will ignore child settings altogether and inherit any parent settings.
745
760
 
746
- class Product < ActiveRecord::Base
747
- has_many :images
748
- has_and_belongs_to_many :sections
761
+ ```ruby
762
+ class Product < ActiveRecord::Base
763
+ has_many :images
764
+ has_and_belongs_to_many :sections
749
765
 
750
- amoeba do
751
- exclude_field :images
752
- propagate :strict
753
- end
754
- end
766
+ amoeba do
767
+ exclude_association :images
768
+ propagate :strict
769
+ end
770
+ end
755
771
 
756
- class Shirt < Product
757
- include_field :images
758
- include_field :sections
759
- prepend :title => "Copy of "
760
- end
772
+ class Shirt < Product
773
+ include_association :images
774
+ include_association :sections
775
+ prepend :title => "Copy of "
776
+ end
777
+ ```
761
778
 
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.
779
+ 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_association` directive takes precedence over the child's `include_association` 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
780
 
764
781
  ##### Parenting and Precedence
765
782
 
@@ -779,85 +796,274 @@ This means that, for example:
779
796
 
780
797
  This version will use both the parent and child settings, so both the images and sections will be copied.
781
798
 
782
- class Product < ActiveRecord::Base
783
- has_many :images
784
- has_and_belongs_to_many :sections
799
+ ```ruby
800
+ class Product < ActiveRecord::Base
801
+ has_many :images
802
+ has_and_belongs_to_many :sections
785
803
 
786
- amoeba do
787
- include_field :images
788
- propagate
789
- end
790
- end
804
+ amoeba do
805
+ include_association :images
806
+ propagate
807
+ end
808
+ end
791
809
 
792
- class Shirt < Product
793
- include_field :sections
794
- end
810
+ class Shirt < Product
811
+ include_association :sections
812
+ end
813
+ ```
795
814
 
796
815
  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
816
 
798
- class Product < ActiveRecord::Base
799
- has_many :images
800
- has_and_belongs_to_many :sections
817
+ ```ruby
818
+ class Product < ActiveRecord::Base
819
+ has_many :images
820
+ has_and_belongs_to_many :sections
801
821
 
802
- amoeba do
803
- include_field :images
804
- propagate
805
- end
806
- end
822
+ amoeba do
823
+ include_association :images
824
+ propagate
825
+ end
826
+ end
807
827
 
808
- class Shirt < Product
809
- include_field [:sections]
810
- end
828
+ class Shirt < Product
829
+ include_association [:sections]
830
+ end
831
+ ```
811
832
 
812
833
  ##### A Relaxed Override Example
813
834
 
814
835
  This version will use both the parent and child settings, so both the images and sections will be copied.
815
836
 
816
- class Product < ActiveRecord::Base
817
- has_many :images
818
- has_and_belongs_to_many :sections
837
+ ```ruby
838
+ class Product < ActiveRecord::Base
839
+ has_many :images
840
+ has_and_belongs_to_many :sections
819
841
 
820
- amoeba do
821
- include_field :images
822
- propagate :relaxed
823
- end
824
- end
842
+ amoeba do
843
+ include_association :images
844
+ propagate :relaxed
845
+ end
846
+ end
825
847
 
826
- class Shirt < Product
827
- include_field :sections
828
- end
848
+ class Shirt < Product
849
+ include_association :sections
850
+ end
851
+ ```
829
852
 
830
853
  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
854
 
832
- class Product < ActiveRecord::Base
833
- has_many :images
834
- has_and_belongs_to_many :sections
855
+ ```ruby
856
+ class Product < ActiveRecord::Base
857
+ has_many :images
858
+ has_and_belongs_to_many :sections
835
859
 
836
- amoeba do
837
- include_field [:images]
838
- propagate
839
- end
840
- end
860
+ amoeba do
861
+ include_association [:images]
862
+ propagate
863
+ end
864
+ end
841
865
 
842
- class Shirt < Product
843
- include_field :sections
844
- end
866
+ class Shirt < Product
867
+ include_association :sections
868
+ end
869
+ ```
870
+
871
+ ### Validating Nested Attributes
872
+
873
+ 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.
874
+
875
+ For example this will throw a validation error saying that your posts are invalid:
876
+
877
+ ```ruby
878
+ class Author < ActiveRecord::Base
879
+ has_many :posts
880
+
881
+ amoeba do
882
+ enable
883
+ end
884
+ end
885
+
886
+ class Post < ActiveRecord::Base
887
+ belongs_to :author
888
+ validates_presence_of :author
889
+
890
+ amoeba do
891
+ enable
892
+ end
893
+ end
894
+
895
+ author = Author.find(1)
896
+ author.amoeba_dup
897
+
898
+ author.save # this will fail validation
899
+ ```
900
+
901
+ Where this will work fine:
902
+
903
+ ```ruby
904
+ class Author < ActiveRecord::Base
905
+ has_many :posts, :inverse_of => :author
906
+
907
+ amoeba do
908
+ enable
909
+ end
910
+ end
911
+
912
+ class Post < ActiveRecord::Base
913
+ belongs_to :author, :inverse_of => :posts
914
+ validates_presence_of :author
915
+
916
+ amoeba do
917
+ enable
918
+ end
919
+ end
920
+
921
+ author = Author.find(1)
922
+ author.amoeba_dup
923
+
924
+ author.save # this will pass validation
925
+ ```
926
+
927
+ This issue is not amoeba specific and also occurs when creating new objects using `accepts_nested_attributes_for`, like this:
928
+
929
+ ```ruby
930
+ class Author < ActiveRecord::Base
931
+ has_many :posts
932
+ accepts_nested_attributes_for :posts
933
+ end
934
+
935
+ class Post < ActiveRecord::Base
936
+ belongs_to :author
937
+ validates_presence_of :author
938
+ end
939
+
940
+ # this will fail validation
941
+ author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
942
+ ```
943
+
944
+ This issue with `accepts_nested_attributes_for` can also be solved by using `:inverse_of`, like this:
945
+
946
+ ```ruby
947
+ class Author < ActiveRecord::Base
948
+ has_many :posts, :inverse_of => :author
949
+ accepts_nested_attributes_for :posts
950
+ end
951
+
952
+ class Post < ActiveRecord::Base
953
+ belongs_to :author, :inverse_of => :posts
954
+ validates_presence_of :author
955
+ end
956
+
957
+ # this will pass validation
958
+ author = Author.create({:name => "Jim Smith", :posts => [{:title => "Hello World", :contents => "Lorum ipsum dolor}]})
959
+ ```
960
+
961
+ 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.
962
+
963
+
964
+ ## Cloning using custom method
965
+
966
+ If you need to clone model with custom method you can use `through`:
967
+
968
+ ```ruby
969
+ class ChildPrototype < ActiveRecord::Base
970
+ amoeba do
971
+ through :become_child
972
+ end
973
+
974
+ def become_child
975
+ self.dup.becomes(Child)
976
+ end
977
+ end
978
+
979
+ class Child < ChildPrototype
980
+ end
981
+ ```
982
+
983
+ After cloning we will get instance of `Child` instead of `ChildPrototype`
984
+
985
+ ## Remapping associations
986
+
987
+ If you will need to do complex cloning with remapping associations name you can user `remapper`:
988
+
989
+ ```ruby
990
+ class ObjectPrototype < ActiveRecord::Base
991
+ has_many :child_prototypes
992
+
993
+ amoeba do
994
+ method :become_real
995
+ remapper :remap_associations
996
+ end
997
+
998
+ def become_real
999
+ self.dup().becomes( RealObject )
1000
+ end
1001
+
1002
+ def remap_associations( name )
1003
+ :childs if name == :child_prototypes
1004
+ end
1005
+ end
1006
+
1007
+ class RealObject < ObjectPrototype
1008
+ has_many :childs
1009
+ end
1010
+
1011
+ class ChildPrototype < ActiveRecord::Base
1012
+ amoeba do
1013
+ method :become_child
1014
+ end
1015
+
1016
+ def become_child
1017
+ self.dup().becomes( Child )
1018
+ end
1019
+ end
1020
+
1021
+ class Child < ChildPrototype
1022
+ end
1023
+ ```
1024
+
1025
+ In result we will get next:
1026
+
1027
+ ```ruby
1028
+ prototype = ObjectPrototype.new
1029
+ prototype.child_prototypes << ChildPrototype.new
1030
+ object = prototype.amoeba_dup
1031
+ object.class # => RealObject
1032
+ object.childs.first.class #=> Child
1033
+ ```
845
1034
 
846
1035
  ## Configuration Reference
847
1036
 
848
1037
  Here is a static reference to the available configuration methods, usable within the amoeba block on your rails models.
849
1038
 
1039
+ ### through
1040
+
1041
+ Set method what we will use for cloning model instead of `dup`.
1042
+
1043
+ for example:
1044
+
1045
+ ```ruby
1046
+ amoeba do
1047
+ through :supper_pupper_dup
1048
+ end
1049
+
1050
+ def supper_pupper_dup
1051
+ puts "multiplied by budding"
1052
+ self.dup
1053
+ end
1054
+ ```
1055
+
850
1056
  ### Controlling Associations
851
1057
 
852
1058
  #### enable
853
1059
 
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.
1060
+ 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_association` or `exclude_association` 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.
855
1061
 
856
- #### include_field
1062
+ #### include_association
857
1063
 
858
1064
  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.
859
1065
 
860
- #### exclude_field
1066
+ #### exclude_association
861
1067
 
862
1068
  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.
863
1069
 
@@ -873,9 +1079,11 @@ Here is a static reference to the available configuration methods, usable within
873
1079
 
874
1080
  for example
875
1081
 
876
- amoeba do
877
- propagate :strict
878
- end
1082
+ ```ruby
1083
+ amoeba do
1084
+ propagate :strict
1085
+ end
1086
+ ```
879
1087
 
880
1088
  will choose the strict parenting style of inherited settings.
881
1089
 
@@ -887,12 +1095,30 @@ Here is a static reference to the available configuration methods, usable within
887
1095
 
888
1096
  for example:
889
1097
 
890
- amoeba do
891
- raised :relaxed
892
- end
1098
+ ```ruby
1099
+ amoeba do
1100
+ raised :relaxed
1101
+ end
1102
+ ```
893
1103
 
894
1104
  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.
895
1105
 
1106
+ #### remapper
1107
+
1108
+ Set the method what will be used for remapping of association name. Method will have one argument - association name as Symbol. If method will return nil then association will not be remapped.
1109
+
1110
+ for example:
1111
+
1112
+ ```ruby
1113
+ amoeba do
1114
+ remapper :childs_to_parents
1115
+ end
1116
+
1117
+ def childs_to_parents(association_name)
1118
+ :parents if association_name == :childs
1119
+ end
1120
+ ```
1121
+
896
1122
  ### Pre-Processing Fields
897
1123
 
898
1124
  #### nullify
@@ -909,15 +1135,21 @@ Here is a static reference to the available configuration methods, usable within
909
1135
 
910
1136
  #### set
911
1137
 
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"}]`.
1138
+ Set a field to a given value. This should 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"}]`.
913
1139
 
914
1140
  #### regex
915
1141
 
916
1142
  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
1143
 
1144
+ #### override
1145
+
1146
+ 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.
1147
+
1148
+ 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.
1149
+
918
1150
  #### customize
919
1151
 
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
1152
+ 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
1153
 
922
1154
  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
1155
 
@@ -933,7 +1165,9 @@ The behavior when copying polymorphic `has_many` associations is also undefined.
933
1165
 
934
1166
  You may run the rspec tests like this:
935
1167
 
936
- bundle exec rspec spec
1168
+ ```sh
1169
+ bundle exec rspec spec
1170
+ ```
937
1171
 
938
1172
  ### TODO
939
1173