class Mongoid::Orderable::Handlers::Base

Attributes

doc[R]

Public Class Methods

new(doc) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 9
def initialize(doc)
  @doc = doc
end

Protected Instance Methods

allowed?(field) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 133
def allowed?(field)
  cond_if = orderable_if(field)
  cond_unless = orderable_unless(field)

  (cond_if.nil? || resolve_condition(cond_if)) &&
    (cond_unless.nil? || !resolve_condition(cond_unless))
end
any_field_changed?() click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 35
def any_field_changed?
  orderable_keys.any? {|field| changed?(field) }
end
apply_all_positions() click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 39
def apply_all_positions
  orderable_keys.map {|field| apply_one_position(field, move_all[field]) }
end
apply_one_position(field, target_position) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 43
def apply_one_position(field, target_position)
  return unless allowed?(field) && changed?(field)

  set_lock(field) if use_transactions

  f = orderable_field(field)
  scope = orderable_scope(field)
  scope_changed = orderable_scope_changed?(field)

  # Set scope-level lock if scope changed
  if use_transactions && persisted? && scope_changed
    set_lock(field, true)
    scope_changed = orderable_scope_changed?(field)
  end

  # Get the current position as exists in the database
  current = if !persisted? || scope_changed
              nil
            elsif persisted? && !embedded?
              scope.where(_id: _id).pluck(f).first
            else
              orderable_position(field)
            end

  # If scope changed, remove the position from the old scope
  if persisted? && !embedded? && scope_changed
    existing_doc = doc.class.unscoped.find(_id)
    self.class.new(existing_doc).send(:remove_one_position, field)
  end

  # Return if there is no instruction to change the position
  in_list = persisted? && current
  return if in_list && !target_position

  # Use $inc operator to shift the position of the other documents
  target = resolve_target_position(field, target_position, in_list)
  if !in_list
    scope.gte(f => target).inc(f => 1)
  elsif target < current
    scope.where(f => { '$gte' => target, '$lt' => current }).inc(f => 1)
  elsif target > current
    scope.where(f => { '$gt' => current, '$lte' => target }).inc(f => -1)
  end

  # If persisted, update the field in the database atomically
  doc.set({ f => target }.merge(changed_scope_hash(field))) if use_transactions && persisted?
  doc.send("orderable_#{field}_position=", target)
end
changeable_keys(field) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 157
def changeable_keys(field)
  [orderable_field(field)] | scope_keys(field)
end
changed?(field) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 152
def changed?(field)
  return true if new_record? || !doc.send(orderable_field(field)) || move_all[field]
  changeable_keys(field).any? {|f| doc.send("#{f}_changed?") }
end
changed_scope_hash(field) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 167
def changed_scope_hash(field)
  scope_keys(field).each_with_object({}) do |f, hash|
    hash[f] = doc.send(f) if doc.send("#{f}_changed?")
  end
end
lock_scope(field, generic = false) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 181
def lock_scope(field, generic = false)
  sel = orderable_scope(field).selector
  scope = ([collection_name] + (generic ? [field] : sel.to_a.flatten)).map(&:to_s).join('|')
  { scope: scope }
end
move_all() click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 106
def move_all
  doc.send(:move_all)
end
remove_all_positions() click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 92
def remove_all_positions
  orderable_keys.each do |field|
    remove_one_position(field)
  end
end
remove_one_position(field) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 98
def remove_one_position(field)
  return unless allowed?(field)
  f = orderable_field(field)
  current = orderable_position(field)
  set_lock(field) if use_transactions
  orderable_scope(field).gt(f => current).inc(f => -1)
end
resolve_condition(condition) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 141
def resolve_condition(condition)
  case condition
  when Proc
    condition.arity.zero? ? doc.instance_exec(&condition) : condition.call(doc)
  when Symbol
    doc.send(condition)
  else
    condition || false
  end
end
resolve_target_position(field, target_position, in_list) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 110
def resolve_target_position(field, target_position, in_list)
  target_position ||= 'bottom'

  unless target_position.is_a? Numeric
    target_position = case target_position.to_s
                      when 'top' then (min ||= orderable_top(field))
                      when 'bottom' then (max ||= orderable_bottom(field, in_list))
                      when 'higher' then orderable_position(field).pred
                      when 'lower' then orderable_position(field).next
                      when /\A\d+\Z/ then target_position.to_i
                      else raise Mongoid::Orderable::Errors::InvalidTargetPosition.new(target_position)
                      end
  end

  if target_position <= (min ||= orderable_top(field))
    target_position = min
  elsif target_position > (max ||= orderable_bottom(field, in_list))
    target_position = max
  end

  target_position
end
scope_keys(field) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 161
def scope_keys(field)
  orderable_scope(field).selector.keys.map do |f|
    doc.fields[f]&.options&.[](:as) || f
  end
end
set_lock(field, generic = false) click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 173
def set_lock(field, generic = false)
  return unless use_transactions
  model_name = doc.class.orderable_configs[field][:lock_collection].to_s.singularize.classify
  model = Mongoid::Orderable::Models.const_get(model_name)
  attrs = lock_scope(field, generic)
  model.where(attrs).find_one_and_update(attrs.merge(updated_at: Time.now), { upsert: true })
end
use_transactions() click to toggle source
# File lib/mongoid/orderable/handlers/base.rb, line 31
def use_transactions
  false
end