Rails form search or create - ruby-on-rails

In a Product model where it as an associated brand, I would like to have an input field where I can select an existing brand or add a new one.
In the form I have:
<%= f.fields_for :brand do |b| %>
<div class="form-group">
<%= b.label :name, t('brand.one') %>
<%= b.select :name, options_from_collection_for_select(Brand.all, :name, :name, product.brand.name), { include_blank: true}, class: '0select2-find-or-create' %>
</div>
<% end %>
and in the models:
Product:
belongs_to :brand
accepts_nested_attributes_for :brand, limit: 1
Brand:
has_many :products
But every time I change the Brand of a product it will change that brand's (by brand id) name, instead of changing the id.
Also for the part of creating a new brand I will try to use select2 with the tags option. Any other suggestion?

I think it might be because of your options_from_collection_for_select(Brand.all, :name, :name, product.brand.name)
I believe for the second parameter you want id and not :name.
Reference:http://apidock.com/rails/v4.2.1/ActionView/Helpers/FormOptionsHelper/options_from_collection_for_select

Related

Couldn't find Manufacturer with 'id'= (blank)

I am trying to create a web application to practise my Ruby on Rails skill. I have a few entities in my database manufacturers, models, tints, prices
manufacturers {id, name} - stores the make of the car
models {id, manufacturer_id, name} - stores the models of the car
tints {id, manufacturer_id, model_id, front, sides, rear} - stores the length of tint required
prices {id, description, price } - stores the price of the item
I created a page to generate a quotation for window tinting. The page includes drop-down menus to let user to select manufacturer, model, type of film(front), type of film(side+rear)
Below is the code for the form
<%= form_tag('/quotation/tints/generate') do %>
<%= label :manufacturer_id, 'Manufacturer' %>
<div class="field">
<%= collection_select(:tint, :manufacturer_id, Manufacturer.order(:name), :id, :name, {:prompt => "Select Manufacturer"}) %>
</div>
Model:
<div class="field">
<%= grouped_collection_select(:tint, :model_id, Manufacturer.order(:name), :models, :name, :id, :name, {:prompt => "Select Model"}) %>
</div>
<%= label :price_front, 'Front Tint' %>
<div class="field">
<%= collection_select(:price, :price_front, Price.order(:name), :id, :name, {:prompt => "Select Front Tint"}) %>
</div>
<%= label :price_rear, 'Size and Back Tint' %>
<div class="field">
<%= collection_select(:price, :price_rear, Price.order(:name), :id, :name, {:prompt => "Select Side & Rear Tint"}) %>
</div>
<div class="form-group">
<%= submit_tag 'Submit' %>
</div>
<% end %>
When the form is submitted, it should be redirected to /quotation/tints/generate and display the value from the dropdown menu. However, I received an error, saying that Couldn't find Manufacturer with 'id'=. The code that caused the error is shown below
def generate
#manufacturers = Manufacturer.find(params[:manufacturer_id])
end
Here is the parameter from the debug log
{"utf8"=>"✓",
"authenticity_token"=>"Pl2bXiRT0AoF4i0h1RCHDbuvaKJNZOkV5ULQHKxDQgZzBWWLJ2mH7ddb9akwgxbloxBIHoVaT3pcwoIGcRufpg==",
"tint"=>{"manufacturer_id"=>"7", "model_id"=>"6"},
"price"=>{"price_front"=>"1", "price_rear"=>"2"},
"commit"=>"Submit"}
I can see that the id of each drop down value are shown up correctly in the parameter list. However, I coundn't able to print the value at /quotation/tints/generate nor get the name of the manufacturer or model.
Here is routes.rb:
get '/quotation/tints' => 'tints#quotation', :as => 'tints_quotation'
post '/quotation/tints/generate' => 'tints#generate', :as => 'generate_tints_quotation'
Tint.rb:
class Tint < ApplicationRecord
has_many :manufacturers
has_many :models
belongs_to :manufacturer
belongs_to :model
validates_uniqueness_of :model_id, :scope => [:manufacturer_id]
end
Model.rb:
class Model < ApplicationRecord
belongs_to :manufacturer, :dependent => :destroy
validates :name, :presence => true
validates_uniqueness_of :name, :scope => [:manufacturer_id]
before_save :capitalize_content
end
Manufacruter.rb:
class Manufacturer < ApplicationRecord
has_many :models, :dependent => :destroy
validates :name, :presence => true, uniqueness: { case_sensitive: false }
before_save :capitalize_content
end
tints.controller.rb:
def quotation
render 'quotation'
end
def generate
#manufacturers = Manufacturer.find(params[:manufacturer_id])
end
generate.html.erb:
<%= #manufacturers.name %>
I'm trying to print the manufacturer selected
I have tried multiple ways to define it, but I am still facing the same error. Any help is greatly appreciated.
In your params, manufacturer_id is a nested value of tint, as opposed to being a direct key of the params hash. Try the following:
def generate
#manufacturers = Manufacturer.find(params[:tint][:manufacturer_id])
end

