I have seen this question, but the only answer there suggests creating a custom route, which I am reluctant to do. What are the alternatives, using Rails' default RESTful routes?
In particular, I've begun using this setup:
# routes.rb
# Note the singular resource
resource :all_apples, path: 'apples/all', only: :destroy
# all_apples_controller.rb
class AllApplesController
def destroy
# Something like:
User.find(params[:id]).apples.delete_all
end
end
Then I can do DELETE /apples/all to delete all apple records.
I would also have a separate ApplesController with the standard individual CRUD actions, so I can still do, for example, GET /apples.
Would this be a "RESTful" way to solve the problem? Are there any notable issues with it?
Note
My actual use case has to do with token revocation. I want an endpoint that revokes all of a user's web tokens. I'm currently using DELETE /users/:id/tokens/all, as described above.
I have been thinking about this.
I really don't like to do anything like this on a controller.
I would create a special nested route for users
# config/routes.rb
resources :users do
delete '/delete_all_apples', to: 'users#delete_all_apples'
end
So now we have a special route to delete all apples from users
on the console you can find this routes like this
rails routes | grep delete_all_apples
# user_delete_all_apples DELETE /users/:user_id/delete_all_apples(.:format) users#delete_all_apples
On your view your link should be
<%= link_to 'Destroy All Apples', user_delete_all_apples_path(user.id), data: {:confirm => 'Are you sure?'}, :method => :delete %>
Your Controller
class UsersController < ApplicationController
def delete_all_apples
User.find(params[:id]).delete_all_apples
end
end
On your Model
class User < ActiveRecord::Base
has_many :apples
def delete_all_apples
apples.delete_all
end
end
Related
I have never found a good solution for this problem. I have the following routes structure:
resources :contents
namespace :admin do
resources :contents
end
When I call content_path(content) I want the id to be the slug of the content, while when I call admin_content_path(content) I want the id to be the id of the content. I just want the id not to be related to the model (actually the id is the returning value of the to_param method of the model), but to the route.
I would like to avoid defining helper methods for every route, it's a weak solution in my opinion.
I know I can write admin_content_path(id: content.id) or content_path(id: content.slug), but this is just an hack actually. Also, this is especially annoying in form_for, since I can't write
form_for #content
but I'm forced to use
form_for #content, url: #content.new_record? ? admin_contents_path : admin_contents_path(id: #content.id)
Usually, you would change the route to:
resources :contents, param: :slug
and then you override to_param method to become:
class Content < ApplicationRecord
def to_param
slug
end
end
And finally in your controller, you replace Content.find(params[:id] with Content.find_by(slug: params[:slug]).
That will give you URLs like /contents/foo-bar when you call content_path(content).
In your case, you can additionally create a subclass that overrides the to_param method:
module Admin
class Content < ::Content
def to_param
id && id.to_s # This is the default for ActiveRecord
end
end
end
Since your admin/contents_controller.rb is namespaced under Admin (e.g Admin::ContentsController), it will by default use the Admin::Content class instead of the normal Content class, and thus the object itself and all routes should be as you like them to be, including forms.
I would say that's two different problems : URL generation for your resources on the user front-end side (using slugs) and URL generation for your admin forms.
Obviously in your admin, you will never be able to just write form_for #resource because your admin is namespaced, so the minimum would at least be form_for [:admin, #resource].
Let's say you have to_param on some of your models to return a slug, you may create your own customised helpers on your admin back-office to always return a path namespaced to /admin/ and using the id of the record.
One generic way to do that is adding this kind of code in your Admin root controller.
class Admin::AdminController < ApplicationController
helper_method :admin_resource_path, :edit_admin_resource_path
def admin_resource_path(resource)
if resource.new_record?
polymorphic_path([:admin, ActiveModel::Naming.route_key(resource)])
else
polymorphic_path([:admin, ActiveModel::Naming.singular_route_key(resource)], id: resource.id)
end
end
def edit_admin_resource_path(resource)
polymorphic_path([:edit, :admin, ActiveModel::Naming.singular_route_key(resource)], id: resource.id)
end
end
Then in your form you can use form_for(#user, url: admin_resource_path(#user). It will work on both user creation and user edition.
You will be able to use those helpers also in your controllers to redirect...
Well, I found a nice solution, but only on Rails >= 5.1 (which is in rc1 at the moment), using the brand new direct method:
namespace :admin do
resources :contents
end
# Maps admin content paths in order to use model.id instead of model.to_param
{ admin_content: :show, edit_admin_content: :edit }.each do |direct_name, action|
direct direct_name do |model, options|
options.merge(controller: 'admin/contents', action: action, id: model.id)
end
end
I have a Users table which also has a manager's id to implement a self-join. when I login as a a manager and click on "My subordinates", I should see my subordinates. The subordinates are also from the User table.
So my question is
What should I say here <%= link_to "My Subordinates", ????_path %>(I mean like user_path.).
How should the model and controller logic be?
I would do something like #ryanfelton said, but instead of overwriting the index method, i would create a new one specifically for the subordinates.
class Manager::UsersController < ApplicationController
before_action :ensure_manager! #this one check the manager_id or any other condition to be manager
def sobordinates
#subordinates = #user.subordinates
end
end
#routes.rb
namespace :manager do
resources :users do
collection do
get :subordinates
end
end
end
This way you can maintain the index of users and you have a method only for the subordinates.
Be aware that you need to create a subordinates.html.erb inside the users folder >
app/views/manager/users/subordinates.html.erb
EDIT:
You where asking for the model and the link also so, here it goes:
The link: after editing the routes.rb, go to the console and use rake routes
and search for the subordinates link. Add the _path or _url depending on the use you are whiling for that path.
The model, I strongly recommend you to read the official documentation about relations: http://guides.rubyonrails.org/association_basics.html. That would help you more than having the answer for copying and pasting :)
I would recommend namspacing a users_controller.rb.
So it would be in the folder app/controllers/manager/users_controller.rb
class UsersController < ApplicationController
before_action :ensure_manager!
def index
#manager.users
end
end
In the routes.rb you would have this route:
namespace :manager do
resources :users
end
So ultimately your path would be manager_users_path
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
I have nested resources like so
resources :users do
resources :widgets
end
When I have #widget, to get a proper routing from my helpers i need to use user_widget_path(#widget.user, #widget) is there any way to tell rails to automatically pull out the user from #widget object? So i could just use user_widget_path(#widget)
#apneadiving is totally right. But you can improve a little your approach:
link_to "user", user_widget_path(#widget.user, #widget)
can be presented shorter:
link_to "user", [#widget.user, #widget]
UPD
Also you can rewrite user_widget_path as you want:
class ApplicationController < ActionController::Base
helper_method :user_widget_path
private
def user_widget_path(widget)
super(widget.user, widget)
end
end
You should also rewrite edit_user_widget_path, new_user_widget_path. And better to wrap it as an external Module.
There is no automatic method to do this. But you could create your own application helper, it's pretty straight.
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.