class Magiq::Query
Attributes
model[R]
params[R]
raw_params[R]
scope[R]
solo_param[R]
Public Class Methods
apply(*params, &block)
click to toggle source
# File lib/magiq/query.rb, line 61 def self.apply(*params, &block) opts = params.last.is_a?(Hash) ? params.pop : {} builder.add_listener(:apply, params, opts, &block) end
builder()
click to toggle source
# File lib/magiq/query.rb, line 8 def self.builder @builder ||= Builder.new end
by(column, opts = {}, &block)
click to toggle source
# File lib/magiq/query.rb, line 154 def self.by(column, opts = {}, &block) param(column, { solo: true, type: :id, alias: Magiq::Utils.pluralize(column.to_s).to_sym, array: :always }.merge(opts)) if block_given? apply(column, &block) else apply(column) do |ids| if ids.empty? nil else tbl = model.table_name sql = ids.each_with_index.map { |raw_id, i| id = raw_id.is_a?(Numeric) ? raw_id : "'#{raw_id}'" "WHEN #{id} THEN #{i}" }.join(' ') scope.where(column => ids).order("CASE #{tbl}.#{column} #{sql} END") end end end end
check(*params, &block)
click to toggle source
# File lib/magiq/query.rb, line 66 def self.check(*params, &block) opts = params.last.is_a?(Hash) ? params.pop : {} builder.add_listener(:check, params, opts, &block) end
def_param(key, opts = {})
click to toggle source
# File lib/magiq/query.rb, line 57 def self.def_param(key, opts = {}) builder.add_param(key, opts) end
exclusive(*params)
click to toggle source
# File lib/magiq/query.rb, line 75 def self.exclusive(*params) builder.add_constraint(:exclusive, params) end
has_pagination(opts = {})
click to toggle source
# File lib/magiq/query.rb, line 79 def self.has_pagination(opts = {}) max_page_size = opts[:max_page_size] || Magiq[:max_page_size] min_page_size = opts[:min_page_size] || Magiq[:min_page_size] default_page_size = opts[:default_page_size] || Magiq[:default_page_size] param :page, type: :whole param :page_size, type: :whole check :page, :page_size, any: true do |page, page_size| if page && page < 1 bad! "The value provided for `page` must be 1 or greater, but " \ "#{page.inspect} was provided." end if page_size && page_size > max_page_size bad! "The maximum permitted value for `page_size` is " \ "#{max_page_size}, but #{page_size.inspect} was provided." elsif page_size && page_size < min_page_size bad! "The minimum permitted value for `page_size` is " \ "#{min_page_size}, but #{page_size.inspect} was provided." end end apply do next if solo? page = params[:page] page_size = params[:page_size] || default_page_size new_scope = scope.page(page) page_size ? new_scope.per(page_size) : new_scope end end
model(&block)
click to toggle source
# File lib/magiq/query.rb, line 12 def self.model(&block) @model_proc = block end
model_proc()
click to toggle source
# File lib/magiq/query.rb, line 16 def self.model_proc @model_proc end
mutual(params, opts = {})
click to toggle source
# File lib/magiq/query.rb, line 71 def self.mutual(params, opts = {}) builder.add_constraint(:mutual, params, opts) end
new(params)
click to toggle source
# File lib/magiq/query.rb, line 271 def initialize(params) @raw_params = params @listeners = {} end
param(*args, &block)
click to toggle source
# File lib/magiq/query.rb, line 53 def self.param(*args, &block) params(*args, &block) end
params(*keys, &block)
click to toggle source
# File lib/magiq/query.rb, line 36 def self.params(*keys, &block) opts = keys.last.is_a?(Hash) ? keys.pop : {} if block_given? types = opts.delete(:type) keys.each do |k| type = types.is_a?(Hash) ? types[k] : types def_param(k, opts.merge(type: type)) end apply(*keys, &block) else def_param(keys[0], opts) end end
range(field, opts = {})
click to toggle source
# File lib/magiq/query.rb, line 182 def self.range(field, opts = {}) lt_param = :"#{field}_lt" lte_param = :"#{field}_lte" gt_param = :"#{field}_gt" gte_param = :"#{field}_gte" eq_param = field.to_sym param(lt_param, type: opts[:type] || :whole) param(lte_param, type: opts[:type] || :whole) param(gt_param, type: opts[:type] || :whole) param(gte_param, type: opts[:type] || :whole) param(eq_param, type: opts[:type] || :whole) exclusive(gt_param, gte_param) exclusive(lt_param, lte_param) mutual(eq_param, exclusive: [lt_param, lte_param, gt_param, gte_param]) check do next if Magiq::Utils.present?(params[eq_param]) lt_par = if (lt_val = params[lte_param]) lte_param elsif (lt_val = params[lt_param]) lt_param end gt_par = if (gt_val = params[gte_param]) gte_param elsif (gt_val = params[gt_param]) gt_param end next unless lt_par && gt_par if lt_val > gt_val bad! "A value of #{lt_val} was provided for `#{lt_par}` but a value " \ "of #{gt_val} was provided for `#{gt_par}`. The permitted value of " \ "`#{lt_par}` must be less than the permitted value provided for " \ "`#{gt_par}`." end if lt_val == gt_val bad! "The same value of #{gt_val} was provided for both `#{lt_par}` " \ "and #{gt_par}. The permitted value of `#{lt_par}` must be " \ "less than the permitted value provided for `#{gt_par}`." end end apply(eq_param) do |val| if is_unqualified?(field) scope.where("#{field} = ?", val) else scope.where(model.arel_table[field].eq(val)) end end apply(gt_param) do |val| if is_unqualified?(field) scope.where("#{field} > ?", val) else scope.where(model.arel_table[field].gt(val)) end end apply(lt_param) do |val| if is_unqualified?(field) scope.where("#{field} < ?", val) else scope.where(model.arel_table[field].lt(val)) end end apply(gte_param) do |val| if is_unqualified?(field) scope.where("#{field} >= ?", val) else scope.where(model.arel_table[field].gte(val)) end end apply(lte_param) do |val| if is_unqualified? scope.where("#{field} <= ?", val) else scope.where(model.arel_table[field].lte(val)) end end end
scope(&block)
click to toggle source
# File lib/magiq/query.rb, line 20 def self.scope(&block) @scope_proc = block end
scope_proc()
click to toggle source
# File lib/magiq/query.rb, line 24 def self.scope_proc @scope_proc || -> { model.unscoped } end
sort(fields)
click to toggle source
# File lib/magiq/query.rb, line 122 def self.sort(fields) param :sort, type: :string, array: :always, limit: fields.size apply :sort do |raw_vals| vals = raw_vals.is_a?(Array) ? raw_vals : raw_vals.split(',') vals.reduce(scope) do |scope, val| col = if val.start_with?('-') direction = :desc val.sub('-', '').to_sym elsif val.start_with?('+') direction = :asc val.sub('+', '').to_sym else bad! "A sort order was not specified for the sort field value " \ "'#{val}', it must be prefixed with either a plus (+#{val}) for " \ "ascending order, or a minus (-#{val}) for decending order" end unless fields.include?(col) bad! "A provided sorting field: #{col}, is unknown or unsortable. The " \ "permitted values are: #{fields.join(', ')}" end if is_unqualified?(col) scope.order("\"#{col}\" #{direction}") else scope.order(col => direction) end end end end
toggle(*fields)
click to toggle source
# File lib/magiq/query.rb, line 113 def self.toggle(*fields) fields.each do |field| param(field, type: :bool) apply(field) do |val| scope.where(field => val) end end end
unqualified(fields)
click to toggle source
# File lib/magiq/query.rb, line 32 def self.unqualified(fields) unqualified_columns.concat(fields) end
unqualified_columns()
click to toggle source
# File lib/magiq/query.rb, line 28 def self.unqualified_columns @unqualified_columns ||= [] end
Public Instance Methods
apply!()
click to toggle source
# File lib/magiq/query.rb, line 372 def apply! each_listener_for :apply do |seek_params, opts, op| next update_scope! instance_exec(&op) if seek_params.empty? next if !opts[:any] && !seek_params.all? { |p| params.key?(p) } vals = seek_params.map { |p| params[p] } update_scope! instance_exec(*vals, &op) end end
bad!(message)
click to toggle source
# File lib/magiq/query.rb, line 386 def bad!(message) raise BadParamError, message end
builder()
click to toggle source
# File lib/magiq/query.rb, line 276 def builder self.class.builder end
check!()
click to toggle source
# File lib/magiq/query.rb, line 359 def check! @model = instance_exec(&self.class.model_proc) @scope = instance_exec(&self.class.scope_proc) each_listener_for :check do |seek_params, opts, op| next instance_exec(&op) if seek_params.empty? next if !opts[:any] && !seek_params.all? { |p| params.key?(p) } vals = seek_params.map { |p| params[p] } instance_exec(*vals, &op) end end
each_listener_for(type, &block)
click to toggle source
# File lib/magiq/query.rb, line 394 def each_listener_for(type, &block) listeners_for(type).each do |t, params, opts, op| block.(params, opts, op) end end
extract!()
click to toggle source
# File lib/magiq/query.rb, line 289 def extract! @params = {} raw_params.each_pair do |raw_key, raw_value| key = raw_key.to_sym next unless (param = builder.params[key]) begin next unless (value = param.extract(raw_value)) @params[param.key] = value rescue BadParamError => e raise BadParamError, "The `#{param.key}` parameter is invalid: " \ "#{e.message}" end end @params.keys.each do |p| next unless (found = builder.params[p]) next unless found.solo? if @params.size > 1 raise BadParamError, "The `#{found.key}` parameter can only be used " \ "by itself in a query." else @has_solo_param = true @solo_param = found end end end
is_unqualified?(column)
click to toggle source
# File lib/magiq/query.rb, line 285 def is_unqualified?(column) self.class.unqualified_columns.include?(column) end
listeners_for(type)
click to toggle source
# File lib/magiq/query.rb, line 390 def listeners_for(type) builder.listeners_for(type) end
solo?()
click to toggle source
# File lib/magiq/query.rb, line 382 def solo? @has_solo_param ? true : false end
to_scope()
click to toggle source
# File lib/magiq/query.rb, line 400 def to_scope extract! verify! check! apply! @scope end
update_scope!(new_scope)
click to toggle source
# File lib/magiq/query.rb, line 280 def update_scope!(new_scope) return unless new_scope @scope = new_scope end
verify!()
click to toggle source
# File lib/magiq/query.rb, line 320 def verify! if !@params raise RuntimeError, "verify! was called before extract!" end builder.constraints.each do |(op, keys, opts)| case op when :exclusive found_keys = keys.select { |k| params.key?(k) } next if found_keys.empty? || found_keys.one? raise ParamsError, "The following parameters are not permitted " \ "to be provided together: #{found_keys.join(', ')}" when :mutual exclusives = opts[:exclusive] && Array(opts[:exclusive]) || [] found_keys = keys.select { |k| params.key?(k) } found_excl = exclusives.select { |k| params.key?(k) } next if found_keys.empty? next if found_keys.empty? && found_excl.empty? next if found_keys == keys && found_excl.empty? if found_excl.any? raise ParamsError, "The provided " \ "parameter#{found_keys.one? ? '' : 's'}: " \ "#{found_keys.map { |k| "`#{k}`" }.join(', ')} " \ "#{found_keys.one? ? 'is' : 'are'} mutually exclusive to: " \ "#{found_excl.map { |k| "`#{k}`" }.join ', '}." end raise ParamsError, "The provided " \ "parameter#{found_keys.one? ? '' : 's'}: " \ "#{found_keys.map { |k| "`#{k}`" }.join(', ')} requires: " \ "#{(keys - found_keys).map { |k| "`#{k}`" }.join(', ')}." end end end