Skip to content
This repository was archived by the owner on Jul 11, 2020. It is now read-only.

Latest commit

 

History

History
1039 lines (773 loc) · 32.6 KB

File metadata and controls

1039 lines (773 loc) · 32.6 KB

Daylight Users Guide

Daylight is extensions built on top of ActiveResource. Everything you can do with ActiveResource is available to you in Daylight.

Once you have an API developed with Daylight, you will want to be able to use it. As an end-user of an API, it may be distributed to you in a gem (or other means) and you may not have access to how the API is fulfilled.

Your API developers will either supply documentation or you can look at the client models. The client models will describe what functionality is available to you. Follow the your API developers instructions on how to setup the API or refer to the installation steps for options.

Table of Contents

Client Model Example

Imagine you are building a blog, the client models that act as proxy to the server-side are with which you will be interacting.

As we describe what Daylight can do in addition to ActiveResource refer to these client models in the following Post example:

  class API::V1::Post < Daylight::API
    scope :published, :updated

    belongs_to :blog
    belongs_to :author, class_name: 'api/v1/user'

    has_one :company, through: :blog

    has_many :comments,   use: 'resource'
    has_many :commenters, class_name: 'api/v1/user'

    remote :top_comments, class_name: 'api/v1/comment'
  end

All of the client models can be interacted with in the example application.

Namespace and Version

Namespace is the root module for all your client models and can be seen in this example as the 'API' in the module. By default, without a supplied namespace to the setup!, the 'API' module will be used. You can examine the version:

  Daylight::API.namespace #=> 'API'

Daylight client models will be versioned and this can be seen in this example as the V1 module. By default, without a supplied version to the setup!, the most recent version will be selected. You can examine the version:

  Daylight::API.version #=> 'V1'

When you develop using a Daylight API. You do not need to specify the version in your constant names as they are aliased to the currently selected version for your convinience:

  API::Post  #=> API::V1::Post

We will use the aliased version of the constant names in the following examples unless otherwise noted.

request_id

Daylight will generate a UUID and send an 'X-Request-Id' header with each request to API server. No other steps are neccessary.

If your application is already using a request_id you can tell Daylight to use it for its requests. Put the following in your Rack middleware or around_filter:

  Daylight::API.request_id.use(my_request_uuid) do
    yield
  end

Optionally, you could allow Daylight to generate a UUID for you for use in your application:

  Daylight::API.request_id.use do |uuid|
    puts uuid                     # => 3a336db1-973e-4e5f-b82f-53de6cfb4c6c
    puts Daylight::API.request_id # => 3a336db1-973e-4e5f-b82f-53de6cfb4c6c
    yield
  end

More information can be found in to rdocs for RequestId


ActiveResource Overview

With a Post you can use the following ActiveResource functionality as you've become accustomed. Find a Post and examine its attributes:

  post = API::Post.find(1)  #=> #<API::V1::Post:0x007ffa8c4159e0 ..>
  post.title                #=> "100 Best Albums of 2014"

Get all instances of Post, or just the first or last:

  API::Post.all            #=> [#<API::V1::Post:0x007ffa8d59abe8 ..>,
                           #=>  #<API::V1::Post:0x007ffa8d59a788 ..>, ...]

  API::Post.first          #=> #<API::V1::Post:0x007ffa8d59abe8 ..>
  API::Post.last           #=> #<API::V1::Post:0x007ffa8c4763d0 ..>

NOTE: Daylight add a limit condition to get the first Post as an optimization. There is no optimization for last as it is equivalent to Post.all.to_a.last

You can create, update, delete a Post. Here's an example of an update:

  post = API::Post.find(1)  #=> #<Bluesky::V1::Zone:0x007ffa8c44fde8 ..>
  post.title = "100 Best Albums of All Time"
  post.save                 #=> true

