Back to .md Directory

Retrieving Records in ApiResource

The simplest way to retrieve a record is by its ID

May 2, 2026
0 downloads
0 views
ai
View source

Retrieving Records in ApiResource

Finding by id

The simplest way to retrieve a record is by its ID

Resource::Person.find(1)
# GET /people/1.json

Simple conditions

To add simple query params, you can just use the .where method

Resource::Person.where(first_name: 'Aaron').all
# GET /people.json?first_name=Aaron

Scopes

Scopes must be activated in the Server application and are then read by the client application via the {file:docs/ResourceDefinition.md Resource Definition}.

Activating a scope in the Server application

# app/models/person.rb
class Person < ActiveRecord::Base

  include LifebookerCommon::Model::Resource

  scope :birthday_on, -> date {
   where(birthday: date)
  }

  scope :aaron, -> {
    where(name: 'Aaron')
  }

end

Using a scope in the Client application

# GET /people.json?birthday_on[date]=2014-01-01
@people = Resource::Person.birthday_on(Date.parse('2014-01-01'))

@people.each do |person|
  ...
end

Scopes can be chained together

# GET /people.json?birthday_on[date]=2014-01-01&aaron=1
@people = Resource::Person
  .birthday_on(Date.parse('2014-01-01'))
  .aaron

@people.each do |person|
  ...
end

Applying scopes in the Client application

ApiResource::Base gives us the ability to apply scopes that are valid according to the {file:docs/ResourceDefinition.md Resource Definition} directly from params that are passed in

# In the Client application
class PeopleController < ApplicationController

  def index
    @people = Resource::Person.add_scopes(params)
  end

end

This reads the params supplied to the controller to see if there are any valid scopes and then applies them. These scopes follow the same rules as the ones that are sent to the server

Sequence

  1. Client supplies params to our client app

     # curl http://clienthost/people.html?birthday_on[date]=2014-01-01
    
  2. Client controller parses params, passes them to {ApiResource.add_scopes} and creates a ScopeCondition to generate params to the server

  3. Resource::Person makes a request to the Server application. This request has the same parameters as what was passed in to the client application assuming those parameters were valid for a scope

     # GET http://serverhost/people.json?birthday_on[date]=2014-01-01
    
  4. Server application returns the data as JSON and Resource::Person instantiates its records for use in the Client application

Automatically generated scopes

api_resource_server and {ApiResource::Base} automatically provide several scopes for all descendants of ActiveRecord::Base and ApiResource::Base respectively

IDs

You can supply an array of ids to a resource to scope by only resources in that set of ids

# in the Client application
Resource::Person.add_scopes(ids: [1, 2, 3])
# => GET /people.json?ids[]=1&ids[]=2&ids[]=3

Type

In order to limit your scope to a subclass of a class that users Single Table Inheritance, you can supply the type

# in the Server application
class Carpenter < Person
end

# in the Client application
Resource::Person.type('Carpenter')
# => GET /people.json?type=Carpenter

# back in the Server application Person.add_scopes
# converts

Person.all

# into

Carpenter.all

Page and PerPage

In order to paginate, you can pass page and per_page scopes into your ApiResource::Base subclass

Resource::Person.per_page(20).page(1)

# or

Resource::Person.add_scopes(per_page: 20, page: 1)

# either of these produce
GET /people.json?page=1&per_page=20

In the Server application add_scopes will take care of applying the pagination using its integration with {https://github.com/mislav/will_paginate will_paginate}

In addition, the Server application will include a header denoting the number of records in the set so that {https://github.com/mislav/will_paginate will_paginate} can render its page helpers on the client side

Note that in order to enable this feature the Server application must use respond_to

# in a controller in the Client application
@people = Resource::Person.add_scopes(
  params.merge(page: 2, per_page: 20)
)

# in a view in the Client application
- @people.each do |person|
  %tr
    %td ...
# renders the pagination links
= will_paginate(@people)

Types of scopes

  1. Static scopes

     # Server code
     scope :x, where(x: 'Val')
     OR
     scope :x, -> { where(x: 'Val') }
    
     # Produces request from client
     Resource::Person.x
     # ?x=1
    
  2. Scopes with required parameters

     # Server code
     scope :birthday_between, -> start, end {
       where("birthday_between ? AND ?", start, end)
     }
    
     # Produces request from the client
     @people = Resource::Person.birthday_between(
       start: Date.today,
       end: Date.tomorrow
     )
     # ?birthday_between[start]=2014-01-01&birthday_between[end]=2014-01-02
    
  3. Scopes with varargs

     # server code
     scope :first_name, -> *names {
       where(first_name: names)
     }
    

    Produces request from the client

    @people = Resource::Person.first_name('Bill', 'Sue')

    ?first_name[]=Bill&first_name[]=Sue

Including associated data

Oftentimes we will need data from two resources, which would produce n+1 API calls unless we sideload our data

Concept

If, for example we loaded a list of 50 People, but needed their State data we might have to make 50 requests to the State API endpoint

Server application

# in app/models/person.rb
class Person < ActiveRecord::Base

  belongs_to :state

  # Attributes
  #
  # :first_name, :last_name, :state_id

end

# in app/models/state.rb
class State < ActiveRecord::Base
  # Attributes
  #
  # :name
end

# in app/controllers/people_controller.rb
def index
  respond_with(Person.add_scopes(params))
end

# in app/controllers/states_controller.rb
def index
  respond_with(State.add_scopes(params))
end

Client application

# in lib/resource/state.rb
module Resource
  class State < ApiResource::Base
  end
end

# in lib/resource/person.rb
module Resource
  class Person < ApiResource::Base
  end
end

# in a controller
@people = Resource::Person.includes(:state)

# this results in
#
# GET /people.json
#
# and
#
# GET /states.json?ids[]=person1.state_id&ids[]=person2.state_id ...

{ApiResource::Base} knows how to include data from any association where ids are embedded in the response of the base object and the association is present in the {file:docs/ResourceDefinition.md Resource Definition}

Related Documents