class FrozenRecord::Scope
Constants
- ARRAY_INTERSECTION
- 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 137 def ==(other) self.class === other && comparable_attributes == other.comparable_attributes end
average(attribute)
click to toggle source
# File lib/frozen_record/scope.rb, line 89 def average(attribute) pluck(attribute).sum.to_f / count end
exists?()
click to toggle source
# File lib/frozen_record/scope.rb, line 101 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 scope = self if @limit || @offset scope = limit(nil).offset(nil) end scope.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 50 def find_by(criterias) where(criterias).first end
find_by!(criterias)
click to toggle source
# File lib/frozen_record/scope.rb, line 54 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) find_by(@klass.primary_key => id) end
first!()
click to toggle source
# File lib/frozen_record/scope.rb, line 58 def first! first or raise RecordNotFound, "No record matched" end
hash()
click to toggle source
# File lib/frozen_record/scope.rb, line 133 def hash comparable_attributes.hash end
ids()
click to toggle source
# File lib/frozen_record/scope.rb, line 81 def ids pluck(primary_key) end
last!()
click to toggle source
# File lib/frozen_record/scope.rb, line 62 def last! last or raise RecordNotFound, "No record matched" end
limit(amount)
click to toggle source
# File lib/frozen_record/scope.rb, line 121 def limit(amount) spawn.limit!(amount) end
maximum(attribute)
click to toggle source
# File lib/frozen_record/scope.rb, line 97 def maximum(attribute) pluck(attribute).max end
minimum(attribute)
click to toggle source
# File lib/frozen_record/scope.rb, line 93 def minimum(attribute) pluck(attribute).min end
offset(amount)
click to toggle source
# File lib/frozen_record/scope.rb, line 125 def offset(amount) spawn.offset!(amount) end
order(*ordering)
click to toggle source
# File lib/frozen_record/scope.rb, line 117 def order(*ordering) spawn.order!(*ordering) end
pluck(*attributes)
click to toggle source
# File lib/frozen_record/scope.rb, line 70 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 129 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 85 def sum(attribute) pluck(attribute).sum end
to_a()
click to toggle source
# File lib/frozen_record/scope.rb, line 66 def to_a query_results end
where(criterias = :chain)
click to toggle source
# File lib/frozen_record/scope.rb, line 105 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 113 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 257 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 166 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 144 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 236 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
limit!(amount)
click to toggle source
# File lib/frozen_record/scope.rb, line 278 def limit!(amount) @limit = amount self end
matching_records()
click to toggle source
# File lib/frozen_record/scope.rb, line 177 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 246 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) scoping { @klass.public_send(method_name, *args, &block) } else super end end
offset!(amount)
click to toggle source
# File lib/frozen_record/scope.rb, line 283 def offset!(amount) @offset = amount self end
order!(*ordering)
click to toggle source
# File lib/frozen_record/scope.rb, line 271 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 173 def query_results slice_records(matching_records) end
scoping() { || ... }
click to toggle source
# File lib/frozen_record/scope.rb, line 155 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 183 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? usable_indexes = indexed_where_values.map { |(attribute, value)| [attribute, value, indices[attribute].query(value)] } usable_indexes.sort_by! { |r| r[2].size } records = usable_indexes.shift.last # If the index is 5 times bigger that the current set of records it's not worth doing an array intersection. # The value is somewhat arbitrary and could be adjusted. useless_indexes, usable_indexes = usable_indexes.partition { |_, _, indexed_records| indexed_records.size > records.size * 5 } unindexed_where_values += useless_indexes.map { |a| a.first(2) } unless usable_indexes.empty? if ARRAY_INTERSECTION records = records.intersection(*usable_indexes.map(&:last)) else usable_indexes.each do |_, _, indexed_records| records &= indexed_records end end end end if FrozenRecord.enforce_max_records_scan && @klass.max_records_scan && records.size > @klass.max_records_scan raise SlowQuery, "Scanning #{records.size} records is too slow, the allowed maximum is #{@klass.max_records_scan}. Try to find a better index or consider an alternative storage" 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 228 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 220 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 162 def spawn clone.clear_cache! end
where!(criterias)
click to toggle source
# File lib/frozen_record/scope.rb, line 261 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 266 def where_not!(criterias) @where_not_values += criterias.map { |k, v| [k.to_s, Matcher.for(v)] } self end