Nested Forms with find_or_create_by method - ruby-on-rails

I've been stuck on this issue all day now and I'm fairly certain there is an easy fix that I am just not seeing due to my inexperience. A bit of background on what I'm trying to do before I discuss my problem. I have a model called Companies that can have many Locations. Similarly, a location can have multiple Companies. Because of this I created a has_many :through relationship.
class Company < ApplicationRecord
has_many :company_locations
has_many :locations, :through => :company_locations
accepts_nested_attributes_for :company_locations
accepts_nested_attributes_for :locations
end
class Location < ApplicationRecord
has_many :company_locations
has_many :companies, :through => :company_locations
end
class CompanyLocation < ApplicationRecord
belongs_to :company
belongs_to :location
end
Because of this structure, when a Company's location is created/updated I want to check whether this location (by name) exists. If it does, I use form the association between the Company and that Location. If it does not, the location is created and then the association is created. It is my understanding that the best way to do this is through a find_or_create_by method. However, the various ways I have tried do not seem to be creating this functionality.
Right now just to get something working my view for new Companies is this:
<h1> Add Company </h1>
<%= form_for :company, url: companies_path do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name%>
</p>
<p>
<%= f.label :website %><br>
<%= f.text_field :website%>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<%=fields_for :locations do |location_form|%>
<%= location_form.label :name, 'Location' %>
<%= location_form.text_field :name %>
<%end%>
<p>
<%= f.submit %>
</p>
<% end %>
Now inside the create action in my Companies controller is where I am experiencing difficulties. As said before, I want to check if the location that is being added to the company already exists or not. Because of this, I am using a find_or_create_by method. However, I cannot seem to figure out how to properly handle the strong params/slice the params in a way to make this work without error.
def new
#company = Company.new
#company_locations = #company.company_locations.build
#location = #company_locations.build_location
end
def create
#company = Company.new(company_params)
#location = Location.find_or_create_by(name: (company_params.slice(:location_attributes[0][:name)))
#company.locations << #location
#company.save
redirect_to #company
end
private
def company_params
params.require(:company).permit(:name,:website, :description, location_attributes: [:name])
end
Currently, I'm getting an error saying 'no implicit conversion of Symbol into Integer' which leads me to believe that I am accessing the hash wrong, however, any other method I have tried results in the a Location being created with "NULL" set as the name. I'm really stumped on this one, and to completely honest I'm not sure I am approaching this nested form correctly. In the future, I hope to use JQuery/Javascript/Cocoon to be able to dynamically add fields in the form to add more locations at once. I've been trying to follow other Stack Overflow posts and forums to no avail. Any help/guidance is much appreciated! Thank you.
UPDATE
Still stuck on this one. Here is the params hash from the server log for an example company:
{"utf8"=>"✓",
"authenticity_token"=>"xpDOq6D5YRZ3VUXLBLgu8SfRIkRXgMQHXIRUtArNp1smtXShB/i54fQQVEHgqy64kdj1R+u0t/JVihLCXQVZpg==",
"company"=>{"name"=>"Google", "website"=>"www.google.com", "description"=>"Google is a search engine."},
"locations"=>{"name"=>"Mountain View"},
"commit"=>"Save Company"}
UPDATE 2
Params hash after Pavan's suggestions.
{"utf8"=>"✓",
"authenticity_token"=>"xJHZVYSfHmjR3BOMS49yRzwD35NV5F7uyCou8yOmtKAktGNfI57Gn1KZAgavnHIOigoIkOnQLRvBJGiFdG5KXQ==",
"company"=>{"name"=>"Logitech", "website"=>"www.logitech.com", "description"=>"This is logitech"},
"locations"=>{"name"=>"Chicago"},
"commit"=>"Save Company"}
UPDATE 3
Changed the form for tag to:
<%= form_for #company, url: companies_path do |f| %>
This is the new updated params hash:
{"utf8"=>"✓",
"authenticity_token"=>"UiiwNXzOiDqZd0Vv1dDu4jkyRQU4e9LKLixqKH+rvKCyDQo/289QzRoyVOUxw+6rjzuSBoRPoT8nIixeKGNCXQ==",
"company"=>{"name"=>"Seagate", "website"=>"www.seagate.com", "description"=>"This is seagate"},
"locations"=>{"name"=>"Los Angeles "},
"commit"=>"Create Company"}