Get associated resources:

  post = API::Post.find(1)  #=> #<API::V1::Post:0x007ffa8c44fde8 ..>
  post.comments             #=> [#<API::V1::Comment:0x007ffa8c4843b8 ..>,
                            #    #<API::V1::Comment:0x007ffa8c48e728 ..>, ...]

Search across the collection of resources:

  posts = API::Post.where(created_by: 101)
  posts.size             #=> 23
  posts.first.created_by #=> 101

You can use any multiple conditions:

  posts = API::Post.where(created_by: 101, blog_id: 1, published: true)
  posts.size             #=> 15
  posts.first.created_by #=> 101
  posts.first.blog_id    #=> 1
  posts.first.published  #=> true

You can use conditions based on results of other searches:

  posts = API::Post.where(created_by: API::User.find_by(username: "reidmix"))
  posts.size             #=> 23
  posts.first.created_by #=> 101

NOTE: This will issue two requests, the first by find_by and the second by where.

Please refer to the ActiveResource documenation for more information.


Refinements

Daylight offers many ways to refine queries across collections. These include conditions, scopes, order, offset, and limit.

Condition Additions

There are several additions to ActiveResource conditions. Which attributes may be refined need to be documented by your API developer but can be inspected on a retrieved instance:

  post = API::Post.find(1)
  post.attributes.keys #=> ["id", "blog_id", "title", "body", "slug",  "published", "published_on", "created_by"]

If there is a natural_key specified for the class, you can use a value to look up by natural_key in addition to the primary_key, like so:

  post = API::Post.find(1)
  post.id          #=> 123
  post.natural_key #=> 'slug'
  post.slug        #=> '100-best-albums-of-2014'

  # look up using natural_key
  post = API::Post.find('100-best-albums-of-2014')
  post.id          #=> 123

Like primary_key lookup, ActiveResource::ResourceNotFound will be thrown when the record cannot be found with the supplied value.

If you know there to be one result or only need the first result, use find_by:

  post = API::Post.find_by(slug: "100-best-albums-of-2014")
  posts.slug #=> "100-best-albums-of-2014"

And where clauses may be chained together similarly to ActiveRecord:

  posts = API::Post.where(created_by: 101).where(blog_id: 1).where(published: true)
  posts.size             #=> 15
  posts.first.created_by #=> 101
  posts.first.blog_id    #=> 1
  posts.first.published  #=> true

In fact there's more to chaining than just where clauses.

Order

As in ActiveRecord you can also refine by limit, offset, and order

  posts = API::Post.order(:published_on)
  posts.map(&:published_on) #=> ['2014-01-01', '2014-06-21', '2014-06-26']

You can also specify the direction or reverse the direction:

  posts = API::Post.order('published_on ASC')
  posts.map(&:published_on) #=> ['2014-01-01', '2014-06-21', '2014-06-26']

  posts = API::Post.order('published_on DESC')
  posts.map(&:published_on) #=> ['2014-06-26', '2014-06-21', '2014-01-01']

Limit and Offset

You can limit the results that are returned by the API:

  posts = API::Post.limit(1)
  posts.size #=> 1

  posts = API::Post.limit(10)
  posts.size #=> 10

And you can offset which resources to be returned:

  posts = API::Post.all
  posts.map(&:id)  #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  posts = API::Post.offset(5)
  posts.map(&:id)  #=> [6, 7, 8, 9, 10]

Scopes

Scopes are conditions made available on the client-side model and executed server-side. The function of a scope needs to be documented by your API developer but which scopes are available be inspected in client model or find in the instance:

  API::Post.scope_names #=> [:published, :updated]

You can call a scope directly on the model class:

  posts = API::Post.published

  # assuming published scope on the server-side is
  # scope :published, -> {where.not(published_on: nil)}

  posts.first.published_on                  #=> true
  posts.all? {|p| p.published_on.present? } #=> true

