How do you handle current user and a list of users? - ruby-on-rails

i'm having a situation (pretty standard for everybody i guess), where i have to do two things :
If a user asks for /user path, i have to present his/her personal information.
If a user asks for /user/:id path, i have to present information about that particular user, or the current user information if :id matches the current_user id.
There are definitely many ways to do that, but what is the best approach ? Should i have 2 different routes like /show/:id and /show_current, handled by different actions, or just have a /show/:id and do the handling in that action ?
Bear in mind that if this is the current view, i need to render a different more detailed view than the one about another view. I think the latter is probably the better way,but what do you think ?

If the current user is, say, 42 then /user/42 and /user would display the same information but /user/23 would display a stripped down public version of 23's data. So, the handler for /user/:id will have to know about the special case of :id being the current user; the handler for /user will also have to know about this special case as that's all it does.
If you use two routes then you'll either be duplicating code, wrapping the real code in an extra layer, or some other bit of busy work. I'd just send them both to the same controller method and that could start with something like this:
user = params['id'] ? User.find(params['id']) : current_user
And then later on you handle the special case of the requested user being the current user in just one place with something simple:
if(user == current_user)
# show the full blob of information
else
# show just the limited public information
end
You could, of course, slice it up into a bunch of little methods, such as show_current_user_info and show_other_user_info, inside the controller. The handler for /user could just be a call to show_current_user_info; the /user/:id handler could call show_current_user_info or show_other_user_info depending on what :id is. This approach smells like pointless layering to me.

In RESTful routing, you've described users_path and user_path(some_id). These map to User#index and User#show. The index method is normally a list of users, but if you are wanting index to show the current_user personal information, you can certainly do that. If you also need a list method, just add it to your controller and create a route to it.

Related

Rails: Why does create/update action point to the same URL like the index action?

When creating or updating a resource, the target URL by default is the same like the index route, for users e.g. localhost:3000/users.
The difference is that instead of POST, PUT method is used (as far as I know).
I find this suboptimal. For example, when having a navigation menu with an item "Create user" in it, I want to set the active CSS class when the item is active. I do this by comparing current_page? to the menu item's URL (e.g. users/new). This works fine, but only when I don't have validation errors. In this case, the URL now isn't users/new anymore, but users.
Is there a good reason why Rails doesn't point to users/new (or users/edit) respectively for POST/PUT requests? Are there any downsides to using them? Is there an easy way to change this behaviour?
The reason is REST.
In a nutshell, Rails treats everything as a resource, and there are conventions followed in order to do so. In a typical CRUD application, you have the Create (POST), Read (GET), Update (PUT/PATCH), and Destroy (DELETE) actions, which are the verbs used to act on resources.
Let's say you have a User resource. A list of users would be found at /users. An individual user, being an individual object of a resource, would then be found at /users/1, where "1" is the identifier of the resource in question.
Now, based on the CRUD verbs available to you, if you wanted to edit a user, what ACTION makes the most sense given the CRUD verbs we talked about? PUT /users/1 is the correct answer; you're updating (the U in CRUD) a particular resource. If you wanted to delete that user? DELETE /users/1 makes sense. If you want to create one? CREATE /users is the logical choice, because you're not acting on a particular object, but on the resource as a whole, similar to GET /users not acting on an individual object, but a collection.
/users/new is a path to a page that will let you do that, but it doesn't mean it should make the CREATE request to /users/new, because "new" doesn't describe a resource the same way.

Which rails controller should I use?

In my app Users can Like Programs. Each of those is a model, Like is polymorphic.
At some point I will want to see all the Users that Like a Program or all Programs a User Likes.
Is it better to have a likes and likers controller action in the users and programs controller? Or should I have the likes controller as a nested resource with both a users and programs action (or an Index which checks which nested resource is being used)?
I realize all of these can work, but wasn't sure what was Rails best practices.
I would structure your app to have a UsersController with a likes member action which returns the Programs that user likes. And then have a ProgramsController with a likers member action, which gives the Users which like that program.
To simplify things further, you could also just include the user's likes in the show action, (and similarly, show the users who like a program in the program's show action), although you may end up fetching more information than is necessary in the show actions by doing it that way.
Restfully, you would have a LikesController and a 'create' action within it would take a user_id and a program_id. It's likely the user will be logged in (and won't be passed in the URL), and it will make sense to create a Like, passing a program_id to a url that looks something like this:
POST /likes, :params => { :program_id => ___ }
You may want to show a list of Likes (index page), perhaps allowing users to edit and delete. If this is the case, all your actions would be on the likes_controller. Usually, it depends on your situation, but a restful design is usually the right place to start.

Rails 'routing' based on model properties

I am trying to do different things on the home page of my application based on properties of the currently authenticated user. For example:
location, user, state, -> destination
/, no user -> a home page
/, user authenticated, state: unverified -> user profile page
/, user authenticated, state: verified -> a content listing
What this looks like is that I am trying to 'route' based on the current user's state (as represented by a state machine). These 3 actions already exist in 3 different controllers (I call them 'pages', 'users', and 'posts'), but while one can call another controller's view, one can't call another controller's action, making it a little tough to not repeat myself. There's a number of ways to deal with this, but I'm not sure what The Rails Way is for this, so I thought I'd ask. I see as my options:
Use redirect_to in a hypothetical 'redirect controller', but I want the page to appear under /, so this isn't what I want.
Get fancy with a routing constraint (not sure this is possible; need sessions/cookies available in routing and I'm not sure that's the case)
Pull the logic for the particular actions out of their respective controllers, toss them into ApplicationController, and use them directly based on the user's state in a hypothetical controller (or just toss it into pages).
Repeat myself significantly, either in the controller, the views, or both
Yet-unknown options, I'm open to suggestions.
I'm leaning towards the third option, with the obvious downside that some piece of those controllers will now more or less inexplicably live in the ApplicationController (unless, god help me, I do some sort of Lovecraftian include-on-extend). Having this code live in two places feels dirty to me.
Am I missing something obvious?
Would a single action that uses a helper to pick the right partial based on the current state of the user work?
Also, take a look at using ActiveSupport::Concern instead of getting all Lovecraftian include-on-extend. http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

Preventing discoverability in a RESTfully routed model

I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.

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 /users?email=foo#bar.com
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.
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
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/byemail?email=foo#example.com. 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.
The first thing you can do is make your GETs as smart as possible. In your example, this can be handled programmatically. The argument can be processed this way:
Is a number? It's a userid;
Has a # in it? It's an email;
Otherwise? It's a username.
But I assume that you're not just talking about this example and want something to handle the general case rather than just this specific one.
There are basically two ways of dealing with this:
Add extra path information eg /users/email/me#here.com, /users/name/cletus; or
Be more specific in your "top-level" URL eg /user-by-email/me#here.com, /user-by-name/cletus.
I would handle it programmatically if you can.
Regarding the "ByEmail" request, have you considered creating a new email resource.
GET /email/foo_at_bar_dot_com
The response could contain a link to the related user.
I see so many people trying to apply RESTful design principles to their URL structure and then mapping those urls to procedural handler code. e.g. GET = Show, or is it GET = Index or ShowByEmail. By doing this you are really just pretending to do a RESTful design and then trying to create a mapping between a resource oriented URL space and procedurally oriented implementation. That is really hard to do and the procedural nature keeps leaking out into the URLs.
Resource oriented design often requires a very different way of thinking about problems that we are used to and unfortunately many of the frameworks out there keep sucking us back into the RPC model.
You might be able to set up different routes for different tasks. So for this case you could have one route to a method in UserControll dedecated to getting a user by email, and another for getting the information by credentials.

Resources