I see a couple of mistakes
You need to build the associations correctly
def new
#company = Company.new
#company_locations = #company.company_locations.build
#location = #company.locations.build #since you have defined nested attributes in company
end
Change location_attributes to locations_attributes
def company_params
params.require(:company).permit(:name,:website, :description, locations_attributes: [:name])
end
And try using company_params.slice(:locations_attributes[0][:name])

Related

Using form_with with a self-referential resource in Rails 5

I have a self-referential resource, Node, set up like this:
class Node < ApplicationRecord
belongs_to :parent, class_name: 'Node', foreign_key: 'node_id', optional: true
has_many :children, class_name: 'Node', foreign_key: 'node_id'
end
It works fine on the console, but I want to be able to navigate to a node and create children nodes from there, and I'm having trouble doing so.
For example, if I open /nodes/1, there should be a form there, and every node I create from that form should automatically populate node_id with the current node's id, 1.
How do I set up form_with and the nodes_controller so that it will allow me to achieve this?
The closest I got was by creating a special method (with the appropriate route):
def create_child
#node = Node.new(node_params)
#node.node_id = params[:id]
if #node.save
redirect_to node_path(params[:id])
end
end
private
def node_params
params.require(:node).permit(:name)
end
And then setting up the form like this:
<%= form_with url: "create_child" do |form| %>
<%= form.text_field :name %>
<%= form.submit %>
<% end %>
The request goes through, but then I get this error:
ActionController::ParameterMissing (param is missing or the value is empty: node)
But as far as I can tell the Node object should have been created in the create_child method.
Any ideas?
I've kind of solved it.
The form:
<%= form_with model: #node.children.build do |form| %>
<%= form.text_field :name %>
<%= form.hidden_field :node_id %>
<%= form.submit %>
<% end %>
And the controller:
def create
#node = Node.new(node_params)
if #node.save
redirect_to #node.parent
end
end
It's not perfect because it interferes with the displaying of the children nodes: if I put the form before the list of children, the list breaks. However, this is ok for my purposes, so I'll take it. Unless someone posts a better alternative I'll be accepting this as the answer.

How do I save this nested form in Rails 4 with a has many through association?

The problem that I have here is that I have a nested form that won't save to the database and I'm suspecting it's because the proper attributes aren't being passed into the form prior to being saved. Right now I'm trying to pass these values through hidden fields but I'm thinking there's probably a more "Railsy" way to do this. This is the form that I have created to do this:
<%= form_for #topic do |f| %>
<%= render "shared/error_messages", object: f.object %>
<%= f.fields_for :subject do |s| %>
<%= s.label :name, "Subject" %>
<%= collection_select :subject, :id, Subject.all, :id, :name, {prompt:"Select a subject"} %>
<% end %>
<%= f.label :name, "Topic" %>
<%= f.text_field :name %>
<div class="text-center"><%= f.submit class: "button radius" %></div>
<% end %>
This form generates a params hash that looks like this:
{"utf8"=>"✓", "authenticity_token"=>"PdxVyZa3X7Sc6mjjQy1at/Ri7NpR4IPUzW09Fs8I710=", "subject"=>{"id"=>"5"}, "topic"=>{"name"=>"Ruby"}, "commit"=>"Create Topic", "action"=>"create", "controller"=>"topics"}
This my model for user.rb:
class User < ActiveRecord::Base
has_many :topics
has_many :subjects, through: :topics
end
In my subject.rb file:
class Subject < ActiveRecord::Base
has_many :topics
has_many :users, through: :topics, dependent: :destroy
validates :name, presence: true
end
In my topic.rb file:
class Topic < ActiveRecord::Base
belongs_to :subject
belongs_to :user
accepts_nested_attributes_for :subject
validates :name, presence: true
end
class TopicsController < ApplicationController
before_filter :require_login
def new
#topic = Topic.new
#topic.build_subject
end
def create
#topic = Topic.new(topic_params)
#topic.user_id = current_user.id
#topic.subject_id = params[:subject][:id]
if #topic.save
flash[:success] = "Success!"
render :new
else
flash[:error] = "Error!"
render :new
end
end
private
def topic_params
params.require(:topic).permit(:name,:subject_id,:user_id, subjects_attributes: [:name])
end
end
So I'm getting closer to having a successful form submission! I placed the method accepts_nested_attributes_for in the join model, which in this case is in topic.rb. I don't really know why this works but I'm thinking it allows Rails to properly set the ":user_id" and ":subject_id" compared to placing accepts_nested_attributes_for on a model containing the "has_many through" relationship. I saw it on this post btw: http://makandracards.com/makandra/1346-popular-mistakes-when-using-nested-forms
NOW, I still have a problem where the ":subject_id" isn't being properly saved into the database. Would I have to pass in a hidden field to do this or would I have to do something else like nest my routes?
Wow that took forever to figure this one out. Since I have a has_many through relationship and I'm trying to created a nested form involving one of these models the problem I was having was I was placing the "accepts_nested_attributes_for" in the wrong model I was placing it in the has_many through model in the subject.rb file when it should have been placed in the model responsible for the join between two tables.
Also I made a super idiotic mistake on this line when I was trying to save the ":subject_id". I was writing this: #topic.subject_id = params[:subject_id][:id] instead of something like this:
#topic.subject_id = params[:subject][:id]
It was a really dumb mistake (probably because I was copying a pasting code from another controller haha)
Anyways I hope others can learn from my mistake if they ever want to do a nested form on models with a "has_many through" relationship, in certain cases the "accepts_nested_attributes_for" method will go on the JOIN table and NOT on the model with the "has_many through" relationship

