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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/caprese/controller/concerns/query.rb', line 161

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



134
135
136
137
138
# File 'lib/caprese/controller/concerns/query.rb', line 134

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:



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/caprese/controller/concerns/query.rb', line 144

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



236
237
238
# File 'lib/caprese/controller/concerns/query.rb', line 236

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



224
225
226
227
228
229
230
231
# File 'lib/caprese/controller/concerns/query.rb', line 224

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



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/caprese/controller/concerns/query.rb', line 202

def queried_record_scope
  unless @queried_record_scope
    scope = record_scope(unnamespace(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_for_resource_identifier(resource_identifier) ⇒ ActiveRecord::Base

Given a resource identifier, finds or builds a resource

Parameters:

  • resource_identifier (Hash)

    the resource identifier for the resource

Returns:

  • (ActiveRecord::Base)

    the found or built resource for the relationship



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/caprese/controller/concerns/query.rb', line 94

def record_for_resource_identifier(resource_identifier)
  if (type = resource_identifier[:type])
    # { type: '...', id: '...' }
    if (id = resource_identifier[:id])
      begin
        get_record!(
          type,
          Caprese.config.resource_primary_key,
          id
        )
      rescue RecordNotFoundError => e
        raise e unless record_scope(type.to_sym).is_a?(ActiveRecord::NullRelation)
      end

      # { type: '...', attributes: { ... } }
    elsif contains_constructable_data?(resource_identifier)
      record_scope(type.to_sym).build

      # { type: '...' }
    else
      raise RequestDocumentInvalidError.new(field: :base)
    end
  else
    # { id: '...' } && { attributes: { ... } }
    raise RequestDocumentInvalidError.new(field: :type)
  end
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