How do I reference an existing instance of a model in a nested Rails form?

I'm attempting to build a recipe-keeper app with three primary models:
Recipe - The recipe for a particular dish
Ingredient - A list of ingredients, validated on uniqueness
Quantity - A join table between Ingredient and Recipe that also reflects the amount of a particular ingredient required for a particular recipe.
I'm using a nested form (see below) that I constructed using an awesome Railscast on Nested Forms (Part 1, Part 2) for inspiration. (My form is in some ways more complex than the tutorial due to the needs of this particular schema, but I was able to make it work in a similar fashion.)
However, when my form is submitted, any and all ingredients listed are created anew—and if the ingredient already exists in the DB, it fails the uniqueness validation and prevents the recipe from being created. Total drag.
So my question is: Is there a way to submit this form so that if an ingredient exists whose name matches one of my ingredient-name fields, it references the existing ingredient instead of attempting to create a new one with the same name?
Code specifics below...
In Recipe.rb:
class Recipe < ActiveRecord::Base
attr_accessible :name, :description, :directions, :quantities_attributes,
:ingredient_attributes
has_many :quantities, dependent: :destroy
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
In Quantity.rb:
class Quantity < ActiveRecord::Base
attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
And in Ingredient.rb:
class Ingredient < ActiveRecord::Base
attr_accessible :name
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
Here's my nested form that displays at Recipe#new:
<%= form_for #recipe do |f| %>
<%= render 'recipe_form_errors' %>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<h3>Ingredients</h3>
<div id='ingredients'>
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient_attributes do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name %>
<% end %>
<%= ff.label :amount %>
<%= ff.text_field :amount, size: "10" %>
<%= ff.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %><br>
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
</div><br>
<%= f.label :description %><br>
<%= f.text_area :description, rows: 4, columns: 100 %><br>
<%= f.label :directions %><br>
<%= f.text_area :directions, rows: 4, columns: 100 %><br>
<%= f.submit %>
<% end %>
The link_to and link_to_function are there to allow the addition and removal of quantity/ingredient pairs on the fly, and were adapted from the Railscast mentioned earlier. They could use some refactoring, but work more or less as they should.
Update: Per Leger's request, here's the relevant code from recipes_controller.rb. In the Recipes#new route, 3.times { #recipe.quantities.build } sets up three blank quantity/ingredient pairs for any given recipe; these can be removed or added to on the fly using the "Add ingredient" and "remove" links mentioned above.
class RecipesController < ApplicationController
def new
#recipe = Recipe.new
3.times { #recipe.quantities.build }
#quantity = Quantity.new
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
redirect_to #recipe
else
render :action => 'new'
end
end
You shouldn't put the logic of ingredients match into view - it's duty of Recipe#create to create proper objects before passing 'em to Model. Pls share the relevant code for controller
Few notes before coming to code:
I use Rails4#ruby2.0, but tried to write Rails3-compatible code.
attr_acessible was deprecated in Rails 4, so strong parameters are used instead. If you ever think to upgrade your app, just go with strong parameters from the beginning.
Recommend to make Ingredient low-cased to provide uniform appearance on top of case-insensitivity
OK, here we go:
Remove attr_accessible string in Recipe.rb, Quantity.rb and Ingredient.rb.
Case-insensitive, low-cased Ingredient.rb:
class Ingredient < ActiveRecord::Base
before_save { self.name.downcase! } # to simplify search and unified view
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
end
<div id='ingredients'> part of adjusted form to create/update Recipe:
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name, size: "10" %>
<% end %>
...
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
We should use :ingredient from Quantity nested_attributes and Rails will add up _attributes-part while creating params-hash for further mass assignment. It allows to use same form in both new and update actions. For this part works properly association should be defined in advance. See adjusted Recipe#new bellow.
and finally recipes_controller.rb:
def new
#recipe = Recipe.new
3.times do
#recipe.quantities.build #initialize recipe -> quantities association
#recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association
end
end
def create
#recipe = Recipe.new(recipe_params)
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
def update
#recipe = Recipe.find(params[:id])
#recipe.attributes = recipe_params
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
private
def prepare_recipe
#recipe.quantities.each do |quantity|
# do case-insensitive search via 'where' and building SQL-request
if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first
quantity.ingredient_id = quantity.ingredient.id = ingredient.id
end
end
end
def recipe_params
params.require(:recipe).permit(
:name,
:description,
:directions,
:quantities_attributes => [
:id,
:amount,
:_destroy,
:ingredient_attributes => [
#:id commented bc we pick 'id' for existing ingredients manually and for new we create it
:name
]])
end
In prepare_recipe we do the following things:
Find ID of ingredient with given name
Set foreign_key quantity.ingredient_id to ID
Set quantity.ingredient.id to ID (think what happens if you don't do that and change ingredient name in Recipe)
Enjoy!

Correct usage of simple_form collection_check_boxes

I have set up 2 models as following :
class Sector < ActiveRecord::Base
attr_accessible :summary, :title, :sector_ids
belongs_to :platform
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url
has_many :sectors
end
and a form which tries to use example from here as follows :
<%= simple_form_for #platform, :html => { :class => 'form-vertical'} do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="row-fluid">
<div class="span12">
<div class="span6">
<%= field_set_tag "General" do %>
<%= f.input :name %>
<%= f.input :url %>
<%= f.collection_check_boxes :sector_ids, Sector.all, :title, :title %>
<% end %>
</div>
</div>
</div>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
nevertheless, after I try to submit the form I get the following error :
Can't mass-assign protected attributes: sector_ids
What am I missing here? I successfully migrated the database after adding appropriate associations, but it seems like Rails doesnt really know what to do now wit the sector ids that are selected.
Solution :
class Sector < ActiveRecord::Base
attr_accessible :summary, :title
belongs_to :platforms
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url, :platform_attributes, :sector_ids
has_many :sectors
end
and in the view :
<%= f.association :sectors, :as => :check_boxes %>
Ofcourse, do not forget to run "rake db"migrate", in case you havent done it yet. I was also required to restart the server in order for the changes to apply.
I hope this helps someone.
Try adding { :sector_ids => [] } to your Platform permits list.
https://github.com/rails/strong_parameters#nested-parameters
First I think you should remove :sectors_ids from the attr_accessible of Sector and try.
If the above doesn't works (which probably may not work), see the documentation of simple_form, specially read https://github.com/plataformatec/simple_form#associations and https://github.com/plataformatec/simple_form#collection-check-boxes; the last one makes what you are trying to do, but, in the associations they use has_and_belongs_to_many associations.
Finally, check this out https://github.com/plataformatec/simple_form/issues/341
Update
The solution is to make a has_and_belongs_to_many relationship, there is no way out, because, you want to associate some sectors to one platform, but, you want to associate the same sectors to other platforms, otherwise you won't need checkbox, only nested forms to keep adding new records. You have to make this change in your database design and structure.

Rails updating multiple models on a single form

Im writing a form which uses formtastic to manage the BusinessUnit model, however when creating a new BusinessUnit it also has to create a number of other record types. The associations between the models are as below:
class BusinessUnit < ActiveRecord::Base
has_many :business_unit_sites
has_many :locations
class BusinessUnitSite < ActiveRecord::Base
belongs_to :site
belongs_to :business_unit
class Site < ActiveRecord::Base
has_many :locations
has_many :business_unit_sites
class Location < ActiveRecord::Base
belongs_to :business_unit
belongs_to :site
When a BusinessUnit is created, a Site must also be created using BusinessUnitSite as a join table. In addition a Location record should be created which must hold a foreign key to the new Site record and this is where Im having problems.
I can create a new Location using a nested form (below) but the Site will have to be created manually.
<%= semantic_form_for #business_unit do |f| %>
<%= f.inputs do %>
<%= f.input :name %>
<%= f.input :business_unit_id %>
<%= f.input :business_unit_group, :include_blank => false %>
<%= f.input :business_unit_type %>
<%= f.input :tax_region, :include_blank => false %>
<%= f.semantic_fields_for :locations do |l| %>
<%= l.input :name, :label => "Location Name" %>
<% end %>
<% end %>
<%= f.buttons %>
<% end %>
What is the best way to create the Location, Site records and ensure that Location holds the foreign key of the newly created Site?
You probably want to do something like using the "fields_for" approach for the sub-objects in your form.
See this related answer:
Multiple objects in a Rails form
More info about fields_for:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html
http://apidock.com/rails/ActionView/Helpers/FormHelper/fields_for

get association values in rails forms

I'm building an app where a User has tasks and a task has a location.
The tasks and locations are in a nested_form using formtastic_cocoon, which is the formtastic gem with a jQuery extension.
The location.address field is an autocomplete text field searching on addresses that already exist in the database. So when the user selects the address, a hidden location_id in the form is populated.
What I am trying to do is that when the user goes to edit the task, I want it to display the currently selected address, but I don't see anywhere that I can retrieve that value. I can get the location_id, as that is in the task model, but I can't seem to get the associated location.address.
the models are
class Task < ActiveRecord::Base
attr_accessible :user_id, :date, :description, :location_id
belongs_to :user
has_one :location
end
class Location < ActiveRecord::Base
attr_accessible :address, :city, :state, :zip
has_many :tasks
end
then in my form, I have
<div class="nested-fields">
<%= link_to_remove_association "remove task", f %>
<div class="searchAddress">
< input type="text" value="HERE IS WHERE I WANT TO SHOW THE ADDRESS" >
</div>
<%= f.hidden_field :location_id %>
<%= f.inputs :date, description %>
</div>
----------- edited to include all formtastic code ---------------
form.html.erb
<%= semantic_form_for #user, :html=>{:multipart=true} do |form| %>
<%= form.inputs :username, :photo %>
<%= form.semantic_fields_for :tasks do |builder | %>
<%= render 'task_fields', :f => builder %>
<% end %>
<% end %>
------- end edit --------------
I have tried outputing different manner of 'f', but don't see any reference to the associated locations, yet if I debug User.Task[0].Location outside of the form, I get the correct location details. How do I get that inside the form??
--------- update ------------
getting a bit closer on this. It turns out I can output
<%= debug f.object %>
I get the task object returned. Unfortunately it does not include the location object, just the value of the location_id field.
Did you try #task.location.address ?
Your approach is right. But there is some slide miss-conception declaring association. I'ld like to change as below:
# app/models/task.rb
class Task < ActiveRecord::Base
attr_accessible :user_id, :location_id, :date, :description
belongs_to :user
belongs_to :location
end
As we are storing location foreign key in the tasks table, it's better to declare belongs_to association here.
# app/models/location.rb
class Location < ActiveRecord::Base
attr_accessible :address, :city, :state, :zip
has_many :tasks
# this method will return full address of a location
def full_address
[address, city, state, zip].reject(&:blank?).join(", ")
end
end
Then you need to add the full_address of a selected location.
# app/views/users/_task_fields.html.erb
<div class="nested-fields">
<%= link_to_remove_association "remove task", f %>
<div class="searchAddress">
<input type="text" value="<%= f.object.location&.full_address %>">
</div>
<%= f.hidden_field :location_id %>
<%= f.inputs :date, description %>
</div>

Resources