Which rails controller should I use? - ruby-on-rails

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.

Related

Complicated Website Structure - Rails

I what I consider to be an advanced beginner with Ruby on Rails (and web design in general). I have this project and I'm having trouble wrapping my head around how to implement a recent development.
Initially the project was a simple blog for businesses to post articles that promote their company. It was meant for businesses in a specific town only and I had no trouble setting that up. Now we're trying to broaden out and let the site cater to many towns.
We want to have a home page where you select a town. That takes you to a list of blog posts from businesses in that town. You can navigate to a directory, contact us form, profile page etc. My problem is, I'd like each town to act like mini-site, so the directory only shows business associated with the town and the header image changes to reflect which town you've chosen.
My initial approach to this problem was to set up a towns scaffold and put in associations between towns, users and posts. The problem is 'storing' the chosen town somehow so that I can, for example, show only businesses for a specific town in the directory.
At the moment I have, in the posts model
belongs_to :user
In the user model
has_many :posts
has_many :towns
I have an action in towns controller to handle the directory which only has the line:
#users = User.order('trading_name ASC').where(:town => #town.id.to_s)
and then I want to display those users in the directory view.
I also have the following in my towns controller
before_action :set_town, only: [:show, :edit, :update, :destroy, :directory]
To be honest though, I can't fully wrap my head around the associations needed or even if this is the correct path to take. As it stands I get the following error when I try to access the directory view
Couldn't find Town without an ID
and it references the following method in towns controller (I'm using the friendly_id gem)
def set_town
#town = Town.friendly.find(params[:id])
end
Am I heading down the correct route with these associations (and missing something) or is there a better approach I could be taking. Also, I hope I've explained myself clearly. Please ask if there's something that's confusing.
I'm not sure exactly what your requirements are, but I'd say:
A user probably doesn't "have many" towns.
A user does "have many" posts.
A town does "have many" posts.
A post belongs to a town, and also a user.
Then, on your home page, you'd have a form with a drop-downlist of towns, and it should sumbit to an action like choose_town or something.
Your choose_town action should save the name or id of the town in a Cookie. You know about cookies? They basically allow you to save little bits of info between requests, for a specific user. They are stored in a users browser, but are sent back/forth from the server on every request. See http://guides.rubyonrails.org/action_controller_overview.html#cookies
Then, your set_town method, called every request, will look in the cookie to find the right town.
edit: #japed mentioned session, along with cookies. The session is identified by a cookie - however, information stored in the session is saved on the server, and is not sent back and forth between the browser and the server. In this case, either one is probably fine.

Different update / edit methods available to different users

I have a model Post, which is submitted and graded by different Users. The submitter and grader are identified by submitter_id and grader_id in Post model. Note that an user is both a submitter himself and a grader to others.
I want to make sure that the submitter can only edit the content of the Post but not the grade. Likewise, the grader can only edit the grade but not the content.
Is multiple edit methods the way to go? How should I accomplish this otherwise?
You can have a role column in your users table, and the role can be either submitter or grader. Not sure what you are using for authentication, but in case you are using devise, you can access the currently logged in user with current_user helper (in case you are using something else, figure this part out, or add a new helper).
Now in your update method, you can do something like this:
# Controller
# scope post to current user, so that a user cannot edit someone else's post. A crude way to achieve this is post = Post.find(params[:id])
post = current_user.posts.find(params[:id])
post.content = params[:content] if post.submitter?(current_user.id)
post.grade = params[:grade] if post.grader?(current_user.id)
post.save!
# Model - Post.rb
def submitter?(user_id)
self.submitter_id == user_id
end
def grader?(user_id)
self.grader_id == user_id
end
The advantage of keeping those methods in the model is that in case you permission logic changes (who is submitter, or a grader), you need to change it at a single location. DRY.
You can modify the above approach to show error messages, and do other similar stuff. In case you are looking for more granular authorization control, you can look into cancan gem:
https://github.com/ryanb/cancan
Your post model should only be concerned with persisting data. Better to use plain old ruby objects to encapsulate the higher order behavior of grading and submitting. Consider using service objects or form objects.
Each service or form object can then include ActiveModel::Model(rails > v4) to get its own validations.
See more about service and form objects here: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
If you only have one submit action and one grade action, its probably ok to keep in one controller. But if you start having multiple actions that are related to submitted, and multiple actions that are related to grading, this sounds like they would make great resources controllers on their own.

How do you handle current user and a list of users?

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.

Where to put this code?

[Rails] Where to put this code?
I have a user1 and when another registered user2 sees the profile of user1, has some buttons on it: ['add as friend', 'give me your number', 'give me your email', 'ask her out', 'view photos']. The 1,2,3,4 are POST, with AJAX. Now, i have to make a new controller named 'ProfileActionsController' or i should put this code in the 'UsersController'?
or maybe a another posiibility? thanks ;)
You can do both. To avoid UsersController from becoming too bulky you should put it new controller which will help for maintainance .
You will most likely have to store some of these relationships in different database tables. For example, User has_many :friends. This design encourages a Friend model. Which leads to a FriendsController and to urls like POST /user/1/friend to create a friendship between the current user (user2) and user 1.
Those belong in a separate controller.
If you need more Ajax actions on a user, defining them in UsersController is the right place. "Give me your number", "Give me your email" and "View Photos", depending on requirements, could be hidden sections of the html, or simple Ajax GET requests to the UserController to render partials or JSON.
Those can stay on the UserController
GENERAL ADVICE: Always try to stay within the 7 actions for each controller (new, create, edit, update, index, show, destroy) - when you feel you need to define your own action, think about which of the 7 it is closest to. Can it be combined gracefully? If not, then is it acting on a separate concept?
If it's an action made on an user (i.e., that in someway modifies a user through its model), then ideally you should put those actions inside the users_controller.

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