activescaffold/active_scaffold

View on GitHub
lib/active_scaffold/data_structures/nested_info.rb

Summary

Maintainability
A
1 hr
Test Coverage
F
0%
module ActiveScaffold::DataStructures
  class NestedInfo
    def self.get(model, params)
      if params[:association].nil?
        ActiveScaffold::DataStructures::NestedInfoScope.new(model, params)
      else
        ActiveScaffold::DataStructures::NestedInfoAssociation.new(model, params)
      end
    rescue ActiveScaffold::ControllerNotFound
      nil
    end

    attr_accessor :association, :child_association, :parent_model, :parent_scaffold, :parent_id, :param_name, :constrained_fields, :scope

    def initialize(model, params)
      @parent_scaffold = "#{params[:parent_scaffold].to_s.camelize}Controller".constantize
      @parent_model = @parent_scaffold.active_scaffold_config.model
    end

    def to_params
      {:parent_scaffold => parent_scaffold.controller_path}
    end

    def new_instance?
      result = @new_instance.nil?
      @new_instance = false
      result
    end

    def habtm?
      false
    end

    def has_many? # rubocop:disable Naming/PredicateName
      false
    end

    def belongs_to?
      false
    end

    def has_one? # rubocop:disable Naming/PredicateName
      false
    end

    def singular_association?
      belongs_to? || has_one?
    end

    def plural_association?
      has_many? || habtm?
    end

    def readonly_through_association?(columns)
      false
    end

    def through_association?
      false
    end

    def readonly?
      false
    end

    def sorted?(*)
      false
    end

    def match_model?(model)
      false
    end

    def create_with_parent?
      false
    end
  end

  class NestedInfoAssociation < NestedInfo
    def initialize(model, params)
      super
      column = parent_scaffold.active_scaffold_config.columns[params[:association].to_sym]
      @param_name = column.model.name.foreign_key.to_sym
      @parent_id = params[@param_name]
      @association = column&.association
      @child_association = association.reverse_association(model) if association
      setup_constrained_fields
    end

    delegate :name, :belongs_to?, :has_one?, :has_many?, :habtm?, :readonly?, :to => :association

    # A through association with has_one or has_many as source association
    # create cannot be called in nested through associations, and not-nested through associations
    # unless is through singular or create columns include through reflection of reverse association
    # e.g. customer -> networks -> firewall, reverse is firewall -> network -> customer,
    # firewall can be created if create columns include network
    def readonly_through_association?(columns)
      return false unless through_association?
      return true if association.through_reflection.options[:through] # create not possible, too many levels
      return true if association.source_reflection.options[:through] # create not possible, too many levels
      return false if create_through_singular? # create allowed, AS has code for this

      # create allowed only if through reflection in record to be created is included in create columns
      !child_association || !columns.include?(child_association.through_reflection.name)
    end

    def create_through_singular?
      association.through_singular? && source_reflection.reverse
    end

    def create_with_parent?
      if has_many? && !association.through?
        false
      elsif child_association || create_through_singular?
        true
      end
    end

    def source_reflection
      @source_reflection ||= ActiveScaffold::DataStructures::Association::ActiveRecord.new(association.source_reflection)
    end

    def through_association?
      association.through?
    end

    def match_model?(model)
      if association.polymorphic?
        child_association&.inverse_klass == model
      else
        association.klass == model
      end
    end

    def sorted?(chain)
      default_sorting(chain).present?
    end

    def default_sorting(chain)
      return @default_sorting if defined? @default_sorting
      return unless association.scope.is_a?(Proc) && chain.respond_to?(:values) && chain.values[:order]
      @default_sorting = chain.values[:order]
      @default_sorting = @default_sorting.map(&:to_sql) if @default_sorting[0].is_a? Arel::Nodes::Node
      @default_sorting = @default_sorting.join(', ')
    end

    def to_params
      super.merge(:association => @association.name, @param_name => parent_id)
    end

    protected

    def setup_constrained_fields
      @constrained_fields = [] if association.belongs_to? || association.through?
      @constrained_fields ||= Array(association.foreign_key).map(&:to_sym)
      return unless child_association && child_association != association

      @constrained_fields << child_association.name
      @constrained_fields << child_association.foreign_type.to_sym if child_association.polymorphic?
    end
  end

  class NestedInfoScope < NestedInfo
    def initialize(model, params)
      super
      @scope = params[:named_scope].to_sym
      @param_name = parent_model.name.foreign_key.to_sym
      @parent_id = params[@param_name]
      @constrained_fields = []
    end

    def to_params
      super.merge(:named_scope => @scope)
    end

    def name
      scope
    end
  end
end