You may call multiple scopes on a model:

  posts = API::Post.published.edited

  # assuming published scope on the server-side is
  # scope :edited, -> {where.not(edited_on: nil)}

  posts.first.published_on                  #=> true
  posts.first.edited_on                     #=> true

  posts.all? {|p| p.published_on.present? } #=> true
  posts.all? {|p| p.edited_on.present? }    #=> true

Chaining

All of the above refinements are as limited to the one being used. Daylight allows all or any combination of the refinements to be chained together for better searches:

  # NONE: get all posts
  posts = API::Post.all
  posts.map(&:id)          #=> [10, 3, 2, 4, 7, 5, 6, 1, 9, 8]

  # SCOPE: get published posts
  posts = API::Post.published
  posts.map(&:id)          #=> [3, 2, 7, 5, 6, 1, 9, 8]
  posts.first.published_on #=> '2013-09-03'

  # WHERE 1 condition: get posts for blog_id=2
  posts = API::Post.where(blog_id: 2)
  posts.map(&:id)           #=> [2, 5, 1, 9, 8]
  posts.map(&:blog_id)      #=> [2, 2, 2, 2, 2]

  # WHERE 2 conditions: get posts for blog_id=101 AND created_by=2
  posts = API::Post.where(blog_id: 2).where(created_by: 101)
  posts.map(&:id)          #=> [2, 9, 8]
  posts.map(&:created_by)  #=> [101, 101, 101]

  # ORDER: get posts for blog_id=2 AND created_by=101 order by published_on
  posts = API::Post.where(blog_id: 2).where(created_by: 101).order(:published_on)
  posts.map(&:id)           #=> [2, 8, 9]
  posts.map(&:published_on) #=> ['2014-01-01', '2014-06-21', '2014-06-26']

  # OFFSET: get posts for blog_id=2 AND created_by=101 order by published_on after the first one
  posts = API::Post.where(blog_id: 2).where(created_by: 101).order(:published_on).offset(1)
  posts.map(&:id)           #=> [8, 9]

  # LIMIT: get posts for blog_id=2 AND created_by=2 order_by published_on and just the second one
  posts = API::Post.where(blog_id: 2).where(created_by: 101).order(:published_on).offset(1).limit(1)
  posts.map(&:id)           #=> [8]

  post = API::Post.where(blog_id: 2).where(created_by: 101).order(:published_on).offset(1).limit(1).first
  post.id                   #=> 8
  post.blog_id              #=> 2
  post.created_by           #=> 101
  post.published_on         # '2014-06-21'

NOTE: Since offset and limit can be chained together, you can use these with your favorite paginator.

In all of these cases, Daylight issues only one request per search. See Request Parameters for further reading.

Just like ActiveRecord, each part of the chain has its own context and can be inspected individually.

  published_posts = API::Post.published
  first_published = published_posts.order(:published_on).first

  first_published.id         #=> 2
  published_posts.map(&:id)  #=> [3, 2, 7, 5, 6, 1, 9, 8]

Here you can see a result set can be further refined while not affecting the original result set.


Associations

Associations work as they do today in ActiveResource with one notable exception: client models will perform the lookup for associated objects server-side.

NOTE: This is useful if conditions or configuration is defined on the server-side model to perform correctly. Refer to developing models for more information.

Daylight adds additional functionality directly on the association:

  • add new resources
  • update existing resources
  • add a resource to a collection
  • associate two existing resources
  • delete from an association

Currently, ActiveResource will only let you associate a resource by setting the foreign_key directly on a model.

If you want to use the original behavior where the foreign_key is used to lookup up associated objects, you can pass the use: 'resource' to the has_many association.

  has_many :comments, use: 'resource'

Like the default ActiveResource behavior, this will return a ActiveResource::Collection that cannot be chained, nor can nested resources be set in the collection.

Nested Resources

When manipulating resources on an association, we call these Nested Resources.

INFO: We call it "Nested Resource" because data for them are sent as a nested hash on the parent resource and server-side employ the accepts_nested_attributes_for mechanism.

