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