I have a model Whitelabel and a User has_many :whitelables
I have a custom method current_whitelabel (like authlogic or restful_auth for current_user)
I want my users to manage their whitelabels (ie: edit_whitelabels_path(id)).
But I don't want to send the whitelabel ID in params when it refers to the current_whitelabel.
So my idea is to create two resources: map.resources whitelabels and map.resource whitelabel.
But I don't like this so much. Is there any sexier way to accomplish it ?
Ok I finally solved my problem.
Each whitelabel has his own subdomain (thanks to subdomain_fu), so I just need a single resource whitelabel in my routes to do action on my current_whitelabel and if I want to do action on others whitelabels, I just have to switch the subdomain.
Thanks EmFi for trying to answer to my strange question.
In your controller action you can do this:
class WhitelabelsController < ActionController
def edit
#whitelabel = params[:id] ? Whitelabel.find(params[:id]) : current_whitelabel
redirect_to whitelabels_url unless #whitelabel
....
end
...
end
Now rails will treat /whitelabel/edit as /whitelabel/edit/#{current_whitelabel.id} without specifying the id.
If this happens for more than one action you can do it as a before filter. Just be sure to remove all #whitelabel = Whitelable.find(params[:id]) lines from the individual actions.
class WhitelabelsController < ActionController
before_filter :select_whitelabel, :except => [:index, :new]
def select_whitelabel
#whitelabel = params[:id] ? Whitelabel.find(params[:id]) : current_whitelabel
redirect_to whitelabels_url unless #whitelabel
end
...
end
Answering the more clearly stated question in the comment:
You can use a singular resource in tandem with the above code to have the effect you want.
config/routes.rb
map.resource :my_whitelabel, :controller => "whitelabels", :member => {:dashboard => :get}
Then in the whitelabels controller use the above code. This keeps things DRY by using the same controller for different paths with the same actions. The resource defines a dashboard action, so you'll have to add that to the controller too. But if you're using the before_filter version there should be no problem.
Related
I have a pretty typical situation where I have a '/dashboard' which should render a different view for different user roles (i.e. client, admin, etc.).
I am open to more elegant suggestions but my thought was to have one route definition for dashboard like so:
routes.rb
resource :dashboard
and to have a dashboards_controller.rb like so:
class DashboardsController < ApplicationController
def show
if current_user.has_role?('sysadmin')
// show system admin dashboard
elsif
// show a different dashboard
// etc
end
end
end
Now I would like that each dashboard gets built in its role specific namespaced dashboard_controller, ex: controllers/admin/dashboard_controller.rb. This way, each dashboard can be appropriately built up in the right place.
The way I am trying to do this is to redirect from my main dashboards_controller to the admin/dashboard_controller like so:
redirect_to :controller => 'admin/dashboard_controller', :action => 'index'
But it is not working, presumably because I am not sure how to reference a namespaced controller from here.
How can I achieve this?
(If there is a more elegant solution I am open but I thought this was pretty good).
I am using devise and cancancan.
You can do per-role dashboard, for example:
routes.rb
scope '/:role' do
resources :dashboard
end
resources :dashboard
Then to redirect with the role, just simply:
redirect_to :controller => 'dashboard', :action => 'index', :role => 'admin'
The Better Way
If you want custom dispatch per controller, you should consider using filter. E.g. given admin/dashboard only accessible by admin and user/dashboard only accessible by default user, you may want to create files like this:
routes.rb
namespace 'admin' do
resources :dashboard
end
namespace 'user' do
resources :dashboard
end
Then you create these files:
app/controllers/admin/dashboards_controller.rb
app/controllers/admin/admin_base_controller.rb
app/controllers/user/dashboards_controller.rb
app/controllers/user/user_base_controller.rb
For each files:
# app/controllers/admin/dashboards_controller.rb
class Admin::DashboardsController < Admin::AdminBaseController; end
# app/controllers/admin/admin_base_controller.rb
class Admin::AdminBaseController < ApplicationController
before_action :ensure_admin!
def ensure_admin!
redirect_to controller: 'user/dashboards', action: index unless current_user.has_role?('sysadmin')
end
end
# Now you've got the idea. Similar things for the rest of the files:
# app/controllers/user/dashboards_controller.rb
# app/controllers/user/user_base_controller.rb
Then you can try visiting it at admin/dashboards and user/dashboards, it should be redirected to its role accordingly.
It's good to use named route helpers rather than providing controller and action explicitly.
Consider adding routes as follows:
namespace :admin do
resource :dashboard, controller: 'dashboard '
end
Then you can call :
redirect_to admin_dashboard_url
Remember, it's resource, not resources. So it is going to process dashboard_controller#show, not dashboard_controller#index
I have a like model, recording which user liked which record. I used polymorphic association so a user can like many models.
Currently I use nested-resources to handle likes.
POST /items/:item_id/likes
DELETE /items/:item_id/likes/:id
Now for some reasons I want to get rid of the use of like_id by designing a better route. This is because it will be easier to cache a fragment view.
Note that item model is only one of a few models which are likable, and I want to avoid code duplication if possible.
What's a good way to design routes and controllers that will not use like_id but also allows better code reuse in controller?
Possible implementation
I was thinking of routes like this:
POST /items/:item_id/like
DELETE /items/:item_id/like
I won't use nested like resource. Instead I place a like action in items controller. It will determine if the request is a POST or a DELETE and act accordingly. This however doesn't feel DRY.
I don't know about Rails necessarily, but in Zend Framework I would create a front controller plugin to route all requests with methods 'LIKE' and 'UNLIKE' to a particular controller which then deduces which route was requested, and subsequently which resource was requested, and then performs the necessary actions to 'like' or 'unlike' that resource in the name of the requesting user.
Why? Because the user is 'like'-ing or 'unlike'-ing the resource in question, not 'creating a like' or 'deleting a like'. Sure, in the backend, the 'like' is a record in a cache or database that gets created or deleted -- but the semantics of a resource are not necessarily equivalent that of whichever method is used to persist that resource.
What you need is Singular Resources.
routes.rb
resources :items do
resource :like, only: [:create, :destroy]
end
likes_controller.rb
class LikesController < ApplicationController
before_action :load_likeable
def create
#like = Like.where(likeable: #likeable, user: current_user).first_or_create
redirect_back(fallback_location: #likeable)
end
def destroy
#like = Like.find_by(likeable: #likeable, user: current_user).destroy
redirect_back(fallback_location: #likeable)
end
private
def load_likeable
klass = [Recording].detect { |c| params["#{c.name.underscore}_id"] }
#likeable = klass.find(params["#{klass.name.underscore}_id"])
end
end
likes_helper.rb
module LikesHelper
def like_button_for(item)
if item.liked
form_tag recording_like_path(item), method: :delete do
button_tag "UnLike"
end
else
form_tag recording_like_path(item), method: :post do
button_tag "Like"
end
end
end
end
item.liked is method from Item model
So currently I have something like /users/1/ when I want to view a user profile. How can I go through routes.rb to change that to /user/chiggins/ where chiggins is a unique username?
You need is to override to_param method in User model:
class User
def to_param
username
end
end
Then rails will use it automagically for routing. See http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-i-to_param
Another possibility to consider would be the friendly_id gem - https://github.com/norman/friendly_id
Nowadays there is a :param argument on the resource declaration.
http://guides.rubyonrails.org/routing.html#overriding-named-route-parameters
You can get per-resource identifier customization by redefining the member_scope and nested_scope methods on the Resource instance.
resources :users do
#scope[:scope_level_resource].tap do |u|
def u.member_scope
"#{path}/:username"
end
def u.nested_scope
"#{path}/:#{singular}_username"
# member_scope also usable here, assuming username will be nowhere in nested routes.
end
end
end
Regarding the question about #nested_scope below: It gets used when you do something like this in routing:
resources :members do
resources :playlists, only: :index
end
Then, the param would be :member_username instead of just :username. This is useful in the playlists controller when assembling the collection so you can infer the scope of the request.
The best way is to define a route with a custom param:
match "/users/:username" => "users#show"
In your controller, the plain old params[:id] will be params[:username], and you can get the user from de DB using:
User.find_by_username(params[:username])
In my "routes.rb" file I have the following line:
resource :users
which gives me a bunch of named routes for accessing my User model in a RESTful manner.
Now, I've made some additions to the User model including creating a special class of user. These are still stored in the User model but there is a "special" flag in the database that identifies them as special.
So, is it possible to create special_users resource? For example, I'd like to have a "special_users_path" as a named route to "/special_users" which will return an index of only the special users when you perform a GET on the URL.
Is there a way to do this?
In Rails routing, a 'resource' refers to the standard 7 routes that are created for RESTful resources: index, show, new, create, edit, update and destroy. Normally that is enough, but sometimes you might want to create another action.
In the model, you want to create a scope that only returns special users:
class User < ActiveRecord::Base
scope :special, where(:special => true)
end
On the controller side, there are two ways to go about this. What you are suggesting is the creation of an additional action:
match "/users/special" => "users#special"
resource :users
In the controller, your special action would return the scope you just created:
class UsersController < ApplicationController
def special
#users = User.special
end
end
That will do what you ask, but I would suggest NOT doing it this way. What if you add other flags later that you want to search by? What if you want to search by multiple flags? This solution isn't flexible enough for that. Instead, keep the routes the way they are:
resource :users
and just add an additional line to your controller:
class UsersController < ApplicationController
def index
#users = User.all
#users = #users.special if params[:special]
end
end
and now, when you want to display special users, simply direct the user to /users?special=true
This approach is much more future-proof, IMO.
(This answer is assuming Rails-3. If you're still using 2.3 let me know)
You could set the special_users as a resource:
resource :special_users
If you need to point it to a special controller, you could specify it with:
resource :special_users, :controller => :users
But I would really suggest you to not creating another controller for retrieving a kind of user, but using a param to get them:
class UsersController < ApplicationController
def index
users = case params[:type].to_s
when "special"
User.special_users # Using named scopes
else
User.all
end
end
end
When you use the users_path to call the special users:
users_path(:type => :special)
Imagine you have two defined routes:
map.resources articles
map.resources categories, :has_many => :articles
both accessible by helpers/paths
articles_path # /articles
category_articles_path(1) # /category/1/articles
if you visit /articles, index action from ArticlesController is executed.
if you visit /category/1/articles, index action from ArticlesController is executed too.
So, what is the best approach for conditionally selecting only the scoped articles depending on the calling route?
#if coming from the nested resource route
#articles = Articles.find_by_category_id(params[:category_id])
#else
#articles = Articles.all
You have two choices here, depending on how much your logic and your view is tied to the scope.
Let me explain further.
The first choice is to determine the scope within your controller, as already explained by the other responses. I usually set a #scope variable to get some additional benefits in my templates.
class Articles
before_filter :determine_scope
def index
#articles = #scope.all
# ...
end
protected
def determine_scope
#scope = if params[:category_id]
Category.find(params[:category_id]).articles
else
Article
end
end
end
The reason for the #scope variable is that you might need to know the scope of your request outside the single action. Let's assume you want to display the number of records in your view. You need to know whether you are filtering by category or not. In this case, you simply need to call #scope.count or #scope.my_named_scope.count instead of repeating each time the check on params[:category_id].
This approach works well if your views, the one with category and the one without category, are quite similar. But what happens when the listing filtered by category is completely different compared to the one without a category? This happens quite often: your category section provides some category-focused widgets while your article section some article-related widgets and filter. Also, your Article controller has some special before_filters you might want to use, but you don't have to use them when the article listing belongs to a category.
In this case, you might want to separate the actions.
map.resources articles
map.resources categories, :collection => { :articles => :get }
articles_path # /articles and ArticlesController#index
category_articles_path(1) # /category/1/articles and CategoriesController#articles
Now the listing filtered by category is managed by the CategoriesController and it inherits all the controller filters, layouts, settings... while the unfiltered listing is managed by the ArticlesController.
This is usually my favorite choice because with an additional action you don't have to clutter your views and controllers with tons of conditional checks.
I often like to separate those actions. When the resulting actions are very similar you can separate the scopes inside the controller easy by seeing if params[:category_id] is present etc (see #SimoneCarletti answer).
Normally separating actions in the controller by using custom routes gives you most flexibility and clear results. Following code results in normal route helper names but the routes are directed to specific actions in controller.
In routes.rb:
resources categories do
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_articles'
end
end
resources articles, :except => [:index] do
get :index, :on => :collection, :action => 'index_all'
end
Then you can have in ArticlesController.rb
def index_all
#articles = #articles = Articles.all
render :index # or something else
end
def index_categories
#articles = Articles.find_by_category_id(params[:category_id])
render :index # or something else
end
Having only a single nested resource, using a conditional based on the params to determine it's scope would be the easiest approach. This is likely the way to go in your case.
if params[:category_id]
#articles = Category.find(params[:category_id]).articles
else
#articles = Article.all
end
However, depending on what other nested resources you have for the model, sticking with this approach can get quite tedious. In which case, using a plugin like resource_controller or make_resourceful will make this much simpler.
class ArticlesController < ResourceController::Base
belongs_to :category
end
This will actually do everything you'd expect. It gives you all your standard RESTful actions and will automatically setup the scope for /categories/1/articles.
if params[:category_id].blank?
# all
else
# find by category_id
end
I like to consider the action independent from the route. No matter how they get there, make a reasonable decision as to what to do.