Rails: Route to custom controller action - ruby-on-rails

I have a really hard time understanding routes and I hope someone can help me.
Here's my custom controller
class SettingsController < ApplicationController
before_filter :authenticate_user!
def edit
#user = current_user
end
def update
#user = User.find(current_user.id)
if #user.update_attributes(params[:user])
# Sign in the user bypassing validation in case his password changed
sign_in #user, :bypass => true
redirect_to root_path
else
render "edit"
end
end
end
and I have the file settings/edit.html.erb and my link
<li><%= link_to('Settings', edit_settings_path) %></li>
The route
get "settings/edit"
doesn't work for this, because then I get
undefined local variable or method `edit_settings_path' for #<#<Class:0x00000001814ad8>:0x00000002b40a80>
What route do I have to give this? I can't figure it out. If I put "/settings/edit" instead of a path it messes up as soon as I'm on a other resource page because the resource name is put BEFORE settings/edit
Thx

Following should do:
get 'settings/edit' => 'settings#edit', :as => :edit_settings
# you can change put to post as you see fit
put 'settings/edit' => 'settings#update'
If you use /settings/edit directly in link, you shouldn't have problem with other resource name being prepended in path. However, without the leading slash, i.e. settings/edit it might have that issue.
Reason why edit_settings_path is not working might be because you didn't declare a named route. You have to use :as option to define by which method you will be generating this path/url.

If you want to explicitly define the route, you would use something like
get 'settings/edit' => 'settings#edit', :as => edit_settings
This statement defines that when a GET request is received for setting/edit, call the SettingsController#edit method, and that views can reference this link using 'edit_settings_path'.
Take some time to read the Rails guide on routing. It explains routing better than any other reference out there.
Also keep in mind the rake routes task, that lists the details of all the routes defined in your application.

Related

Devise - Allow both individual and admin user creation

I have seen the few links on here regarding this topic but as yet have been unable to crack the problem. I want my application to allow users to register but also to register other users. So far my code lucks like so (though at this point I'm not sure I'm even on the right path and have probably included stuff which is not required just to make it work):
routes.rb
resources :devise
devise_for :users, path: 'devise'
devise_scope :user do
get 'users/registrations/admin_new' => 'devise/registrations#admin_new'
post 'users/registrations/admin_create' => 'devise/registrations#admin_create'
end
These are the two methods I have referenced in my route.
controllers/user/registration_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
def admin_new
puts "---------------"
#user = User.new
end
def admin_create
puts "---------------"
end
end
My view is as follows but shortened so as not to include all the fields:
views/devise/registrations/admin_new.html.haml
= simple_form_for(User.new, url: users_registrations_admin_create_path(User.new)) do |f|
...
This set up has gotten me to the point that the url:
http://localhost:3000/users/registrations/admin_new
loads the form alright however the puts I inserted does not appear in my console when it loads which is strange. Also i previously had the form as:
= simple_form_for(#user, url: users_registrations_admin_create_path(#user)) do |f|
...
however this resulted in the error:
undefined method `model_name' for nil:NilClass
which is why I changed to User.new instead of #user. When i submit the form it returns the error:
The action 'admin_create' could not be found for Devise::RegistrationsController
I'm at a loss as to why this is. I'm also unsure as to whether the structure of my devise is correct given the controllers are contained within a users folder while the views are contained with a devise folder. Any help on this would be greatly appreciated.
your route is problem.
post 'users/registrations/admin_create' => 'devise/registrations#admin_create'
which means this route to admin_create action in Devise::RegistrationsController
so router find a action in Devise::RegistrationsController but action "admin_create" exists in Users::RegistrationsController,
change
'devise/registrations#admin_create'
=> 'user/registrations#admin_create'
update1
I check your route out.
assigned route path to controller in 'controllers/devise/registrations_controller.rb'
post 'users/registrations/admin_create' => 'devise/registrations#admin_create'
PREFIX users_registrations_admin_create
URL PATTERN /users/registrations/admin_create(.:format)
Controller&Action devise/registrations#admin_create
assigned route path to controller in 'controllers/user/registrations_controller.rb'
post 'users/registrations/admin_create' => 'user/registrations#admin_create'
PREFIX users_registrations_admin_create
URL PATTERN /users/registrations/admin_create(.:format)
Controller&Action user/registrations#admin_create
only 'devise' controller name changed to 'user' in route, then fine.

