Module: Caprese::Query

Extended by:
ActiveSupport::Concern
Included in:
Controller
Defined in:
lib/caprese/controller/concerns/query.rb

Instance Method Summary collapse

Instance Method Details

#apply_sorting_pagination_to_scope(scope) ⇒ Relation

Applies query_params and query_params to a given scope

Parameters:

  • scope (Relation)

    the scope to apply sorting and pagination to

Returns:

  • (Relation)

    the sorted and paginated scope



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/caprese/controller/concerns/query.rb', line 129

def apply_sorting_pagination_to_scope(scope)
  return scope if scope.empty?

  if query_params[:sort].try(:any?)
    ordering = {}
    query_params[:sort].each do |sort_field|
      ordering = ordering.merge(
        if sort_field[0] == '-' # EX: -created_at, sort by created_at descending
          { actual_field(sort_field[1..-1]) => :desc }
        else
          { actual_field(sort_field) => :asc }
        end
      )
    end
    scope = scope.reorder(ordering)
  end

  if query_params[:offset] || query_params[:limit]
    offset = query_params[:offset].to_i || 0

    if offset < 0
      offset = scope.count + offset
    end

    limit = query_params[:limit] && query_params[:limit].to_i || self.config.default_page_size
    limit = [limit, self.config.max_page_size].min

    scope.offset(offset).limit(limit)
  else
    page_number = query_params[:page].try(:[], :number)
    page_size = query_params[:page].try(:[], :size).try(:to_i) || self.config.default_page_size
    page_size = [page_size, self.config.max_page_size].min

    scope.page(page_number).per(page_size)
  end
end

#get_record(scope, column, value) ⇒ APIRecord

Gets a record in a scope using a column/value to search by

Examples:

get_record(:orders, :id, '1e0da61f-0229-4035-99a5-3e5c37a221fb')
  # => Order.find_by(id: '1e0da61f-0229-4035-99a5-3e5c37a221fb')

Parameters:

  • scope (Symbol, Relation)

    the scope to find the record in if Symbol, call record_scope for that type and use it if Relation, use it as a scope

  • column (Symbol)

    the name of the column to find the record by

  • value (Value)

    the value to match to a column/row value

Returns:

  • (APIRecord)

    the record that was found



102
103
104
105
106
# File 'lib/caprese/controller/concerns/query.rb', line 102

def get_record(scope, column, value)
  scope = record_scope(scope.to_sym) unless scope.respond_to?(:find_by)

  scope.find_by(column => value) unless scope.empty?
end

#get_record!(scope, column, value) ⇒ Object

Note:

Fails with error 404 Not Found if the record was not found

Gets a record in a scope using a column/value to search by

See Also:



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/caprese/controller/concerns/query.rb', line 112

def get_record!(scope, column, value)
  scope = record_scope(scope.to_sym) unless scope.respond_to?(:find_by!)

  begin
    scope.find_by!(column => value) unless scope.is_a?(Array) && scope.empty?
  rescue ActiveRecord::RecordNotFound => e
    fail RecordNotFoundError.new(
      model: scope.name.underscore,
      value: value
    )
  end
end

#indexObject

Standardizes the index action since it always does the same thing



15
16
17
18
19
20
21
# File 'lib/caprese/controller/concerns/query.rb', line 15

def index
  render(
    json: queried_collection,
    fields: query_params[:fields],
    include: query_params[:include]
  )
end

#queried_collectionRelation

Gets the collection that was queried, i.e. the sorted & paginated queried_record_scope

Returns:

  • (Relation)

    the queried collection



204
205
206
# File 'lib/caprese/controller/concerns/query.rb', line 204

def queried_collection
  @queried_collection ||= apply_sorting_pagination_to_scope(queried_record_scope)
end

#queried_recordActiveRecord::Base

Gets the record that was queried, i.e. the record corresponding to the primary key in the route param (/:id), in the queried_record_scope

Returns:

  • (ActiveRecord::Base)

    the queried record



192
193
194
195
196
197
198
199
# File 'lib/caprese/controller/concerns/query.rb', line 192

def queried_record
  @queried_record ||=
    get_record!(
      queried_record_scope,
      self.config.resource_primary_key,
      params[:id]
    )
end

#queried_record_scopeRelation

Gets the scope by which to query controller records, taking into account custom scoping and the filters provided in the query

Returns:

  • (Relation)

    the record scope of the queried controller



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/caprese/controller/concerns/query.rb', line 170

def queried_record_scope
  unless @queried_record_scope
    scope = record_scope(unversion(params[:controller]).to_sym)

    if scope.any? && query_params[:filter].try(:any?)
      if (valid_filters = query_params[:filter].select { |k, _| scope.column_names.include? actual_field(k).to_s }).present?
        valid_filters.each do |k, v|
          scope = scope.where(actual_field(k) => v)
        end
      end
    end

    @queried_record_scope = scope
  end

  @queried_record_scope
end

#query_paramsHash

The params that affect the query and subsequent response

Examples:

INCLUDE ASSOCIATED RESOURCES

`GET /api/v1/products?include=merchant`

INCLUDE NESTED ASSOCIATED RESOURCES

`GET /api/v1/products?include=merchant.currency`

FIELDSETS

`GET /api/v1/products?include=merchant&fields[product]=title,description&fields[merchant]=id,name`

SORT

`GET /api/v1/products?sort=updated_at`

SORT DESCENDING

`GET /api/v1/products?sort=-updated_at`

PAGINATION

`GET /api/v1/products?page[number]=1&page[size]=5`

LIMIT AND OFFSET

`GET /api/v1/products?limit=1&offset=1`

LIMIT AND OFFSET (GET LAST)

`GET /api/v1/products?limit=1&offset=-1`

FILTERING

`GET /api/v1/products?filter[venue_id]=10`

Returns:

  • (Hash)

    the params that modify our query



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/caprese/controller/concerns/query.rb', line 62

def query_params
  if @query_params.blank?
    @query_params = params.except(:action, :controller)

    # Sort query by column, ascending or descending
    @query_params[:sort] = @query_params[:sort].split(',') if @query_params[:sort]

    # Convert fields params into arrays for each resource
    @query_params[:fields].each do |resource, fields|
      @query_params[:fields][resource] = fields.split(',')
    end if @query_params[:fields]
  end

  @query_params
end

#record_scope(type) ⇒ Relation

Note:

We use the term scope, because the collection may be all records of that type, or the records may be scoped further by overriding this method

Gets a collection of type type, providing the collection as a ‘record scope` by which to query records

Parameters:

  • type (Symbol)

    the type to get a record scope for

Returns:

  • (Relation)

    the scope of records of type type



86
87
88
# File 'lib/caprese/controller/concerns/query.rb', line 86

def record_scope(type)
  record_class(type).all
end

#showObject

Standardizes the show action since it always does the same thing



24
25
26
27
28
29
30
# File 'lib/caprese/controller/concerns/query.rb', line 24

def show
  render(
    json: queried_record,
    fields: query_params[:fields],
    include: query_params[:include]
  )
end