diff --git a/README.md b/README.md index 4cd1add..819e8b1 100644 --- a/README.md +++ b/README.md @@ -307,194 +307,7 @@ This example will copy the post's configuration data and keep tags associated wi ### Field Preprocessors -#### Nullify - -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: - -```ruby -class Topic < ActiveRecord::Base - has_many :posts -end - -class Post < ActiveRecord::Base - belongs_to :topic - has_many :comments - - amoeba do - enable - nullify :date_published - nullify :topic_id - end -end - -class Comment < ActiveRecord::Base - belongs_to :post -end -``` - -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. - -Unlike inclusive and exclusive styles, specifying null fields will not automatically enable amoeba to copy all child records. As with any active record object, the default field value will be used instead of `nil` if a default value exists on the migration. - -#### Set - -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. - -```ruby -class Post < ActiveRecord::Base - amoeba do - set :state_tracker => "open_for_editing" - end -end -``` - -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. - -#### Prepend - -You may add a string to the beginning of a copied object's field during the copy phase: - -```ruby -class Post < ActiveRecord::Base - amoeba do - enable - prepend :title => "Copy of " - end -end -``` - -#### Append - -You may add a string to the end of a copied object's field during the copy phase: - -```ruby -class Post < ActiveRecord::Base - amoeba do - enable - append :title => "Copy of " - end -end -``` - -#### Regex - -You may run a search and replace query on a copied object's field during the copy phase: - -```ruby -class Post < ActiveRecord::Base - amoeba do - enable - regex :contents => {:replace => /dog/, :with => 'cat'} - end -end -``` - -#### Custom Methods - -##### Customize - -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: - -```ruby -class Post < ActiveRecord::Base - amoeba do - prepend :title => "Hello world! " - - customize(lambda { |original_post,new_post| - if original_post.foo == "bar" - new_post.baz = "qux" - end - }) - - append :comments => "... know what I'm sayin?" - end -end -``` - -or this, using an array: - -```ruby -class Post < ActiveRecord::Base - has_and_belongs_to_many :tags - - amoeba do - include_association :tags - - customize([ - lambda do |orig_obj,copy_of_obj| - # good stuff goes here - end, - - lambda do |orig_obj,copy_of_obj| - # more good stuff goes here - end - ]) - end -end -``` - -##### Override - -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. - -```ruby -class Post < ActiveRecord::Base - amoeba do - prepend :title => "Hello world! " - - override(lambda { |original_post,new_post| - if original_post.foo == "bar" - new_post.baz = "qux" - end - }) - - append :comments => "... know what I'm sayin?" - end -end -``` - -#### Chaining - -You may apply a single preprocessor to multiple fields at once. - -```ruby -class Post < ActiveRecord::Base - amoeba do - enable - prepend :title => "Copy of ", :contents => "Copied contents: " - end -end -``` - -#### Stacking - -You may apply multiple preprocessing directives to a single model at once. - -```ruby -class Post < ActiveRecord::Base - amoeba do - prepend :title => "Copy of ", :contents => "Original contents: " - append :contents => " (copied version)" - regex :contents => {:replace => /dog/, :with => 'cat'} - end -end -``` - -This example should result in something like this: - -```ruby -post = Post.create( - :title => "Hello world", - :contents => "I like dogs, dogs are awesome." -) - -new_post = post.amoeba_dup - -new_post.title # "Copy of Hello world" -new_post.contents # "Original contents: I like cats, cats are awesome. (copied version)" -``` - -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. +See [Field Preprocessors](docs/field_preprocessors.md) ### Precedence diff --git a/docs/field_preprocessors.md b/docs/field_preprocessors.md new file mode 100644 index 0000000..28bc46b --- /dev/null +++ b/docs/field_preprocessors.md @@ -0,0 +1,239 @@ +## Field Preprocessors + +Fields can be configured with preprocessors to modify the value in a duplicate. +These are: + +* Nullify +* Set +* Prepend and append +* Regex +* Customize and override + +More details of each of these are given below. + +With each preprocessor, multiple fields can be configured together or +separately so, for example, the following are equivalent: + +```ruby +amoeba do + enable + prepend title: 'Copy of ', contents: 'Copied contents: ' +end + +# or + +amoeba do + enable + prepend title: 'Copy of ' + prepend contents: 'Copied contents: ' +end +``` + +Multiple preprocessing filters can be applied to a field, such as: + +```ruby +amoeba do + enable + prepend title: 'Copy of ' + append title: ' <- copied' + regex title: { replace: /cat/, with: 'dog' } +end +``` + +**Note:** 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. + +### Nullify + +A field in a duplicate can be set to `nil` with the following configuration: + +```ruby +class Post < ActiveRecord::Base + amoeba do + enable + nullify :date_published + end +end +``` + +#### Example + +```ruby +irb> original = Post.create(date_published: '19 March 2021') +irb> original.date_published +=> Fri, 19 Mar 2021 +irb> copy = original.amoba_dup +irb> copy.date_published +=> nil +``` + +A `belongs_to` association can also be removed by applying `nullify` to the id +field: + +```ruby +class Post < ActiveRecord::Base + belongs_to :topic + + amoeba do + enable + nullify :topic_id + end +end +``` + +### Set + +The value of a field can be replaced in a duplicate with a set value: + +```ruby +class Post < ActiveRecord::Base + amoeba do + enable + set state_tracker: "open_for_editing", counter: 0 + end +end +``` + +#### Example + +```ruby +irb> original = Post.create(state_tracker: 'completed', counter: 10) +irb> original.state_tracker +=> "completed" +irb> original.counter +=> 10 +irb> copy = original.amoba_dup +irb> copy.state_tracker +=> "open_for_editing" +irb> copy.counter +irb> 0 +``` + +### Prepend and append + +A string may be added to the beginning or end of a field: + +```ruby +class Post < ActiveRecord::Base + amoeba do + enable + prepend title: "Copy of " + append contents: " (New version)" + end +end +``` + +**Note:** `prepend` and `append` can only be used for string and text data +types. + +#### Example + +```ruby +irb* original = Post.create( +irb* title: 'Lorem ipsum dolor sit amet.', +irb* contents: 'Nulla pharetra porta metus id porttitor.' +irb> ) +irb> original.title +=> "Lorem ipsum dolor sit amet." +irb> original.contents +=> "Nulla pharetra porta metus id porttitor." +irb> copy = original.amoeba_dup +irb> copy.title +=> "Copy of Lorem ipsum dolor sit amet." +irb> copy.contents +=> "Nulla pharetra porta metus id porttitor. (New version)" +``` + +### Regex + +A string or text field can be modified by replacing a substring matching a +regular expression: + +```ruby +class Post < ActiveRecord::Base + amoeba do + enable + regex contents: {replace: /[aeiou]/, with: '?'} + end +end +``` + +**Note:** `regex` can only be used for string and text data types. + +#### Example + +```ruby +irb> original = Post.create(contents: 'Nulla pharetra porta metus id porttitor.') +irb> original.contents +=> "Nulla pharetra porta metus id porttitor." +irb> copy = original.amoeba_dup +irb> copy.contents +=> "N?ll? ph?r?tr? p?rt? m?t?s ?d p?rtt?t?r." +``` + +### Customize and override + +One or more lambdas can be used to make customized modifications to field +before other preprocessing (with `customize`) or after (with `override`). + +Both `customize` and `override` take as an argument a single lambda or an +array of multiple lambdas which are processed in order. Each lambda takes two +arguments; the original and the duplicate record. + +```ruby +class Post < ActiveRecord::Base + amoeba do + enable + append title: ' <<<', contents: ' ???' + + customize( + [ + lambda { |original_post,new_post| + # `(counter)` will be added after the '<<<' is appended to the title + new_post.title += " (#{original_post.counter})" + }, + lambda { |original_post, new_post| + # counter in the original record is incremented after it has been used to modify the title + original_post.counter += 1 + # Ensure that the counter is updated in the database + original_post.save + } + ] + ) + + override(lambda { |original_post, new_post| + # `(counter)` will be added before '???' is appended to the contents + new_post.contents += " (#{original_post.counter})" + }) + end +end +``` + +#### Example + +```ruby +irb* original = Post.create( +irb* title: 'Lorem ipsum dolor sit amet.', +irb* contents: 'Nulla pharetra porta metus id porttitor.', +irb* counter: 5 +irb> ) +irb> original.title +=> "Lorem ipsum dolor sit amet." +irb> original.contents +=> "Nulla pharetra porta metus id porttitor." +irb> original.counter +=> 5 +irb> copy = original.amoeba_dup +irb> copy.title +=> "Lorem ipsum dolor sit amet. <<< (5)" +irb> copy.contents +=> "Nulla pharetra porta metus id porttitor. (5) ???" +irb> copy.counter +=> 5 +irb> original.counter +=> 6 +```