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

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.

Related

How can get the selected option id in rails

I am trying to get the id of selected option from dropdown but i get the nill id when i create the project
def create
developer_id = params[:developer_id]
parameters = project_params.merge({ user_id: current_user.id, developer_id: developer_id })
#project = Project.new(parameters)
respond_to do |format|
if #project.save
format.html { redirect_to projects_path, flash: { success: 'Project added successfully ' } }
else
format.html { render :new }
end
end
end
def project_params
params.require(:project).permit(:name, :user_id)
end
new.html.erb
<%= form_for :project, :html => {:class=>"form-group"}, url: projects_path do |f| %>
Add task: <%=f.text_field :name, class:"form-control" %><br>
<h2>Select Developer</h2>
<%= f.select :developer_id, options_for_select(#users.collect {|user|["#{user.name}","#{user.id}"]}) %><br>
<%= f.submit "Add" %>
<% end %>
The developer_id is nested under project just like the other params.
developer_id = params[:project][:developer_id]
or
developer_id = params.dig(:project, :developer_id)
When you are unsure where to find certain params or if they even exist then the easiest way is certainly to look into your applications log file. There you should see the incoming request and all the params and how they are nested.

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

Why is my form not saving? form_for question

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.

Rails 4 render action=>new does not go through new action

I have most common in rails code
def new
#company = Company.new
#companies = Company.order(:name).pluck(:name, :id)
end
def create
#company = Company.find(params["company"]["id"]) rescue nil
unless #company
render action: 'new'
return
end
status = #company.update_attributes(total_licenses: params["company"]["total_licenses"].to_i, assigned_licenses: 0)
if status == true
redirect_to users_super_admin_index_path, flash: {notice: "License has been allocated to company."}
else
render action: 'new'
end
end
but when somthing wrong it should render action new, but it directly render template hence #company remains nil and throws an error
ActionView::Template::Error (First argument in form cannot contain nil or be empty)
I want to find the permanent and right solution, no hacks please :) . And the reason why i am facing this problem.
In my view -
<%= form_for #company, url: licenses_path, method: "post" do |f| %>
<%= f.label :id, 'Select Company' %><br/>
<%= f.select :id, #companies, :include_blank => "Select Company", required: true %><br/><br/>
<%= f.label :total_licenses, 'License' %><br/>
<%= f.text_field :total_licenses, required: true%><br/><br/>
<%= f.submit 'Assign'%>
<% end %>
Remember that render(action: ...) does not actually run the method in question, it just renders out the template. You will need to manually trigger the new method to do this.

Make and use temporary attribute for a form

Ruby on Rails 4.1
The form has an option to select the table column name. I want to input text into the table column selected by the form. To do this I am trying to make temporary attributes that the form can use to store the value and examine in the create method. Then assign the text to the correct column, then save.
Controller:
def new
#word = Word.new
#language = Word.new(params[:language])
#translation = Word.new(params[:translation])
#language_options = Word.column_names
end
def create
#word = Word.new(word_params)
if #language == "arabic"
#word.arabic == #translation
end
respond_to do |format|
if #word.save
format.html { redirect_to #word, notice: 'Word was successfully created.' }
format.json { render :show, status: :created, location: #word }
else
format.html { render :new }
format.json { render json: #word.errors, status: :unprocessable_entity }
end
end
end
The form:
<%= simple_form_for(#word) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :name, placeholder: 'English String' %>
<%= f.input :language, collection: #language_options %>
<%= f.input :translation, placeholder: 'Translated String' %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
This is the error I get:
undefined method `language' for #<Word:0x007f6116b1bcb8>
Which is because there is not a language attribute for the form to use. So I was trying to make a temporary one in the controller new().
Is there a way to do this or do I have to make :language and :translation in a database table to reference in the form?
Virtual Attribute
You may benefit from using an attr_accessor in your model
This creates a virtual attribute which works the same as the "real" attributes in your model:
#app/models/word.rb
Class Word < ActiveRecord::Base
attr_accessor :column_name
end
This will allow you to assign values to this attribute which won't be saved into the db, which sounds like what you want:
#app/views/words/new.html.erb
<%= simple_form_for(#word) do |f| %>
<%= f.input :column_name do %>
<%= f.select :column_name, #language_options %>
<% end %>
<% end %>
When you submit this form, it will then give you the column_name attribute to edit:
#app/controllers/words_controller.rb
Class WordsController < ApplicationController
def create
# ... you'll have "column_name" attribute available
end
end

Resources