class RankedModel::Ranker::Mapper
Attributes
instance[RW]
ranker[RW]
Public Class Methods
new(ranker, instance)
click to toggle source
# File lib/ranked-model/ranker.rb, line 26 def initialize ranker, instance self.ranker = ranker self.instance = instance validate_ranker_for_instance! end
Public Instance Methods
current_at_position(_pos)
click to toggle source
# File lib/ranked-model/ranker.rb, line 90 def current_at_position _pos if (ordered_instance = finder.offset(_pos).first) RankedModel::Ranker::Mapper.new ranker, ordered_instance end end
handle_ranking()
click to toggle source
# File lib/ranked-model/ranker.rb, line 52 def handle_ranking case ranker.unless when Proc return if ranker.unless.call(instance) when Symbol return if instance.send(ranker.unless) end update_index_from_position assure_unique_position end
has_rank?()
click to toggle source
# File lib/ranked-model/ranker.rb, line 96 def has_rank? !rank.nil? end
position()
click to toggle source
# File lib/ranked-model/ranker.rb, line 76 def position instance.send "#{ranker.name}_position" end
rank()
click to toggle source
# File lib/ranked-model/ranker.rb, line 86 def rank instance.send "#{ranker.column}" end
relative_rank()
click to toggle source
# File lib/ranked-model/ranker.rb, line 80 def relative_rank escaped_column = instance_class.connection.quote_column_name ranker.column finder.where("#{escaped_column} < #{rank}").count(:all) end
reset_ranks!()
click to toggle source
# File lib/ranked-model/ranker.rb, line 72 def reset_ranks! finder.update_all(ranker.column => nil) end
update_rank!(value)
click to toggle source
# File lib/ranked-model/ranker.rb, line 64 def update_rank! value # Bypass callbacks # instance_class. where(instance_class.primary_key => instance.id). update_all(ranker.column => value) end
validate_ranker_for_instance!()
click to toggle source
# File lib/ranked-model/ranker.rb, line 33 def validate_ranker_for_instance! if ranker.scope && !instance_class.respond_to?(ranker.scope) raise RankedModel::InvalidScope, %Q{No scope called "#{ranker.scope}" found in model} end if ranker.with_same if (case ranker.with_same when Symbol !instance.respond_to?(ranker.with_same) when Array array_element = ranker.with_same.detect {|attr| !instance.respond_to?(attr) } else false end) raise RankedModel::InvalidField, %Q{No field called "#{array_element || ranker.with_same}" found in model} end end end
Private Instance Methods
assure_unique_position()
click to toggle source
# File lib/ranked-model/ranker.rb, line 182 def assure_unique_position if ( new_record? || rank_changed? ) if (rank > RankedModel::MAX_RANK_VALUE) || rank_taken? rearrange_ranks end end end
current_first()
click to toggle source
# File lib/ranked-model/ranker.rb, line 287 def current_first @current_first ||= begin if (ordered_instance = finder.first) RankedModel::Ranker::Mapper.new ranker, ordered_instance end end end
current_last()
click to toggle source
# File lib/ranked-model/ranker.rb, line 295 def current_last @current_last ||= begin if (ordered_instance = finder. reverse. first) RankedModel::Ranker::Mapper.new ranker, ordered_instance end end end
current_order()
click to toggle source
# File lib/ranked-model/ranker.rb, line 279 def current_order @current_order ||= begin finder.unscope(where: instance_class.primary_key.to_sym).collect { |ordered_instance| RankedModel::Ranker::Mapper.new ranker, ordered_instance } end end
find_next_two(_rank)
click to toggle source
# File lib/ranked-model/ranker.rb, line 330 def find_next_two _rank ordered_instances = finder.where(instance_class.arel_table[ranker.column].gt _rank).limit(2) if ordered_instances[1] { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ), :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) } elsif ordered_instances[0] { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) } else {} end end
find_previous_two(_rank)
click to toggle source
# File lib/ranked-model/ranker.rb, line 342 def find_previous_two _rank ordered_instances = finder(:desc).where(instance_class.arel_table[ranker.column].lt _rank).limit(2) if ordered_instances[1] { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ), :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) } elsif ordered_instances[0] { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) } else {} end end
finder(order = :asc)
click to toggle source
# File lib/ranked-model/ranker.rb, line 249 def finder(order = :asc) @finder ||= {} @finder[order] ||= begin _finder = instance_class columns = [instance_class.primary_key.to_sym, ranker.column] if ranker.scope _finder = _finder.send ranker.scope end case ranker.with_same when Symbol columns << ranker.with_same _finder = _finder.where \ ranker.with_same => instance.attributes[ranker.with_same.to_s] when Array ranker.with_same.each do |column| columns << column _finder = _finder.where column => instance.attributes[column.to_s] end end unless new_record? _finder = _finder.where.not instance_class.primary_key.to_sym => instance.id end _finder.reorder(ranker.column.to_sym => order).select(columns) end end
instance_class()
click to toggle source
# File lib/ranked-model/ranker.rb, line 106 def instance_class ranker.class_name.nil? ? instance.class : ranker.class_name.constantize end
neighbors_at_position(_pos)
click to toggle source
# File lib/ranked-model/ranker.rb, line 309 def neighbors_at_position _pos if _pos > 0 if (ordered_instances = finder.offset(_pos-1).limit(2).to_a) if ordered_instances[1] { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ), :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) } elsif ordered_instances[0] { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) } else { :lower => current_last } end end else if (ordered_instance = finder.first) { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instance ) } else {} end end end
new_record?()
click to toggle source
# File lib/ranked-model/ranker.rb, line 123 def new_record? instance.new_record? end
position_at(value)
click to toggle source
# File lib/ranked-model/ranker.rb, line 110 def position_at value instance.send "#{ranker.name}_position=", value update_index_from_position end
rank_at(value)
click to toggle source
# File lib/ranked-model/ranker.rb, line 115 def rank_at value instance.send "#{ranker.column}=", value end
rank_at_average(min, max)
click to toggle source
# File lib/ranked-model/ranker.rb, line 173 def rank_at_average(min, max) if (max - min).between?(-1, 1) # No room at the inn... rebalance_ranks position_at position else rank_at( ( ( max - min ).to_f / 2 ).ceil + min ) end end
rank_changed?()
click to toggle source
# File lib/ranked-model/ranker.rb, line 119 def rank_changed? instance.send "#{ranker.column}_changed?" end
rank_taken?()
click to toggle source
# File lib/ranked-model/ranker.rb, line 305 def rank_taken? finder.except(:order).where(ranker.column => rank).exists? end
rearrange_ranks()
click to toggle source
# File lib/ranked-model/ranker.rb, line 190 def rearrange_ranks _scope = finder escaped_column = instance_class.connection.quote_column_name ranker.column # If there is room at the bottom of the list and we're added to the very top of the list... if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE # ...then move everyone else down 1 to make room for us at the end _scope. where( instance_class.arel_table[ranker.column].lteq(rank) ). update_all( "#{escaped_column} = #{escaped_column} - 1" ) # If there is room at the top of the list and we're added below the last value in the list... elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank # ...then move everyone else at or above our desired rank up 1 to make room for us _scope. where( instance_class.arel_table[ranker.column].gteq(rank) ). update_all( "#{escaped_column} = #{escaped_column} + 1" ) # If there is room at the bottom of the list and we're added above the lowest value in the list... elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank # ...then move everyone else below us down 1 and change our rank down 1 to avoid the collission _scope. where( instance_class.arel_table[ranker.column].lt(rank) ). update_all( "#{escaped_column} = #{escaped_column} - 1" ) rank_at( rank - 1 ) else rebalance_ranks end end
rebalance_ranks()
click to toggle source
# File lib/ranked-model/ranker.rb, line 217 def rebalance_ranks ActiveRecord::Base.transaction do if rank && instance.persisted? origin = current_order.index { |item| item.instance.id == instance.id } if origin destination = current_order.index { |item| rank <= item.rank } destination -= 1 if origin < destination current_order.insert destination, current_order.delete_at(origin) end end gaps = current_order.size + 1 range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f gap_size = (range / gaps).ceil reset_ranks! current_order.each.with_index(1) do |item, position| new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE if item.instance.id == instance.id rank_at new_rank else item.update_rank! new_rank end end reset_cache end end
reset_cache()
click to toggle source
# File lib/ranked-model/ranker.rb, line 102 def reset_cache @finder, @current_order, @current_first, @current_last = nil end
update_index_from_position()
click to toggle source
# File lib/ranked-model/ranker.rb, line 127 def update_index_from_position case position when :first, 'first' if current_first && current_first.rank rank_at_average current_first.rank, RankedModel::MIN_RANK_VALUE else position_at :middle end when :last, 'last' if current_last && current_last.rank rank_at_average current_last.rank, RankedModel::MAX_RANK_VALUE else position_at :middle end when :middle, 'middle' rank_at_average RankedModel::MIN_RANK_VALUE, RankedModel::MAX_RANK_VALUE when :down, 'down' neighbors = find_next_two(rank) if neighbors[:lower] min = neighbors[:lower].rank max = neighbors[:upper] ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE rank_at_average min, max end when :up, 'up' neighbors = find_previous_two(rank) if neighbors[:upper] max = neighbors[:upper].rank min = neighbors[:lower] ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE rank_at_average min, max end when String position_at position.to_i when 0 position_at :first when Integer neighbors = neighbors_at_position(position) min = ((neighbors[:lower] && neighbors[:lower].has_rank?) ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE) max = ((neighbors[:upper] && neighbors[:upper].has_rank?) ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE) rank_at_average min, max when NilClass if !rank position_at :last end end end