lib/json_api_client/query/builder.rb
require 'active_support/all'
module JsonApiClient
module Query
class Builder
attr_reader :klass
delegate :key_formatter, to: :klass
def initialize(klass, opts = {})
@klass = klass
@primary_key = opts.fetch( :primary_key, nil )
@pagination_params = opts.fetch( :pagination_params, {} )
@path_params = opts.fetch( :path_params, {} )
@additional_params = opts.fetch( :additional_params, {} )
@filters = opts.fetch( :filters, {} )
@includes = opts.fetch( :includes, [] )
@orders = opts.fetch( :orders, [] )
@fields = opts.fetch( :fields, [] )
end
def where(conditions = {})
# pull out any path params here
path_conditions = conditions.slice(*klass.prefix_params)
unpathed_conditions = conditions.except(*klass.prefix_params)
_new_scope( path_params: path_conditions, filters: unpathed_conditions )
end
def order(*args)
_new_scope( orders: parse_orders(*args) )
end
def includes(*tables)
_new_scope( includes: parse_related_links(*tables) )
end
def select(*fields)
_new_scope( fields: parse_fields(*fields) )
end
def paginate(conditions = {})
scope = _new_scope
scope = scope.page(conditions[:page]) if conditions[:page]
scope = scope.per(conditions[:per_page]) if conditions[:per_page]
scope
end
def page(number)
_new_scope( pagination_params: { klass.paginator.page_param => number || 1 } )
end
def per(size)
_new_scope( pagination_params: { klass.paginator.per_page_param => size } )
end
def with_params(more_params)
_new_scope( additional_params: more_params )
end
def first
paginate(page: 1, per_page: 1).to_a.first
end
def last
paginate(page: 1, per_page: 1).pages.last.to_a.last
end
def build(attrs = {})
klass.new @path_params.merge(attrs.with_indifferent_access)
end
def create(attrs = {})
klass.create @path_params.merge(attrs.with_indifferent_access)
end
def params
filter_params
.merge(pagination_params)
.merge(includes_params)
.merge(order_params)
.merge(select_params)
.merge(primary_key_params)
.merge(path_params)
.merge(additional_params)
end
def to_a
@to_a ||= _fetch
end
alias all to_a
def find(args = {})
if klass.raise_on_blank_find_param && args.blank?
raise Errors::NotFound, nil, 'blank .find param'
end
case args
when Hash
scope = where(args)
else
scope = _new_scope( primary_key: args )
end
scope._fetch
end
def method_missing(method_name, *args, &block)
to_a.send(method_name, *args, &block)
end
def hash
[
klass,
params
].hash
end
def ==(other)
return false unless other.is_a?(self.class)
hash == other.hash
end
alias_method :eql?, :==
protected
def _fetch
klass.requestor.get(params)
end
private
def _new_scope( opts = {} )
self.class.new( @klass,
primary_key: opts.fetch( :primary_key, @primary_key ),
pagination_params: @pagination_params.merge( opts.fetch( :pagination_params, {} ) ),
path_params: @path_params.merge( opts.fetch( :path_params, {} ) ),
additional_params: @additional_params.deep_merge( opts.fetch( :additional_params, {} ) ),
filters: @filters.merge( opts.fetch( :filters, {} ) ),
includes: @includes + opts.fetch( :includes, [] ),
orders: @orders + opts.fetch( :orders, [] ),
fields: @fields + opts.fetch( :fields, [] ) )
end
def path_params
@path_params.empty? ? {} : {path: @path_params}
end
def additional_params
@additional_params
end
def primary_key_params
return {} unless @primary_key
@primary_key.is_a?(Array) ?
{klass.primary_key.to_s.pluralize.to_sym => @primary_key.join(",")} :
{klass.primary_key => @primary_key}
end
def pagination_params
if klass.paginator.ancestors.include?(Paginating::Paginator)
# Original Paginator inconsistently wraps pagination params here. Keeping
# default behavior for now so as not to break backward compatibility.
@pagination_params.empty? ? {} : {page: @pagination_params}
else
@pagination_params
end
end
def includes_params
@includes.empty? ? {} : {include: @includes.join(",")}
end
def filter_params
@filters.empty? ? {} : {filter: @filters}
end
def order_params
@orders.empty? ? {} : {sort: @orders.join(",")}
end
def select_params
if @fields.empty?
{}
else
field_result = Hash.new { |h,k| h[k] = [] }
@fields.each do |field|
if field.is_a? Hash
field.each do |k,v|
field_result[k.to_s] << v
field_result[k.to_s] = field_result[k.to_s].flatten
end
else
field_result[klass.table_name] << field
end
end
field_result.each { |k,v| field_result[k] = v.join(',') }
{fields: field_result}
end
end
def parse_related_links(*tables)
Utils.parse_includes(klass, *tables)
end
def parse_orders(*args)
args.map do |arg|
case arg
when Hash
arg.map do |k, v|
operator = (v == :desc ? "-" : "")
"#{operator}#{k}"
end
else
"#{arg}"
end
end.flatten
end
def parse_fields(*fields)
fields = fields.split(',') if fields.is_a? String
fields.map do |field|
case field
when Hash
field.each do |k,v|
field[k] = parse_fields(v)
end
field
else
Array(field).flatten.map { |i| i.to_s.split(",") }.flatten.map(&:strip)
end
end.flatten
end
end
end
end