module Refinery::Crud::ClassMethods

Public Instance Methods

crudify(model_name, options = {}) click to toggle source
# File lib/refinery/crud.rb, line 50
def crudify(model_name, options = {})
  options = ::Refinery::Crud.default_options(model_name).merge(options)
  class_name = options[:class_name]
  singular_name = options[:singular_name]
  plural_name = options[:plural_name]

  module_eval %(
    def self.crudify_options
      #{options.inspect}
    end

    prepend_before_action :find_#{singular_name},
                          :only => [:update, :destroy, :edit, :show]
    prepend_before_action :merge_position_into_params!, :only => :create

    def new
      @#{singular_name} = #{class_name}.new
    end

    def create
      @#{singular_name} = #{class_name}.new(#{singular_name}_params)
      if @#{singular_name}.save
        flash.notice = t(
          'refinery.crudify.created',
          :what => "'\#{@#{singular_name}.#{options[:title_attribute]}}'"
        )

        create_or_update_successful
      else
        create_or_update_unsuccessful 'new'
      end
    end

    def edit
      # object gets found by find_#{singular_name} function
    end

    def update
      if @#{singular_name}.update_attributes(#{singular_name}_params)
        flash.notice = t(
          'refinery.crudify.updated',
          :what => "'\#{@#{singular_name}.#{options[:title_attribute]}}'"
        )

        create_or_update_successful
      else
        create_or_update_unsuccessful 'edit'
      end
    end

    def destroy
      # object gets found by find_#{singular_name} function
      title = @#{singular_name}.#{options[:title_attribute]}
      if @#{singular_name}.destroy
        flash.notice = t('destroyed', :scope => 'refinery.crudify', :what => "'\#{title}'")
      end

      redirect_to redirect_url
    end

    # Finds one single result based on the id params.
    def find_#{singular_name}
      @#{singular_name} = find_#{singular_name}_scope.find(params[:id])
    end

    def find_#{singular_name}_scope
      _finder_scope = #{class_name}.includes(#{options[:include].map(&:to_sym).inspect})
      _finder_scope = _finder_scope.friendly if _finder_scope.respond_to?(:friendly)
      _finder_scope
    end

    # Find the collection of @#{plural_name} based on the conditions specified into crudify
    # It will be ordered based on the conditions specified into crudify
    # And eager loading is applied as specified into crudify.
    def find_all_#{plural_name}(conditions = #{options[:conditions].inspect})
      @#{plural_name} = find_#{singular_name}_scope
                          .where(conditions)
                          .order("#{options[:order]}")
    end

    def merge_position_into_params!
      # if the position field exists, set this object as last object, given the conditions of this class.
      if #{class_name}.column_names.include?("position") && params[:#{singular_name}][:position].nil?
        params[:#{singular_name}].merge!({
          position: ((#{class_name}.where(#{options[:conditions].inspect}).maximum(:position)||-1) + 1)
        })
      end
    end

    # Paginate a set of @#{plural_name} that may/may not already exist.
    def paginate_all_#{plural_name}
      # If we have already found a set then we don't need to again
      find_all_#{plural_name} if @#{plural_name}.nil?

      @#{plural_name} = @#{plural_name}.paginate(:page => params[:page], :per_page => paginate_per_page)
    end

    def paginate_per_page
      if #{options[:per_page].present?.inspect}
        #{options[:per_page].inspect}
      elsif #{class_name}.methods.map(&:to_sym).include?(:per_page)
        #{class_name}.per_page
      end
    end

    def redirect_url
      if params[:page].present?
        page = params[:page].to_i rescue 1
        page -= 1 while #{class_name}.paginate(:page => page).empty? && page > 1
        #{options[:redirect_to_url]}(:page => page)
      else
        #{options[:redirect_to_url]}
      end
    end

    # If the controller is being accessed via an ajax request
    # then render only the collection of items.
    def render_partial_response?
      if request.xhr?
        render plain: render_to_string(partial: '#{plural_name}', layout: false).html_safe,
               layout: 'refinery/flash' and return false
      end
    end

    def create_or_update_successful
      if from_dialog?
        self.index
        @dialog_successful = true
        render :index
      else
        if /true|on|1/ === params[:continue_editing]
          if request.xhr?
            render :partial => '/refinery/message'
          else
            redirect_to :back
          end
        else
          redirect_back_or_default redirect_url
        end
      end
    end

    def create_or_update_unsuccessful(action)
      if request.xhr?
        render :partial => '/refinery/admin/error_messages', :locals => {
                 :object => @#{singular_name},
                 :include_object_name => true
               }
      else
        render :action => action
      end
    end

    # Returns a weighted set of results based on the query specified by the user.
    def search_all_#{plural_name}
      # First find normal results.
      find_all_#{plural_name}(#{options[:search_conditions].inspect})

      # Now get weighted results by running the query against the results already found.
      @#{plural_name} = @#{plural_name}.with_query(params[:search])
    end

    def #{singular_name}_params
      raise "Please override #{singular_name}_params with your desired parameter security."
    end

    # Ensure all methods are protected so that they should only be called
    # from within the current controller.
    protected :find_#{singular_name},
              :find_#{singular_name}_scope,
              :find_all_#{plural_name},
              :paginate_all_#{plural_name},
              :paginate_per_page,
              :render_partial_response?,
              :search_all_#{plural_name},
              :#{singular_name}_params,
              :redirect_url,
              :create_or_update_successful,
              :create_or_update_unsuccessful,
              :merge_position_into_params!
  ), __FILE__, __LINE__

  # Methods that are only included when this controller is searchable.
  if options[:searchable]
    if options[:paging]
      module_eval %(
        def index
          search_all_#{plural_name} if searching?
          paginate_all_#{plural_name}

          render_partial_response?
        end
      ), __FILE__, __LINE__
    else
      module_eval %(
        def index
          if searching?
            search_all_#{plural_name}
          else
            find_all_#{plural_name}
          end

          render_partial_response?
        end
      ), __FILE__, __LINE__
    end

  else
    if options[:paging]
      module_eval %(
        def index
          paginate_all_#{plural_name}

          render_partial_response?
        end
      ), __FILE__, __LINE__
    else
      module_eval %(
        def index
          find_all_#{plural_name}
          render_partial_response?
        end
      ), __FILE__, __LINE__
    end

  end

  if options[:sortable]
    module_eval %(
      def reorder
        find_all_#{plural_name}
      end

      # Based upon https://github.com/matenia/jQuery-Awesome-Nested-Set-Drag-and-Drop
      def update_positions
        previous = nil
        params.to_unsafe_h[:ul].each do |_, list|
          list.each do |index, hash|
            moved_item_id = hash['id'][/\\d+\\z/]
            @current_#{singular_name} = #{class_name}.find_by_id(moved_item_id)

            if @current_#{singular_name}.respond_to?(:move_to_root)
              if previous.present?
                @current_#{singular_name}.move_to_right_of(#{class_name}.find_by_id(previous))
              elsif !@current_#{singular_name}.root?
                @current_#{singular_name}.move_to_root
              end
            else
              @current_#{singular_name}.update_columns position: index
            end

            if hash['children'].present?
              update_child_positions(hash, @current_#{singular_name})
            end

            previous = moved_item_id
          end
        end

        #{class_name}.rebuild! if #{class_name}.respond_to?(:rebuild!)

        after_update_positions
      end

      def update_child_positions(_node, #{singular_name})
        list = _node['children']['0']
        child_positions_changed = false
        list.sort_by { |k, v| k.to_i}.map { |item| item[1] }.each_with_index do |child, index|
          child_id = child['id'].split(/#{singular_name}\_?/).reject(&:empty?).first
          child_#{singular_name} = #{class_name}.where(:id => child_id).first
          child_positions_changed ||= #{singular_name}.children[index] != child_#{singular_name}
          if child_positions_changed
            child_#{singular_name}.move_to_child_of(#{singular_name})
          end

          if child['children'].present?
            update_child_positions(child, child_#{singular_name})
          end
        end
      end

      def after_update_positions
        head :ok
      end

      protected :after_update_positions
    ), __FILE__, __LINE__
  end

  module_eval %(
    class << self
      def pageable?
        #{options[:paging]}
      end
      alias_method :paging?, :pageable?

      def sortable?
        #{options[:sortable]}
      end

      def searchable?
        #{options[:searchable]}
      end
    end
  ), __FILE__, __LINE__

end