'Build' error when nesting form attributes - ruby-on-rails

I'm encountering an error when trying to create a new nested form.
I have 3 models:
class Child < ActiveRecord::Base
has_many :hires
has_many :books, through: :hires
end
class Hire < ActiveRecord::Base
belongs_to :books
belongs_to :children
accepts_nested_attributes_for :books
accepts_nested_attributes_for :children
end
class Book < ActiveRecord::Base
has_many :hires
has_many :children, through: :hires
belongs_to :genres
end
I'm trying to set up a view which allows children to 'hire' 2 books.
The view looks like this:
<%= form_for(#hire) do |f| %>
<%= hires_form.label :child %><br>
<%= hires_form.select(:child, Child.all.collect {|a| [a.nickname, a.id]}) -%>
<%= f.fields_for :books do |books_form| %>
<%= books_form.label :book %><br>
<%= books_form.select(:book, Book.all.collect {|a| [a.Title, a.id]}) -%>
<%= books_form.label :book %><br>
<%= books_form.select(:book, Book.all.collect {|a| [a.Title, a.id]}) -%>
<% end %>
<%= f.submit %>
<% end %>
The controller looks like this:
class HiresController < ApplicationController
...
def new
#hire = Hire.new
2.times { #hire.books.build }
end
def create
#hire = Hire.new(hire_params)
respond_to do |format|
if #hire.save
format.html { redirect_to #hire, notice: 'Hire was successfully created.' }
format.json { render :show, status: :created, location: #hire }
else
format.html { render :new }
format.json { render json: #hire.errors, status: :unprocessable_entity }
end
end
end
...
private
# Use callbacks to share common setup or constraints between actions.
def set_hire
#hire = Hire.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def hire_params
params.require(:hire).permit(:book, :child, books_attributes: [:id, :book, :child, :_destroy])
end
end
I'm getting the error:
undefined method `build' for nil:NilClass
I feel this is something obvious that i'm missing but any help would be great!

belongs_to :books
belongs_to :children
You need to singularize these.
belongs_to :book
belongs_to :child
Also see section 2.4 of the rails guides as a reference "The has_many :through Association":
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Keep in mind that you are using a belongs_to on the Hire object.
Notice the methods that are added when using the belongs_to association:
association(force_reload = false)
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
So you should be able to use the method build_book on the Hire object (eg. #hire.build_book of #hire.build_child).
The methods added by the has_many association are:
collection(force_reload = false)
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {}, ...)
collection.create(attributes = {})
collection.create!(attributes = {})
Which can be used on your Book and Child objects. For example: #book.childeren.build or #child.books.build.
For reference see the rails guides section 4.2 and 4.3 'Methods Added by ..':
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association

Related

simple_fields_for with check_boxes error

Good night friends!
In a form with many through, I need to display all the objects of a given class (tool), with a checkbox field and text field next to it. My form is as follows:
= simple_form_for #service, html: { class: 'form-horizontal' } do |f|
- #tools.each do |tool|
= f.simple_fields_for :instrumentalisations, tool do |i|
= i.input :tool_id, tool.id, as: :check_boxes
= i.input :amount
But I'm getting the following error:
Undefined method `tool_id 'for # <Tool: 0x007faef0327c28>
Did you mean To_gid
Models
class Service < ApplicationRecord
has_many :partitions, class_name: "Partition", foreign_key: "service_id"
has_many :steps, :through => :partitions
has_many :instrumentalisations
has_many :tools, :through => :instrumentalisations
accepts_nested_attributes_for :instrumentalisations
end
class Tool < ApplicationRecord
has_many :instrumentalisations
has_many :services, :through => :instrumentalisations
accepts_nested_attributes_for :services
end
class Instrumentalisation < ApplicationRecord
belongs_to :service
belongs_to :tool
end
Controller
def new
#service = Service.new
#service.instrumentalisations.build
end
def edit
#tools = Tool.all
end
def create
#service = Service.new(service_params)
respond_to do |format|
if #service.save
format.html { redirect_to #service, notice: 'Service was successfully created.' }
format.json { render :show, status: :created, location: #service }
else
format.html { render :new }
format.json { render json: #service.errors, status: :unprocessable_entity }
end
end
end
def service_params
params.require(:service).permit(:name, :description, :price, :runtime, :status, step_ids: [], instrumentalisations_attributes: [ :id, :service_id, :tool_id, :amount ])
end
Thank you!
the error is quite simple: tool doesn't has a tool_id method. BUT, why are you asking this to a tool object instead of a instrumentalisation object?
So, you're trying to create some instrumentalisations but you're passing a tool as object:
f.simple_fields_for :instrumentalisations, **tool** do |i|
fields_for requires a record_name, whith in this case is :instrumentalisations and the 2nd arg is the record_object, which should be an instrumentalisations object and not a tool object.
So to fix it would have to pass an instrumentalisation object. You can accomplish that by:
f.simple_fields_for :instrumentalisations, Instrumentalisation.new(tool: tool) do |i|
Of course this isn't the best solution since if you edit this object would be building a lot of new instrumentalisations.
I'd recommend the cocoon gem, which makes it easier to handle nested forms!

Update form not working at all in many-to-many relationship

In my app there is a many-to-many relationship between recipe and ingredient, everything is working fine but update.
When I update a recipe, i can update any value associated to recipe table in my database but ingredients are not modified
Here is the recipe model
class Recipe < ActiveRecord::Base
after_create :save_implementos
after_create :save_ingredientes
has_many :HasImplemento, dependent: :destroy
has_many :HasIngrediente, dependent: :destroy
has_many :ingredientes, through: :HasIngrediente
has_many :implementos, through: :HasImplemento
#CUSTOM SETTER#
def ingredientes=(value)
#ingredientes = value
end
def implementos=(value)
#implementos = value
#raise #implementos.to_yaml
end
private
#Guarda los implemenos de una receta
def save_implementos
#raise self.id.to_yaml
#implementos.each do |implemento_id|
HasImplemento.create(implemento_id: implemento_id, recipe_id: self.id)
end
end
def save_ingredientes
#raise #ingredientes.to_yaml
#ingredientes.each do |ingrediente_id|
HasIngrediente.create(ingrediente_id: ingrediente_id, recipe_id: self.id)
end
end
Here is the ingredient model
class Ingrediente < ActiveRecord::Base
has_many :has_ingredientes
has_many :recipes, through: :HasIngrediente
end
and Here is the join table
class HasIngrediente < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingrediente
end
I think you just forgot the accepts_nested_attributes_for in your Recipe model:
accepts_nested_attributes_for :ingredients
Your models have a lot of non-conventional code, which is likely the reason for them not working as required:
Use snake_case to define your associations (not CamelCase)
If you're saving nested/associated data, use accepts_nested_attributes_for
Only use callbacks to deal with the model directly; not to deal with other models
#app/models/recipe.rb
class Recipe < ActiveRecord::Base
has_and_belongs_to_many :ingredientes
has_and_belongs_to_many :implementos
accepts_nested_attributes_for :ingredientes, :implementos #-> if you wanted to create new ones
end
Because you're only creating join table records with your callbacks, you can get away with using << or populating the collection_singular_ids value (if you're using existing records):
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def new
#recipe = Recipe.new recipe_params
#recipe.save
end
private
def recipe_params
params.require(:recipe).permit(:implementos_ids, :ingredientes_ids)
end
end
This will allow you to use:
#app/views/recipes/new.html.erb
<%= form_for #recipe do |f| %>
<%= f.collection_select :implementos_ids, Implementos.all, :id, :name %>
<%= f.collection_select :ingredientes_ids, Ingrediente.all, :id, :name %>
<%= f.submit %>
<% end %>
-
I recommend has_and_belongs_to_many because it does not appear that you're populating your join table with any more data than the two model references.
The difference between has_many :through and has_and_belongs_to_many is has_many :through gives you the ability to store extra data in the join table. If you don't need this, has_and_belongs_to_many is far simpler to maintain.
Thus, to answer your question directly, to update your recipe, you can use the following (with my updated code):
#app/views/recipes/edit.html.erb
<%= form_for #recipe do |f| %>
<%= f.collection_select :implementos_ids, Implementos.all, :id, :name %>
<%= f.collection_select :ingredientes_ids, Ingrediente.all, :id, :name %>
<%= f.submit %>
<% end %>
#app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
def edit
#recipe = Recipe.find params[:id]
end
def update
#recipe = Recipe.find params[:id]
#recipe.update recipe_params
end
private
def recipe_params
params.require(:recipe).permit(:implementos_ids, :ingredientes_ids)
end
end
This will set the implementos_ids & ingredientes_ids values for your recipe, which will update the associations automatically.

Ruby on Rails : Updating multiple models in a single form

I have 3 models User, House and Order.
Order Model
class Order < ActiveRecord::Base
belongs_to :from_house, :class_name => "House"
belongs_to :to_house, :class_name => "House"
belongs_to :user
accepts_nested_attributes_for :from_house, :to_house, :user
end
My House Model.
class House < ActiveRecord::Base
belongs_to :user
belongs_to :place
belongs_to :city
end
My user model.
class User < ActiveRecord::Base
has_many :orders
has_many :houses
end
In my order form I have something like this
<%= form_for #order do |f| %>
... # order fields
<%= f.fields_for :user do |i| %>
... # your from user forms
<% end %>
<%= f.fields_for :from_house do |i| %>
... # your from house forms
<% end %>
<%= f.fields_for :to_house do |i| %>
... # your to house forms
<% end %>
...
<% end %>
I haven't changed much in controller from the default. The controller code
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render action: 'show', status: :created, location: #order }
else
format.html { render action: 'new' }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
def order_params
params.require(:order).permit( :shift_date, user_attributes: [:name, :email, :ph_no], from_house_attributes: [:place_id, :floor, :elevator, :size], to_house_attributes: [:place_id, :floor, :elevator])
end
When I submit the form, as expected a Order gets created with a new from_house and to_house along with a new user. But however my user_id in house table remains NULL. How can I make the houses(both from and to) reference the user created after submit.
The User is not logged in, So there is no current_user. We have to create a new user based on the details given. That user has to be associated with the houses (from and to).
I hope I'm clear. If not please let me know.
P.S: This question is an extension to this Ruby on rails: Adding 2 references of a single model to another model
I think this change in app/models/order.rb should do the trick:
class Order < ActiveRecord::Base
belongs_to :user
belongs_to :from_house, class_name: 'House'
belongs_to :to_house, class_name: 'House'
accepts_nested_attributes_for :user, :from_house, :to_house
validates :user, :from_house, :to_house, presence: true
def from_house_attributes=(attributes)
fh = build_from_house(attributes)
fh.user = self.user
end
def to_house_attributes=(attributes)
th = build_to_house(attributes)
th.user = self.user
end
end
Now, try this in your Rails console:
params = { user_attributes: { name: 'New name', email: 'name#example.com' }, from_house_attributes: { name: 'From house name' }, to_house_attributes: { name: 'to house name' } }
o = Order.new(params)
o.save
o.from_house
o.to_house
Cheers!

False name? Patient::Diagnosi

I have to models:
class Patient < ActiveRecord::Base
attr_accessible :bis_gultigkeit, :geburtsdatum, :krankenkassennummer, :kvbereich, :landercode, :name, :namenszusatz, :plz, :statuserganzung, :strasse, :titel, :versichertennumer, :versichertenstatus, :vorname, :wohnort, :geschlecht, :telefon, :email, :gewicht
has_many :diagnosis
end
class Diagnose < ActiveRecord::Base
attr_accessible :beschreibung, :code, :seite, :sicherheit, :typ, :patient_id
belongs_to :patient
end
How you can see the two models have an association.
So that i want to display on the patient show page all of his diagnosis.
def show
#patient = Patient.find(params[:id])
#diagnosis = #patient.diagnosis
respond_to do |format|
format.html # show.html.erb
format.json { render json: #patient }
end
end
And in my view i call:
<%= #diagnosis.inspect %>
But somehow i get the error:
uninitialized constant Patient::Diagnosi
I cannot explain me why i get this error? And why does it say Diagnosi? I mean my model name is Diagnose! Thanks
You can call Diagnose.class_name.pluralize to see how rails pluralizes it.
I guess it is "Diagnoses", so you shoudl call:
#diagnoses = #patient.diagnoses
and
<%= #diagnoses.inspect %>

HABTM relationships and accepts_nested_attributes_for

I have a form that lets me create new blog posts and I'd like to be able to create new categories from the same form.
I have a habtm relationship between posts and categories, which is why I'm having trouble with this.
I have the following 2 models:
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories # should this be singular?
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
attr_accessible :name
end
My form lets me pick from a bunch of existing categories or create a brand new one. My form is as follows.
# using simple_form gem
.inputs
= f.input :title
= f.input :body
# the line below lets me choose from existing categories
= f.association :categories, :label => 'Filed Under'
# I was hoping that the code below would let me create new categories
= f.fields_for :category do |builder|
= builder.label :content, "Name"
= builder.text_field :content
When I submit my form, it gets processed but the new category is not created. My command prompt output tells me:
WARNING: Can't mass-assign protected attributes: category
But, if I add attr_accessible :category, I get a big fat crash with error message "unknown attribute: category".
If I change the fields_for target to :categories (instead of category) then my form doesn't even display.
I've spent a while trying to figure this out, and watched the recent railscasts on nested_models and simple_form but couldn't get my problem fixed.
Would this be easier if I was using a has_many :through relationship (with a join model) instead of a habtm?
Thanks to everyone who answered. After much trial and error, I managed to come up with a fix.
First of all, I switched from a HABTM to a has_many :through relationship, calling my join model categorization.rb (instead of categorizations_posts.rb) - NB: the fix detailed below will likely work with a HABTM too:
Step 1: I changed my models to look like this:
# post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
attr_accessible :title, :body, :category_ids
accepts_nested_attributes_for :categories
end
#category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, :through => :categorizations
attr_accessible :name, :post_ids
end
#categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
end
From the post model above: obviously, the accessor named :category_ids must be present if you want to enable selecting multiple existing categories, but you do not need an accessor method for creating new categories... I didn't know that.
Step 2: I changed my view to look like this:
-# just showing the relevent parts
= fields_for :category do |builder|
= builder.label :name, "Name"
= builder.text_field :name
From the view code above, it's important to note the use of fields_for :category as opposed to the somewhat unintuitive fields_for :categories_attributes
Step 3
Finally, I added some code to my controller:
# POST /posts
# POST /posts.xml
def create
#post = Post.new(params[:post])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
def update
#post = Post.find(params[:id])
#category = #post.categories.build(params[:category]) unless params[:category][:name].blank?
# stuff removed
end
Now, when I create a new post, I can simultaneously choose multiple existing categories from the select menu and create a brand new category at the same time - it's not a case of one-or-the-other
There is one tiny bug which only occurs when editing and updating existing posts; in this case it won't let me simultaneously create a new category and select multiple existing categories - if I try to do both at the same time, then only the existing categories are associated with the post, and the brand-new one is rejected (with no error message). But I can get round this by editing the post twice, once to create the new category (which automagically associates it with the post) and then a second time to select some additional existing categories from the menu - like I said this is not a big deal because it all works really well otherwise and my users can adapt to these limits
Anyway, I hope this helps someone.
Amen.
In your form you probably should render the fields_for once per category (you can have multiple categories per post, hence the habtm relation). Try something like:
- for category in #post.categories
= fields_for "post[categories_attributes][#{category.new_record? ? category.object_id : category.id}]", category do |builder|
= builder.hidden_field :id unless category.new_record?
= builder.label :content, "Name"
= builder.text_field :content
I have made my application and my nested form works with HABTM.
My model is :
class UserProfile < ActiveRecord::Base
attr_accessible :name, :profession
has_and_belongs_to_many :cities
belongs_to :user
attr_accessible :city_ids, :cities
def self.check_city(user,city)
user.cities.find_by_id(city.id).present?
end
end
class City < ActiveRecord::Base
attr_accessible :city_name
has_and_belongs_to_many :user_profiles
end
In my form I have:
-# just showing the relevent parts
= f.fields_for :cities do|city|
= city.text_field :city_name
And at my controller:
def create
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.build_user_profile(params[:user_profile])
respond_to do |format|
if #user_profile.save
format.html { redirect_to #user_profile, notice: 'User profile was successfully created.' }
format.json { render json: #user_profile, status: :created, location: #user_profile }
else
format.html { render action: "new" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
def update
params[:user_profile][:city_ids] ||= []
if params[:user_profile][:cities][:city_name].present?
#city= City.create(:city_name=>params[:user_profile][:cities][:city_name])
#city.save
params[:user_profile][:city_ids] << #city.id
end
#user=current_user
params[:user_profile].delete(:cities)
#user_profile = #user.user_profile
respond_to do |format|
if #user_profile.update_attributes(params[:user_profile])
format.html { redirect_to #user_profile, notice: 'User profile was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user_profile.errors, status: :unprocessable_entity }
end
end
end
This code works.
Maybe you should try it with (not testet):
attr_accessible :category_attributes
And HBTM relations arent really recommened... But I use them on my own :P

Resources