Rails 6 pass param in render :new after failed save - ruby-on-rails

I have a standard create method in the controller responsible for creating a new Transaction record. The Transaction record has a mandatory transaction_type field which I hide in the view and automatically assigning it a value by passing it a params[:filter] so I have one _form for both withdrawal and deposit transactions, like below:
#index.html.erb
<%= link_to 'Add funds', new_transaction_path(filter: 'deposit') %>
<%= link_to 'Withdraw Funds', new_transaction_path(filter: 'withdrawal') %>
#new.html.erb
<%= render 'form', transaction: #transaction, transaction_type: params[:filter] %>
#_form.html.erb
<%= simple_form_for #transaction do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<%= f.text_field :transaction_type, value: transaction_type, type: "hidden" %>
<%= f.input :amount, placeholder: 'Amount', label: false %>
<%= f.button :submit, 'Submit' %>
<% end %>
If for some reason the validation fails, to properly display the errors, the :new view will be rendered. Unfortunately, in this case, if the user fills out the entire form again (after first failed), the record will not be created because params[:filter] was not passed. Is there any way to pass original params[:filter] directly to the view?
#controller
# POST /transactions
def create
#transaction = wallet.transactions.new(transaction_params)
if #transaction.save
redirect_to :index, notice: 'Transaction was successfully created.'
else
render :new
end
end

While I understand the aspect of reusing the view code you really should consider creating separate routes and controllers and solving the code duplication issues by using inheritance and locals instead of by sneaking along a hidden parameter.
resources :deposits, :withdrawls, only: [:new, :create]
class TransactionsController < ApplicationController
helper_method :create_transaction_path
def new
#transaction = Transaction.new
render 'transactions/new'
end
def create
#transaction = Transaction.new(transaction_params) do |t|
t.transaction_type = transaction_type
end
if #transaction.save
yield #transaction if block_given?
success_response
else
yield #transaction if block_given?
failure_response
end
end
private
def transaction_type
controller_name.singularize
end
def create_transaction_path
polymorphic_path(controller_name)
end
def transaction_params
params.require(:transaction)
.permit(:foo, :bar, :baz)
end
def success_response
redirect_to transactions_path,
notice: 'Transaction was successfully created.'
end
def failure_response
render 'transactions/new'
end
end
class DepositsController < TransactionsController
# POST /deposits
def create
super do |transaction|
# do something just when making a deposit
end
end
end
class WithdrawlsController < TransactionsController
# POST /withdrawls
def create
super do |transaction|
# do something just when making a withdrawl
end
end
end
# app/views/transactions/_form.html.erb
<%= simple_form_for transaction, url: create_transaction_path do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<%= f.input :amount %> # Do not use placeholders instead of labels
<%= f.button :submit, 'Submit' %>
<% end %>
<%= link_to 'Add funds', new_deposit_path %>
<%= link_to 'Withdraw Funds', new_withdrawl_path %>
Why?
Because it gives you endpoints that do a single job and it also gives you the obvious structure for your code when the requirements diverge as they most certainly will.

Stashing the value as a hidden field in the form is the right idea, but you're using two different parameter names for the same thing.
Your link_to call passes the transaction type as filter:
<%= link_to 'Add funds', new_transaction_path(filter: 'deposit') %>
In your form, you are putting it in a hidden field called :transaction_type. Thus, when the form submits the value now goes to your controller in params[:transaction_type]. One simple fix is to change the name of your hidden field:
<%= hidden_field_tag :filter, params[:filter] %>

Related

Validation fails on update, form keeps going to create method?

