class FrozenRecord::Scope

Constants

DISALLOWED_ARRAY_METHODS

Public Class Methods

new(klass) click to toggle source
# File lib/frozen_record/scope.rb, line 27
def initialize(klass)
  @klass = klass
  @where_values = []
  @where_not_values = []
  @order_values = []
  @limit = nil
  @offset = nil
end

Public Instance Methods

==(other) click to toggle source
# File lib/frozen_record/scope.rb, line 132
def ==(other)
  self.class === other &&
  comparable_attributes == other.comparable_attributes
end
average(attribute) click to toggle source
# File lib/frozen_record/scope.rb, line 84
def average(attribute)
  pluck(attribute).sum.to_f / count
end
exists?() click to toggle source
# File lib/frozen_record/scope.rb, line 96
def exists?
  !empty?
end
find(id) click to toggle source
# File lib/frozen_record/scope.rb, line 40
def find(id)
  raise RecordNotFound, "Can't lookup record without ID" unless id
  find_by_id(id) or raise RecordNotFound, "Couldn't find a record with ID = #{id.inspect}"
end
find_by(criterias) click to toggle source
# File lib/frozen_record/scope.rb, line 45
def find_by(criterias)
  where(criterias).first
end
find_by!(criterias) click to toggle source
# File lib/frozen_record/scope.rb, line 49
def find_by!(criterias)
  where(criterias).first!
end
find_by_id(id) click to toggle source
# File lib/frozen_record/scope.rb, line 36
def find_by_id(id)
  matching_records.find { |r| r.id == id }
end
first!() click to toggle source
# File lib/frozen_record/scope.rb, line 53
def first!
  first or raise RecordNotFound, "No record matched"
end
hash() click to toggle source
# File lib/frozen_record/scope.rb, line 128
def hash
  comparable_attributes.hash
end
ids() click to toggle source
# File lib/frozen_record/scope.rb, line 76
def ids
  pluck(primary_key)
end
last!() click to toggle source
# File lib/frozen_record/scope.rb, line 57
def last!
  last or raise RecordNotFound, "No record matched"
end
limit(amount) click to toggle source
# File lib/frozen_record/scope.rb, line 116
def limit(amount)
  spawn.limit!(amount)
end
maximum(attribute) click to toggle source
# File lib/frozen_record/scope.rb, line 92
def maximum(attribute)
  pluck(attribute).max
end
minimum(attribute) click to toggle source
# File lib/frozen_record/scope.rb, line 88
def minimum(attribute)
  pluck(attribute).min
end
offset(amount) click to toggle source
# File lib/frozen_record/scope.rb, line 120
def offset(amount)
  spawn.offset!(amount)
end
order(*ordering) click to toggle source
# File lib/frozen_record/scope.rb, line 112
def order(*ordering)
  spawn.order!(*ordering)
end
pluck(*attributes) click to toggle source
# File lib/frozen_record/scope.rb, line 65
def pluck(*attributes)
  case attributes.length
  when 1
    to_a.map(&attributes.first.to_sym)
  when 0
    raise NotImplementedError, '`.pluck` without arguments is not supported yet'
  else
    to_a.map { |r| attributes.map { |a| r[a] }}
  end
end
respond_to_missing?(method_name, *) click to toggle source
Calls superclass method
# File lib/frozen_record/scope.rb, line 124
def respond_to_missing?(method_name, *)
  array_delegable?(method_name) || @klass.respond_to?(method_name) || super
end
sum(attribute) click to toggle source
# File lib/frozen_record/scope.rb, line 80
def sum(attribute)
  pluck(attribute).sum
end
to_a() click to toggle source
# File lib/frozen_record/scope.rb, line 61
def to_a
  query_results
end
where(criterias = :chain) click to toggle source
# File lib/frozen_record/scope.rb, line 100
def where(criterias = :chain)
  if criterias == :chain
    WhereChain.new(self)
  else
    spawn.where!(criterias)
  end
end
where_not(criterias) click to toggle source
# File lib/frozen_record/scope.rb, line 108
def where_not(criterias)
  spawn.where_not!(criterias)
end

Protected Instance Methods

array_delegable?(method) click to toggle source
# File lib/frozen_record/scope.rb, line 237
def array_delegable?(method)
  Array.method_defined?(method) && !DISALLOWED_ARRAY_METHODS.include?(method)