Has_Many Simple Form Nested Objects Form Form Not Posting User Values One Model has authentication

I am pretty sure my problem is a bit more complicated than the has_many nested forms questions that I have seen on stackoverflow
I have two models, one is company.rb and the other one is job.rb
the company.rb
has_many :jobs
accepts_nested_attributes_for :jobs, allow_destroy: true
attr_accessible :jobs_attributes
the job.rb has this
belongs_to :company
in my companies controller I have this
def new
#company = current_company.jobs.new
end
def create
#the actual create process where the params from the forms got sent here
#company = current_company.jobs.new(params[:company])
end
I have devise for company under authentication so I want to create job for the currently logged in company
this is my form
<%= simple_form_for #company do |f| %>
<%= f.input :title %>
<%= f.input :description %>
<%= f.button :submit %>
<% end %>
every entry of job is getting created but the values are not passed into it? I am having empty job recorded with no company_id in the record as well.
I have been working on this for two days can somebody help me out? I tried fields_for on the form, but nothing is getting posted?
this is in my log
Processing by JobsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"9P+tEDamlP9+jEHU31gL93hc4GyhXTZoe3zoMopKHa0=", "job"=>{"title"=>"software", "description"=>"make software"}, "commit"=>"Create Job"}
You're creating the job not the company, so you need to use params[:job].
Change the line in create action to:
#company = current_company.jobs.new(params[:job])
And it would be good if you use #job variable name as it will be more descriptive.

has_many through form new method, no id, no elements