Goal: Update existing records with a modal without needing to link_to a new page.
Issue: I assume my issue is that I am unable to identify the exact record on the page with the form because I can't know this until the form is submitted.
ShopProduct Controller:
def new
#shop_product = ShopProduct.new
end
def create
#shop_product = ShopProduct.new(shop_product_params)
#shop = Shop.find_by(params[:shop_id])
product = Product.find_by(params[:product_id])
#shop_product.product_id = product.id
#shop_product.shop_id = #shop.id
if #shop_product.save!
redirect_to '/'
flash[:notice] = "saved"
else
redirect_to '/'
flash[:notice] = "no saved"
end
end
def update
#shop_product = ShopProduct.find_by(store_variant_id: params[:store_variant_id])
respond_to do |format|
if #shop_product.update_attributes!(product_id: params[:product_id], sync: params[:sync])
format.html { redirect_to #shop_product, notice: 'Shop product was successfully updated.' }
format.json { render :show, status: :ok, location: #shop_product }
else
format.html { render :edit }
format.json { render json: #shop_product.errors, status: :unprocessable_entity }
end
end
end
Aside from linkingto a new page, I can only think of defining directly on the
I load the form from this ShopDashboardController:
def product_variants
#shop = Shop.find(params[:shop_id])
session = ShopifyAPI::Session.new(domain: #shop.shopify_domain, token: #shop.shopify_token, api_version: '2019-04')
ShopifyAPI::Base.activate_session(session)
#in_store_products = ShopifyAPI::Product.find(:all)
#in_store_product = ShopifyAPI::Product.find(params[:shopify_product_id])
#in_store_variants = ShopifyAPI::Variant.find(:all, params: { product_id: params[:shopify_product_id]})
#shop_products = ShopProduct.where(shop_id: #shop)
#products = Product.all
#shop_product = ShopProduct.find_or_create_by(store_variant_id: params[:store_variant_id])
end
Now, as mentioned above, the only unique record for any ShopProduct is the id and the store_variant_id... If i use find_by in the def product_variants, the page won't load due to not being able to identify the #shop_product. I am unable to pass those params through because there may be multiple store_variant_ids, so I pass the Shop.id and ShopProduct.store_product_id only. But the store_product_id isn't a unique identifier as multiple records can have the same one. The only unique records are the id and store_variant_id.
Form (the variant is from a do loop):
<% #in_store_variants.each do |variant| %>
...
<%= form_for #shop_product do |f| %>
<%= f.collection_select :product_id, #products, :id, :sku %>
<%= f.hidden_field :store_product_id, value: variant.product_id %>
<%= f.hidden_field :store_variant_id, value: variant.id %>
<%= f.hidden_field :shop_id, value: #shop.id %>
<%= f.hidden_field :sync, value: true %>
<%= f.submit "Sync" %>
...
<% end %>
I am able to create new records only.
When i use the form again to update I get:
ActiveRecord::RecordInvalid (Validation failed: Store variant has already been taken):
app/controllers/shop_products_controller.rb:61:in `create'
Model ShopProduct:
belongs_to :product
has_one :shop
has_one :order
validates :store_variant_id, uniqueness: true, on: :create
If the record exists, shouldn't it update? Or is there something I am missing here?
It is possible to pursue my goal with rails/ruby alone or is javascript needed?
UPDATE:
I tried defining the ShopProduct on the front-end like so:
<% #in_store_variants.each do |variant| %>
<% shop_product = #shop_products.find_by(store_variant_id: variant.id) %>
<%= form_for shop_product do |f| %>
<%= f.collection_select :product_id, #products, :id, :sku %>
<%= f.hidden_field :store_product_id, value: variant.product_id %>
<%= f.hidden_field :store_variant_id, value: variant.id %>
<%= f.hidden_field :shop_id, value: #shop.id %>
<%= f.hidden_field :sync, value: true %>
<%= f.submit "Sync" %>
<% end %>
When submitting:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"gaMboYCSE8v63TVzmgx4pZDMhoz205f1MV+VMhmFA/WWhVh5Pcu6u/qayU8lDmjeRXw==", "shop_product"=>{"product_id"=>"1", "store_product_id"=>"1965345", "store_variant_id"=>"19364273", "shop_id"=>"1", "sync"=>"true"}, "commit"=>"Sync", "id"=>"12"}
Error:
NoMethodError (undefined method `update' for nil:NilClass):
or with update attributes:
NoMethodError (undefined method `update_attributes!' for nil:NilClass):
If it's finding it, shouldn't it be working? The param is being passed
It's because that form only caters the create action. Usually, if you need to update a resource, you go to /shop_products/:id/edit.
But if you really wanted to reuse that form, it's a little bit complicated adding more conditions, but what you want is to send a PUT request to /shop_products/:id and it would call the #update action of your controller. A form, by default, sends a POST request so consider that.

Rails 5 - Render New with Parameter

I have checked old similar post but but I'm still having problems.
When I create a new listing the URL looks as below:
http://localhost:3000/listings/new?plan=1
In the form_for I'm hiding some fields when plan=1
<%= form_for(#listing) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.hidden_field :plan, :value => #plan %>
<%= f.label :Title %>
<%= f.text_field :title, class: 'form-control' %>
<!-- don't show description for standard plan -->
<% if #plan != 1.to_s %>
<%= f.label :Description %>
<%= f.text_area :description, :rows => 10,
placeholder: "Describe your business...", class: 'form-control' %>
<% end %>
In the ListingsController I set the plan in before action:
before_action :set_plan, only: [:new, :create]
def set_plan
#plan = params[:plan]
end
so I can use it as hidden field on the form above.
My problem starts when I save the form with errors.
The new form URL changes to http://localhost:3000/listings.
I can see the #plan=1 still on the form:
<input value="1" type="hidden" name="listing[plan]" id="listing_plan" />
, but now the form is showing all fields (which should be hidden due to this condition <% if #plan != 1.to_s %>)
Actions in ListingsController:
def new
#listing = Listing.new
end
def create
#listing = current_user.listings.build(listing_params) if logged_in?
#plan = #listing.plan
if #listing.save
flash[:success] = "Listing created!"
redirect_to #listing
else
render 'new'
end
end
I have tried something like that but no success:
render :action => 'new', :plan => #listing.plan
and few other things
How can I hide those fields on error?
You should be able to solve this by modifying your ListingsController to set the plan on create. Specifically, in ListingsController.rb:
# app/controllers/listings_controller.rb
before_action :set_plan, only: [:new, :create]
Also, be sure your listing_params method permits :plan.
If you want this to function on update, then add :update to your before_action as well.
I have it fixed.
#plan = params[:plan]
was returning string (when the field in DB is integer), so I changed it to:
#plan = params[:plan].to_i
and now in the form I have the below for all actions:
<% if #plan != 1 %>

Rails: If my Quiz model should be created on completing the quiz, how does form_for know what to refer to?

I've been told that I should not create my Quiz object before my quiz is completed; A user could go to the quiz page, not complete it, and there would be an 'unused' quiz sitting on the database. I can see the logic of that.
I CAN'T see how my quiz is supposed to work without being passed a #quiz object. Here's my QuizzesController, which, when the quiz is needed, gets routed to the 'new' action:
class QuizzesController < ApplicationController
def index
end
def new
#user = current_user
#quiz = Quiz.create(user_id: current_user.id)
end
def create
#results = Quiz.where(user_id: current_user.id).last
redirect_to results_path
end
end
At the moment, you can see that I'm coding the actions as simply as possible. Later, in the 'new' action, I'll add a test to see if the current_user has done the quiz and, if so, redirect to results_path.
Here is my form partial which is rendered as part of quizzes/new.html.erb:
<%= form_for(#quiz) do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%= f.check_box(:answer2) %>
<%= f.check_box(:answer3) %>
<%= f.check_box(:answer4) %>
<%= f.check_box(:answer5) %>
<%= f.check_box(:answer6) %>
<%= f.check_box(:answer7) %>
<%= f.check_box(:answer8) %>
</p>
<p>
<%= f.submit("Get my results!") %>
</p>
<% end %>
Once again, the quiz is very simple while I figure out what's going on.
But I'd like to know, if the #quiz object is not created in the 'new' action, what would I pass into form_for to build the form?
You can instantiate a Quiz object without saving it to the database:
def new
#user = current_user
#quiz = Quiz.new(user_id: current_user.id)
end
The generally used sequence of requests/actions is the following:
The new action just initializes the model's instance with default values, and renders the record with empty fields, usually in a edit view.
def new
#quiz = Quiz.new(user_id: current_user.id)
render :edit
end
create action create the record, and after the create action you should render either the view of the newly created record by redirection to show action with the same view, or to redirect to a new action, in case you are creating a sequence of the same instances of a model.
def create
#quiz = Quiz.create(params)
render :show # or redirect_to :new
end
edit action is to prepare edit fields, is it renders edit view with filled-in fields.
def edit
#quiz = Quiz.where(id: params[:id]).first
end
update action updates the record with values set in edit view, then it renders the show view on the current record.
def update
#quiz = Quiz.update(params)
render :show
end
show action just shows the model's found out with stored in the DB values, then it renders show view with filled-in fields.
def show
#quiz = Quiz.where(id: params[:id]).first
end
So in your show.erb view you get rendering the newly built, or found out instance of Quiz:
<%= form_for #quiz, url: {action: "create"} do |f| %>
<p>
<%= f.check_box(:answer1) %>
<%# ... %>
</p>
<p>
<%= f.submit "Create Quiz" %>
</p>
<% end %>
But I prefer simple-form gem:
<%= simple_form_for #quiz do |f| %>
<%= f.input :answer1, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.button :submit %>
<% end %>

Multiple update forms for one model

I want to have multiple forms on one page. Let's make an example to understand what I want:
I have a page for my admins, let's say it's the admins#show page. My admin has to change his name on one form on this page and on another form his age. I know I could create one form but I want to have multiple forms (because this is just an example). So my admins#show page looks something like this:
<%= form_for #admin do |a| %>
<%= a.label :name %>
<%= a.text_field :name %>
<%= a.submit "Submit name change" %>
<% end %>
<%= form_for #admin do |e| %>
<%= e.label :age %>
<%= e.number_field :age %>
<%= e.submit "Submit age change" %>
<% end %>
But in my controller, I don't know really how this works and here is my problem. I think I have something like this, but how could I divide the form inputs in the update method?:
def edit
#admin = Admin.find(params[:id])
end
def update
#admin= Admin.find(params[:id])
if #admin.update_attributes(:name=> admin_params1[:name])
redirect_to #admin
else
render 'edit'
end
if #admin.update_attributes(:age=> admin_params2[:age])
redirect_to #admin
else
render 'edit'
end
end
private
def admin_params1
params.require(:admin).permit(:name)
end
def admin_params2
params.require(:admin).permit(:age)
end
Its a bit Unorthodox what you are doing, but as you insisted and only its an example, I guess you can handle the update method by doing like this
def update
#admin= Admin.find(params[:id])
if params[:commit] == "Submit name change"
if #admin.update_attributes(admin_params1)
redirect_to #admin
else
render 'edit'
end
elsif params[:commit] == "Submit age change"
if #admin.update_attributes(admin_params2)
redirect_to #admin
else
render 'edit'
end
end
end
Note: Not Tested!
Well, I think you could create other non-REST methods in the controller and then add named routes in your config/routes then add your two different forms similar to this;
<%= form_for :admin_name, url: admin_name_path, method: :post do |a| %>
<%= a.label :name %>
<%= a.text_field :name %>
<%= a.submit "Submit name change" %>
<% end %>
<%= form_for :admin_age, url: admin_age_path, method: :post do |e| %>
<%= e.label :age %>
<%= e.number_field :age %>
<%= e.submit "Submit age change" %>
<% end %>
Then something like this;
def update_age
#admin = Admin.find(params[:admin_age][:id])
if params[:admin_age]
#admin.update_attributes(:age=> params[:admin_age][:age])
redirect_to #admin
else
render 'edit'
end
end
def update_name
#admin = Admin.find(params[:admin_name][:id])
if params[:admin_name]
#admin.update_attributes(:name=> params[:admin_name][:name])
redirect_to #admin
else
render 'edit'
end
end
** not tested for bugs

Incorrect param submitting

I have a form for casting your vote for your favourite image.
<%= form_for(#imagevote) do |f| %>
<% #miniature.collections(:photo).each do |collection| %>
<% if collection.photo.exists? %>
<td><div class="photo1">
<%= link_to image_tag(collection.photo.url(:thumb), :retina => true), collection.photo.url(:original), :retina => true, :class => "image-popup-no-margins" %>
<%= f.radio_button(:collection_id, collection.id) %>
<%= f.hidden_field :voter_id, :value => current_user.id %>
<%= f.hidden_field :voted_id, :value => collection.user_id %>
<%= f.hidden_field :miniature_id, :value => #miniature.id %>
<p>Painted by <%= link_to collection.user.name, collection.user %></p>
</div></td>
<% end %>
<% end %>
<%= f.submit "Vote" %>
<% end %>
Everything submits correctly except for the hidden_field :voted_id which for some reason duplicates the current_user.id.
UPDATE
I've tried logging in as another user and it seems that :voted_id is not duplicating current_user.id but rather that it is always "7" which was the :user_id I was using to test it before. Now logged in as user number 4 it is still entering the :voted_id as 7. I'm lost.
The link to the imagevotes view is as follows:
<%= link_to "See more and change your vote.", edit_imagevote_path(:miniature_id => #miniature, :voter_id => current_user.id) %>
Here is my image votes controller
class ImagevotesController < ApplicationController
respond_to :html, :js
def new
#imagevote = Imagevote.new
#miniature = Miniature.find(params[:miniature_id])
end
def edit
#imagevote = Imagevote.find_by_miniature_id_and_voter_id(params[:miniature_id],params[:voter_id])
#miniature = Miniature.find(params[:miniature_id])
end
def create
#imagevote = Imagevote.new(imagevote_params)
if #imagevote.save
flash[:success] = "Vote registered"
redirect_to :back
else
flash[:success] = "Vote not registered"
redirect_to :back
end
end
def update
#imagevote = Imagevote.find(params[:id])
if #imagevote.update_attributes(imagevote_params)
flash[:success] = "Vote changed."
redirect_to :back
else
redirect_to :back
end
end
private
def imagevote_params
params.require(:imagevote).permit(:collection_id, :voter_id, :voted_id, :miniature_id)
end
end
You only have one #imagevote object, but you are outputting the hidden fields inside your collection loop so you will have multiple fields in the form referencing the same attribute on the model: if you check the html that is generated, you should see multiple hidden fields with the same name attribute.
The way that browsers handle multiple inputs with the same name means that the param that comes through for :voted_id will always be the :user_id from the last collection.
It's difficult to say because you didn't provide your model and your loop code stripped.
I would guess that you loop over collection that belongs to the current_user. And in this case you will have current_user.id always be the same as collection.user_id. May be you wanted to see collection.photo_id?

Resources