module OpenStax::Utilities::ActsAsNumberable
Public Instance Methods
acts_as_numberable(options={})
click to toggle source
Adds code to an ActiveRecord
object so that it can be sorted. @example Model is ordered globally using a 'number' field
class MyModel < ActiveRecord::Base acts_as_numberable
@example Model is ordered globally using a 'position' field
class MyModel < ActiveRecord::Base acts_as_numberable :number_field => :position
@example Model is ordered within a container class using a position field
class MyModel < ActiveRecord::Base belongs_to :other_model acts_as_numberable :container => :other_model, :number_field => :position
@param :container The relationship that contains this model in an order.
Note that this code assumes the foreign key for this container is found by appending "_id" onto the container name, which might not always be the case.
@param :number_field The column to use as the sorting number, given either
as a string or a symbol. The default is 'number'
@param :table_class By default this code assumes that the database table
name to use for this model can be derived from the model's class name; in some cases (e.g. STI) this is not the case and this parameter can be used to manually specify the class from which to derive the database table name.
# File lib/openstax/utilities/acts_as_numberable.rb, line 32 def acts_as_numberable(options={}) configuration = {} configuration.update(options) if options.is_a?(Hash) container_column = nil container_column_symbol = nil # When calling assign_number below, you normally want to run a query against # self.class to figure out what the next available number is; however, if the # class acting as numberable is using STI, self.class will return the child class # which is likely not what we want. In these cases, we can specify the base # class here (the class that has the same name as the DB table) so that it is used # instead. table_class = configuration[:table_class] number_field = (configuration[:number_field] || 'number').to_s if !configuration[:container].nil? container_column = configuration[:container].to_s + "_id" container_column_symbol = configuration[:container].to_sym end uniqueness_scope_string = container_column.nil? ? "" : ":scope => :#{container_column}," class_eval <<-EOV include ActsAsNumberable::BasicInstanceMethods before_validation :assign_number, :on => :create validates :#{number_field}, :uniqueness => { #{uniqueness_scope_string} :allow_nil => true}, :numericality => { :only_integer => true, :greater_than_or_equal_to => 0, :allow_nil => true } after_destroy :mark_as_destroyed attr_accessor :destroyed attr_accessor :changed_sets attr_protected :#{number_field} scope :ordered, order('#{number_field} ASC') scope :reverse_ordered, order('#{number_field} DESC') def self.sort!(sorted_ids) return if sorted_ids.blank? items = [] ActiveRecord::Base.transaction do items = find_in_specified_order(sorted_ids) items.each do |item| item.send('#{number_field}=', nil) item.save! end items.each_with_index do |item, ii| item.send('#{number_field}=', ii) item.save! end end items end def table_class #{table_class} end def number_field '#{number_field}' end EOV if !configuration[:container].nil? class_eval <<-EOV include ActsAsNumberable::ContainerInstanceMethods # When we had nested acts_as_numberables, there were cases where the # objects were having their numbers changed (as their peers were being # removed from the container), but then when it came time to delete those # objects they still had their old number. So just reload before # destroy. before_destroy(prepend: true) {self.reload} after_destroy :remove_from_container! def container_column '#{container_column}' end def container '#{configuration[:container]}' end def table_class #{table_class} end EOV end end