Multiple update forms for one model - ruby-on-rails

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

Related

Rails 6 pass param in render :new after failed save

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] %>

How to redirect two buttons on a different path with the same action?

I try to create discount coupons subtracting total basket of my customers.
I'm not sure it's the best way to do it but I created a solution that works almost.
The only problem is that when I create coupon, I can't render the same page because my order's update method redirect to the checkout page. I want to different redirections, one for checkout when the customer click on the checkout button on my cart page, the other when the customer create a coupon on the same cart page. The two buttons uses the same update action.
Any idea how to solve it ?
Here's my orders_controller:
def update
#order = current_order
update_coupon
if #order.update(order_params)
redirect_to checkout_path
else
render 'new'
end
end
private
def update_coupon
if #order.update(:coupon => params[:order])
redirect_to cart_path
end
end
Here's my carts/show.html.erb:
<p>Total TTC: <%= number_to_currency #order.subtotal %></p>
<% if #order.add_reduc.nil? %>
<% else %>
<p style="color:green;">-<%= number_to_currency #order.add_reduc, id: "new_reduc" %></p>
#set coupon value to nil
<%= form_for #order do |f| %>
<%= f.hidden_field :coupon, :value => nil %>
<%= f.submit "x" %>
<% end %>
<p>Price after reduction: <%= number_to_currency #order.subtotal_with_reduc %></p>
<p style="color:green;"><%= #order.coupon_description %></p>
<% end %>
<%= form_for #order do |f| %>
<%= f.text_field :coupon, placeholder:'place your coupon' %>
<%= f.submit "Go coupon" %>
<% end %>
and my order.rb :
COUPONS = {
'MAREDUC' => '25% off',
'CHOCOLOVER' => '10€ free',
'PAPLAFUN' => '10% off'
}
def subtotal_all_inclusive
if self.add_reduc.nil?
subtotal + shipping
else
subtotal_with_reduc + shipping
end
end
def coupon_description
COUPONS[coupon]
end
def add_reduc
if self.coupon == "MAREDUC"
subtotal * 25 / 100
elsif self.coupon == "CHOCOLOVER" && self.subtotal >= 50
10
elsif self.coupon == "PAPLAFUN"
subtotal * 10 /100
else
nil
end
end
You may add a hidden field in the coupon form:
<%= form_for #order do |f| %>
<%= hidden_field_tag :from_coupon, 1 %>
<%= f.text_field :coupon, placeholder:'place your coupon' %>
<%= f.submit "Go coupon" %>
And then:
def update
#order = current_order
if params[:from_coupon]
update_coupon
else
if #order.update(order_params)
redirect_to checkout_path
else
render 'new'
end
end
end
Or you may create an additional action in routes.rb:
resources :orders do
member do
put 'update_coupon'
end
end
And then:
<%= form_for #order, :url => update_coupon_order_path(#order), :method => :put do |f| %>
And then remove private in controller.
Pass additional param to url and check if it present make different actions. Example
<%= form_for #order, url: order_path(#order, additional: "your_param") do |f| %>

Nested signup form does not render correctly after invalid submission

I have a combined/nested signup form, which registers a new organization plus a member (two models with a 1:many relationship).
When submitting invalid information to the form (that is, I fill in invalid info for the organization and no info at all for the member) it should render the combined signup form again with the error message. Instead in this scenario after submitting the invalid information, it only renders the part of the signup form for signing up the organization (without the member part of the form). If I enter invalid information for both organization and member, then it does render correctly the full combined form.
Does anyone have an idea what is wrong with the code?
The controller includes:
def new
if (logged_in?)
flash[:danger] = "You're already logged in"
redirect_to root_url
end
#organization = Organization.new
#member = #organization.members.build
end
def create
#organization = Organization.new(organizationnew_params)
if #organization.save
#organization.members.each do |single_member|
single_member.send_activation_email
end
flash[:success] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new' # This is the relevant render line.
end
end
The new view:
<%= render partial: "registrationform", locals: { url: organizations_path } %>
The partial (registrationform):
<% if local_assigns.has_key? :url %>
<%= form_for #organization, url: url do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<h4>Details of the organization:</h4>
<%= f.text_field :name, class: 'form-control' %>
<%= f.fields_for :members do |p| %>
<h4>Your personal details:</h4>
<%= p.email_field :email, class: 'form-control' %>
<%= p.password_field :password, class: 'form-control' %>
<%= p.password_field :password_confirmation, class: 'form-control' %>
<% end %>
<%= f.submit "Sign up" %>
<% end %>
<% else %>
<%= flash.now[:danger] = "Error: no url defined" %>
<% end %>
Routes:
get 'signup/organization' => 'organizations#new', as: 'register'
In your new action, you are doing #organization.members.build, which is not in your edit action. When the validation fails, it will render your new action, but won't run your new action. You could try putting #organization.members.build in the else clause of your create action like:
else
#organization.members.build if #organization.members.blank?
render 'new'
end

View not recognising model attributes? Rails

I am trying to make it so that this form here will display certain fields based on the type of the website. In this case, I want it to display the form for when project.type == Website.
However I keep getting
undefined method `type' for #<Project::ActiveRecord_Relation:0x007ffe1cb543a8>
I am sure i can call .type normally because it works in the console.
Here are my files:
#views/assets/_new_asset.html.erb
<%= simple_form_for #asset do |f| %>
<% if #project.type == 'Website' %>
<%= f.input :name %>
<%= f.input :url %>
<%= f.button :submit %>
<% end %>
<% end %>
Here is my assets/controller
#controller/assets_controller.rb
class AssetsController < ApplicationController
def new
#asset = Asset.new
project = Asset.where(:project_id)
#project = Project.where(:id == project)
end
def create
#asset = current_user.assets.build(asset_params)
if #asset.save
flash[:notice] = "Asset successfully added."
redirect_to(#project, :action => 'show')
else
render(:action => 'new')
end
end
private
def asset_params
params.require(:asset).permit(:id, :type,:url, :page_rank, :rev_company ,:social_pages)
end
end
Well, you are getting back an object of ActiveRecord::Relation, not your model instance, thus the error since there is no method called type in ActiveRecord::Relation.
This should work
#project = Project.where(:id == project).first
OR
You can do like this too
<% if #project.first.type == 'Website' %>
Doing #project.first.type works because #project.first is returning the first instance of the model that was found by the where
#views/assets/_new_asset.html.erb
<%= simple_form_for #asset do |f| %>
<% if (#project.type == 'Website') %>
<%= f.input :name %>
<%= f.input :url %>
<%= f.button :submit %>
<% else %>
You Should not see this line.
<% end %>
In Controller
#controller/assets_controller.rb
class AssetsController < ApplicationController
def new
#asset = Asset.new
# As if i have no idea from where youre getting :project_id
# in your code so i changed that. add that to asset_params
# if required. Thanks!!!
#project = Project.where(id: params[:project_id]).take
end
def create
#asset = current_user.assets.build(asset_params)
if #asset.save
flash[:notice] = "Asset successfully added."
redirect_to(#project, :action => 'show')
else
render(:action => 'new')
end
end
private
def asset_params
params.require(:asset).permit(:id, :type,:url, :page_rank, :rev_company ,:social_pages)
end
end

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