How do I create a parent from child in Rails? - ruby-on-rails

In my app I'm receiving this error.
Couldn't find Vendor with ID=1 for InventoryItem with ID=
InventoryItem.rb
belongs_to :vendor
accepts_nested_attributes_for :vendor
Vendor.rb
has_many :inventory_items
_form.html.erb
<%= simple_nested_form_for #inventory_item, :html => {:class => 'form-inline' } do |f| %>
<h2>Inventory Data</h2>
<%= f.input :name, :input_html => {:autocomplete => :off, :placeholder => 'Item Name' }%>
<%= f.input :id, :as => :hidden %>
<%= f.simple_fields_for :vendor do |v| %>
<%= v.input :name, :label => 'Vendor name', :input_html => {:autocomplete => :off, :placeholder => 'Vendor Name' } %>
<%= v.input :id, :as => :hidden %>
<% end %>
<% end %>
----snip----
My parameters hash comes out accordingly
{"utf8"=>"✓",
"authenticity_token"=>"ZY9fum4XGStTMNbpRQxrzmP7PT3A6BUU+wOymV0fZ/c=",
"inventory_item"=>{"name"=>"testing",
"id"=>"7678",
"vendor_attributes"=>{"name"=>"test",
"id"=>"1"},
"item_instances_attributes"=>{"0"=>{"barcode"=>"",
"funding_source"=>"",
"serial"=>"",
"version"=>"",
"website_id"=>"",
"item_type"=>"Retail",
"type_of_at"=>"Vision",
"os"=>"Mac",
"registration_key"=>"",
"dealer_price"=>"",
"retail_price"=>"",
"reuse_price"=>"",
"estimated_current_purchase_price"=>"",
"cost_to_consumer_for_loan"=>"",
"repair_status"=>"Working",
"date_reviewed"=>"10-15-2012",
"qr_url"=>"",
"location"=>"",
"restrictions"=>"",
"notes"=>""}}},
"commit"=>"Create Inventory item"}
inventory_items_controller.rb
def create
params[:inventory_item].delete(:estimated_dealer_price)
#inventory_item = InventoryItem.create(params[:inventory_item])
#inventory_item.name = inventory_item.name.downcase
if inventory_item.save
redirect_to(inventory_items_path, :notice => "Item created.")
else
render 'new'
end
end
The controller is receiving the id and attempting to find the right vendor (which exists), has issues when left to the built-in rails methods for finding the vendor and building the relationship.
The input for vendor name is an autocomplete which assigns the id to the hidden id field.
possible solutions:
Handle manually in the controller, fetching the id and building the relationship
change the form so that the inventory_item.vendor.name autocompletes inventory_item.vendor_id and strip the name if an id is provided
fix something I'm missing?

Sounds like you have it in reverse, normally child should not be creating parent records and you should check if its posible to make it in more standard approach of parent child relationship.
That being said you can do something like this
InventoryItem << ActiveRecord::Base
belongs_to :vendor
def vendor_attributes=(params)
self.vendor = Vendor.find(params[:id]) || Vendor.create_by_name!(params[:name])
end
end

Related

Nested form with collection_select and multiple selection

