module ActsAsList
  # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
  # The class that has this specified needs to have a +position+ column defined as an integer on
  # the mapped database table.
  #
  # Todo list example:
  #
  #   class TodoList < ActiveRecord::Base
  #     has_many :todo_items, :order => "position"
  #   end
  #
  #   class TodoItem < ActiveRecord::Base
  #     belongs_to :todo_list
  #     acts_as_list :scope => :todo_list
  #   end
  #
  #   todo_list.first.move_to_bottom
  #   todo_list.last.move_higher
  module ClassMethods
    # Configuration options are:
    #
    # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
    # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt> 
    #   (if it hasn't already been added) and use that as the foreign key restriction. It's also possible 
    #   to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
    #   Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
    def acts_as_list(options = {})
      configuration = { :column => "position", :scope => "1 = 1" }
      configuration.update(options) if options.is_a?(Hash)

      configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/

      if configuration[:scope].is_a?(Symbol)
        scope_condition_method = %(
          def scope_condition
            if #{configuration[:scope].to_s}.nil?
              "#{configuration[:scope].to_s} IS NULL"
            else
              "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
            end
          end
        )
      else
        scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
      end

      class_eval <<-EOV
        include ActsAsList::InstanceMethods

        def acts_as_list_class
          ::#{self.name}
        end

        def position_column
          '#{configuration[:column]}'
        end

        #{scope_condition_method}

        before_destroy :remove_from_list
        before_create  :add_to_list_bottom
      EOV
    end
  end
end