wearefine/fae

View on GitHub
app/controllers/concerns/fae/cloneable.rb

Summary

Maintainability
B
4 hrs
Test Coverage
module Fae
  module Cloneable
    extend ActiveSupport::Concern

    private

    def create_from_existing(id)
      @item = @klass.find(id)
      @cloned_item = @item.dup
      update_cloned_attributes(@cloned_item)
      @cloned_item.on_prod = false if @item.respond_to?(:on_prod)

      if @cloned_item.save
        update_cloneable_associations
        redirect_to @index_path + '/' + @cloned_item.id.to_s + '/edit'
      else
        build_assets
        flash[:alert] = @cloned_item.errors.full_messages
        render action: 'edit'
      end
    end

    # array of symbols used for setting up associations on cloned object
    def associations_for_cloning
      []
    end

    # array of symbols used for cloning only specific attributes
    def attributes_for_cloning
      @klass.column_names
    end

    def update_cloned_attributes(item)
      attribute_names = attributes_for_cloning.map(&:to_s) - ['id']
      item.attributes.each do |attribute, value|
        if attribute_names.include? attribute
          rename_unique_attribute(item, attribute, value) if attr_is_unique?(item, attribute)
        else
          item.send("#{attribute}=", nil)
        end
      end
    end

    # set cloneable attributes and associations
    def update_cloneable_associations
      associations_for_cloning.each do |association|
        type = @klass.reflect_on_association(association)
        through_record = type.through_reflection

        if through_record.present?
          clone_join_relationships(through_record.plural_name)
        else
          clone_has_one_relationship(association,type) if type.macro == :has_one
          clone_has_many_relationships(association) if type.macro == :has_many
        end
      end
    end

    def clone_has_one_relationship(association,type)
      old_record = @item.send(association)

      if old_record.present?
        new_record = old_record.dup
        if ['::Fae::Image','::Fae::File'].include?(type.options[:class_name])
          new_record.send("#{type.options[:as]}_id" + '=', @cloned_item.id) if new_record.send("#{type.options[:as]}_id").present?
          new_record.send("#{type.options[:as]}_type" + '=', @cloned_item.class.name) if new_record.send("#{type.options[:as]}_type").present?
          Fae::AssetCloner.new(old_record, new_record, :asset).set_file if old_record.asset.present? && old_record.asset.url.present?
          new_record.save
        else
          new_record.send("#{@klass_singular}_id" + '=', @cloned_item.id) if new_record.send("#{@klass_singular}_id").present?
        end
        new_record.attributes.each do |attribute, value|
          rename_unique_attribute(new_record, attribute, value) if attr_is_unique?(new_record, attribute.first)
        end
      end
    end

    def clone_has_many_relationships(association)
      if @item.send(association).present?
        @item.send(association).reverse.each do |record|
          new_record = association.to_s.classify.constantize.find_by_id(record.id).dup
          new_record.send("#{@klass_singular}_id" + '=', @cloned_item.id) if new_record.send("#{@klass_singular}_id").present?
          # check if associations have unique attributes
          new_record.attributes.each do |attribute, value|
            rename_unique_attribute(new_record, attribute, value) if attr_is_unique?(new_record, attribute.first)
          end

          @cloned_item.send(association) << new_record
        end
      end
    end

    def clone_join_relationships(object)
      if @item.send(object.to_sym).present?
        @item.send(object.to_sym).reverse.each do |record|
          copied_join = object.classify.constantize.find_by_id(record.id).dup
          copied_join.send("#{@klass_singular}_id" + '=', @cloned_item.id)
          @cloned_item.send(object.to_sym) << copied_join
        end
      end
    end

    def rename_unique_attribute(item, attribute, value)
      index = 2
      symbol = attribute.to_sym
      value = unique_name(item, attribute, value, index.to_s)

      begin
        record = item.class.where(symbol => value)
        unless record.empty?
          new_index = index + 1
          value = value.chomp(index.to_s) + new_index.to_s
          index = new_index
        end
      end while record.present?

      item[symbol] = value
    end

    def attr_is_unique?(item, attribute)
      item.class.validators_on(attribute.to_sym).map(&:class).include? ActiveRecord::Validations::UniquenessValidator
    end

    def unique_name(item, attribute, value, suffix)
      item.class.validators_on(attribute.to_sym).each do |validator|
        if validator.class.name.include?('LengthValidator') && validator.options[:maximum].present?
          max_length = validator.options[:maximum] - (suffix.length + 1)
          value = value[0...max_length]
          break
        end
      end

      "#{value}-#{suffix}"
    end
  end
end