Why is my form not saving? form_for question - ruby-on-rails

I have this form. I am new to rails and I am trying to write a simple ecommerce site. This is the only part not working. (It worked 2 days ago I sear)
<%= form_tag line_items_path do%>
<%binding.pry%>
<%= hidden_field_tag :lite_item, :order_id, #order.id%>
<%= hidden_field_tag :line_item, :menu_item_id, #menu_item.id%>
<%= number_field_tag :line_item, :quantity, 1 %>
<%= submit_tag "Add to Cart"%>
<% end %>
It gives params that look like:
#<ActionController::Parameters {"authenticity_token"=>"VECKnS5SBot1rCyekepPXZa7TyTYkfFi0KdNRTB617ZnelmQo8Lkz_cJmQ8nAmCHUdDlPu1mpkhrPvMKysfjew", "order_id"=>"1", "menu_item_id"=>"1", "quantity"=>"1", "commit"=>"Add to Cart", "controller"=>"line_items", "action"=>"create"} permitted: false>
The controller for the view looks like this:
class MenusController < ApplicationController
def index
#menu_items = MenuItem.all
end
def show
#menu_item = MenuItem.find(params[:id])
#line_items = current_order.line_items.build
end
end
The form is really going through the line_items controller
def create
binding.pry
#line_item = LineItem.create(line_item_params)
if #line_item.save
#order.line_item_id = #line_item.id
#order.save
redirect_to cart_path(#current_cart), notice: "Item added to cart."
else
redirect_to menu_path(#menu_item), alert: "Item did not add to cart."
end
end
With strong params like this
def line_item_params
params.require(:line_item).permit(:menu_item_id, :quantity, :order_id)
end
It should use the line_items_path POST>
If anything else is needed just ask. Thanks in advance.

There are a lot of problems here.
The signature is hidden_field_tag(name, value = nil, options = {}). So the parameters you would actually be creating with that form is:
{
"lite_item" => "order_id", # check your spelling...
"line_item" => "quantity"
}
Oops. And that not even going to happen as <%= number_field_tag :line_item, :quantity, 1 %> will raise since you're passing an integer where the method expects a hash.
If you really have to create the inputs manually you would want:
<%= hidden_field_tag "line_item[order_id]", #order.id %>
But since you actually have a model there is no reason why you should be using form_tag instead of form_for(#line_item) or form_with(model: #line_item).
<%= form_for(#line_item) do |form| %>
<%= form.hidden_field :order_id %>
<%= form.hidden_field :menu_item_id %>
<%= form.number_field :quantity %>
<%= form.submit_tag "Add to Cart"%>
<% end %>
The controller should also use the correct pluralization for the instance variable:
def show
#menu_item = MenuItem.find(params[:id])
#line_item = current_order.line_items.build
end
Your create method is also pretty questionable. All you should need is:
def create
# use .new not .create
#line_item = LineItem.new(line_item_params)
if #line_item.save
redirect_to cart_path(#current_cart), notice: "Item added to cart."
else
redirect_to menu_path(#menu_item), alert: "Item did not add to cart."
end
end
I have no idea why you think you need to update #order here. Your controller should just really be adding a row to what is essentially a join table.

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

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.

Ruby param is missing or the value is empty: group

I'm developing an app with Ruby on Rails. Here is my controller action:
def new
#group = current_user.groups.build(group_params)
if #group.save
flash[:success] = "Group created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
My form:
<%= form_for(#group) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_area :name, placeholder: "Group's name" %>
<%= f.text_area :hashtag, placeholder: "Category" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
When I try to use:
private
def group_params
params.require(:group).permit(:name, :hashtag)
end
I get the following exception:
param is missing or the value is empty: group
I've tried to change :group in require but nothing seems to be working. Any ideas?
param is missing or the value is empty: group
I am trying to display the form but the exception is shown before I
can hit the submit button
Well then you are calling group_params in the wrong method. You should call it in create method. Let me clear up your code which should resolve your problem.
def new
#group = current_user.groups.build
end
def create
#group = current_user.groups.build(group_params)
if #group.save
flash[:success] = "Group created!"
redirect_to root_url
else
#feed_items = []
render 'static_pages/home'
end
end
private
def group_params
params.require(:group).permit(:name, :hashtag)
end
Explanation to the error:
If you see the params hash while displaying the form you won't see any :group key. Since you are calling group_params in the new method, the require will raise an exception when the key is not present,so is the error.

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?

What's the solution for nil.update_attributes in ruby?

I'm unable to crack this error. Can't figure out why #customer is being assigned to value nil.
"You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.update_attributes"
Here is a snippet of the code :
def cedit
#title = "Edit Customer Information"
#customer = Customer.find(params[:id])
if request.post? and params[:customer]
attribute = params[:attribute]
case attribute
when "fname"
try_to_update #customer, attribute
when "email"
try_to_update #customer, attribute
when "add"
try_to_update #customer, attribute
end
end
end
private
def try_to_update(customer, attribute)
if customer.update_attributes(params[:customer])
flash[:notice] = "Customer's details updated."
redirect_to :action => "record", :controller => "c2"
end
end
First of all your code looks very none-rails like and breaks a couple of rails best practices. I would strongly recommend you read the official Rails guide and try to see if you can refactor some of your code.
I have too little information on what you are trying to do in the grand scale of things so I can't give you a full fledged answer. But you probably want to do something along these lines.
class CustomersController < ApplicationController
def update
#customer = Customer.find(params[:id])
if #customer.update_attributes(params[:customer])
flash[:notice] = "Customer updated"
end
redirect_to customer_path(#customer)
end
end
The view could look something like this:
<%= form_for(:customer) do |f| %>
<%= f.text_field :fname %>
<%= f.text_field :email %>
<%= f.text_field :add %>
<%= f.submit_tag "Update" %>
<% end %>
Good luck!

Resources