Not all nested resources can be manipulated on the model, you can see which objects are accepted by inspecting the instance:

  post = API::Post.find(1)
  post.nested_resources #=> [:author, :comments]

In this example, posts will reject updates to blog, company, and commenters nested objects.

To create a new nested object is simple, create the object and set it on the has_one or has_many association:

Creating a Nested Resource

You can create a new nested resource for a new or existing resources. For example a new post:

  post = API::Post.new
  post.title  = "100 Best Albums of 2014"
  post.author = API::User.new(username: 'reidmix')
  post.save       #=> true
  post.id         #=> 43

  # reload the original object to see the new user
  post = API::Post.find(43)
  post.author.id  #=> 101
  post.created_by #=> 101 (foreign_key on post)

  # you can look up the new user directly
  user = API::User.find(101)
  user.username #=> "reidmix"

This will work on an existing post:

  post = API::Post.first
  post.author = API::User.new(username: 'dmcinnes')
  post.save       #=> true

  # reload the original object to see the new user
  post = API::Post.first
  post.author.id  #=> 102
  post.created_by #=> 102 (foreign_key on post)

  # you can look up the new user directly
  user = API::User.find(102)
  user.username #=> "dmcinness"

Creating a Nested Resource in a Collection

You can also create a nested object via a collection on a new or existing resource. For example, on our new post:

  post = API::Post.new
  post.comments #=> []
  post.comments << API::Comment.new(message: 'First!')
  post.save #=> true

  # reload the original object to see the new comment
  post = API::Post.first
  post.comments.first.id      #=> 321
  post.comments.first.message #=> "First!"

  # you can look up the new comment
  comment = API::Comment.find(321)
  comment.post_id #=> 1

You can also add a nested object to an existing collection:

  post = API::Post.first
  post.comments #=> []
  post.comments << API::Comment.new(message: 'Last!')
  post.save #=> true

  # reload the original object to see the new comment
  post = API::Post.first
  post.comments.last.id      #=> 322
  post.comments.last.message #=> "Last!"

  # you can look up the new comment
  comment = API::Comment.find(322)
  comment.post_id #=> 1

Updating a Nested Resource

Updates to nested resources are saved by saving the parent resource.

  post = API::Post.first
  post.author.full_name = "Reid MacDonald"
  post.save #=> true

  post = API::Post.first
  post.author.full_name #=> "Reid MacDonald"

The same is true of nested objects in collections:

  post = API::Post.first
  post.comments[0].message = "First!"
  post.save #=> true

  post = API::Post.first
  post.comments[0].message #=> "First!"

NOTE: In the previous example the following will NOT work: post.comments.first.message = "First!" The collection isn't loaded when you use first, so Daylight cannot save any changes to the collection.

Associating an Existing Nested Resources

Associating using an existing nested records is possible with Daylight. The nested record does not need to be new as they do in ActiveRecord.

Setting an existing nested resource on a new or existing parent resource will associate them:

  post = API::Post.first

  post.author = API::User.find_by(username: 'reidmix')
  post.save #=> true

  post.created_by #=> 101
  post.author.id  #=> 101

This also will work to add to a collection on a new or existing resource:

  post = API::Post.first

  post.commenters << API::User.find_by(username: 'reidmix')
  post.save #=> true

  post = API::Post.first
  post.commenters.find {|c| c.username == 'reidmix'} # #<API::V1::User:0x007fe2cfc45ce8 ..>

Deleting Nested Resources

A nested resource can be removed from a collection:

  post = API::Post.first
  post.comments.count #=> 4
  post.comments.shift
  post.save #=> true

  post = API::Post.first
  post.comments.count #=> 3

A collection can also be reset:

  post = API::Post.first
  post.comments.count #=> 4
  post.comments = []

  post.save #=> true

  post = API::Post.first
  post.comments.count #=> 0

More Chaining

Along with the collection returned by queries across collections, you may continue to apply refinements to associations.

