I'm using Rails 7. I wanted to create a link using link_to that inserts some custom form fields for a many-to-many relation into a form via a hotwired frame elsewhere on the page. In order to customize the form fields generated (setting defaults etc) I need to include some custom parameters in the request:
<%= link_to "Add crew to ship",
new_crew_path(ship_id: ship.id),
data: { turbo_method: :get,
turbo_stream: true } %>
The HTML looks correct, but when clicking the link, the parameter seems to have been stripped away:
<a data-turbo-method="get" data-turbo-stream="true" href="/crews/new?ship_id=1">Add crew to ship</a>
...
Started GET "/crews/new" for 127.0.0.1 at 2023-01-24 13:49:23 +0100
Processing by CrewsController#new as TURBO_STREAM
If I remove the data map defining turbo_stream: true so it becomes a regular request, it works fine:
<%= link_to "Add crew to ship",
new_crew_path(ship_id: ship.id) %>
Started GET "/crews/new?ship_id=1" for 127.0.0.1 at 2023-01-24 13:53:26 +0100
Processing by CrewsController#new as HTML
Incoming parameters: {"ship_id"=>"1"}
Same for changing it to a POST request, so it goes to the #create action of the controller instead, that also works:
<%= link_to "Add crew to ship",
crews_path(ship_id: ship.id),
data: { turbo_method: :post,
turbo_stream: true } %>
Started POST "/crews?ship_id=1" for 127.0.0.1 at 2023-01-24 13:58:01 +0100
Processing by CrewsController#create as TURBO_STREAM
Incoming parameters: {"ship_id"=>"1"}
So I have workarounds. Nevertheless, out of curiousity, is there some way to make GET turbo requests submit the custom params? From a REST perspective it feels like the most correct request method.
When you add turbo_method: :get the link is treated as a form and GET forms overwrite url query params with form params. As a test:
// app/javascript/application.js
document.addEventListener("turbo:before-fetch-request", (event) => {
console.log(event.target)
})
// you get this "fake" form in the console, that Turbo submits instead of a link:
// <form data-turbo="true" action="http://localhost:3000/?ship_id=1" hidden="" method="get" data-turbo-stream=""></form>
If you just change the link to this:
<%= link_to "Add crew to ship",
new_crew_path(ship_id: ship.id),
data: { turbo_stream: true } %>
Now it's a regular GET turbo stream link, and query params should stay untouched.
For some extra details:
https://github.com/hotwired/turbo/pull/647
it's worth emphasizing that declaring [data-turbo-method="get"] on an
<a> element is redundant, and should be avoided
https://github.com/hotwired/turbo/pull/821
Related
The routes define
resources :interruptions do
collection do
post :pause
post :restart
end
end
However, within an intervento_controller show action, where #interruption = Interruption.new is declared, a form to create a related record
<%= form_for #interruption, url: pause_interruptions_path do |f| %>
<%= f.hidden_field :intervento_id, value: #intervento.id %>
<%= submit_tag "Pausa", name: 'pausa_intervento' %>
<% end %>
rails is routing to the intervento action, not the one stated in the form_for url declaration.
Started PATCH "/interventos/32" for ::1 at 2017-01-17 13:04:49 +0100
Processing by InterventosController#update as HTML
Parameters: {"utf8"=>"✓", [...] "interruption"=>{"intervento_id"=>"32"}, "pausa_intervento"=>"Pausa", "id"=>"32"}
The model Intervento does allow accepts_nested_attributes_for :interruptions, however I expected this not be necessary as the form declares the action to be routed to. Also, only one record is being created or edited at a time.
How can this be achieved?
Your pause action is defined on collection, i.e /interventos/post,
however, your form is defined for an interruption instance.
You think you meant to do
resources :interruptions do
member do
post :pause
# post :restart
end
end
and you also need to change pause_interruptions_path accordingly.
Also try use f.submit
submit_tag seems being treated as a input field.
I'd like to get the paramenter from the URL on my view/html.
For example, I'm showing an specific item from my data base, and the URL is like this: "http://localhost:3000/menus/index.%23%3CMenu::ActiveRecord_Relation:0x007f50ed153250%3E?id=6"
What I want is: when I click on the New Button of that specific item, the form opens with the URL "http://localhost:3000/menus/new", but I want somehow pass that id=6 to this NEW view.
I am using this code on the html to open the form: <%= link_to 'Novo', new_menu_path %>
I also tried: <%= link_to 'Novo', new_menu_path(#menus, :id) %>, but the id param is always null/empty.
How do I do that?
Thank you
To pass an extra param to an url when you define a link_to
<%= link_to 'Novo', new_menu_path(id: #menu.id, other_param: "hello") %>
will generate http://localhost:3000/menus/new?id=6&other_param=hello
this will add the following to the params hash
Parameters: {"id"=>"6", "other_param"=>"hello"}
well :id is just a symbol, you need to tell the route helper what to bind to it. For example
<%= link_to 'Novo', new_menu_path(id: params[:id]) %>
which should give you something like /menus/new?id=6
When the form_for is as so:
<%= form_for #reservation, remote: true do |f| %>
I get the following params:
Parameters: {"utf8"=>"✓", "reservation"=>{"party_size"=>"1",
"persons_attributes"=>{"0"=>{"first_name"=>"Big Name",
"last_name"=>"test", "meal_id"=>"1", "id"=>"24"},
"1"=>{"first_name"=>"", "last_name"=>"", "meal_id"=>"1"}},
"address"=>"test", "city"=>"Test", "state"=>"te", "zip"=>"te"},
"commit"=>"submit", "id"=>"24"}
But when I put a custom controller onto the form_for, as so
<%= form_for #reservation, url: {controller: 'static_pages'}, remote: true do |f| %>
I get the following params:
Parameters: {"utf8"=>"✓", "reservation"=>{"party_size"=>"1",
"persons_attributes"=>{"0"=>{"first_name"=>"Big Name",
"last_name"=>"test", "meal_id"=>"1", "id"=>"24"},
"1"=>{"first_name"=>"", "last_name"=>"", "meal_id"=>"1"}},
"address"=>"test", "city"=>"Test", "state"=>"te", "zip"=>"te"},
"commit"=>"submit", "id"=>"index"}
How do I ensure that the params[:id] remains the same? All I need is the standard form_for with a different controller called (the same for is used for both new\ create and edit\ update).
'form_for' with an active record object tries to identify the URL from the object. If it has an ID, it uses the update route, otherwise it uses the create route. Initially it identifies it as something like
/reservation/1
But when you explicitly pass something as the URL it will override it. Now our URL looks like
/reservation/index
I recommend looking into the 'form_for' documentation, but what could really solve your problem would be to explicitly pass the whole URL, instead of just the controller. That way you could get the ID in the right place
In the form you can place the following line of code which will pass the :id to the controller in the POST request.
<%= hidden_field_tag(:id, params[:id]) %>
You might have to add a method to convert it to an integer within the controller.
params[:id].to_i
Depending on how you have your db setup.
I have the following in my products index page:
<%= button_to "Add", user_order_orderitems_path(user_id: current_user.id, item_id: x.id, order_id: current_user.group.current_order.id), class: "btn btn-mini" %>
Which I can see from the logs is being picked up by my Orderitems#create action in my controller ok. This looks like:
def create
#orderitem = Orderitem.new(orderitem_params)
if #orderitem.save
redirect_to items_path
else
redirect_to items_path
end
end
private
def orderitem_params
params.require(:orderitem).permit(:user_id, :order_id, :item_id)
end
end
The params specified in the button_to call are being created and are showing up in the logs as:
Started POST "/users/1/orders/1/orderitems?item_id=2264" for 127.0.0.1 at 2013-07-03 22:45:24 +0100
Processing by OrderitemsController#create as HTML
Parameters: {"authenticity_token"=>"blah=", "item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}
Fnally - the problem - my strong_params method can't process these params as the three params I care about are not nested in a hash with 'Orderitem's as a key. I would expect, for my create action to work, I need something like:
Parameters: {"authenticity_token"=>"blah=", "orderitems"=>{"item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}}
but I can't for the life of me work out how, with button_to I am able to do this - I have tried a form_for, but this also failed to work. Banging my head on a brick wall for a couple of days on this one...So, how can post my three ids to my OrderItemsController create action from an index view for Products but bypassing any form_for or new actions? Is it possible?
Please let me know if I am approaching this scenario (adding an item to a basket) in completely the wrong way.
This way you can treat a standard hash as one supported by strong parameters
raw_parameters = {"authenticity_token"=>"blah=", "item_id"=>"2264", "user_id"=>"1", "order_id"=>"1"}
parameters = ActionController::Parameters.new(raw_parameters)
parameters.permit(:user_id, :order_id, :item_id)
I got a view with the following code:
<%= form_for(#stock,:url=>{:action=>"buyback"},:html=>{:class=>"form-horizontal"}) do |f| %>
My routes.rb file shows:
post '/stocks/buyback'
When I click the submit button of the form, it does not trigger the method buyback of the controller but instead update.
The log file shows:
Started PUT "/stocks/buyback" for 127.0.0.1 at 2012-07-22 19:46:07 +0800
Processing by StocksController#update as HTML
Its triggering the controller method Update instead of buyback. Why?
try this
<%= form_for(#stock,:url=>{:action=>"buyback"},:method => :post, :html=>{:class=>"form-horizontal"}) do |f| %>