redirect_to # syntax meaning

What does the code
redirect_to #user
when used in a controller method, mean in Rails?
Where exactly does it redirect to? Does it redirect to a Users controller (If so, which method of the controller), or does it not go through the controller and instead go directly to a view?
Basically it looks up a bunch of stuff about how your resource routes work
think of it like this
send("#{#user.class.name.underscore.downcase}_path", #user)
this is probably not the exact code but it should help you visualize whats actually going on.
The controller always runs, in this case it would be the show action of your users controller unless you have some funky router options.
rake routes explains how the routes are laid out in this case show is
get /users/:id => users#show :as => "user_path"
the substitution from your incoming model works like this
a regex is created from your route
:id being a param
would match to inbound_object to the path function which is #user
:id is replaced with #user.id
From the redirect_to docs:
Redirects the browser to the target specified in options. This parameter can take one of three forms:
...
Record - The URL will be generated by calling url_for with the options, which will reference a named URL for that record.
And from the url_for docs:
Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will trigger the named
route for that record. The lookup will happen on the name of the
class. So passing a Workshop object will attempt to use the
workshop_path route. If you have a nested route, such as
admin_workshop_path you’ll have to call that explicitly (it’s
impossible for url_for to guess that route).
This is the equivalent of writing
redirect_to user_path(#user)
More Rails magic at work here, for better or worse.
it calls the show function of users like this
redirect_to user_path(#user)
important point : if we see the routes then :id is passed but here object is getting passed
which get converts into the id of the user by
def to_param
self.id
end
which is available in model and we can overwrite it to
so basically to conversion take place first
redirect_to #user => redirect_to user_path(#user)
then
#user => #user.id

Rails route dependent on current user

I'd like to create a rails route for editing a user's profile.
Instead of having to use /users/:id/edit, I'd like to have a url like /edit_profile
Is it possible to create a dynamic route that turns /edit_profile into /users/{user's id}/edit, or should I do thing in a controller or?
You might want to create a separate controller for this task but you could also continue using users_controller and just check whether there is a params[:id] set:
def edit
if params[:id]
#user = User.find(params[:id])
else
#user = current_user
end
end
But you should note that /users normally routes to the index action and not show if you still have the map.resources :users route. But you could set up a differently called singular route for that:
map.resources :users
map.resource :profile, :controller => "users"
This way /users would list all the users, /users/:id would show any user and /profile would show the show the currently logged in users page. To edit you own profile you would call '/profile/edit'.
Since a route and controller serve two different purposes, you will need both.
For the controller, assuming you're storing the user id in a session, you could just have your edit method do something like:
def edit
#user = User.find(session[:user_id])
end
Then have a route that looks something like:
map.edit_profile "edit_profile", :controller => "users", :action => "edit"
This route would give you a named route called edit_profile_path
Tomas Markauskas's answer could work, but here's the answer to your question from the Rails Guide:
get 'edit_profile', to: 'users#edit'
So, when someone goes to www.yoursite.com/edit_profile, it will route to www.yoursite.com/users/edit.
Then, in your controller you can access the user with
#user = User.find(session[:current_user_id])
Assuming you set that session variable when someone logs in. Also, don't forget to check if they're logged in. This will work if your using Resourceful Routing (the Rails default) or not.
Source: http://guides.rubyonrails.org/routing.html
make the route as
get '/users/:id/edit', to: 'users#edit', as: 'edit_profile'
As explained in this link section 'The hard way' :
http://augustl.com/blog/2009/styling_rails_urls/
The url will be
/users/edit_profile
Because the ID is no longer in the URL, we have to change the code a bit.
class User < ActiveRecord::Base
before_create :create_slug
def to_param
slug
end
def create_slug
self.slug = self.title.parameterize
end
end
When a user is created, the URL friendly version of the title is stored in the database, in the slug column.
For better understanding read the link below
http://blog.teamtreehouse.com/creating-vanity-urls-in-rails
write it in any home controler.
def set_roots
if current_user
redirect_to dashboard_home_index_path
else
redirect_to home_index_path
end
end
in routes.rb file
root :to => 'home#set_roots'
match "/find_roots" => "home#set_roots"

Rails redirect_to post method?

redirect_to :controller=>'groups',:action=>'invite'
but I got error because redirect_to send GET method I want to change this method to 'POST' there is no :method option in redirect_to what will I do ? Can I do this without redirect_to.
Edit:
I have this in groups/invite.html.erb
<%= link_to "Send invite", group_members_path(:group_member=>{:user_id=>friendship.friend.id, :group_id=>#group.id,:sender_id=>current_user.id,:status=>"requested"}), :method => :post %>
This link call create action in group_members controller,and after create action performed I want to show groups/invite.html.erb with group_id(I mean after click 'send invite' group_members will be created and then the current page will be shown) like this:
redirect_to :controller=>'groups',:action=>'invite',:group_id=>#group_member.group_id
After redirect_to request this with GET method, it calls show action in group and take invite as id and give this error
Couldn't find Group with ID=invite
My invite action in group
def invite
#friendships = current_user.friendships.find(:all,:conditions=>"status='accepted'")
#requested_friendships=current_user.requested_friendships.find(:all,:conditions=>"status='accepted'")
#group=Group.find(params[:group_id])
end
The solution is I have to redirect this with POST method but I couldn't find a way.
Ugly solution: I solved this problem which I don't prefer. I still wait if you have solution in fair way.
My solution is add route for invite to get rid of 'Couldn't find Group with ID=invite' error.
in routes.rb
map.connect "/invite",:controller=>'groups',:action=>'invite'
in create action
redirect_to "/invite?group_id=#{#group_member.group_id}"
I call this solution in may language 'amele yontemi' in english 'manual worker method' (I think).
The answer is that you cannot do a POST using a redirect_to.
This is because what redirect_to does is just send an HTTP 30x redirect header to the browser which in turn GETs the destination URL, and browsers do only GETs on redirects
It sounds like you are getting tripped up by how Rails routing works. This code:
redirect_to :controller=>'groups',:action=>'invite',:group_id=>#group_member.group_id
creates a URL that looks something like /groups/invite?group_id=1.
Without the mapping in your routes.rb, the Rails router maps this to the show action, not invite. The invite part of the URL is mapped to params[:id] and when it tries to find that record in the database, it fails and you get the message you found.
If you are using RESTful routes, you already have a map.resources line that looks like this:
map.resources :groups
You need to add a custom action for invite:
map.resources :groups, :member => { :invite => :get }
Then change your reference to params[:group_id] in the #invite method to use just params[:id].
I found a semi-workaround that I needed to make this happen in Rails 3. I made a route that would call the method in that controller that requires a post call. A line in "route.rb", such as:
match '/create', :to => "content#create"
It's probably ugly but desperate times call for desperate measures. Just thought I'd share.
The idea is to make a 'redirect' while under the hood you generate a form with method :post.
I was facing the same problem and extracted the solution into the gem repost, so it is doing all that work for you, so no need to create a separate view with the form, just use the provided by gem function redirect_post() on your controller.
class MyController < ActionController::Base
...
def some_action
redirect_post('url', params: {}, options: {})
end
...
end
Should be available on rubygems.

Is it possible to change the default action for a RESTful resource?

In Ruby on Rails, is it possible to change a default action for a RESTful resource, so than when someone, for example, goes to /books it gets :new instead of the listing (I don't care if that means not being able to show the listing anymore)?
I'd point out that if you are pointing /books to /books/new, you are going to be confusing anyone who is expecting REST. If you aren't working alone, or if you are and have other come on board later, or if you expect to expose an API to the outside, the REST convention is that /books takes you to a listing, /books/new is where you create a new record.
Not sure why would you do such a thing, but just add this
map.connect "/books", :controller => "books", :action => "new", :conditions => { :method => :get}
to your config/routes.rb before the
map.resources :books
and it should work.
Yes. You should be able to replace your index method in your controller...
def index
#resource = Resource.new
# have your index template with they proper form
end
In the same vein, you can just do
def index
show
end
def index
redirect_to new_book_path
end
I think would be the simplest way.

Resources