I'm trying to create an order confirmation page for my Rails 6 app. The idea is that user will see a preview of the item they are creating before submitting and the object being saved in the database. Below desired flow:
User visits /cash_transactions/withdrawals/new
User enters data and clicks submit
User is redirected to /cash_transactions/withdrawals/confirm which
displays the entry
User clicks confirm to save object to db or cancel
Object is saved
I followed two main threads that describe this type of action, but they are quite old - 11 and 12 years old. Nevertheless based on that I've created below code:
# controllers/cash_transactions/withdrawals_controller.tb
module CashTransactions
class WithdrawalsController < CashTransactions::BaseController
(...)
def confirm
#cash_transaction = CashTransaction.new(cash_transaction_params)
render 'cash_transactions/_confirm'
end
end
end
# routes.rb
namespace :cash_transactions do
resources :withdrawals, only: %i[new create] do
collection do
post :confirm
end
end
end
With corresponding views:
# app/views/cash_transactions/new.html.erb
<%= render 'cash_transactions/form', cash_transaction: #cash_transaction %>
# views/cash_transactions/_form
# the form is rendered for cash_transaction create action
<%= simple_form_for cash_transaction, url: { action: :confirm } do |f| %>
<%= f.input :amount %>
<%= f.button :submit, 'Submit' %>
<% end %>
# confirmation page under views/cash_transactions/_confirm.html.erb
<div>
Total of withdrawal: <%= #cash_transaction.amount.to_i %>
</div>
<%= link_to 'Confim', cash_transactions_withdrawals_path(#cash_transaction), method: :post %>
<%= link_to 'Cancel', cash_transactions_path %>
And everything works until the user clicks confirm button in views/cash_transactions/_confirm.html.erb - instead of creating a record an error appears:
param is missing or the value is empty: cash_transaction
Did you mean?
authenticity_token
action
controller
_method
where did I go wrong? or there is a completely different way to do so?
tl/dr: You need to add parameters to your create request.
Why this is happening
The /confirm view is being rendered with an (unsaved) #cash_transaction object, however that object is not being used and so the information is being lost when the page is rendered.
The line:
<%= link_to 'Confim', cash_transactions_withdrawals_path(#cash_transaction), method: :post %>
Will submit a POST request with no parameters to the /cash_transactions/withdrawals#create (because you've given it no parameters to post). It doesn't know to include the params from the previous request.
There are a few options to fix this... you can add params as URL parameters in link_to like this, however I wouldn't recommend posting with params in the URL.
You can use button_to instead, and pass in the cash_transaction arguments from the previous request in the params: option (or pull them out of the unsaved #cash_transaction object).
Approach #1 - reuse create params
# Get them from the params sent in the previous request. In the controller...
def create
#cash_transaction = CashTransaction.create!(cash_transaction_params)
# etc...
end
#...
protected
def cash_transaction_params
params[:cash_transaction].permit(:amount, :whatever)
end
helper_method :cash_transaction_params
# In the view
<%= button_to 'Confirm', {action: 'create', params: cash_transaction_params}
Approach #2 - Access attributes from the model you built
<%= button_to 'Confirm', {action: 'create', params: #cash_transaction.attributes.slice('amount', 'other_attribute') }
Or you could do something like render the form again but hidden and have the "confirm" button submit the hidden form (with { action: :create } instead of { action: :confirm}). This last solution is probably the easiest to understand.
Related
I have a form in the edit file under the related view folder. I want to direct this form to the editing route.
Here is my form,
<%= form_with(model: #trade, local: true) do |form| %>
<%= form.button "Accept", name: "button_action", value: "accept" %>
<%= form.button "Deny", name: "button_action", value: "decline" %>
<% end %>
My edit action in related controller,
def edit
#trade = Trade.find(params[:id])
if params['button_action'] == 'accept'
#trade.update(status: 1)
else
#trade.update(status: 2)
end
redirect_to root_path
end
And my routes,
book_trades GET /book/:book_id/trades(.:format) trades#index
POST /book/:book_id/trades(.:format) trades#create
new_book_trade GET /book/:book_id/trades/new(.:format) trades#new
edit_book_trade GET /book/:book_id/trades/:id/edit(.:format) trades#edit
book_trade GET /book/:book_id/trades/:id(.:format) trades#show
PATCH /book/:book_id/trades/:id(.:format) trades#update
PUT /book/:book_id/trades/:id(.:format) trades#update
DELETE /book/:book_id/trades/:id(.:format) trades#destroy
But I couldn't direct and get no route matches error. I'm new to ruby and programming, what's the point I'm missing?
Thanks in advance.
The edit method is for rendering the form. The form action will send a PUT request to the update method.
You need to keep the move the logic in the edit method to the update method
I am surprisingly having a hard time making button_to work the way I want to in Rails. I am inside users show page (localhost:3000/users/1). I want to create an availability (availability_controller.rb) from there. I have a create method inside availability controller.
Here is the availability controller:
def create
#availability = current_user.availability.build(available_on: Date.today, available_hour: params[:available_hour])
if #availability.save
flash[:notice] = "New availability added!"
redirect_to current_user
else
flash[:error] = "Error adding availability"
redirect_to current_user
end
end
It takes in a params, available_hour.
I was, fortunately, able to create a form to do what I need using form method:
<form method="post" action="/availability">
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<button name="available_hour" value=0 type="submit" class="nowhere-block">Add Availability</button>
</form>
I hardcoded value of 0 to be sent to availability create method whenever user clicked on the button. This works great and I can probably go on with life.
However, I thought the code above is a little verbose, I wanted to achieve the same thing with button_to. I have looked at the button_to docs but I could not find the information I am looking for.
I tried <%= button_to "Post Availability", #availability, { available_hour: 5 } %> but it gave me No route matches [POST] "/users/1" error. Somehow it is still accessing user path.
I have tried other combinations as well unsuccessfully:
<%= button_to "Post Availability", availability_path, {controller: "availability", available_hour: 5 } %>
<%= button_to "Post Availability", #availability, remote: true, class: "nowhere-block" %>
How can I, from inside user show, access availability controller's create method?
Update:
Current routes.rb:
devise_for :users
resources :users
resources :friendships
resources :availability
root to: "users#index"
availability_index GET /availability(.:format) availability#index
POST /availability(.:format) availability#create
new_availability GET /availability/new(.:format) availability#new
edit_availability GET /availability/:id/edit(.:format) availability#edit
availability GET /availability/:id(.:format) availability#show
PATCH /availability/:id(.:format) availability#update
PUT /availability/:id(.:format) availability#update
DELETE /availability/:id(.:format) availability#destroy
You are passing a parameter wrong, also if you want to use #availability have to be defined in users_controller#show as #availability = Availability.new
Answer
<%= button_to "Post Availability", availability_path(available_hour: 5) %>
#or
<%= button_to "Post Availability", Availability.new, params: {available_hour: 5} %>
Try this
<%= button_to "Post Availability", availability_path(available_hour: 5) %>
Hi I am wondering why I am not getting an error when I submit a form with an action set to a method that isn't set up yet. After submission it just keeps me on the same page with different url parameters.
Here is the form:
<form>
<%= form_tag( drop_piece_path, :method => "post" ) do %>
<% 7.times do |col| %>
<%= label_tag col %>
<%= radio_button_tag(:column, col) %>
<% end %>
<%= submit_tag("Enter move")%>
<% end %>
</form>
Here is my route:
post 'drop_piece' => 'connect_four#drop_piece', as: :drop_piece
And here is my drop_piece method in my controller:
def drop_piece
redirect_to fake_path #should raise an error because fake_path is not a real path
end
I am confused because since my form is set to submit to the drop_piece_path shouldn't that trigger the drop_piece method in my controller? Which should then raise an error?
Again, right now after form submission the application just stays on the same page with params corresponding to whichever radio button I selected.
Why doesn't this trigger my drop_piece method in my connect_four controller? Thanks for the help.
This happens because you have nested form tags. One is <form> tag and another one is <%= form_tag... %>. HTML spec doesn't allow nesting forms, so the outer one is submitted. Since it doesn't have action attribute, it is submitted to the current URL.
I have tried to create a mailer using the following code:
routes code
resources :listings do
member do
put :lead
end
end
mailer controller code
def lead(listing)
#listing = listing
mail(to: #listing.leadrecepient, subject: "test")
end
standard controller code
def lead
Enquiry.lead(#listing).deliver
end
view
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<%= listing.text_field :name %>
<%= listing.submit %>
<% end %>
In the context of a business directory, I want it so that there is a enquiry form on each listing page that when filled out and submitted, the information is sent to the relative listing email.
The problem however is that when I type into the form and click submit, I get the following error:
param is missing or the value is empty: listing
This seems to be because I have it in the "listing" controller which controls the showing and creation of the business listing itself. I therefore have strong params for a new listing which contains all the new listing variables:
def listing_params
params.require(:listing).permit(:fullname, :jobtitle, :email, :franchisename, :leadrecepint, :shortdescription, :longdescription, :website, :branchcount, :scale, :mininvestment, :investmentrange, :category, :hexbg, :logourl, :facebook, :twitter, :linkedin, :googleplus, :approved)
end
How do I go about fixing this? I'm a beginner if I'm honest, could really do with some help to get this mailer working! Thanks.
Strong params are for when you are submitting new resources or modifications to resources. To protect against people adding extra parameters that may circumvent security or other aspects of your application unexpectedly.
If you are adding an action to an existing resource that the user is authorized to access, which this appears to be, you want to just find the object by ID, and use it. So instead of finding it using the params filtered through listing_params, just find it like this in the controller:
def lead
listing = Listing.find(params[:id])
Enquiry.lead(listing).deliver
redirect_to listing
end
And invoke it using a simple link, instead of this:
<%= form_for lead_listing_path(#listing), method: :put do |listing| %>
<%= listing.text_field :name %>
<%= listing.submit %>
<% end %>
Just use this in your view:
= link_to 'Go!', lead_listing_path(#listing), method: :put
Nothing more to it.
In a Rails project, I have the following controller action for the controller exchanges.rb:
def update_ordid
# Get the active exchange
#exchange = Exchange.find(params[:id])
# Decide which order ID field to update
active_order_field = params[:ordfld]
# Save the order ID
order_id = params[:ordid]
if active_order_field == 1 then
#exchange.order_id_1 = order_id
else
#exchange.order_id_2 = order_id
end
#active_exchange.save
respond_with(#exchange)
end
I've set up a route to this controller action:
resources :exchanges do
collection do
get 'update_ordid'
end
end
I want to call this action that accepts an order ID from a form on an exchanges show.html.erb page. I need to pass three values:
The ID of the current exchange, such as the integer in this example URL localhost:3000/exchanges/2 (This is the page the form is on)
The order ID as input from a text-field
Which of the two possible exchange fields the action should update
Next I need to create a custom form which will pass these values as parameters to the action. I haven't been able to find a good tutorial on how to do this yet, but my first thought was to set up the following:
<%= form_for(#exchange) do |f| %>
<div class="field">
<%= f.label :ordid, "Order ID" %><br>
<%= f.text_field :ordid, class: "form-control" %>
</div>
<% if #isrequestor == true %>
<%f.hidden_field :ordfld, :value => "1" %>
<% else %>
<%f.hidden_field :ordfld, :value => "2" %>
<% end %>
<div class="actions">
<%= f.submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
This gives me a NoMethodError stating the method 'ordid' is undefined. I'm guessing I need to modify the first line of code to associate the form with the custom action I've set up, but have no idea how to do so properly.
Yah, I got your point. So you wanted the following thing:
You wrote an custom action
You wanted to submit a form that action
You have registered your action in the router.
So let me answer the following solutions and find some mistakes you made in your code.
# in route.rb
resources :exchanges do
patch :update_ordid, on: :member # this is the best practice I would say,
#when you are trying to modify an existing record. So this action will only
#be reached with patch http methods
# on :member action an parameter id is required.
end
now if you generate your routes by running:
bundle exec rake routes
you will see a path like:
update_ordid_exchange /exchange/:id/update_ordid # :id parameter for exchange record
in your form set the url:
<%= form_for(#exchange, url: update_ordid_exchange_path) do |f| %>
or
<%= form_for(#exchange, url: url_for(controller: :exchange, action: update_ordid)) do |f| %>
Now then you will this form can submit this values within the parameter in your desire field.
So let me summarize things up here:
1. Setup your route properly
2. Check the url based on your route by generating rake routes command as shown above
3. Set the proper url and check if http method is correctly define in your form helper. For member actions, form helper by default use patch as http method. you just have to set the url.
Hope you understand my flow.