class JsonapiCompliable::Query
@attr_reader [Hash] params hash of query parameters with symbolized keys @attr_reader [Resource] resource the corresponding Resource
object
Attributes
TODO: This class could use some refactoring love!
TODO: This class could use some refactoring love!
Public Class Methods
This is the structure of +Query#to_hash+ used elsewhere in the library @see to_hash
@api private @return [Hash] the default hash
# File lib/jsonapi_compliable/query.rb, line 12 def self.default_hash { filter: {}, sort: [], page: {}, include: {}, stats: {}, fields: {}, extra_fields: {} } end
# File lib/jsonapi_compliable/query.rb, line 24 def initialize(resource, params) @resource = resource @params = params @params = @params.permit! if @params.respond_to?(:permit!) end
Public Instance Methods
All the keys of the include_hash
For example, let's say we had
{ posts: { comments: {} }
#association_names
would return
[:posts, :comments]
@return [Array<Symbol>] all association names, recursive
# File lib/jsonapi_compliable/query.rb, line 75 def association_names @association_names ||= Util::Hash.keys(include_hash) end
The relevant include directive @see jsonapi-rb.org @return [JSONAPI::IncludeDirective]
# File lib/jsonapi_compliable/query.rb, line 33 def include_directive @include_directive ||= JSONAPI::IncludeDirective.new(params[:include]) end
The include, directive, as a hash. For instance
{ posts: { comments: {} } }
This will only include relationships that are
-
Available on the
Resource
-
Whitelisted (when specified)
So that users can't simply request your entire object graph.
@see Util::IncludeParams
@return [Hash] the scrubbed include directive as a hash
# File lib/jsonapi_compliable/query.rb, line 50 def include_hash @include_hash ||= begin requested = include_directive.to_hash whitelist = nil if resource.context whitelist = resource.context.sideload_whitelist whitelist = whitelist[resource.context_namespace] if whitelist end whitelist ? Util::IncludeParams.scrub(requested, whitelist) : requested end end
A flat hash of sanitized query parameters. All relationship names are top-level:
{ posts: { filter, sort, ... } comments: { filter, sort, ... } }
@example sorting
# GET /posts?sort=-title { posts: { sort: { title: :desc } } }
@example pagination
# GET /posts?page[number]=2&page[size]=10 { posts: { page: { number: 2, size: 10 } }
@example filtering
# GET /posts?filter[title]=Foo { posts: { filter: { title: 'Foo' } }
@example include
# GET /posts?include=comments.author { posts: { include: { comments: { author: {} } } } }
@example stats
# GET /posts?stats[likes]=count,average { posts: { stats: [:count, :average] } }
@example fields
# GET /posts?fields=foo,bar { posts: { fields: [:foo, :bar] } }
@example extra fields
# GET /posts?fields=foo,bar { posts: { extra_fields: [:foo, :bar] } }
@example nested parameters
# GET /posts?include=comments&sort=comments.created_at&page[comments][size]=3 { posts: { ... }, comments: { page: { size: 3 }, sort: { created_at: :asc } }
@see default_hash @see Base#query_hash
@return [Hash] the normalized query hash
# File lib/jsonapi_compliable/query.rb, line 124 def to_hash hash = { resource.type => self.class.default_hash } association_names.each do |name| hash[name] = self.class.default_hash end fields = parse_fields({}, :fields) extra_fields = parse_fields({}, :extra_fields) hash.each_pair do |type, query_hash| hash[type][:fields] = fields hash[type][:extra_fields] = extra_fields end parse_filter(hash) parse_sort(hash) parse_pagination(hash) parse_include(hash, include_hash, resource.type) parse_stats(hash) hash end
Check if the user has requested 0 actual results They may have done this to get, say, the total count without the overhead of fetching actual records.
@example Total Count, 0 Results
# GET /posts?page[size]=0&stats[total]=count # Response: { data: [], meta: { stats: { total: { count: 100 } } } }
@return [Boolean] were 0 results requested?
# File lib/jsonapi_compliable/query.rb, line 163 def zero_results? !@params[:page].nil? && !@params[:page][:size].nil? && @params[:page][:size].to_i == 0 end
Private Instance Methods
# File lib/jsonapi_compliable/query.rb, line 171 def association?(name) resource.association_names.include?(name) end
# File lib/jsonapi_compliable/query.rb, line 201 def parse_fields(hash, type) field_params = Util::FieldParams.parse(params[type]) hash[type] = field_params end
# File lib/jsonapi_compliable/query.rb, line 206 def parse_filter(hash) if filter = params[:filter] filter.each_pair do |key, value| key = key.to_sym if association?(key) symbolized_hash = value.to_h.each_with_object({}) do |(k, v), hash| hash[k.to_sym] = v end hash[key][:filter].merge!(symbolized_hash) else hash[resource.type][:filter][key] = value end end end end
# File lib/jsonapi_compliable/query.rb, line 175 def parse_include(memo, incl_hash, namespace) memo[namespace] ||= self.class.default_hash memo[namespace].merge!(include: incl_hash) incl_hash.each_pair do |key, sub_hash| key = Util::Sideload.namespace(namespace, key) memo.merge!(parse_include(memo, sub_hash, key)) end memo end
# File lib/jsonapi_compliable/query.rb, line 242 def parse_pagination(hash) if pagination = params[:page] pagination.each_pair do |key, value| key = key.to_sym if [:number, :size].include?(key) hash[resource.type][:page][key] = value.to_i else hash[key][:page] = { number: value[:number].to_i, size: value[:size].to_i } end end end end
# File lib/jsonapi_compliable/query.rb, line 223 def parse_sort(hash) if sort = params[:sort] sorts = sort.split(',') sorts.each do |s| if s.include?('.') type, attr = s.split('.') if type.starts_with?('-') type = type.sub('-', '') attr = "-#{attr}" end hash[type.to_sym][:sort] << sort_attr(attr) else hash[resource.type][:sort] << sort_attr(s) end end end end
# File lib/jsonapi_compliable/query.rb, line 187 def parse_stats(hash) if params[:stats] params[:stats].each_pair do |namespace, calculations| if namespace == resource.type || association?(namespace) calculations.each_pair do |name, calcs| hash[namespace][:stats][name] = calcs.split(',').map(&:to_sym) end else hash[resource.type][:stats][namespace] = calculations.split(',').map(&:to_sym) end end end end
# File lib/jsonapi_compliable/query.rb, line 256 def sort_attr(attr) value = attr.starts_with?('-') ? :desc : :asc key = attr.sub('-', '').to_sym { key => value } end