module Searchkick class Relation NO_DEFAULT_VALUE = Object.new # note: modifying body directly is not supported # and has no impact on query after being executed # TODO freeze body object? delegate :body, :params, to: :query delegate_missing_to :private_execute def initialize(model, term = "*", **options) @model = model @term = term @options = options # generate query to validate options query end # same as Active Record def inspect entries = results.first(11).map!(&:inspect) entries[10] = "..." if entries.size == 11 "#<#{self.class.name} [#{entries.join(', ')}]>" end def execute Searchkick.warn("The execute method is no longer needed") private_execute self end # experimental def limit(value) clone.limit!(value) end # experimental def limit!(value) check_loaded @options[:limit] = value self end # experimental def offset(value = NO_DEFAULT_VALUE) # TODO remove in Searchkick 6 if value == NO_DEFAULT_VALUE private_execute.offset else clone.offset!(value) end end # experimental def offset!(value) check_loaded @options[:offset] = value self end # experimental def page(value) clone.page!(value) end # experimental def page!(value) check_loaded @options[:page] = value self end # experimental def per_page(value = NO_DEFAULT_VALUE) # TODO remove in Searchkick 6 if value == NO_DEFAULT_VALUE private_execute.per_page else clone.per_page!(value) end end # experimental def per_page!(value) check_loaded @options[:per_page] = value self end # experimental def where(value = NO_DEFAULT_VALUE) if value == NO_DEFAULT_VALUE Where.new(self) else clone.where!(value) end end # experimental def where!(value) check_loaded if @options[:where] @options[:where] = {_and: [@options[:where], ensure_permitted(value)]} else @options[:where] = ensure_permitted(value) end self end # experimental def rewhere(value) clone.rewhere!(value) end # experimental def rewhere!(value) check_loaded @options[:where] = ensure_permitted(value) self end # experimental def order(*values) clone.order!(*values) end # experimental def order!(*values) values = values.first if values.size == 1 && values.first.is_a?(Array) check_loaded (@options[:order] ||= []).concat(values) self end # experimental def reorder(*values) clone.reorder!(*values) end # experimental def reorder!(*values) check_loaded @options[:order] = values self end # experimental def select(*values, &block) if block_given? private_execute.select(*values, &block) else clone.select!(*values) end end # experimental def select!(*values) check_loaded (@options[:select] ||= []).concat(values) self end # experimental def reselect(*values) clone.reselect!(*values) end # experimental def reselect!(*values) check_loaded @options[:select] = values self end # experimental def includes(*values) clone.includes!(*values) end # experimental def includes!(*values) check_loaded (@options[:includes] ||= []).concat(values) self end # experimental def only(*keys) Relation.new(@model, @term, **@options.slice(*keys)) end # experimental def except(*keys) Relation.new(@model, @term, **@options.except(*keys)) end def loaded? !@execute.nil? end private def private_execute @execute ||= query.execute end def query @query ||= Query.new(@model, @term, **@options) end def check_loaded raise Error, "Relation loaded" if loaded? # reset query since options will change @query = nil end # provides *very* basic protection from unfiltered parameters # this is not meant to be comprehensive and may be expanded in the future def ensure_permitted(obj) obj.to_h end end end