I'm not completely new to Ruby on Rails but it is not my most proficient framework so I was hoping someone could help me wrap my head around some code I'm trying to understand.
controller:
def new
#biz = Business.new
end
def apply
#biz = Business.new(business_params)
token = SecureRandom.hex(4)
#biz.verify_token = token
if #biz.save
message = #biz.sentMessages.new
message.send_verify_email
redirect_to waiting_verification_path
else
render 'new'
end
end
routes:
get 'apply', to: 'businesses#new', as: 'apply'
post 'apply', to: 'businesses#apply', as: 'applied'
view:
= simple_form_for #biz, url: apply_path, html: {autocomplete: "off"} do |f|
I understand that the first line of the routes directs a request at /apply to the new action in the controller which creates a new business object and renders the new view, which I have included a snippet from. This snippet includes the form action which directs a successful submission to the apply_path.
My understanding indicates that this apply_path is a named helper for the first routes line when I believe it should be directed to the second line, whose helper would be applied_path, and would then be handled by the apply action on the controller.
What is really causing me confusion is that this functionality works. So the submission is in fact being routed to the second routes line and being handled by the apply action in the controller. If you could explain how this is happening I would appreciate it tremendously. Thank you.
The line
= simple_form_for #bix, url: apply_path
generates something like
<form action="/apply" method="post">
..
</form>
POST is the default method on forms, so when the form is then POST-ed Rails looks at the routes.rb file and sees a match on this line.
post 'apply', to: 'businesses#apply', as: 'applied'
If however you had done it like this
= simple_form_for #bix, url: apply_path, method: :get
The form is submitted via GET and then Rails would find a match on the first line in routes.rb.
get 'apply', to: 'businesses#new', as: 'apply'
It doesn't matter what the name of the routes_helpers are, all that matters is the generated url. I hope that clears thing out, if not, just ask for more clarification.
The reason this is working is that you have two named routes that map to the same path. applied_path is /apply, and apply_path is /apply. These two named paths are IDENTICAL. The difference comes when the form performs a POST to /apply, it sees a POST and immediately goes to the second route. While a GET to that same /apply path will go to the first route.
On this note, you only really need to name one of the routes and just use that name everywhere. This is why, when you declare a resource in your routes file:
resources :users
You end up with a "POST /users" and a "GET /users" path which routes to users#create and users#index respectively, based on what kind of request was sent - BUT you only have one users_path named route, which always returns "/users"
The generated form will look like
<form action="/apply" method="post" autocomplete="off" class="simple_form new_business">
Note the method attribute: Because #biz is a new record, SimpleForm knows this should be HTTP POST'ed to the url specified (AFAIK persisted records will still create a method="post" on the form, but an additional hidden field with name="_method" and value="put" is inserted).
Since both route helpers, apply_path and applied_path, just generate the string '/apply', it does not matter which URL you define in your form. This should equally well work:
= simple_form_for #biz, url: applied_path, html: {autocomplete: "off"} do |f|
When you submit the form, your browser creates an HTTP request, like this:
POST /apply HTTP/1.1
<header information>
<post body>
and Journey (the Rails router module) will take the information POST + /apply to find the route to businesses#apply.
To make the route definition a little more concise, you could rewrite it as
scope as: :apply do
get '/apply', to: 'businesses#new'
post '/apply', to: 'businesses#apply'
end
Related
I'm creating a Rails application where there is a resource used as general settings for the whole website. I set everything as follows:
config/routes.rb
authenticate :user do
scope '/admin' do
resource :basics #This is my resource's name, put in singular
# ...
end
end
controllers/basics_controller.rb
private
# Use callbacks to share common setup or constraints between actions.
def set_basic
#basic = Basic.first
end
views/basics/_form.html.erb
<%= form_with(model: #basics, local: true) do |form| %>
Here is what rake routes shows about this resource:
new_basics GET /admin/basics/new(.:format) basics#new
edit_basics GET /admin/basics/edit(.:format) basics#edit
basics GET /admin/basics(.:format) basics#show
PATCH /admin/basics(.:format) basics#update
PUT /admin/basics(.:format) basics#update
DELETE /admin/basics(.:format) basics#destroy
POST /admin/basics(.:format) basics#create
I don't have any trouble making the form show up. However, when I submit it, I get the following error:
No route matches [POST] "/admin/basics/edit"
When I look at the HTML generated form, I can see that it passes "/admin/basics/edit" as an action while it should be just "/admin/basics" in my situation.
What should I do to make this work?
Thank you in advance
It looks like maybe you have the wrong instance variable passed to the form_with method? IIRC, Rails assumes the form action should be the same as the current path if the model argument is nil.
#basics is not defined (at least in the controller code you shared). Replacing it with #basic might work for edits (when Basic.first exists), but not create (if Basic.first doesn't exist).
You could try initializing #basic in your controller like this
def set_basic
#basic = Basic.first_or_initialize
end
Then pass it to form_with
<%= form_with(model: #basic, local: true) do |form| %>
This is what my routes currently look like:
which gives
On my homepage I have a create vacancy button
<%= link_to "plaats", new_employer_vacancy_path(:employer_id)%>
Which should be linked to the line from the first image
get '/employers/:employer_id/vacancies/new', to: 'vacancies#new', as: 'new_employer_vacancy'
In the vacancies_controller#new - create I have:
def new
#vacancy = Vacancy.new
#employervacancy = Employervacancy.new
end
def create
#vacancy = Vacancy.create(vacancy_params)
createEmployervacancy
redirect_to employer_vacancy_path(current_employer, #vacancy)
end
def createEmployervacancy
#employer = current_employer
Employervacancy.create(vacancy_id: #vacancy.id, employer_id: #employer.id)
end
But whenever I click the button I get redirected to some other method in my vacancies_controller that is totally irrelevant.
How is this even possible? Don't I clearly define that when that path is clicked he should go to vacancies#new? and not to vacancies#show_specific_employer_vacancies?
EDIT
After following the answers I am indeed being linked to the correct route.
First, it gave me this error.
After trying to pass the current_employer.id instead of #employer like suggested I got following error:
For your routes, you'd better to change into nested route for easily maintaining routes.
Remove these codes:
get '/employers/:employer_id/vacancies/:id', to:"vacancies#show_specific_employer_vacancies", as: "employer_vacancy"
get '/employers/:employer_id/vacancies/edit/:id' ...
get '/employers/:employer_id/vacancies/index' ...
get '/employers/:employer_id/vacancies/new' ...
path '/employers/:employer_id/vacancies/:id' ...
change into:
resources :employers do
resources :vacancies
end
Try to use basic routes here because you use standard simple form url. For example:
<%= simple_form_for(#employee, #vacancy) %>
The simple_form_for will generate url well if you use nested routes above.
Finally, in your link you have to add #employer_id
<%= link_to "plaats", new_employer_vacancy_path(:employer_id => #employer_id)%>
I hope this help you
Your router cannot tell the difference between your employer_vacancy and new_emplyer_vacancy routes because the :id parameter accepts anything. Because of this, when you point your browser to "/employers/5/vacancies/new", the route is taking your employer_vacancy route and assigning {:employer_id => 5, :id => "new"} instead of going to your new_employer_vacancy route (because routes are first-come-first-serve).
To correct this, add a constraint to your first route to ensure that only numbers (and not the string "new") is accepted into the employer_vacancy route:
get '/employers/:employer_id/vacancies/:id',
to: 'vacancies#show_specific_emplyer_vacancies',
as: 'employer_vacancy',
constraints: { id: /\d+/ } # <- This line
As Wes Foster said rails router is trying to find a first match.
It means that given a path /employers/999/vacancies/new your router looks through the routes and when it sees get '/employers/:employer_id/vacancies/:id he thinks that this route matches. So :employer_id is 999 and :id is new.
I'd suggest to put the route with :id at the end of employers routes:
...
get '/employers/:employer_id/vacancies/new'
...
get '/employers/:employer_id/vacancies/:id'
Btw this is better than adding a constraint because:
It is easier
It doesn't pollute routes file
Later you may want to change ids to be hashed or alphabetic and then you'd have to change the constraint
I have a Hotel resource in my Rails app. I added a few custom, non-RESTful actions for some additional functionality.
I have the following routes.rb file:
resources :hotels do
post 'sort', on: :collection
post 'update_hotel_settings', on: :collection
end
and here is the output from rake routes:
....
sort_hotels POST /hotels/sort(.:format) hotels#sort
update_hotel_settings POST /hotels/update_hotel_settings(.:format) hotels#update_hotel_settings
hotels GET /hotels(.:format) hotels#index
POST /hotels(.:format) hotels#create
new_hotel GET /hotels/new(.:format) hotels#new
edit_hotel GET /hotels/:id/edit(.:format) hotels#edit
hotel GET /hotels/:id(.:format) hotels#show
PUT /hotels/:id(.:format) hotels#update
DELETE /hotels/:id(.:format) hotels#destroy
The update_hotel_settings action is where some general settings are saved to the DB.
Here is the beggining of the form being sent to that action:
<%= simple_form_for(#store, url: update_hotel_settings_hotels_path, html: {class: 'form-horizontal', id: 'hotel-settings-form'}) do |f| %>
giving:
<form accept-charset="UTF-8" action="/hotels/update_hotel_settings" class="simple_form form-horizontal" id="hotel-settings-form" method="post" novalidate="novalidate">...
However after submitting the form, the POST request is caught by the hotels' REST update action, with an exception about net being able to find hotel with id="update_hotel_settings".
Obviously this is an unwanted result, and I can't seem to find it's cause.
Shouldn't the update action be triggered by PUT/PATCH methods, and not POST? I also tried using another name for the route+action, instead of "update_hotel_settings", with no luck.
What's most annoying is that the sort action, listed right above, works fine! (only difference is the 'sort' action is fired via ajax, but not sure that should matter)
EDIT:
Contrary to what you might think, nested route mappers actually take precedent over the resource's 7 default routes.
EDIT 2: Another Clue, Another Question!
At first I was embarrassed to notice I forgot the method: :post option in the simple_form_for's arguments, and when I added it - Kapow! the form routes to the right action! weird isn't it? especially when there was already a method="post" attribute in the form tag!
WHAT IS GOING ON?
Routes are caught top to bottom. Meaning, if there is a match, there it stops. So, all you have to do is take the update_hotel_settings route out of the resource and before it.
match '/hotels/update_hotel_settings', to: 'hotels#update_hotel_settings', via: 'post', as :update_hotel_settings_hotels
resources :hotels do
[...]
I have this URL for my edit form :
<%=form_for #cad,:url =>{:action => "update",:controller => "cad" } do |f| %>
And it should point to "/cad/update",but the URL is pointing to "cad/6".
Please help.
Thanks in advance
it's perfectly fine if you follow restful routes for update it's a member route
There are 2 types of route
first one is collection route which will work on in general for all object like index action and second one is member route which will work on specific object like show,edit,update,destroy etc ,
In your case update is member route it has http verbs is put and it's basically post request
you can check http method
and You don't need url hash on form rails pick it routes based on object
I'm trying to create a very simple site the sends and receives parameters from the URL (or link), the app should support any number of parameters, i.e. http://localhost:3000/action=receive&controller=pages&email=mail%40site.com&name=Vinny, and then be able to receive/print them.
I think I have located the problem to my route. It currently looks like this:
match 'pages/*params' => 'pages#receive'
I create the link that sends the params like this:
<%= link_to "Send Params", :action => "receive", :name => "Vinny", :email => "mail#site.com" %>
When I click the link I get the following error.
No route matches [GET] "/assets"
And the URL looks like this:
http://localhost:3000/assets?action=receive&controller=pages&email=mail%40site.com&name=Vinny
Note asset?.
If my route instead looks like this:
match 'pages/:name/:email' => 'pages#receive'
It works, but then I'm limited to the specified params.
Any tips on how to solve this would be great.
Okay, I think I see what is happening. Specifying parameters in the routes is not necessary to get the values into the controller's action. So, replace the map you specified earlier in the routes.rb with
resources :pages
If you add to your link
:controller => "pages"
It should go to the correct controller + action. (this may not be necessary in your case)
In that action, you can grab all of the params from the hash
email = params[:email]
account = params[:name]
Etc.
Info on routing
Info on params