Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In RESTful design, what's the best way to support different kinds of GETs?

In a current project I need to support finding a User by login credentials and also by email address. I know that in RESTful design you use a GET to find resources. In Rails...

GET /users    # => UsersController.index -- find all the users

GET /users/1  # => UsersController.show -- find a particular user

But I also need something akin to...

GET /users?username=joe&password=mysterio

GET /[email protected]

Is it conventional to add additional routes and actions beyond index and show?

Or is it more common to put conditional logic in the show action to look at the params and detect whether we're finding by one thing or another?

There's a similar issue with PUT requests. In one case I need to set a User to be "active" (user.active = true), and in another case I just need to do a general form-based editing operation.

Thanks guys. Eventually I'm going to figure out this REST stuff.

like image 774
Ethan Avatar asked Dec 30 '22 05:12

Ethan


2 Answers

I'm new to SO, so I can't comment, but the checked green answer is not RESTful.

In a RESTful world, your controller grabs all the parameters and passes it to the model layer for processing. Typically, you shouldn't create another action.

Instead, you should do do something like this:

def show
  @user = User.find_by_login_or_email(params[:user])
  ... #rest of your action
end

Your model can have a method like this:

class User
  self.find_by_login_or_email(params)
    return find_by_login(params[:login]) unless params[:login].blank?
    return find_by_email(params[:email]) unless params[:email].blank?
    nil #both were blank
  end
end

Your view could look like this:

<%= f.text_field :user, :email %>

or

<%= f.text_field :user, :login %>

Note: untested code, so may be buggy...but the general line of thinking is usually not to create new actions for every one-off rule. Instead, look to see if you can push the logic into the models. If your controllers start to have too many non-standard actions, then it may be time to re-evaluate your domain modeling, and perhaps it's refactor the actions to some new models.

ps: you should never pass in passwords via a GET like that

like image 57
Chris Lee Avatar answered Jan 01 '23 17:01

Chris Lee


I don't know how much of this is convention, but this is what I would do. I would add another action, as long as it's specifically related to that resource. In your example, show is a find by userid, so it makes sense as another action on UsersController. You can turn it into a sentence that makes sense, "get me the user with this email address"

For the other one, GET /users?username=joe&password=mysterio, I would do that as another resource. I assume you're thinking that action would log in the user if the password were correct. The verb GET doesn't make sense in that context.

You probably want a 'session' resource (BTW, this is how restful_auth works). So you would say "create me a session for this user", or something like POST /sessions where the body of the post is the username & password for the user. This also has the good side effect of not saving the password in the history or letting someone capture it on the HTTP proxy.

So your controller code would look something like this:

class UsersController < ActionController::Base

    def show
      @user = User.find_by_id(params[:id])
      # etc ...
    end

    def show_by_email
      @user = User.find_by_email(params[:email)
    end
end

class SessionsController < ActionController::Base
  def create
     # ... validate user credentials, set a cookie or somehow track that the 
     # user is logged in to be able to authenticate in other controllers
  end
end

You would set up your routes like this:

map.connect "/users/byemail", :controller => "users", :action => "show_by_email", :conditions => { :method => :get }
map.resources :users
map.resources :sessions

That will get you URLs like /users/[email protected]. There are issues with encoding the email directly in the URL path, rails sees the '.com' at the end and by default translates that into the :format. There's probably a way around it, but this is what I had working.

Also like cletus says, there are ways to make your route match based on the format of the parts of the URL, like all numbers or alphanumeric, but I don't know off hand how to make that work with the dots in the url.

like image 22
Otto Avatar answered Jan 01 '23 18:01

Otto