Ruby on Rails - Differentiating Plural VS Singular Resource in a Rest API

Ruby on Rails - differentiating plural vs singular resource in a REST API

Rails (3) has a lot of conventions when it comes to singular vs plural. For example, model classes are always singular (Person), while the corresponding tables are always plural (people). (For example, Person.all maps to select * from people.)

For routes, there's a concept of a singular resource as well as a plural resource. So if you did resource :account then you would get paths like /account for the default path or /account/edit for a path to a form to edit the account. (Note that Rails uses /account with a PUT method to actually update the account. /account/edit is a form to edit the account, which is a separate resource from the account itself.) If you did resources :people, however, then you would get paths like /people, /people/1, and /people/1/edit. The paths themselves indicate whether there can only be one instance of a given type of resource, or whether there can be multiple instances distinguished by some type of identifier.

How can Rails' REST implementation be said to be REST at all?

What makes a system RESTful or not isn't so much the verb it uses, but the fact the interactions are driven by the hypermedia (in this case, this includes code-on-demand JavaScript).

The GET/PUT/POST/DELETE mapping you're quoting works fine for CRUD operations, but isn't necessarily appropriate for everything (and isn't necessarily supported by all browsers). This is a reasonable rule of thumb to design a RESTful system, but it's neither sufficient nor necessary. (It's in the same category of ideas as what people insist on when they want "RESTful" URIs: there's no such thing.)

Part of this comes under influence of books like RESTful Web Services. It's a good book, but it came out at a time where WS-*/SOAP was predominant, and where everything was tunnelled through POST. A reaction to SOAP was to make people aware that there were other HTTP verbs they could use.

For REST, what really matters in particular is to respect the notions behind the URI concept, the semantics of each HTTP verb (no side-effects, idempotent requests, ..., where appropriate and if you're using HTTP) and the HATEOS principle. It's an architectural style, don't stress too much about what POST/PUT/DELETE the JavaScript does underneath.

You should certainly read these:

  • REST APIs must be hypertext-driven
  • It is okay to use POST

EDIT: (Following comments)

What I should have said was this: If most frameworks turn DELETE
http://server.tld/resource/1 into POST
http://server.tld/resource/1?_method=DELETE, then what's the advantage
of that approach over just using POST
http://server.tld/resource/1?my_method=delete in the first place?

  • It makes the overall goal a little bit cleaner/clearer, in terms of API design. REST frameworks can ultimately only be as RESTful as one uses them, since REST is an architectural style, not a protocol or an implementation.
  • In addition, strictly speaking, the URI includes the query component, so http://server.tld/resource/1 and http://server.tld/resource/1?my_method=DELETE could identify different resources in principle (although that wouldn't be a good design choice).
  • It's good for the framework to be able to expose DELETE/PUT as such directly, but have a fallback solution via POST for the clients that don't support it.

    The reason for this is that clients that do support DELETE/PUT will be able to make use of them, and make assumptions about their usage. It's not so much of a problem for the client to treat a request as non-idempotent even if it was idempotent in principle as the opposite. If the client thinks it can send at most 1 request (non-idempotent) when it could have sent N+1 of the requests, this doesn't cause major problems; if the client thinks it can send N+1 times the same request for something that shouldn't be idempotent, this can cause quite a lot of problems. Emulating PUT through POST is OK, you're just not making the most of PUT, emulating POST through PUT would be problematic.

Rails routing and Rest : make resources alias

You're correct. Your approach is not very RESTful or conventional.

If you need to separate parts of your application that are public facing and a dashboard or admin section backend you would normally set it up like so:

Rails.application.routes.draw do

# public facing routes
resources :activities, only: [:show, :index]
resources :tasks, only: [:show, :index]

namespace :management do
resources :activities
resources :tasks
end
end

Note that you should be using the plural form for your routes and controllers!
This would create "public" routes at:

GET  /activities       ActivitiesController#index
GET /activities/:id ActivitiesController#show

And also namespaced routes:

GET|POST          /management/activities            
GET|PATCH|DELETE /management/activities/:id
GET /management/activities/new
GET /management/activities/:id/edit

These routes would go to Management::ActivitiesController. This lets you provide different "representations" of a resource while still sticking to the rails conventions.

Updated.

If you want to do resource scoped roles you would do it like so:

# routes rb
Rails.application.routes.draw do
resources :roles, except: [:new, :create]

[:articles, :tasks].each do |resource_name|
resources resource_name do
resources :roles, only: [:create, :index], module: resource_name.to_s
end
end
end

       Prefix Verb   URI Pattern                           Controller#Action
roles GET /roles(.:format) roles#index
edit_role GET /roles/:id/edit(.:format) roles#edit
role GET /roles/:id(.:format) roles#show
PATCH /roles/:id(.:format) roles#update
PUT /roles/:id(.:format) roles#update
DELETE /roles/:id(.:format) roles#destroy
article_roles GET /articles/:article_id/roles(.:format) articles/roles#index
POST /articles/:article_id/roles(.:format) articles/roles#create
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
task_roles GET /tasks/:task_id/roles(.:format) tasks/roles#index
POST /tasks/:task_id/roles(.:format) tasks/roles#create
tasks GET /tasks(.:format) tasks#index
POST /tasks(.:format) tasks#create
new_task GET /tasks/new(.:format) tasks#new
edit_task GET /tasks/:id/edit(.:format) tasks#edit
task GET /tasks/:id(.:format) tasks#show
PATCH /tasks/:id(.:format) tasks#update
PUT /tasks/:id(.:format) tasks#update
DELETE /tasks/:id(.:format) tasks#destroy

This means to add a manager for a task you would do:

POST tasks/1/roles
{
name: "manager",
user_id: 666
}

# - resource_id   [integer]
# - resource_type [string]
# - user_id [integer, foreign key]
# - name [string]
class Role < ActiveRecord::Base
belongs_to :resource, polymorpic: true
belings_to :user
end

class Article < ActiveRecord::Base
has_many :roles, as: :resource
# ...
end

class Task < ActiveRecord::Base
has_many :roles, as: :resource
# ...
end
class User < ActiveRecord::Base
has_many :roles
end

# do the normal index, delete, update here
class RolesController < ApplicationController
# ...
end

# abstract controller class for reuse
class NestedRolesController < ApplicationController

before_filter :set_resource

def create
@role = @resource.roles.create(role_params)
respond_with(@role)
end

def role_params
params.require(:role).permit(:user_id, :name)
end
end

class Articles::RolesController < NestedRolesController
def set_resource
@resource = Article.find(params[:article_id])
end
end

class Tasks::RolesController < NestedRolesController
def set_resource
@resource = Task.find(params[:task_id])
end
end

Client Server API pattern in REST (unreliable network use case)

If it isn't reasonable for duplicate resources to be created (e.g. products with identical titles, descriptions, etc.), then unique identifiers can be generated on the server which can be tracked against created resources to prevent duplicate requests from being processed. Unlike Darrel's suggestion of generating unique IDs on the client, this would also prevent separate users from creating duplicate resources (which you may or may not find desirable). Clients will be able to distinguish between "created" responses and "duplicate" responses by their response codes (201 and 303 respectively, in my example below).

Pseudocode for generating such an identifier — in this case, a hash of a canonical representation of the request:

func product_POST
// the canonical representation need not contain every field in
// the request, just those which contribute to its "identity"
tags = join sorted request.tags
canonical = join [request.name, request.maker, tags, request.desc]
id = hash canonical

if id in products
http303 products[id]
else
products[id] = create_product_from request
http201 products[id]
end
end

This ID may or may not be part of the created resources' URIs. Personally, I'd be inclined to track them separately — at the cost of an extra lookup table — if the URIs were going to be exposed to users, as hashes tend to be ugly and difficult for humans to remember.

In many cases, it also makes sense to "expire" these unique hashes after some time. For example, if you were to make a money transfer API, a user transferring the same amount of money to the same person a few minutes apart probably indicates that the client never received the "success" response. If a user transfers the same amount of money to the same person once a month, on the other hand, they're probably paying their rent. ;-)

What is the difference between POST and PUT in HTTP?

Overall:

Both PUT and POST can be used for creating.

You have to ask, "what are you performing the action upon?", to distinguish what you should be using. Let's assume you're designing an API for asking questions. If you want to use POST, then you would do that to a list of questions. If you want to use PUT, then you would do that to a particular question.

Great, both can be used, so which one should I use in my RESTful design:

You do not need to support both PUT and POST.

Which you use is up to you. But just remember to use the right one depending on what object you are referencing in the request.

Some considerations:

  • Do you name the URL objects you create explicitly, or let the server decide? If you name them then use PUT. If you let the server decide then use POST.
  • PUT is defined to assume idempotency, so if you PUT an object twice, it should have no additional effect. This is a nice property, so I would use PUT when possible. Just make sure that the PUT-idempotency actually is implemented correctly in the server.
  • You can update or create a resource with PUT with the same object URL
  • With POST you can have 2 requests coming in at the same time making modifications to a URL, and they may update different parts of the object.

An example:

I wrote the following as part of another answer on SO regarding this:

POST:

Used to modify and update a resource

POST /questions/<existing_question> HTTP/1.1
Host: www.example.com/

Note that the following is an error:

POST /questions/<new_question> HTTP/1.1
Host: www.example.com/

If the URL is not yet created, you
should not be using POST to create it
while specifying the name. This should
result in a 'resource not found' error
because <new_question> does not exist
yet. You should PUT the <new_question>
resource on the server first.

You could though do something like
this to create a resources using POST:

POST /questions HTTP/1.1
Host: www.example.com/

Note that in this case the resource
name is not specified, the new objects
URL path would be returned to you.

PUT:

Used to create a resource, or
overwrite it. While you specify the
resources new URL.

For a new resource:

PUT /questions/<new_question> HTTP/1.1
Host: www.example.com/

To overwrite an existing resource:

PUT /questions/<existing_question> HTTP/1.1
Host: www.example.com/

Additionally, and a bit more concisely, RFC 7231 Section 4.3.4 PUT states (emphasis added),

4.3.4. PUT

The PUT method requests that the state of the target resource be
created or replaced with the state defined by the representation
enclosed in the request message payload.

Where should I create a shortened URL to my users' profile page for MVC architecture?

I think you have the right idea with using a callback, but I'd create an observer instead, so as not to pollute the User class with calls out to an external API. Here's more information about observers in rails.

Table Naming Dilemma: Singular vs. Plural Names

Others have given pretty good answers as far as "standards" go, but I just wanted to add this... Is it possible that "User" (or "Users") is not actually a full description of the data held in the table? Not that you should get too crazy with table names and specificity, but perhaps something like "Widget_Users" (where "Widget" is the name of your application or website) would be more appropriate.



Related Topics



Leave a reply



Submit