Sorry for long post, usually don't post here often.
Rails 3.1
I have a Company model. Company has many custom properties. All properties names are standardized (e.g. company size, company revenue, etc). Some property values are custom and some are "standartized" (relation to another table CompanyProperty - property_value_id).
What I am trying to do is a form where new Company Properties are added. In a form I try to add different properties at once. Everything works except I cannot figure out how to add multiple values via collection_select.
Here are models:
Company:
has_many :properties, :class_name => 'CompanyProperty'
CompanyProperty:
belongs_to :company
belongs_to :property, :class_name => 'Property'
belongs_to :property_value, :class_name => 'PropertyValue', :foreign_key => 'properties_value_id'
Property:
has_many :company_properties
has_many :property_values
PropertyValue:
belongs_to :properties
has_many :company_properties
Form (marked comment # problem where using collection_select with multiple selection):
<%= form_for(#company, :url => {:action => 'update_company', :id => #company.id}, :html => {:multipart => true}) do |f| %>
<% #company.properties.each.with_index do |p,i| %>
<%= f.fields_for :properties, p do |builder| %>
<%= p.property.title %><br />
<% if p.property.standard == 0 %>
<%= builder.hidden_field :property_id %>
<%= builder.text_field :value %> <br />
<% elsif p.property.standard == 1 %>
<%= builder.hidden_field :property_id %>
<%= builder.collection_select(:properties_value_id, p.property.property_values, :id, :text_value, {:include_blank => true},
{:class => 'form-control'}) %>
# Problem:
<% elsif p.property.standard == 2 %>
<%= builder.hidden_field :property_id %>
<%= builder.collection_select(:properties_value_id, p.property.property_values, :id, :text_value, {:include_blank => false},
{:multiple => true, :size => p.property.property_values.count, :class => 'form-control'}) %>
<% end %>
<% end %>
<% end %>
<%= submit_tag("Save", :class => "btn btn-primary btn-block") %>
<% end %>
Controller:
def update_company
#company = Company.find(params[:id])
if #company.update_attributes(params[:company])
render :text => "Saved"
else
error = #company.errors.full_messages.map{|o| "<li>" + o + "</li>" }.join("") + "</ul>"
render :text => error
end
end
All property_id and values are saved, except values from collection_select with multiple selection. The post parameters goes something like this (4 records created, expected result - 6 new records):
Parameters: {"company"=>{"properties_attributes"=>{"0"=>{"property_id"=>"1", "properties_value_id"=>"2"}, "1"=>{"property_id"=>"2", "value"=>"34"},
"2"=>{"property_id"=>"3", "properties_value_id"=>["", "4", "5", "6"]},
"3"=>{"property_id"=>"4", "value"=>"34"}}}, "commit"=>"Save", "id"=>"16"}
property_id=>3 is that particular property that I'm trying to save. Expected result is to see three new records on CompanyProperties table with property_id = 3 and values properties_value_id accordingly. But this does not happens. Only one records is being created with property_id=3 and the properties_value_id is 1 (why?, there is no such value).
Plus I cannot understand why there is blank param here "properties_value_id"=>["", "4", "5", "6"]}. There are no such value :/
Could any one help to reach my expected result?
This should be a comment, but I'll post here to keep it readable:
The blank param will likely be a "selector" / "default" value in your select box. Failing that, is there any way you can see how your HTML element may be including a separate element in the property_value_ids?
If you were using Rails 4, I'd recommend your strong params were incorrect - however, as you're using Rails 3, I'd surmise you should do something like this:
<%= builder.collection_select("properties_value_id[]", p.property.property_values, :id, :text_value, {:include_blank => false},
{:multiple => true, :size => p.property.property_values.count, :class => 'form-control'}) %>

Cannot get simple_nested_form to submit data

I am trying to build a simple_nested_form in my Ruby on Rails app. When I submit my form I am getting some unknown error because it is just redirecting back to the form to input again. Here is the output in the rails server console for when I submit the form. It looks like there is some random "0" => thrown in there.
Parameters: {"machine"=>{"name"=>"2134", "ip_adress"=>"2", "machine_employees_attributes"=>{"0"=>{"machine_id"=>"1", "employee_id"=>"2"}}}, "commit"=>"Create Machine"}
I have a machine model which has_many :machine_employees
and a machineemployee model which belongs_to :machine
Do you have any idea why this 0 => could be appearing because I think it is what is giving me the issues.
Here is the code for my models.
Machine
class Machine < ActiveRecord::Base
# Relationships
has_many :machine_employees
has_many :employees, :through => :machine_employees
accepts_nested_attributes_for :machine_employees, :reject_if => lambda{ |me| me[:employee_id].blank? }
attr_accessible :ip_adress, :name, :machine_employees_attributes
# Validations
validates_presence_of :name, :ip_adress
end
MachineEmployee
class MachineEmployee < ActiveRecord::Base
before_validation :set_default
# Relationships
belongs_to :machine
belongs_to :employee
attr_accessible :employee_id, :machine_id, :end_date, :start_date
# Validations
validates_presence_of :employee_id, :machine_id, :start_date
private
# Callback Methods
def set_default
self.start_date = Date.today
self.end_date = nil
end
end
New Machine Form
<div class="row-fluid">
<div class="span3">
<h1>Add a Machine</h1>
<br />
<%= simple_nested_form_for #machine do |f| %>
<%= render "machine_fields", :f => f %>
<%= f.button :submit %>
<%= link_to 'Back', machines_path %>
</div>
<div class="span4">
<h4>Assign an Employee to This Machine</h4>
<%= f.simple_fields_for :machine_employees do |me_form| %>
<!-- render nested machine_employee fields-->
<%= render "machine_employee_fields", :f => me_form %>
<% end %>
</div>
<% end %>
</div>
Machine Employee Fields Partial
<%= f.input :machine_id, :as => :hidden, :input_html => { :value => #machine.id } %>
<%= f.input :employee_id, collection: #employees, :id => :name, :prompt => "Select ..." %>
The 0 is thrown in there because the machine model has_many machine_employees. When you use nested forms, it passes a pseudo-array for has_many relations. So, if you tried to submit 2 machine employees, your hash would look like this:
Parameters: {"machine"=>{"name"=>"2134", "ip_adress"=>"2", "machine_employees_attributes"=>{
"0"=>{"machine_id"=>"1", "employee_id"=>"2"},
"1"=>{"machine_id"=>"1", "employee_id"=>"3"}
}
}, "commit"=>"Create Machine"}
This way you can access the machine_employees passed from the form by doing params[:machine][:machine_employees_attributes][0] or params[:machine][:machine_employees_attributes][1]. Note that if this was a has_one relationship, then the machine_employees_attributes key would be changed to machine_employee_attributes and there would be no numerical index.
I suspect the problem is that your machine model must accept_nested_attributes_for :machine_employees and must also have attr_accessible :machine_employees_attributes.

Simple_Form Association with has_many :through extra field

I have two models, Developers and Tasks,
class Developer < ActiveRecord::Base
attr_accessible :address, :comment, :email, :name, :nit, :phone, :web
has_many :assignments
has_many :tasks, :through => :assignments
end
class Task < ActiveRecord::Base
attr_accessible :description, :name, :sprint_id, :developer_ids
has_many :assignments
has_many :developers, :through => :assignments
end
class Assignment < ActiveRecord::Base
attr_accessible :accomplished_time, :developer_id, :estimated_time, :status, :task_id
belongs_to :task
belongs_to :developer
end
im taking care of the relation by adding an Assignment table, so i can add many developers to one task in particular, now i would also like to be able to manipulate the other fields i added to the joining table like the 'estimated_time', 'accomplished_time'... etc... what i got on my Simple_form is
`
<%= simple_form_for [#sprint,#task], :html => { :class => 'form-horizontal' } do |f| %>
<%= f.input :name %>
<%= f.input :description %>
<%= f.association :developers, :as => :check_boxes %>
<div class="form-actions">
<%= f.button :submit, :class => 'btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
project_sprint_path(#sprint.project_id,#sprint), :class => 'btn' %>
</div>
<% end %>`
This only allows me to select the developers, i want to be able to modify the estimated_time field right there.
Any Suggestions?
I love how simple-form has the association helpers, making it really easy in some cases. Unfortunately, what you want you cannot solve with just simple-form.
You will have to create assignments for this to work.
There are two possible approaches.
For both you will have to add the following to your model:
class Task
accepts_nested_attributes_for :assignments
end
Note that if you are using attr_accesible, you should also add assignments_attributes to it.
The easy approach
Suppose you know how many assignments, maximally, a task would have. Suppose 1 for simplicity.
In your controller, write
def new
#task = Task.build
#task.assignments.build
end
This will make sure there is one new assignment.
In your view write:
= simple_form_for [#sprint,#task], :html => { :class => 'form-horizontal' } do |f|
= f.input :name
= f.input :description
= f.simple_fields_for :assignments do |assignment|
= assignment.association :developer, :as => :select
= assignment.estimated_time
.form-actions
= f.button :submit, :class => 'btn-primary'
= link_to t('.cancel', :default => t("helpers.links.cancel")),
project_sprint_path(#sprint.project_id,#sprint), :class => 'btn'
The problem with this approach: what if you want more than 1, 2 or 3?
Use cocoon
Cocoon is a gem that allows you to create dynamic nested forms.
Your view would become:
= simple_form_for [#sprint,#task], :html => { :class => 'form-horizontal' } do |f|
= f.input :name
= f.input :description
= f.simple_fields_for :assignments do |assignment|
= render `assignment_fields`, :f => assignment
.links
= link_to_add_association 'add assignment', f, :assignments
.form-actions
= f.button :submit, :class => 'btn-primary'
= link_to t('.cancel', :default => t("helpers.links.cancel")),
project_sprint_path(#sprint.project_id,#sprint), :class => 'btn'
And define a partial _assignment_fields.html.haml :
.nested_fields
= f.association :developer, :as => :select
= f.estimated_time
= link_to_remove_association 'remove assignment', f
Hope this helps.
The thing is that by using this:
<%= f.association :developers, :as => :check_boxes %>
You're actually only setting the developer_ids attribute, which will automatically build the assignments for you as it's a has many :through. For this I believe you should probably be using nested attributes, for each of the assignments, and each record would have a select box or similar to choose the related developer for that particular assignment in this task. It's quite similar to what Cojones has answered, but you should not be using check boxes for this association, since you're going to be dealing with a single assignment which contains a single developer. And with nested attributes, you should be able to create as many assignments you want.
That I believe is the easiest way to start with.
I think it should look somehow like this:
= f.simple_fields_for :assignments do |fa|
= fa.association :developer, as: :check_boxes
= fa.input :estimated_time
...

Rails nested model with formtastic is skipping one field?

I have this structure models
class Tournament < ActiveRecord::Base
AGES = ["5u", "6u", "7u", "8u"]
has_many :courts, :dependent => :destroy
accepts_nested_attributes_for :courts, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
class Court < ActiveRecord::Base
belongs_to :tournament, :autosave => true
has_many :ages, :dependent => :destroy
accepts_nested_attributes_for :ages, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
class Age < ActiveRecord::Base
belongs_to :court
Now my forms look like this
_form.html.erb
<%= semantic_form_for #tournament do |f| %>
<%= f.inputs do %>
<%= f.input :name, :hint => "What is the name of the Tournament?" %>
<%= f.semantic_fields_for :courts do |builder| %>
<%= render :partial => "court_fields", :locals => { :f => builder } %>
<% end %>
_court_fields.html.erb
<div class="nested_fields">
<%= f.input :name, :input_html => {:class => "name"} %>
<%= f.semantic_fields_for :ages do |builder| %>
<%= render :partial => "age_fields", :locals => { :f => builder } %>
<% end %>
_age_fields.html.erb
Testing ...am I getting in here
<%= f.input :name, :as => :check_boxes, :collection => Tournament::AGES, :input_html => {:class => "age_limits"} %>
everything seems to work well except nothing shows up in the ages_fields partial...not the checkboxes and not even the dummy text Testing ...am I getting in here is not displaying....any ideas what could be causing the issue
The obvious reason I can think of: are you sure your Court has ages ?
[EDIT] That the Court has the relation was indeed clear to me.
But your code will only show an age for a court if it already exists.
From your output in the comments: the court has no actual ages so no ages are shown.
If you do this in your controller:
def new
#tournament = Tournament.new
#tournament.courts.build
#tournament.courts[0].ages.build
end
This will make sure that you have at least one (empty) court and one (empty) age.
Otherwise you could also consider using a gem like cocoon to dynamically add new elements if needed.
Hope this helps.

How to create a parent model through a child controller in rails 3? (belongs_to association)

I have two resources: Topics and Posts.
I am trying to figure out how I can create a Topic through the Post controller.
The models look like this:
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
validates :name, :presence => true,
:length => { :maximum => 32 }
attr_accessible :name
end
class Post < ActiveRecord::Base
belongs_to :topic, :touch => true
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :topic
attr_accessible :name, :title, :content, :topic
end
posts/_form.html.erb:
<%= simple_form_for #post do |f| %>
<h1>Create a Post</h1>
<%= f.input :name, :label => false, :placeholder => "Name" %>
<%= f.input :title, :label => false, :placeholder => "Title" %>
<%= f.input :content, :label => false, :placeholder => "Content" %>
<%= f.input :topic, :label => false, :placeholder => "Topic" %>
<%= f.button :submit, "Post" %>
<% end %>
posts_controller.rb#create:
def create
#post = Post.new(params[:topic])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
With posts/_form.html.erb I can create posts, but an associated topic is not created along with it. Can anyone tell me why I get this behavior and possibly how to correct it? I'm using Ruby 1.9.2, Rails 3.0.7 and the simple_form gem.
Railscasts has several episodes about this problem.
episode 16(subscription needed)
Source code:
https://github.com/railscasts/016-virtual-attributes-revised
And this episode
http://railscasts.com/episodes/57-create-model-through-text-field
views/products/_form.rhtml
<p>
<label for="product_category_id">Category:</label><br />
<%= f.collection_select :category_id, Category.find(:all), :id, :name, :prompt => "Select a Category" %>
or create one:
<%= f.text_field :new_category_name %>
</p>
models/product.rb
belongs_to :category
attr_accessor :new_category_name
before_save :create_category_from_name
def create_category_from_name
create_category(:name => new_category_name) unless new_category_name.blank?
end
I think Ryan's solution on category is more elegant than #nathanvda 's option 1. As it deal the data in Model instead of Controller. If you need to do same work in different controllers/actions, you will see the benefits.
Depending on what you want to do, i can see two options.
Option 1: Use a text-box to create or find an existing topic (as you had). In your controller you would write something like:
def create
topic_name = params[:post].delete(:topic)
#topic = Topic.find_or_create_by_name(topic_name)
#post = Post.new(params[:post])
#post.topic = #topic
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
That is the quick and dirty way. It will, for each topic you type, try to find that topic, by name, or create it and assign it. But, this is error-prone. If your sets of topics is limited, there is a much easier way.
Option 2: use a select-box, a list of available topics. In your view write:
<%= simple_form_for #post do |f| %>
<h1>Create a Post</h1>
<%= f.input :name, :label => false, :placeholder => "Name" %>
<%= f.input :title, :label => false, :placeholder => "Title" %>
<%= f.input :content, :label => false, :placeholder => "Content" %>
<%= f.association :topic %>
<%= f.button :submit, "Post" %>
<% end %>
That will render a select-box with the possible topics. And in your controller you just have to write:
def create
#post = Post.new(params[:post])
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
else
format.html { render :action => "new" }
end
end
While this second option is really easy, it is less easy to add topics on the fly. You could do something in between, using an autocomplete field, that will either allow looking up values if they exist, or add new values if they don exist.
Hope this helps.
Are you getting mass assignment errors in the server log? You may need to add :topic_attributes to your attr_accessible list.

Resources