Similar to chaining, refinements on assoications.:

  # NONE: get all comments for a post
  comments = API::Post.find(1).comments
  comments.map(&:id)          #=> [11, 33, 32, 54, 17, 15, 16, 1, 90, 81]

  # SCOPE: get a post's edited comments
  comments = API::Post.find(1).comments.edited
  comments.map(&:id)          #=> [33, 32, 17, 15, 16, 1, 90, 81]
  comments.first.edited_on    #=> '2013-09-03'

  # WHERE 1 condition: get a post's comments for blog_id=2
  comments = API::Post.find(1).comments.where(has_images: true)
  comments.map(&:id)           #=> [32, 15, 1, 90, 81]
  comments.map(&:has_images)   #=> [true, true, true, true, true]

  # WHERE 2 conditions: get a post's comments that has_images AND created_by=101
  comments = API::Post.find(1).comments.where(has_images: true).where(created_by: 101))
  comments.map(&:id)          #=> [32, 90, 81]
  comments.map(&:created_by)  #=> [101, 101, 101]

  # ORDER: get a post's comments that has_images AND created_by=101 order by edited_on
  comments = API::Post.find(1).where(has_images: true).where(created_by: 101)).order(:edited_on)
  comments.map(&:id)           #=> [32, 81, 90]
  comments.map(&:published_on) #=> ['2014-01-01', '2014-06-21', '2014-06-26']

  # OFFSET: get post's comments that has_images AND created_by=101 order by edited_on after the first one
  comments = API::Post.find(1),where(has_images: true).where(created_by: 101)).order(:edited_on).offset(1)
  comments.map(&:id)           #=> [80, 91]

  # LIMIT: get post's comments that has_images AND created_by=101 order by edited_on and just the second one
  comments = API::Post.find(1).where(has_images: true).where(created_by: 101)).order(:edited_on).offset(1).limit(1)
  comments.map(&:id)           #=> [80]

  comments = API::Post.find(1).where(has_images: true).where(created_by: 101)).order(:edited_on).offset(1).limit(1).first
  comments.id                   #=> 80
  comments.has_images           #=> true
  comments.created_by           #=> 101
  comments.published_on         # '2014-06-21'

As you could guess, you could end up with very sophisticated queries traversing multiple associations. For example:

API::Post.published.updated.find_by(slug: '100-best-albums-of-2014').comments.edited.where(has_images: true).first.images.approved

Please review Request Frequency to better understand how the requests are composed.

As before with chaining each part of the chain has its own context and can be inspected individually.

  first_published_post    = API::Post.published.first
  comments_with_images    = first_published_post.comments.where(has_images: true)
  my_last_edited_comment  = comments_with_images.where(created_by: 101)).order(:edited_on).last

  my_last_edited_comment.id       #=> 90
  comments_with_images.map(&:id)  #=> [32, 15, 1, 90, 81]

Here you can see a result set can be further refined while not affecting the original result set fetched for the association.


Building Objects

Most of the time, you want to check to see if an object already exists and if it doesn't build that object. ActiveResource already supplies this functionality with first_or_create and first_or_initialze.

Daylight ensures that these methods work with refinements & chaining and ensures the requests are properly formatted for the server.

NOTE: The refinements are expressive but can become very complicated quickly. Daylight uses the where_values generated by the server to build the objects.

first_or_create

The first_or_create method will save the object if it does not already exist.

  post = API::Post.where(slug: '100-best-albums-of-2014').first_or_create
  post.new?   #=> false

  # set an attribute directly
  post.exerpt = "Ranked list of the 100 best albums so far in 2014"
  post.save   #=> true

If there are validation errors the object will be instantiated but it will not be saved. You will be able to view the error messages and see that the object is still new:

  post = API::Post.where(slug: '100-best-albums-of-2014').first_or_create
  post.new?             #=> true
  post.errors.present?  #=> true
  post.errors.messages  #=> {:base=>[Author must be present]}

