I'm making a page with activeadmin to update password of current user. I have a non-persisted model to check validation of password, etc. My problem is that when I try
ActiveAdmin.register UpdatePassword do
actions :edit, :update
end
It creates the routes /update_passwords/:id and /update_passwords/:id/edit.
I want to change those routes to /update_passwords via get and put.
Is there any way to change that?
I couldn't find a way to do it with activeadmin but defining the routes manually worked:
#config/routes.rb
match "/admin/update_passwords" => 'admin/update_passwords#edit', via: :get, as: "admin_update_passwords"
match "/admin/update_passwords" => 'admin/update_passwords#update', via: :post
Though the question is about 2 years old, but you can achieve routing as well as the customized method using collection_action or member_action. Refer this.
It seems to me that the controller name UpdatePassword is confusing.
The paths end up being something like:
edit_admin_update_passwords_path
update_admin_update_passwords_path
I think that this would be better:
ActiveAdmin.register Password do
actions :edit, :update
end
or
ActiveAdmin.register User do
actions :edit, :update
end
Related
Is there a way to use resource routing instead of writing the routes one by one if my methods for which the default expects parameters don't use parameters?
For example, if I had a routes file like below, the expected path for the update method would be like this: /cats/:id (docs)
# routes.rb
Rails.application.routes.draw do
resources :cats, only: [:create, :update]
end
However, I don't require any params for my update method, meaning the path should be /cats.
I know there's a way to rename the params and not use :id, but I didn't find anything on disabling them. I tried adding param: nil to the end of the line but it didn't work.
As I wrote initially, I know this can be done if I write the routes one by one like below. My question is whether I can use resources to do it. Thank you!
# routes.rb
Rails.application.routes.draw do
post 'cats', to: 'cats#create'
put 'cats', to: 'cats#update'
end
This is exactly the use case for singular resources. Quote from the Rails Guides:
Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like /profile to always show the profile of the currently logged in user.
Change our routing to
resource :cats, only: [:create, :update]
And the following routes will be created:
cats PATCH /cats(.:format) cats#update
PUT /cats(.:format) cats#update
POST /cats(.:format) cats#create
As far as I know, there is not, resource is just a helper to create the standard verb-based CRUD routes, if you want custom routes you need to define your update route the way you did in your second example, of course, you can still use resource for your create route and just pass only: :create.
get 'users/:id/edit/settings' => 'users#account'
What is the dry way to reference this path in link_to?
As a side note, I use 'users/:id/edit' to edit name/location/age etc and I am using the route above to edit password and email, because I wish to force the user to authenticate their :current_password before editing these more sensitive attributes. I mention this just to make sure my routing logic is correct.
Just run rake routes and you will see all the routes that you have in you app. It should be to the far right
You can use the as: option to setup a named route.
However I would set it up with conventional rails routes:
Rails.application.routes.draw do
resources :users do
resource :settings, only: [:edit, :update], module: :users
end
end
This would create an idiomatically correct RESTful route.
Using the singular resource creates routes without an id parameter. Also you should only use the name :id for the rightmost dynamic segment in a route to avoid violating the principle of least surprise.
rake routes will show you the following routes:
Prefix Verb URI Pattern Controller#Action
edit_user_settings GET /users/:user_id/settings/edit(.:format) users/settings#edit
user_settings PATCH /users/:user_id/settings(.:format) users/settings#update
PUT /users/:user_id/settings(.:format) users/settings#update
...
As a side note, I use 'users/:id/edit' to edit name/location/age etc
and I am using the route above to edit password and email, because I
wish to force the user to authenticate their :current_password before
editing these more sensitive attributes. I mention this just to make
sure my routing logic is correct.
Your route will in no way enforce this authorization concern.
Instead you should do a check in your controller:
# app/models/users/settings_controller.rb
class Users::SettingsController
before_action :set_user
before_action :check_password, except: [:edit]
def edit
# ...
end
def update
# ...
end
private
def set_user
#user = User.find(params[:user_id])
end
def check_password
# this is an example using ActiveModel::SecurePassword
unless #user.authorize(params[:current_password])
#user.errors.add(:current_password, 'must be correct.')
end
end
end
change it to:
get 'users/:id/edit/settings' => 'users#account', as: :edit_user_settings
and then you can just reference it as:
link_to edit_user_settings_path(#user)
rake routes will probably give you a path something like users_path which you can link to using something like
<%= link_to 'Users', users_path(#id) %>
So I have just created my rails 4.1.2 project with a controller, login_controller.rb and view login.html.erb. I have edited my routes.rb and removed the already put in route by rails after creating a controller and added another default match route which is :controller(/:action(/:id))', :via => :get
When I run my server using rails s and open localhost:3000/login it gives me this error:
The action 'index' could not be found for LoginController
I do not know why is it looking for an action named 'index'. If I am right then the default action taken by rails without actually specifying one is 'index' so I tried to change my URL and requested this:
localhost:3000/login/login
Which gave me this error:
No route matches [GET] "/login/login"
How can I fix this issue? Thanks.
Here's what my rake routes outputs:
Prefix Verb URI Pattern Controller#Action
GET /:controller(/:action:(/:id))(.:format) :controller#:action
Say you have a model User and controller UsersController. In your routes.rb file, you list resources and routes to match based on controller actions. By default, that resource includes :index, :show, :new, :edit, :create, :update, :destroy. You can specify which you want (or not) by doing:
# config/routes.rb
# blacklisting - these are not in your controller
resource :users, exclude: [:show, :destroy], via: [:get, :post]
# whitelisting - these are in your controller
resource :users, only: [:index], via: [:get, :post]
To my understanding, the filename of your view (unless rendered or an asset) corresponds to the controller action. Is the view for your login page named index.html.(erb|haml)?If so, you should define the index even as simply as def index; end. However, I suggest you include something that redirects the user to home or the login page, depending whether they are already logged in.
From there, the real magic happens in your controller as you define a method login. Here, check the incoming parameters and log the user in if correct. Think of this similar to an #update action.
Similar to how you define which actions to include in the routes, you can make your own custom ones. Do note: the order of routes matter. I am not 100% on that concept myself, but this is what I would suggest in your case:
#config/routes.rb
resource :login, only: [:index], via: [:get, :post]
match '/login/login' => 'login#login', via: [:get, :post]
You can also use match to link /login/index to the #login method. The view would be called index still. And just because there is a route for it, you do not need a view for the login action -- it simply parses the parameters, while the index would show error messages.
Add an index action on your LoginController
def index
end
This will give you the desired result. localhost:3000/login/login. Rember to restart server at the end.
app/controller/login_contrtoller.rb
class LoginController < ApplicationController
def login
end
end
routes
get 'login/login'
/app/views/login/login.html.erb
<h1>Login#login</h1>
<p>Find me in app/views/login/login.html.erb</p>
I'm just adding ActiveAdmin to my app, I got a problem using show/edit/destroy action cause my link doesn't point to ID but to users name (in order to be more readable for user).
ActiveAdmin correctly create my link like:
edit link:
http://localhost:3000/admin/users/paul/edit (where paul is the user name)
in that case I get:
Couldn't find User with ID=paul
cause of course Paul is not the id but the user name.
How can I custom ActiveAdmin to use find_by_name(params[:id]) like in my application for all the action show/edit/delete?
In other model I got a so called "SID" which is a generated salted ID and I would like to use also the find_by_sid(params[:id]) as well for other models.
There is a cleaner way to do this:
ActiveAdmin.register User do
controller do
defaults :finder => :find_by_slug
end
end
This will do the job in the app/admin/user.rb :
ActiveAdmin.register User do
before_filter :only => [:show, :edit, :update, :destroy] do
#user = User.find_by_name(params[:id])
end
end
If you followed this railscast: http://railscasts.com/episodes/63-model-name-in-url-revised and have custom routes, you can fix the active_admin routes by placing this in the app/admin/user.rb:
before_filter :only => [:show, :edit, :update, :destroy] do
#user = User.find_by_slug!(params[:id])
end
It's really close to the one shown by afiah, just slightly different.
My User model has the usual id primary key, but it also has a unique login which can be used as an identifier. Therefore, I would like to define routes so that users can be accessed either by id or by login. Ideally, the routes would be something like this:
/users/:id (GET) => show (:id)
/users/:id (PUT) => update (:id)
...
/users/login/:login (GET) => show (:login)
/users/login/:login (PUT) => update (:login)
...
What is the best way to do this (or something similar)?
So far, the best I could come up with is this:
map.resources :users
map.resources :users_by_login,
:controller => "User",
:only => [:show, :edit, :update, :destroy],
:requirements => {:by_login => true}
The usual RESTful routes are created for users, and on top of that, the users_by_login resource adds the following routes (and only those):
GET /users_by_login/:id/edit
GET /users_by_login/:id/edit.:format
GET /users_by_login/:id
GET /users_by_login/:id.:format
PUT /users_by_login/:id
PUT /users_by_login/:id.:format
DELETE /users_by_login/:id
DELETE /users_by_login/:id.:format
These routes are actually mapped to the UserController as well (for the show/edit/update/destroy methods only). An extra by_login parameter is added (equal to true): this way, the UserController methods can tell whether the id parameter represents a login or an id.
It does the job, but I wish there was a better way.
Just check to see if the ID passed to the controller methods is an integer.
if params[:id].is_a?(Integer)
#user = User.find params[:id]
else
#user = User.find_by_login params[:id]
No need to add special routes.
Actually Kyle Boon has the correct idea here. But it is slightly off. When the params variable comes in all the values are stored as strings so his example would return false every time. What you can do is this:
if params[:id].to_i.zero?
#user = User.find_by_login params[:id]
else
#user = User.find params[:id]
end
This way if the :id is an actual string Ruby just converts it to 0. You can test this out by looking at the params hash using the ruby-debug gem.
(I would have just commented but I don't have enough experience to do that yet ;)
Not exactly sure what you are doing here but this may be of some help.
You can define actions that are outside of the automatic RESTful routes that rails provides by adding a :member or :collection option.
map.resources :users, :member => { :login => [:post, :get] }
This will generate routes that look like this:
/users/:id (GET)
...
/users/:id/login (GET)
/users/:id/login (POST)
Another thing you could do just use the login as the attribute that you look up (assuming that it is unique). Check out Ryan Bates screencast on it. In your controller you would have:
def show
#user = User.find_by_login(params[:id])
...
end
He also has another screencast that may help you. The second one talks about custom named routes.