end
clear_cache!() click to toggle source
# File lib/frozen_record/scope.rb, line 161
def clear_cache!
  @comparable_attributes = nil
  @results = nil
  @matches = nil
  self
end
comparable_attributes() click to toggle source
# File lib/frozen_record/scope.rb, line 139
def comparable_attributes
  @comparable_attributes ||= {
    klass: @klass,
    where_values: @where_values.uniq.sort,
    where_not_values: @where_not_values.uniq.sort,
    order_values: @order_values.uniq,
    limit: @limit,
    offset: @offset,
  }
end
compare(record_a, record_b) click to toggle source
# File lib/frozen_record/scope.rb, line 212
def compare(record_a, record_b)
  @order_values.each do |attr, order|
    a_value, b_value = record_a.send(attr), record_b.send(attr)
    cmp = a_value <=> b_value
    next if cmp == 0
    return order == :asc ? cmp : -cmp
  end
  0
end
delegate_to_class(*args, &block) click to toggle source
# File lib/frozen_record/scope.rb, line 233
def delegate_to_class(*args, &block)
  scoping { @klass.public_send(*args, &block) }
end
limit!(amount) click to toggle source
# File lib/frozen_record/scope.rb, line 258
def limit!(amount)
  @limit = amount
  self
end
matching_records() click to toggle source
# File lib/frozen_record/scope.rb, line 172
def matching_records
  sort_records(select_records(@klass.load_records))
end
method_missing(method_name, *args, &block) click to toggle source
Calls superclass method
# File lib/frozen_record/scope.rb, line 222
def method_missing(method_name, *args, &block)
  if array_delegable?(method_name)
    to_a.public_send(method_name, *args, &block)
  elsif @klass.respond_to?(method_name)
    delegate_to_class(method_name, *args, &block)
  else
    super
  end
end
offset!(amount) click to toggle source
# File lib/frozen_record/scope.rb, line 263
def offset!(amount)
  @offset = amount
  self
end
order!(*ordering) click to toggle source
# File lib/frozen_record/scope.rb, line 251
def order!(*ordering)
  @order_values += ordering.map do |order|
    order.respond_to?(:to_a) ? order.to_a : [[order, :asc]]
  end.flatten(1)
  self
end
query_results() click to toggle source
# File lib/frozen_record/scope.rb, line 168
def query_results
  slice_records(matching_records)
end
scoping() { || ... } click to toggle source
# File lib/frozen_record/scope.rb, line 150
def scoping
  previous, @klass.current_scope = @klass.current_scope, self
  yield
ensure
  @klass.current_scope = previous
end
select_records(records) click to toggle source
# File lib/frozen_record/scope.rb, line 176
def select_records(records)
  return records if @where_values.empty? && @where_not_values.empty?

  indices = @klass.index_definitions
  indexed_where_values, unindexed_where_values = @where_values.partition { |criteria| indices.key?(criteria.first) }

  unless indexed_where_values.empty?
    attribute, value = indexed_where_values.shift
    records = indices[attribute].query(value)
    indexed_where_values.each do |(attribute, value)|
      records &= indices[attribute].query(value)
    end
  end

  records.select do |record|
    unindexed_where_values.all? { |attr, matcher| matcher.match?(record[attr]) } &&
    !@where_not_values.any? { |attr, matcher| matcher.match?(record[attr]) }
  end
end
slice_records(records) click to toggle source
# File lib/frozen_record/scope.rb, line 204
def slice_records(records)
  return records unless @limit || @offset

  first = @offset || 0
  last = first + (@limit || records.length)
  records[first...last] || []
end
sort_records(records) click to toggle source
# File lib/frozen_record/scope.rb, line 196
def sort_records(records)
  return records if @order_values.empty?

  records.sort do |record_a, record_b|
    compare(record_a, record_b)
  end
end
spawn() click to toggle source
# File lib/frozen_record/scope.rb, line 157
def spawn
  clone.clear_cache!
end
where!(criterias) click to toggle source
# File lib/frozen_record/scope.rb, line 241
def where!(criterias)
  @where_values += criterias.map { |k, v| [k.to_s, Matcher.for(v)] }
  self
end
where_not!(criterias) click to toggle source
# File lib/frozen_record/scope.rb, line 246
def where_not!(criterias)
  @where_not_values += criterias.map { |k, v| [k.to_s, Matcher.for(v)] }
  self
end