lib/agnostic/duplicate.rb in agnostic-duplicate-1.0.0 vs lib/agnostic/duplicate.rb in agnostic-duplicate-1.0.1
- old
+ new
@@ -82,21 +82,40 @@
# # ...
# attr_duplicable :images
# # ...
# end
# ```
+ #
+ # If you want to apply the `duplicate` over a custom instance object instead
+ # of the default template for the current configuration, then you can pass a
+ # `dup_template` option on the method call
+ #
+ # ```ruby
+ # otherobject # => Object sharing duplicable attributes with 'myobject'
+ # myobject.duplicate dup_template: otherobject
+ # ```
+ #
+ # As the object passed to dup_template should be compliant with the duplicable
+ # attribute list, if there is an error during the process an exception will
+ # be raise according to the type of error:
+ # - Agnostic::Duplicate::ChangeSet::AttributeNotFound
+ # - Agnostic::Duplicate::ChangeSet::CopyError
+ #
module Duplicate
def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set '@duplicable_changesets', []
base.instance_variable_set '@duplicable_options', {}
end
# Duplicates the object
# @return [Duplicate] the new instance object
- def duplicate
- dup_template.tap do |model|
+ # @return opts [Hash] the options for duplicating
+ # @option [Object] dup_template The object over attributes are going to be
+ # copied
+ def duplicate(opts = {})
+ (opts[:dup_template] || dup_template).tap do |model|
apply_changesets!(model)
hook_after_duplicate!(model) if respond_to? :hook_after_duplicate!
end
end
@@ -111,38 +130,60 @@
end
# Contains all kinds of changesets that can be applied to a duplicable
# object
module ChangeSet
+ # Raised when there is an error while trying to copy an attribute
+ class CopyError < StandardError
+ end
+ # Raised when a non existing attribute is tried to be duplicated
+ class AttributeNotFound < StandardError
+ end
+
# Base class for all changesets. Subclasses should implement method
# `apply` (see #apply)
class Base
attr_reader :attributes
def initialize(attributes)
@attributes = attributes
end
+
+ private
+
+ def raise_copy_error_for(attribute)
+ msg = "It wasn't possible to copy attribute '#{attribute}'"
+ fail CopyError, msg, caller
+ end
end
# Defines a changeset where a deep copy wants to be applied to all
# attributes
class DeepCopy < Base
# Applies changes needed on the duplicated new instance object
# @param parent [Duplicate] the original object to be duplicated
# @param model [Duplicate] the duplicated new instance object
def apply(parent, model)
attributes.each do |attribute|
- setter_method = "#{attribute}="
- if model.respond_to?(setter_method)
- model.send(setter_method, dup_attribute(parent, attribute))
- else
- fail "Invalid duplicable attribute '#{attribute}'"
+ unless model.respond_to? "#{attribute}="
+ fail AttributeNotFound, "Attribute: '#{attribute}'", caller
end
+ deep_copy = dup_attribute(parent, attribute)
+ copy_attribute(attribute, model, deep_copy)
end
end
private
+ # @param attribute [Symbol] attribute to be copied
+ # @param parent [Duplicable] the original object to be duplicated
+ # @param model [Duplicable] the duplicated new instance object
+ def copy_attribute(attribute, model, deep_copy)
+ model.send("#{attribute}=", deep_copy)
+ rescue
+ raise_copy_error_for(attribute)
+ end
+
# @param parent [Duplicate] the original object to be duplicated
# @param attribute [Symbol] the attribute to be duplicated
# @return from a duplicable object the duplicated value for the
# attribute specified
def dup_attribute(parent, attribute)
@@ -175,34 +216,45 @@
# Though if the field value is a memory address it copies the memory
# address, and if the field value is a primitive type it copies the value
# of the primitive type.
class ShallowCopy < Base
# Applies changes needed on the duplicated new instance object
- # @param parent [Duplicate] the original object to be duplicated
- # @param model [Duplicate] the duplicated new instance object
+ # @param parent [Duplicable] the original object to be duplicated
+ # @param model [Duplicable] the duplicated new instance object
def apply(parent, model)
attributes.each do |attribute|
- model.send("#{attribute}=", parent.send(attribute))
+ copy_attribute(attribute, parent, model)
end
end
+
+ private
+
+ # @param attribute [Symbol] attribute to be copied
+ # @param parent [Duplicable] the original object to be duplicated
+ # @param model [Duplicable] the duplicated new instance object
+ def copy_attribute(attribute, parent, model)
+ model.send("#{attribute}=", parent.send(attribute))
+ rescue
+ raise_copy_error_for(attribute)
+ end
end
end
private
- # @return [Duplicate] a new instance object based on global duplicable
+ # @return [Duplicable] a new instance object based on global duplicable
# configuration
def dup_template
klass = self.class
if klass.duplicable_option? :new_instance
klass.new
else
dup
end
end
- # Methods added to classes including Duplicate module
+ # Methods added to classes including Duplicable module
module ClassMethods
attr_accessor :duplicable_changesets, :duplicable_options
# Adds a new duplicable changeset for the class.
#
@@ -218,18 +270,17 @@
duplicable_changesets << changeset_class.new(args)
end
# Sets global options for applying changesets
#
- # ## Options available:
- # - `new_instance`: if `true` the duplicated instance is created calling
- # in first place `new` method over the class. if `false` the duplicated
- # instance is created calling to `dup` method over the instance object.
- #
- # @param options [Hash]
- def duplicable_config(options)
- if options.is_a? Hash
- @duplicable_options.merge! options
+ # @param opts [Hash] The options for duplicable configuration
+ # @option opts [Boolean] :new_instance If `true` the duplicated instance
+ # is created calling in first place `new` method over the class. if
+ # `false` the duplicated instance is created calling to `dup` method
+ # over the instance object.
+ def duplicable_config(opts)
+ if opts.is_a? Hash
+ @duplicable_options.merge! opts
keep_valid_options
else
fail ArgumentError, 'Invalid options configuration'
end
end