You can use all of Daylight's refinement chaining to search for a match:

  latest_post = API::Post.where(created_by: 101).order(:published_on).first_or_create
  latest_post.new?      #=> false
  latest_post.author.id #=> 101

first_or_initialize

The first_or_initialize will instatiate the object but not save it automatically.

  post = API::Post.where(slug: '100-best-albums-of-2014').first_or_initialize({
      exerpt: "Ranked list of the 100 best albums so far in 2014"
    })
  post.new?   #=> true
  post.save   #=> true

Again, all of the Daylight's refinement chaining can be used.

Building using an Associations

You can create an object based on a collection for an association.

NOTE: Specifically, only a has_many association. The belongs_to or has_one asscoiations will have a nil object if they are not set (ie. there's no foriegn_key) and will not work.

For example if there is no comment for the the post:

  comment = API::Post.find(1).comments.first_or_initialize({
      message: "Am I the first comment?"
    })
  comment.new?    #=> true
  comment.post_id #=> 1
  comment.save    #=> true

You may apply any refinement to the association:

  comment = API::Post.find(1).comments.where(is_liked: true).first_or_create
  comment.new?    #=> false
  comment.post_id #=> 1

  # Update the message
  comment.message = "You really like me when I said: '#{comment.message}'"
  comment.save    #=> true

Remote Methods

Remote methods are any associated record or collection that is available via a public instance method server-side. For all intents and purposes, the differences between a remote method and an associations are:

  • Remote methods may return a single record
  • Remote methods cannot be chained

FUTURE #4 Remoted methods may be implemented using the association mechanism.

The function of a remoted method needs to be documented by your API developer but which remoted methods are available be inspected in client model.

Given the top_comments remoted method:

  API::Post.find(1).top_comments    #=> [#<API::V1::Comment:0x007ffa8c4843b8 ..>,
                                    #    #<API::V1::Comment:0x007ffa8c48e728 ..>, ...]

As you can see, remote methods cannot be chained:

  API::Post.find(1).top_comments.find_by(user_id: 1)

  #=> NoMethodError: undefined method `find_by' for #<ActiveResource::Collection:0x007f83208937a8>

FUTURE #9: Remote methods cannot be further refined like associations


Error Handling

A goal of Daylight is to offer better handling and messaging to the client when expected errors occur. This will aid in development of both the API and when users of that API are having issues.

Validation Errors

Daylight exposes validation errors on creates and updates. Given a validation on a model:

class Post < ActiveRecord::Base
  validates :title, presence: true
end

When saving this model from the client errors will be exposed similar to ActiveRecord:

post = API::Post.new
post.save             # => false
post.errors.messages  # => {:base=>["Title can't be blank"]}

With the introduction of and use of Strong Parameters unpermitted or missing attributes can be detected if action_on_unpermitted_parameters is set to :raise in configuration:

config.action_controller.action_on_unpermitted_parameters = :raise

Lets say created_at is not permitted on the PostController:

post = API::Post.new(created_at: Time.now)
post.save             # => false
post.errors.messages  # => {'created_at'=>['unpermitted parameter']}

Bad Requests

Daylight will raise an error on unknown attributes. This differes from ActiveRecord where it will be raised immediately because the error is detected by APIController during a save action.

For example, given the same Post model above:

post = API::Post.new(foo: 'bar')
post.save
#=> ActiveResource::BadRequest: Failed.  Response code = 400.
#   Response message = Bad Request.  Root Cause = unknown attribute: foo

Similarly, Daylight raises errors on unknown keys, associations, scopes, or remoted methods. The error will be raise as soon as the request is issued, not just on save actions.

For example, when providing an incorrect condition:

API::Post.find_by(foo: 'bar')
#=> ActiveResource::BadRequest: Failed.  Response code = 400.
#   Response message = Bad Request.  Root Cause = unknown key: foo

If invalid statements are issued server-side they will be raised:

API::Post.published.limit(:foo)
#=> ActiveResource::BadRequest: Failed.  Response code = 400.
#   Response message = Bad Request.  Root Cause = invalid value for Integer(): "foo"

This is also useful developing and detecting errors in your client models Given the client model:

class API::V1::Post < Daylight::API
  scopes :published
  remote :top_comments

  has_many :author
end

If neither published, top_comments, nor author are not setup on the server-side, errors will be raised.

API::Post.published
#=> ActiveResource::BadRequest: Failed.  Response code = 400.
#   Response message = Bad Request.  Root Cause = unknown scope: published

API::Post.by_popularirty
#=> ActiveResource::BadRequest: Failed.  Response code = 400.
#   Response message = Bad Request.  Root Cause = unknown remote: top_comments

API::Post.find(1).author
#=> ActiveResource::BadRequest: Failed.  Response code = 400.
#   Response message = Bad Request.  Root Cause = unknown association: author

Understanding Interaction

To help understand how requests from the client will produce load on the server API, will aid in understanding what load is produced on the API server(s).

Daylight does its best to collect information about a query before issuing the request, but can only do so much. Daylight will still suffer from putting a request in a tight loop as any other web application will.

Request Frequency

A request is issued for any query for a resource or collection of resources. Everytime an association is traversed, a new request sent. All the refinements on a collection is sent along with the request.

Given a large request like:

  API::Post.published.updated.find_by(slug: '100-best-albums-of-2014'). # Post request (with refinements)
    comments.edited.where(has_images: true).first.                      # Comment request (with refinements)
    images.liked.limit(1).                                              # Image request (with refinements)
    map(&:caption).first                                                # No request: iterating over data structure

There are 3 resources/collections retrieved from the server. One each for post, comment, and image. You can see this in the API server logs:

GET "/v1/posts.json?filters[slug]=100-best-albums-of-2014&limit=1&scopes[]=published&scopes[]=updated"
GET "/v1/posts/8/comments.json?filters[has_images]=true&scopes[]=comments"
GET "/v1/comments/1161/images.json?scopes[]=liked&limit=1"

Multi-step requests pretty much match up to the action being performed. From our example on in the README we show creating a post and user and associating the two:

post = API::Post.find_by(slug: '100-best-albums-2014')
post.author = API::User.find_or_create(username: 'reidmix')
post.save

There are 3 queries to the server:

  1. Initial lookup for the post
  2. The creation of the user
  3. Save the post to associate the newly created user

Response Size

Responses are in JSON, but XML can be supported. Response size depends several factors:

  1. The length of each attribute
  2. The number of attributes per resource
  3. The number of resources
  4. The metadata

For example, for a collection of posts:

  {
    "posts": [
      {
        "id": 1,
        "blog_id": "1",
        "title": "100 Best Albums of 2014",
        "created_by": "101",
        "slug": "100-best-albums-of-2014"
        "exerpt":"Ranked list of the 100 best albums so far in 2014",
        "body": "2014 is a year of many albums, here is a...",
        "published": true,
        "updated": false
      },
      {
        "id": 2,
        "blog_id": "1",
        "title": "100 Best Albums of All Time",
        "created_by": "101",
        "slug": "100-best-albums-of-all-time"
        "exerpt":"Ranked list of the 100 best albums evar.",
        "body": "Here is my favorite albums of all time...",
        "published": true,
        "updated": true
      }
    ],
    "meta": {
      "where_values": {
        "blog_id": 1
        },
      "post": {
        "read_only": [
          "slug",
          "published",
          "updated"
        ],
        "nested_resources": [
          "author",
          "comments"
        ]
      }
    }
  }

Here we show 2 posts, but imagine showing every post in each request. Each time a request can be made that will reduce the size of the collection will speed up response times from the server.

Metadata about the response and elements in the collection are also returned per request. Find out more about this in the API Developers Guide

For expensive requests, your API developers may automatically limit the "page size" returned by the server and you will need to paginate through the results.

Please refer to Benchmarks for further reading about response times between the client and the server.