Rails 4 user routing - ruby-on-rails

<%= link_to "Whatever", current_user %>
is linking to /user.id
My routes are setup like this
resource :user, except: [:index, :destroy]
So it should be linking to /user, right?
When I visit /user it says "Couldn't find User without an ID".
My user show action looks like this
def show
#user = User.find(params[:id])
end

The reason you are getting /user.id is because you have defined your routes as
resource :users, except: [:index, :destroy]
Note the singular resource which will create all the routes without any :id. As you don't have an accepting param within the route, the current_user that you are passing is matched to the format i.e., like .html, .js, etc. which in your case becomes .id
I would recommend using resources (note the plural)
resources :users, except: [:index, :destroy]
This will resolve the error Couldn't find User without an ID as you would be passing an params id within your route.
NOTE:
As per Rails convention, controller name should be plural. For UsersController, resources should be resources :users

I have had this happen many times. My fix is to use the path helpers.
<% link_to "Whatever", user_path current_user %>
This will drop the .id and make it /user/id

Related

How to use "link_to" in an Admin section correctly in Rails

So I have an app which has an admin section. The admin section has a challenges controller with an index method and a view index.
I have also a challenges controller seperate from the admin folder. This controller has the whole CRUD.
Every challenge belongs_to a subject. The controller subjects in the admin section has an index method and view. The controller subjects not in the admin section has the whole CRUD.
Now, in the view of subjects (NOT the admin section), I can do something like:
<%= link_to "New Challenge".html_safe, new_subject_challenge_path(#subject) %>
I would like to do the same in the admin-section, but I can't really figure out how to do it. Copying the code throws me an error:
No route matches {:action=>"new", :controller=>"challenges", :subject_id=>nil} missing required keys: [:subject_id]
But I was hoping I could do this without additional routes....
It seems like it should be easy but I don't really know how to handle this. Any help would be very much appreciated... I hope I explained myself well enough.
The admin routes are used with a namespace:
namespace :admin do
resources :paths, only: [:index, :new, :create, :update, :edit]
resources :users, only: [:index, :new, :create, :show, :edit, :update]
end
resources :challenges, except: [:index, :destroy] do
resources :solutions, only: [:create]
end
resources :subjects
The link you are creating points to a route that requires a subject id. In the subjects view it works because Rails can find the subject_id in the #subjectthat you are passing to the path helper.
When you copy and try to re-use the same link in your admin view, I expect that #subject is not assigned and so it cannot find the required subject_id. Provide your admin section view with the subject and it should work!
Also if you want to get a clearer idea of routing, the Rails docs are pretty great.

What is the path for this route?

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) %>

Why does form_for helper require index path for a new action

I have the following in my routes file
resources :details, only: [:show, :edit, :update, :new]
and in new.html.erb
<%= form_for(#detail) do |f| %>
<%= render 'fields', f: f %>
<% end -%>
and in the details controller
def new
#detail = Detail.new
end
When the new.html.erb is rendered, I get the following error message
undefined method `details_path' for #<#<Class:0x007fc4ac6bd730>:0x007fc4b62e7390>
What is causing this?
The cause of this is that the the form_for helper is looking to post to /details. So it is not looking for the index path but the :create path.
So you need to add the :create action to your routes declaration
resources :details, only: [:show, :edit, :update, :new, :create]
If you look at the rendered html in this case, it will look like this
<form class="new_detail" id="new_detail" action="/details" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" />
so you can see why you need the :create path which makes the post /details path.
The create and index actions use the same path but different actions. So if you have either in the routes file, then a details_path method will be generated. In your case you want to make sure you have the create in your routes file:
resources :details, only: [:create, :show, :edit, :update, :new]
To give context to your answer, you need to look up about the resourceful nature of Rails' routes:
Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as GET, POST, PATCH, PUT and DELETE. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
When you say... "form_for helper is looking to post to /details", it's actually looking to post your form data to the create method. The way it does this is through the POST verb on the /details route:
Thus, you need to ensure that you're declaring your create route if you wish to use the resourceful routing structure.
This is why you'll need the following:
#config/routes.rb
resources :details, only: [:show, :edit, :update, :new, :create]
or
resources :details, except: :index
--
Of course, you don't have to use it.
You can use whichever routes you want. The important thing, though, is to understand that Rails is built around convention; HTTP's is towards resources:
The server, which provides resources such as HTML files and other content, or performs other functions on behalf of the client, returns a response message to the client
This is why you have "resourceful" routing etc.
On top of Rails' object orientated nature, you need to appreciate which resources are being used at certain points in your app.
form_for expects a new object to route to the create route. You can override this by using the url: option:
<%= form_for #object, url: none_resourceful_path ...

Ruby on Rails - Path variable without using resources route

I have a controller, let's just call it FruitsController, that grabs all of the fruit and sends it to the index view. In the view, I want to show links to the individual pages for those fruits. I'm using the format:
<% #fruits.each do |fruit| %>
<%= link_to fruit.name, fruit_path(fruit) %>
<% end %>
And this works great when I have the route resources :fruits, but I don't want routes for deleting, saving, and updating, so I don't want to use resources. But when I just do individual routes for showing all and individual fruits, I get the error fruit_path function is not defined, and when I use the function fruits_path it works but it just appends a period to the path like /fruits.1. How can I use the fruit_path function without using resources? Thanks.
There are a number of ways you could do this; in your config/routes.rb, any of the following should work:
resources :fruits, only: :show
resources :fruits, except: [:index, :edit, :destroy, :update] # etc
get 'fruits/:id', to: 'fruits#show', as: :fruit
scope controller: :fruits do
get 'fruits/:id' => :show, as: :fruit
end
You're not limited to just resources, you can customize and create your own routes, for example:
get '/:username/photos', to: 'users#show', as: 'collage'
to: means controller/action, in this case users is the controller and show is the action.
as: creates a path for you 'collage_path'
you can find good info regarding routing =>http://guides.rubyonrails.org/routing.html

Rails route with id after resource

I have nested routes that I'm having difficulty formatting the way I want to.
My routes.rb has the following
resource :loan_application do
scope ":loan_application_id" do
resources :wizard, only: [:show, :update]
end
end
When I click the link to create a new resource:
<%= link_to business_loan_application_path(#user), method: :post %>
I get sent to a URL that looks like the following
http://localhost:3000/businesses/69/163/loan_application/wizard/eligibility
For some reason the loan_id (163) comes before /loan_application. I would like it to come after /loan_application.
When I rake routes I can see the same problem:
business_loan_application_wizard GET /businesses/:business_id/:loan_application_id/loan_application/wizard/:id(.:format) wizard#show
In my application I made the routes without your scope:
resources :loan_application do
resources :wizard, only: [:show, :update]
end
That leads me into exact the pathes I need

Resources