I have a logic question and I cannot figure out how to do it. First of all, I am working on an social networking site, and I completed the site in pure PHP, but now I am re-writing the backend in rails.
My questions is, I generated UsersController, it has new, create, show, edit, update, delete, and destroy.
I thought I could use "new" to display sign up page, "create" to process sign up, "show" to display profile page, "edit" to display account settings and "update" to process edit.
I might have a logic problem here, maybe I should put "new" and "create" in a signup controller. This where I get confused. The problem with the first logic I said, I have 2 layouts, one of them is for before login, and the other one is for after login. (You can imagine Facebook's header before login, and after login).
So, when I have 2 different layout, I cannot use 1 controller in 2 layout. Because sign up page has "before login header design" and account settings and profile has "after login header design". And as you can guess I define layout in controller.
I don't know if I explained well. Thank you.
By default, Rails will look-up a layout with the same name as the controller, or else application.html.erb. But you can also specify one controller-wide (which won't help you, but bear with me)
class SomethingController
layout "some_name"
...
That's still layout-wide, so not what you need.
But you can also specify a specific layout on each call to render in an action:
def edit
#some logic
render "some_template", :layout => "some_layout"
end
Or, to take the default template lookup, but still specify a layout:
def edit
# some logic
render :layout => "some_layout"
end
There's another way you can specify layouts too, which might be especially appropriate for the use case of "one layout if not logged in, another if logged in":
class SomeController
layout :method_name_to_determine_layout
# ... actions ...
protected
def method_name_to_determine_layout
if current_user
"logged_in_layout_name"
else
"not_logged_in_layout_name"
end
end
You can learn more about the different ways to specify layouts in the Layouts and Rendering Rails Guide
Hope this helps.
Rails has basic CRUD default actions. Additionally each action can have different processing depending on the HTTP verb. You can also add custom actions & routes.
It is best to follow standard Rails practices for each default action. For example, "new" action should route to the form to create a new user when accessed via GET. An HTTP POST to the form should route to the "create" action.
If you need to add an additional controller action, do so with a custom method. Again, I stress, simple CRUD actions should follow normal Rails conventions.
Read more about routing
Read this guide many times to understand simple CRUD actions in Rails
Instead of using 1 controller in 2 layouts, I decided to use separate controllers. So, I have profile_controller, which has "edit" and "update" for account settings and "show" to display profile. And I also users_controller, which has followings: login, login_attempt, signup, signup_attempt, etc..
So, I am not putting signup and edit together in 1 controller, instead using 2 different controllers is much better and clean, I guess.
Sounds like you're trying to roll your own authentication.
I'd recommend using Devise... great tutorial here:
The reason for this is two-fold.
Firstly, Devise gives you the ability to split your app between authenticated and non-authenticated users. Namely, it provides the user_signed_in?, devise_controller? and current_user helpers to aid with this.
This might not appear like a big deal, but it will actually help you with your layouts (I'll describe more in a second).
Secondly, Devise is pre-rolled. Your questions about how to handle signups and registrations have already been solved. Of course, there's nothing preventing you from making your own authentication (Devise is just built on Warden after all), but it should give you some ideas on how this has been done already.
In regards your original question (about layouts), the other answer is very good (in terms of setting layouts per method etc).
To add to it, I would say that you have to remember that Rails is a series of classes. As such, setting the layout option in the controller is the best way to ensure you're getting the correct one.
Here's Rails explanation on it:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
layout :your_layout
private
def your_layout
devise_controller? || !user_signed_in? ? "new_user" : "application"
end
end
I've found it better to keep your logic as terse as possible. IE only have one set of logic for the layout. We tend to keep it in the ApplicationController, overriding where necessary.
--
Finally, your questions also highlighted a base misunderstanding of Rails. I'm not being disrespectful; I've found the clearer your perception of the framework, the better you can work with it:
You have to remember several key points with Rails:
It's an MVC (Model View Controller) framework
It's built on Ruby; hence is object orientated
maybe I should put "new" and "create" in a signup controller. This where I get confused.
If you take Devise as a model, you'll see that you could treat your controllers as layers of abstraction for objects. That is, as Devise shows us, you can have sessions and registrations controllers.
I'm not advocating you do this exactly, I am trying to show that if you put the focus onto the objects you're working with, it becomes clearer where your controller actions should be placed.
You'll also be best understanding the CRUD (Create Read Update Destroy) nature of Rails controllers & objects. The standard Rails controller is set up as such:
Whilst this is not strict, it does give you another indication as to the structure your controllers should adhere to.
You may know this stuff already!
Related
Iam new in Ruby on Rails. Normally I work with other web languages and now (of course) I try to compare it with other languages using on web.
But sometimes i have some problem to understand the philosophy and the character of Ruby on Rails. Of course i understand the concept of MVC.
But now Iam not absolutely sure:
Is ist OK to create and use a controller without views? In some cases you need a "class" for some usefull functionality used by other controllers they have views. Or is it a better style to use a module?
I try to find it out by reading a lot of articles and examples, but didnt find detailed information about this content.
When developing Ruby On Rails apps, it's recommended to put most of your business logic in the models, so for the controllers the logic need to be minimal to provide info for the views, so if it doesn't work with a view chance that you need a controller are really low.
Is it OK to create and use a controller without views
Yep it's okay.
The key thing to keep in mind is that Ruby/Rails is object orientated.
This means that every single you do with your controllers/models etc should have the "object" you're manipulating at its core.
With this in mind, it means you can have a controller without corresponding views, as sometimes, you just need to manipulate an object and return a simple response (for example with Ajax).
--
We often re-use views for different actions (does that count as not having a view):
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def search
render :index, layout: false
end
end
The notion of skinny controller, fat model is sound in principle, you have to account for the times when you may need small pieces of functionality that can only be handled by a controller:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
#user = User.find params[:id]
#user.update
respond_to do |format|
format.js {render nothing: true}
format.html
end
end
end
A very rudimentary example of Rails is a drive-thru:
view = input interface
controller = accepts order & delivers
model = gets order packaged etc in backend
There are times when the controller may not need a view (for example, if you update your order with specific dietry requirements), and thus the notion that every controller action has to have a view is false.
It's really about making your controller versatile enough to manage your objects correctly.
Shared controller methods can be placed in ApplicationController. Also, in Rails 4 there are concerns (app/controllers/concerns/) where you can put the modules with methods that can be used by multiple controllers.
Controller handles request and renders corresponding view template. So controller without view is absolutely nonsense.
Every time request will execute this controller, it will just end with missing template error, so you will need to create view folder and put empty files with action name inside of it, which is obviously stupid.
I have a user model that differentiates into 6 roles, and I am defining different variables and directing them to different view files (similar format, but different tables of information), but within the same controller action, because they all have similar pages "overview", "profile", "messages", etc.
Now the controller is really messy, and has multiple if/else statements. I believe I should be changing the routes so that each user has its own controller, eliminating the use of the if/else monstrosity that currently invades the controller.
def index
if current_user.admin?
....
end
if current_user.moderator?
....
end
end
Question: How do I perform the routing such that url will be
www.website.com/1/schedule, where 1 = current_user.id, while having different view files rendered from the different controllers?
I am thinking of doing a AdminController and a ModeratorController to handle this, but am not sure how to do the routing, or if indeed this is the best way to do it. Thanks for advice in advance!
I think you are doing it the wrong way , If you have similar pages for different role then I think you are accessing same model for handling different roles.
If this is the case then you should use Gem like Cancan (authorization library for Ruby on Rails).
To address your second concern, I don't think this would be the best approach. Try this - move your logic away from multiple controllers, and keep the logic in the ApplicationController. I do not think that you should separate the different roles to controllers. Instead, look to your User model and put in a method that checks the privilege level for the different users. You could create methods in your User model that you could call in your controllers to see if the user is allowed access to the action. before_action would be recommended here.
If you decide to keep multiple controllers, I recommend a gem like Authority. Makes it much easier to keep track of different privileges, even across different controllers - I think routing is addressed as well: https://github.com/nathanl/authority
Im using bootstrap & rails and have a user model and post model..users create posts (collections)..
with bootstrap in the navbar i want the user to be able to click a dropdown which displays the name's of their posts..i did this on one controller with a private method and a before_action but i don't want to do this for all the controllers and it didn't work for the application controller...
is there a better way to do this??
I was doing this
def list
#user = User.find_by_username(params[:id])
#collections = #user.collections
end
and a
before_action :list
at the top of the controller
What's the most semantic way to accomplish this??
If you could move both to your application controller, then it would be available to any controller. More generally, I'm not sure if this is the best approach to solve your problem.
These tips might also be useful.
Are you using devise? Or some other authentication plugin? If so you're likely going to have a current_user helper. This would allow you to simply do #collections = current_user.collections
To the extent possible, I recommend using more descriptive names for your actions and parameters. def fetch_list_collections might be a better name or instead of passing a param named id, perhaps your param should be named username. These naming conventions become extremely important both for others who might look at your code as well as for yourself if you return to it and are trying to remember what you wrote N months ago.
Your list action is generating a N+1 queries. Meaning that you're hitting the database multiple times when you should do so just once. See the rails guide on this. You might also look at ways to avoid this w/ devise. Devise is pretty well documented and I'll bet there is something in the wiki discussing this.
You may want to consider limiting when you call this action - at a minimum - a post request to an update action? What about before they've logged in? current_user might be nil and you'd have an error attempting to call a collections method on nil.
Take your time learning this stuff. You don't have to learn it all at once, but I thought the above might be helpful.
I got it to work with this in the application controller
before_action :list
private
def list
#collections = current_user.collections
end
thanks #arieljuod
I am trying to keep my controllers nice a RESTful. One thing I keep running into is the need for a button or link on the site to trigger a specific event on a model. For example:
#user.ban!
Currently, I either make a custom named route on the users controller or if it's a more complex set of related actions, I create a new controller that acts on the same model as another "traditionally named" controller.
What is the best approach in this type of situation? What factors weigh into the decision?
In your routes you would typically have a resources declaration looking something like this
resources :users
The best way to add a restfull route to this is to define a ban method in the users controller and add a member route to the users route so your route ends up looking like this
resources :users do
member do
post :ban, :pay, :whatever
end
end
Use a memeber route for form post put actions, i.e. when using button_to or form_for (plus others) view helpers. Use collections for get requests (i.e. links)
Alternatively you could use <%= button_to 'Ban', #user %> then in the update action for the users controller check the commit params for the text ban and act accordingly
Actually I use this myself occasionally like so
if params[:commit] == 'Ban'
# do something like calling a ban method setting a flash notice or alert and redirecting
else
normal controller flow
end
Better still. Use i18n to display the text on the button and check the same i18n value against the commit param thereby leaving you free to change the text text on the button by updating the i18n yml file without breaking your controller code
First off, what jamesw says is good. There are lots of details here...
http://guides.rubyonrails.org/routing.html#non-resourceful-routes
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
... and I actually go with that for whatever unconventional routes I need. About the "factors that weigh into this decision," though... I would first ask myself if this eccentric action is absolutely needed, because more often than not Rails' "convention over configuration" policy comes in. From experience, I find that it's pretty rare for me to need atypical actions. I guess if you can justify it, though, don't feel guilty and go with it.
I have rarely ever had to make a whole 'nother controller, though.
I started a Rails project recently and decided to use RESTful controllers. I created controllers for my key entities (such as Country) and added index, new, edit, create, show, update and delete. I added my map.resources :country to my routes file and life was good.
After development progressed a little, I started to encounter problems. I sometimes needed extra actions in my controller. First there was the search action that returned the options for my fancy autocompleting search box. Then came the need to display the countries in two different ways in different places in the application (the data displayed was different too, so it wasn't just two views) - I added the index_full action. Then I wanted to show a country by name in the URL, not by id so I added the show_by_name action.
What do you do when you need actions beyond the standard index, new, edit, create, show, update, delete in a RESTful controller in Rails? Do I need to add (and maintain) manual routes in the routes.rb file (which is a pain), do they go in a different controller, do I become unRESTful or am I missing something fundamental?
I guess I am asking, do I need to work harder and add actions into my routes.rb file for the privilege of being RESTful? If I wasn't using map.resources to add the REST goodies, the standard :controller/:action, :controller/:action/:id routes would handle pretty much everything automatically.
I would treat search as a special case of index. Both actions return a collection of resources. The request parameters should specify things like page, limit, sort order, and search query.
For example:
/resources/index # normal index
/resources/index?query=foo # search for 'foo'
And in resources_controller:
before_filter :do_some_preprocessing_on_parameters
def index
#resources = Resource.find_by_param(#preprocessed_params)
end
As for index_full and search_by_name, you might look at splitting your current controller into two. There's a smell about what you've described.
Having said that, you're absolutely right that there's no point in forcing your app to user restful routes when it doesn't deliver anything over /:controller/:action/:id. To make the decision, look how frequently you're using the restful resource route helpers in forms and links. If you're not using them, I wouldn't bother with it.
If I go beyond the standard CRUD actions with my models, I normally just add the methods as required. Searching is something I add to many controllers, but not every one, so I add it and maintain the routes normally:
map.resources :events, :collection => { :search => :get }
Moving these actions to an entirely separate controller might keep some of your controllers RESTful, but I find that keeping them in context is far more useful.
REST does not specify that you can't have additional views. No real world application is going to be able use only the supplied actions; this is why you can add your own actions.
REST is about being able to make stateless calls to the server. Your search action is stateless each time as the data so far is supplied back, correct? Your alternate display action is also stateless, just a different view.
As to if they should be manual routes or a new controller, that depends on how distinct the activity is. Your alternate view, if it provides a full set of CRUD (create, read, update, delete) operations would do well to be in a new controller. If you only have an alternate view to the data, I would just add an alternate view action.
In other words, it doesn't sound like your application is failing to be RESTful, it is more an issue of realizing that the automatically generated feature set is a starting point, not a conclusion.
In my opinion they may have gone a bit off the rails here. What happened to DRY?
I'm just getting back into Rails not having done much development with it since beta and I'm still waiting for the light-bulb to come on here. I'm still giving it a chance but if it hasn't happened for me by the end of my current project I'll probably just drop-back to the old standard routes and define the methods as I actually need them for the next one.
I won't go on to explain more about REST since I think that has been answered in this question, however I will talk a little bit about the default route.
My main problem with the default route is that if you have multiple sites using the same Rails app it can look horrible.
For example there may be controllers that you don't want people to be able to see on one app:
http://example1.somesite.com/example_2/foo/bar/1
compare this to
/:controller/:action/:id
This would go to the controller example_2/foo, action bar and id 1
I consider this to be the main flaw of Rails' default route and this is something that RESTful routes (with subdomain extensions) or only named routes (map.connect 'foo' ... ) can fix.
To remain RESTful in your design, you need to rethink what you call a resource.
In your example a show action for a search controller, (search resource) is the direction to remain restful.
In mine, I have a dashboard controller (show) and controllers for single fields of in-place ecditors (show and update)