Module: Huberry::Sortable

Defined in:
lib/sortable.rb

Defined Under Namespace

Modules: InstanceMethods Classes: InvalidSortableList

Instance Method Summary (collapse)

Instance Method Details

- (Object) assert_sortable_list_exists!(list_name)

Raises InvalidSortableList if list_name is not a valid sortable list



6
7
8
# File 'lib/sortable.rb', line 6

def assert_sortable_list_exists!(list_name)
  raise ::Huberry::Sortable::InvalidSortableList.new("sortable list '#{list_name}' does not exist") unless sortable_lists.has_key?(list_name.to_s)
end

- (Object) sortable(options = {})

Allows you to sort items similar to github.com/rails/acts_as_list by with added support for multiple scopes and lists

Accepts four options:

:column     => The name of the column that will be used to store an item's position in the list. Defaults to :position
:conditions => Any extra constraints to use if you need to specify a tighter scope than just a foreign key. Defaults to {}
:list_name  => The name of the list (this is used when calling all sortable related instance methods). Defaults to nil
:scope      => A foreign key or an array of foreign keys to use as list constraints. Defaults to []

Simple example (works just like rails/acts_as_list)

class Todo < ActiveRecord::Base
  # schema
  #   id           :integer
  #   project_id   :integer
  #   description  :string
  #   position     :integer
  sortable :scope => :project_id
end

@todo = Todo.create(:description => 'do something', :project_id => 1)
@todo_2 = Todo.create(:description => 'do something else', :project_id => 1)
@todo_3 = Todo.create(:description => 'some other task', :project_id => 2)

@todo.position # 1
@todo_2.position # 2
@todo_3.position # 1

@todo.move_down!
@todo_2.reload

@todo.position # 2
@todo_2.position # 1
@todo_3.position # 1

Example with multiple scopes - Stories may or may not be in a sprint, but if we scoped just by :sprint_id, all stories with a nil :sprint_id

                             would be sorted in one giant list instead of being sorted in each of their respective projects. Specifying an 
                             array of scopes fixes this problem.

class Story < ActiveRecord::Base
  # schema
  #   id           :integer
  #   project_id   :integer
  #   sprint_id    :integer
  #   description  :string
  #   position     :integer
  sortable :scope => [:project_id, :sprint_id]
end

Example with multiple lists - Your project management software needs to allow both clients and developers to prioritize todo items separately

                            so that they can be discussed and reviewed during their next meeting. Multiple lists solves this problem.

class Todo < ActiveRecord::Base
  # schema
  #   id                  :integer
  #   project_id          :integer
  #   description         :string
  #   client_priority     :integer
  #   developer_priority  :integer
  sortable :scope => :project_id, :column => :client_priority, :list_name => :client
  sortable :scope => :project_id, :column => :developer_priority, :list_name => :developer
end

@todo = Todo.create(:description => 'do something', :project_id => 1)
@todo_2 = Todo.create(:description => 'do something else', :project_id => 1)

@todo.client_priority # 1
@todo.developer_priority # 1
@todo_2.client_priority # 2
@todo_2.developer_priority # 2

@todo.move_down!(:client)
@todo_2.reload

@todo.client_priority # 2
@todo.developer_priority # 1
@todo_2.client_priority # 1
@todo_2.developer_priority # 2

Any attributes specified as a :scope that are changed on an item cause the item to automatically switch lists when it is saved

Example

class Todo < ActiveRecord::Base
  # schema
  #   id           :integer
  #   project_id   :integer
  #   description  :string
  #   position     :integer
  sortable :scope => :project_id
end

@todo = Todo.create(:description => 'do something', :project_id => 1)
@todo_2 = Todo.create(:description => 'do something else', :project_id => 1)

@todo.position # 1
@todo_2.position # 2

@todo.project_id = 2
@todo.save
@todo_2.reload

@todo.position # 1
@todo_2.position # 1


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/sortable.rb', line 118

def sortable(options = {})
  include InstanceMethods unless include?(InstanceMethods)
  
  cattr_accessor :sortable_lists unless respond_to?(:sortable_lists)
  self.sortable_lists ||= {}
  
  define_attribute_methods
  
  options = { :column => :position, :conditions => {}, :list_name => nil, :scope => [] }.merge(options)
  
  options[:conditions] = options[:conditions].inject(['1 = 1']) do |conditions, (key, value)| 
    conditions.first << " AND #{key.is_a?(Symbol) ? "#{table_name}.#{key}" : key} "
    if value.nil?
      conditions.first << 'IS NULL'
    else
      conditions.first << '= ?'
      conditions << value
    end
  end if options[:conditions].is_a?(Hash)
  options[:conditions] = Array(options[:conditions])
  
  options[:scope] = Array(options[:scope])
  options[:scope].each do |scope|
    options[:conditions].first << " AND (#{table_name}.#{scope} = ?)"
    
    unless instance_methods.include?("#{scope}_with_sortable=")
      define_method "#{scope}_with_sortable=" do |value|
        sortable_scope_changes << scope unless sortable_scope_changes.include?(scope) || new_record? || value.to_s == send(scope).to_s || !self.class.sortable_lists.any? { |list_name, configuration| configuration[:scope].include?(scope) }
        send("#{scope}_without_sortable=".to_sym, value)
      end
      alias_method_chain "#{scope}=".to_sym, :sortable
    end
  end
  
  self.sortable_lists[options.delete(:list_name).to_s] = options
end