dalpo/rails_admin_clone

View on GitHub
lib/rails_admin_clone/model_cloner.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module RailsAdminClone
  class ModelCloner

    def initialize(original_model)
      @original_model = original_model
    end

    def original_model
      @original_model
    end

    def class_model
      original_model.class
    end

    def default_clone
      new_object = clone_object(original_model)
      clone_recursively!(original_model, new_object)

      new_object
    end

    def method_clone(method)
      original_model.send(method)
    end

  protected

    def class_with_strong_parameters?(klass)
      defined?(ActiveModel::ForbiddenAttributesProtection) && klass.include?(ActiveModel::ForbiddenAttributesProtection)
    end

    def timestamp_columns
      %w(created_at created_on updated_at updated_on)
    end

    def attributes_black_list_from_model(model)
      [model.primary_key, model.inheritance_column] + timestamp_columns
    end

    def attributes_black_list_from_association(association)
      model = association.class_name.constantize
      attributes = attributes_black_list_from_model(model)
      attributes + [association.try(:foreign_key), association.try(:type)]
    end

    def get_model_attributes_from(object)
      object.attributes.select do |k,v|
        !attributes_black_list_from_model(object.class).include?(k)
      end
    end

    def get_association_attributes_from(object, association)
      object.attributes.select do |k,v|
        !attributes_black_list_from_association(association).include?(k)
      end
    end

    def assign_attributes_for(object, attributes)
      if class_with_strong_parameters?(object.class)
        object.assign_attributes attributes
      else
        object.assign_attributes attributes, without_protection: true
      end
    end

    def assign_association(association, old_association, new_association)
      assign_attributes_for(new_association, get_association_attributes_from(old_association, association))
      new_association = clone_recursively!(old_association, new_association)
    end

    # deep clone
    def clone_recursively!(old_object, new_object)
      new_object = clone_has_one  old_object, new_object
      new_object = clone_habtm    old_object, new_object
      new_object = clone_has_many old_object, new_object

      new_object
    end

    # clone object without associations
    def clone_object(old_object)
      object = build_from(old_object)
      assign_attributes_for(object, get_model_attributes_from(old_object))

      object
    end

    # clone has_one associations
    def clone_has_one(old_object, new_object)
      old_object.class.reflect_on_all_associations(:has_one).each do |association|
        old_association = old_object.send(association.name)
        build_has_one(new_object, association, old_association) if build_has_one?(old_object, association)
      end

      new_object
    end

    def build_has_one?(object, association)
      object.send(association.name) && association.options[:through].blank?
    end

    def build_has_one(new_object, association, old_association)
      new_object.send(:"build_#{association.name}").tap do |new_association|
        assign_association(association, old_association, new_association)
      end
    end

    # clone has_many associations
    def clone_has_many(old_object, new_object)
      associations = old_object.class.reflect_on_all_associations(:has_many)
        .select{|a| !a.options.keys.include?(:through)}

      associations.each do |association|
        old_object.send(association.name).each do |old_association|
          new_object.send(association.name).build.tap do |new_association|
            assign_association(association, old_association, new_association)
          end
        end
      end

      new_object
    end

    # clone has_and_belongs_to_many associtations
    def clone_habtm(old_object, new_object)
      old_object.class.reflect_on_all_associations.select{|a| a.macro == :has_and_belongs_to_many}.each do |association|
        method_ids = "#{association.name.to_s.singularize.to_sym}_ids"
        new_object.send(:"#{method_ids}=", old_object.send(method_ids))
      end

      new_object
    end

    def build_from(object)
      object.class.new
    end
  end
end