Add custom put method to generated controller - ruby-on-rails

I have records_controller generated through the regular generator scaffold_controller. I would like to add custom method, lets call it share in order to pass there only one parameter with email address. If email exists in my database I would like to add associated within id to record.
I added share method to records_controller:
def share
logger.info record_params
logger.info params
# check email in db and add id
respond_to do |format|
format.js
end
end
form on show view:
<%= form_tag(controller: "records", action: "share", method: "put", remote: true) do %>
<%= label_tag(:user_email, "Share for:") %>
<%= text_field_tag(:user_email) %>
<%= submit_tag("Share") %>
<% end %>
and rule in routes:
put '/records/:id/share', to: 'records#share'
Form renders fine but when I press click I get error:
ActionController::RoutingError (No route matches [POST] "/records/2/share"):

According to the form_tag source code, the parameters you pass to the helper are all considered as url_for_options and return a wrong output like this:
<form action="/records/2/share?method=put&remote=true" method="post">
You need to specify which parameters are url_for_options and which are only options:
<%= form_tag({controller: "records", action: "share"}, method: 'put', remote: true) do %>

Related

Rails 6 create order confirmation before submit

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.

Form couldn't direct to edit path

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

Rails create a model from form that isn't stored in a database

I have an account model that i want to create from the registration controller and form.
index.html.erb
<div id="registration">
<%= form_for(#account) do |f| %>
<% if #account.errors.any? %>
register_controller.rb
class RegisterController < ApplicationController
def index
#account = Account.new
end
def create
#account = Account.new(parames[:account])
end
end
end
end
routes.rb
Rails.application.routes.draw do
get 'register/index'
get 'register/create'
My current issue is undefined method `accounts_path' for #<#:0x007fd9f2fd8468> from the form_for() method
Am I mixing things up because of the names of the classes?
form_for(#account) will auto-set some of the attributes of the form, such as "action", which is where the form submits to. If you want to change this to something else then use the url option. You can pass this a path helper or just put the url in. eg
<!-- if you have a named path you can use the helper for it -->
<%= form_for #article, url: create_register_path %>
or
<!-- alternatively just pass the url -->
<%= form_for #article, url: "/register/create" %>
or
<!-- or you can pass the url as a hash if you prefer -->
<%= form_for #article, url: {controller: "register", action: "create"} %>
http://guides.rubyonrails.org/form_helpers.html
EDIT: I just noticed in your edit to your post that "register/create" is set as a get route. This means you will need to tell your form to use the get method as well: it defaults to post.
<%= form_for #article, url: "/register/create", :method => :get %>

Rails Call Custom Controller Action from Custom Form

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.

Rendering a 'show' template with an additional parameter is causing a routing error

I have a template "groups/show.html.erb" I have link that renders a partial by passing a parameter to the controller. The controller then uses the parameter to identify which JS call to make.
<%= link_to 'Add a Video', group_path(create_video: true), remote: true %>
then in the controller
elsif params[:create_video]
#video = Group.find(params[:id]).videos.build
respond_to do |format|
format.js {render action: 'create_video'}
end
This brings up a partial with a form that creates a video using the create method in the videos_controller. If a validation on the form fails and I try to render "groups/show" I get a routing error:
_create_video.html.erb
<%= form_for #video, :url => group_videos_path(#group) do |f| %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
No route matches {:action=>"show", :controller=>"groups", :create_video=>true}
To make this work I think you can just explicitly match it in routes.rb but is there a better way to do it? Thanks a bunch
You are forgetting to give which group, you should normally do something like
group_path(#group, create_video: true)
hope this helps.

Resources