I've been hacking around with Rails 3.2.11 for a while, and am trying to do this the 'right' way.
I have three models (Reflection, Skill, Utilization) that relate to each other through has_many: through:
Utilization.rb
class Utilization < ActiveRecord::Base
attr_accessible :reflection, :skill, :used_skill #used_skill is a boolean
belongs_to :reflection
belongs_to :skill
end
Reflection.rb
class Reflection < ActiveRecord::Base
## attributes here ##
has_many :utilizations
has_many :skills, through: :utilizations
accepts_nested_attributes_for :utilizations
accepts_nested_attributes_for :skills
end
Skill.rb
class Skill < ActiveRecord::Base
## attributes here ##
has_many :utilizations
has_many :reflections, through: :utilizations
end
Within the app, skills are already defined. The user action I am trying to support is:
User gets form for new Reflection.
User sees a list of Skills and checks off which ones they have used (Utilization).
User posts to create new Reflection and create the associated Utilization objects.
Here is the new method reflection_controller.rb:
class ReflectionsController < ApplicationController
def new
#reflection = Reflection.new
Skill.all.each do |skill|
#reflection.utilizations.build(skill_id: skill.id, used_skill: false)
end
end
end
And an abbreviated _form.html.erb for Reflections
<%= form_for(#reflection) do |f| %>
<% f.fields_for :utilizations do |builder| %>
<%= builder.label :used_skill %>
<%= builder.check_box :used_skill %>
<%= builder.fields_for :skill do |skill| %>
<%= skill.label :description %>
<%= skill.text_field :description %>
<% end %>
<% end %>
<% end %>
So the problem is that even though there are multiple Skills and I .new the Utilization objects and associate them with the #reflection, they don't show up in the form. I've played with the data structures a little bit, and I can reach the point where in ReflectionController.new #reflection.utilizations contains Utilization objects, it still won't work; when I run #reflection.utilizations.count it returns 0. It looks like the problem is that since none of the objects have an id at that time, it simply will not render out in the form. But my understanding is that one should not create objects during the new method…
Is there something obvious I'm missing? Is there a better way to do this? I've seen examples, include Ryan Bates' Railscast where people just use code like:
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
4.times { question.answers.build }
end
end
and supposedly this works fine.
I really appreciate the help. Trying to figure this out has been driving me crazy. This is my first question on SO, and I'm happy to add any clarifying data or additional code if you think it would help.
You forgot to use =:
<%#### Here ####%>
<%= f.fields_for :utilizations do |builder| %>
<%= builder.label :used_skill %>
<%= builder.check_box :used_skill %>
<%#### and here ####%>
<%= builder.fields_for :skill do |skill| %>

How to get Rails build and fields_for to create only a new record and not include existing?

I am using build, fields_for, and accepts_nested_attributes_for to create a new registration note on the same form as a new registration (has many registration notes). Great.
Problem: On the edit form for the existing registration, I want another new registration note to be created, but I don't want to see a field for each of the existing registration notes.
I have this
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
end
and this
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
and in the view I am doing this:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
and it is creating a blank text area for a new registration note (good) and each existing registration note for that registration (no thank you).
Is there a way to only create a new note for that registration and leave the existing ones alone?
EDIT: My previous answer (see below) was bugging me because it's not very nice (it still loops through all the other registration_notes needlessly). After reading the API a bit more, the best way to get the behaviour the OP wanted is to replace:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for :registration_notes, #registration.registration_notes.build do |n| %>
fields_for optionally takes a second parameter which is the specific object to pass to the builder (see the API), which is built inline. It's probably actually better to create and pass the new note in the controller instead of in the form though (just to move the logic out of the view).
Original answer (I was so close):
Just to clarify, you want your edit form to include a new nested registration note (and ignore any other existing ones)? I haven't tested this, but you should be able to do so by replacing:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for #registration.registration_notes.build do |n| %>
EDIT: Okay, from a quick test of my own that doesn't work, but instead you can do:
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content if n.object.id.nil? %>
<% end %>
This will only add the text area if the id of the registration note is nil (ie. it hasn't been saved yet).
Also, I actually tested this first and it does work ;)
If you want to create a new registration form on your edit action, you can just instantiate a new registration_note object. Right now, your form is for the existing registration object.
I believe this is what you want:
class RegistrationsController < ApplicationController
def edit
#new_registration_note = RegistrationNote.new
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
In your view, you should pass a hidden param that references the registration record id:
<%= form_for #new_registration_note do |r| %>
<%= r.hidden_field :registration_id, :value => #registration.id %>
<%= r.text_area :content %>
<% end %>
Now, you can create your new registration note that belongs to #registration. Make sure you have a column in your registration_notes table to point to the registration. You can read more about associations here: http://guides.rubyonrails.org/association_basics.html
Thank you so much for your help as I said in my post the only problem with the approach from "Zaid Crouch"(I don't know how to make a reference to a user hehe) is that if the form has error fields the form will be clear and boom after the page reloading you'll have nothing filled in your form and can you imagine if you form is like 20 or 30 fields that would be a terrible user experience of course
Here is my solution that works with validation models:
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
has_one :new_registration, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration
end
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.build_new_registration
end
end
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
I'm using simple_form in my example if you want to see the same working with validations and transaction take a look at the complete post here:
http://elh.mx/ruby/using-simple_form-for-nested-attributes-models-in-a-has_many-relation-for-only-new-records/
As Heriberto Perez correctly pointed out the solution in the most upvoted answer will simply discard everything if there's a validation error on one of the fields.
My approach is similar to Heriberto's but nevertheless a bit different:
Model:
class Registration < ActiveRecord::Base
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
# Because 0 is never 1 this association will never return any records.
# Above all this association don't return any existing persisted records.
has_many :new_registration_notes, -> { where('0 = 1') }
, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration_notes
end
Controller:
class RegistrationsController < ApplicationController
before_action :set_registration
def edit
#registration.new_registration_notes.build
end
private
def set_registration
#registration = Registration.find(params[:id])
end
def new_registration_params
params.require(:registration).permit(new_registrations_attributes: [:content])
end
end
View:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>

Resources