Artists have many Events. Events have many Artists. The join between these two models is called Performances.
I'm trying to associate Artists with Events on the Event add/edit page. I would like to be able to add an Artist only if it doesn't exist, and create the join (performance) regardless. An Artist should be associated with an Event only once.
It was suggested that I use find_or_create_by_name instead of accepts_nested_attributes_for.
I'm following the Railscasts #102 instructions for Auto-Complete which say to use virtual attributes. I haven't even gotten to the auto-complete part, just trying to get find_or_create_by_name working.
I'm getting "undefined method `artist_name' for #" on the Event edit and new pages. In the Railscast, Ryan gets an undefined method before he adds the methods to the model. But I have the method in the Model.
No idea what to do.
event.rb
validates_presence_of :name, :location
validates_uniqueness_of :name
validates_associated :performances
has_many :performances, :dependent => :delete_all
has_many :artists, :through => :performances
#accepts_nested_attributes_for :artists, :reject_if => proc {|a| a['name'].blank?}, :allow_destroy => true
def artist_name
artist.name if artist
end
def artist_name=(name)
self.artist = Artist.find_by_name(name) unless name.blank?
end
artist.rb
validates_presence_of :name
has_many :mixes
has_many :performances, :dependent => :delete_all
has_many :events, :through => :performances
perfomance.rb
belongs_to :artist
belongs_to :event
events_controller.rb
def create
#event = Event.new(params[:event])
respond_to do |format|
if #event.save
flash[:notice] = 'Event was successfully created.'
format.html { redirect_to(admin_events_url) }
format.xml { render :xml => #event, :status => :created, :location => #event }
else
format.html { render :action => "new" }
format.xml { render :xml => #event.errors, :status => :unprocessable_entity }
end
end
end
_form.html.erb
<% form_for([:admin,#event]) do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :location %><br/>
<%= f.text_field :location %>
</p>
<p>
<%= f.label :date %><br />
<%= f.date_select :date %>
</p>
<p>
<%= f.label :description %><br />
<%= f.text_area :description %>
</p>
<% f.fields_for :artists do |builder| %>
<%= render 'artist_fields', :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add Artist", f, :artists %></p>
<p>
<%= f.submit 'Submit' %> <%= link_to 'Cancel', admin_events_path %>
</p>
<% end %>
_artist_fields.html.erb
<p class="fields">
<%= f.label :artist_name, "Artist"%><br/>
<%= f.text_field :artist_name %>
<%= link_to_remove_fields "remove", f %>
</p>
Personally I would go back to accepts_nested_attributes_for, ryan bates method there was in the days before nested attributes.
In your controller do something like:
def new
#event = Event.find params[:id]
#artist = #event.artists.build
def edit
#event = Event.find params[:event_id]
#artist = #event.artists.find params[:user_id]
While in the view
...
<% f.fields_for :artists, #artist do |builder| %>
...
Related
I would like to combine both values :hours and :minutes and convert them to to_i in seconds. Next is to assign this value (which should be in seconds) to the :time_duration which is a column in the cars db before it creates a new service. The :time_duration is in a hidden_field because there's no reason to render this data in the view.
views
This is my _car_fields.html.erb which is a nested partial inside a view template called, _form.html.erb .
_car_fields.html.erb
<div class="nested-fields">
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :hours %>
<%= f.select :hours, '0'..'8' %>
<%= f.label :minutes %>
<%= f.select :minutes, options_for_select( (0..45).step(15), selected: f.object.minutes )%><br>
<%= f.label :price %><br>
<%= f.text_field :price, :value => (number_with_precision(f.object.price, :precision => 2) || 0) %> <br>
<%= f.label :details %><br>
<%= f.text_area :details %></div>
<%= link_to_remove_association "Remove Car", f, class: 'btn btn-default' %>
<%= f.hidden_field :time_duration, value: %>
<br>
<hr>
</div>
_form.html.erb
<%= simple_form_for #service do |f| %>
<div class="field">
<%= f.label "Select service category" %>
<br>
<%= collection_select(:service, :service_menu_id, ServiceMenu.all, :id, :name, {:prompt => true }) %>
<%= f.fields_for :cars do |task| %>
<%= render 'car_fields', :f => task %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'Add New Car', f, :cars, class: 'btn btn-default' %>
</div><br>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
controller
services_controller
def new
#service = current_tech.services.build
end
def create
#service = current_tech.services.build(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
private
def service_params
params.require(:service).permit(:name, :service_menu_id, cars_attributes: [:tech_id, :name, :hours, :minutes, :price, :details, :_destroy])
end
models
service.rb
class Service < ActiveRecord::Base
belongs_to :tech
belongs_to :service_menu
has_many :cars, dependent: :destroy
accepts_nested_attributes_for :cars, :reject_if => :all_blank, :allow_destroy => true
end
car.rb
class Car< ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
end
First, you can remove the hidden time_duration field from the form, since it is not needed.
Then, you'll create a before_save method for your car model:
car.rb
class Car < ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
before_save :generate_time_duration
def generate_time_duration
self[:time_duration] = hours.hours.to_i + minutes.minutes.to_i
end
end
What this does: Before the car object is saved, it will run the generate_time_duration method. What this method does is it simply sums the hours.to_i and minutes.to_i and assigns it to the car's time_duration attribute.
Update old DB records
Since you're adding this functionality in your application AFTER records have already been created, here is a quick way to update all of your current records:
In your command line, open a rails console by running the command rails c (or rails console)
In the console, run this command: Car.all.each { |c| c.save! }
This is a quick, one-time fix that will loop through all Car records, save them, and subsequently update their time_duration fields.
I'm stuck and I don't know why it is not working right.
I have a model product which has many tags.
When I update the product rails update properly the products attributes but is creating another tag record instead of just updating it.
here is my code:
View form:
<%= form_for ([#product.user, #product]), id: 'edit_form' do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.fields_for :tags do |t| %>
<%= t.label :name %>
<%= t.text_field :name %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
product model:
class Product < ActiveRecord::Base
belongs_to :user, :foreign_key => "user_id"
has_many :tags, :dependent => :destroy
accepts_nested_attributes_for :tags, reject_if: :all_blank, allow_destroy: true, :update_only => true
end
tags model:
class Tag < ActiveRecord::Base
belongs_to :product, :foreign_key => "product_id"
# before_save { name.downcase! }
end
product controller:
def edit
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
format.html
format.js
end
end
def update
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to([#product.user, #product], :notice => 'Product successfully updated.') }
else
format.html { render :action => "edit" }
end
end
end
def product_params
params.require(:product).permit(:name, :description, tags_attributes: :name)
end
You have to pass the tag id in the permit params in your controller
def product_params
params.require(:product).permit(:name, :description, tags_attributes: [:id,:name])
end
I'm struggling to come up with the proper way to design a form that will allow me to input data for two different models. The form is for an 'Incident', which has the following relationships:
belongs_to :customer
belongs_to :user
has_one :incident_status
has_many :incident_notes
accepts_nested_attributes_for :incident_notes, :allow_destroy => false
So an incident is assigned to a 'Customer' and a 'User', and the user is able to add 'Notes' to the incident. I'm having trouble with the notes part of the form. Here how the form is being submitted:
{"commit"=>"Create",
"authenticity_token"=>"ECH5Ziv7JAuzs53kt5m/njT9w39UJhfJEs2x0Ms2NA0=",
"customer_id"=>"4",
"incident"=>{"title"=>"Something bad",
"incident_status_id"=>"2",
"user_id"=>"2",
"other_id"=>"AAA01-042310-001",
"incident_note"=>{"note"=>"This is a note"}}}
It appears to be attempting to add the incident_note as a field under 'Incident', rather than creating a new entry in the incident_note table with an incident_id foreign key linking back to the incident.
Here is the 'IncidentNote' model:
belongs_to :incident
belongs_to :user
Here is the form for 'Incident':
<% form_for([#customer,#incident]) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :other_id, "ID" %><br />
<%= f.text_field :capc_id %>
</p>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<p>
<%= label_tag 'user', 'Assign to user?' %>
<%= f.select :user_id, #users.collect {|u| [u.name, u.id]} %>
</p>
<p>
<%= f.label :incident_status, 'Status?' %>
<%= f.select :incident_status_id, #statuses.collect {|s| [s.name, s.id]} %>
</p>
<p>
<% f.fields_for :incident_note do |inote_form| %>
<%= inote_form.label :note, 'Add a Note' %>
<%= inote_form.text_area :note, :cols => 40, :rows => 20 %>
<% end %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
And finally, here are the incident_controller entries for New and Create.
New:
def new
#customer = current_user.customer
#incident = Incident.new
#users = #customer.users
#statuses = IncidentStatus.find(:all)
#incident_note = IncidentNote.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #incident }
end
end
Create:
def create
#users = #customer.users
#statuses = IncidentStatus.find(:all)
#incident = Incident.new(params[:incident])
#incident.customer = #customer
#incident_note = #incident.incident_note.build(params[:incident_note])
#incident_note.user = current_user
respond_to do |format|
if #incident.save
flash[:notice] = 'Incident was successfully created.'
format.html { redirect_to(#incident) }
format.xml { render :xml => #incident, :status => :created, :location => #incident }
else
format.html { render :action => "new" }
format.xml { render :xml => #incident.errors, :status => :unprocessable_entity }
end
end
end
I'm not really sure where to look at this point. I'm sure it's just a limitation of my current Rails skill (I don't know much). So if anyone can point me in the right direction I would be very appreciative. Please let me know if more information is needed!
Thanks!
Check api for fields_for method and scroll to one-to-many section.
Your model has many :incident_notes, not one incident_note, that is why it doesn't understand relationship and tries to find a field with this name.
So it should be:
<% f.fields_for :incident_notes do |inote_form| %>
<%= inote_form.label :note, 'Add a Note' %>
<%= inote_form.text_area :note, :cols => 40, :rows => 20 %>
<% end %>
It iterates through all incident_notes assigned to incident and produces fields for each of them.
You also have to build at least one note in you new action, otherwise there will be none:
def new
#incident = Incident.new
#incident.incident_notes.build
# ...
end
I have two models, links and tags, associated through a third, link_tags. The following code is in my Link model.
Associations:
class Link < ActiveRecord::Base
has_many :tags, :through => :link_tags
has_many :link_tags
accepts_nested_attributes_for :tags, :allow_destroy => :false,
:reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
end
class Tag < ActiveRecord::Base
has_many :links, :through => :link_tags
has_many :link_tags
end
class LinkTag < ActiveRecord::Base
belongs_to :link
belongs_to :tag
end
links_controller Actions:
def new
#link = #current_user.links.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #link }
end
end
def create
#link = #current_user.links.build(params[:link])
respond_to do |format|
if #link.save
flash[:notice] = 'Link was successfully created.'
format.html { redirect_to links_path }
format.xml { render :xml => #link, :status => :created, :location => #link }
else
format.html { render :action => "new" }
format.xml { render :xml => #link.errors, :status => :unprocessable_entity }
end
end
end
View code from new.html.erb:
<% form_for [current_user, #link], :url => account_links_path do |f| %>
<%= render :partial => "form", :locals => { :f => f } %>
<% end %>
And the corresponding partial:
<%= f.error_messages %>
<p>
<%= f.label :uri %><br />
<%= f.text_field :uri %>
</p>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<h2>Tags</h2>
<% f.fields_for :tags_attributes do |tag_form| %>
<p>
<%= tag_form.label :name, 'Tag:' %>
<%= tag_form.text_field :name %>
</p>
<% unless tag_form.object.nil? || tag_form.object.new_record? %>
<p>
<%= tag_form.label :_delete, 'Remove:' %>
<%= tag_form.check_box :_delete %>
</p>
<% end %>
<% end %>
<p>
<%= f.submit 'Update' %>
</p>
The following line of code, in the create action in the Link controller throws an error:
#link = #current_user.links.build(params[:link])
The error: Tag(#-621698598) expected, got Array(#-609734898)
Are there additional steps needed in the has_many => :through case? These seem to be the only indicated changes for the basic has_many case.
Take a look at the line of your code
<% f.fields_for :tags_attributes do |tag_form| %>
You need to use just :tags instead of :tags_attributes.
This will solve your issue
Make sure that You have build links and tags in your controller like
def new
#link = #current_user.links.build
#link.tags.build
end
I found this here on stackoverflow:
Rails nested form with has_many :through, how to edit attributes of join model?
please tell me if it worked.
In order for this to work, you need to pass in the right params hash:
params = {
:link => {
:tags_attributes => [
{:tag_one_attr => ...}, {:tag_two_attr => ...}
],
:link_attr => ...
}
}
And your controller will look like:
def create
#link = Link.create(params[:link]) # this will automatically build the rest for your
end
Try this:
<% f.fields_for :tags_attributes do |tag_form| %>
In your controller in the new action (that loads the form partial), are you building a #tag through your link?
So you should see something along the lines of:
#link = Link.new
#tag = #link.tags.build
It might be best to post the contents of the new and create action of your links_controller.
try
<% f.fields_for :tags do |tag_form| %>
(ie lose the _attributes in :tag_attributes) That's how I've usually done nested forms
You need to build a tag in your controller or in the view
def new
#link = #current_user.links.build
#link.tags.build
end
#in your view you can just use the association name
<% f.fields_for :tags do |tag_form| %>
I'm still super new with Rails, and just trying to get my first has_many through association set up.
Recipes have many ingredients, and each ingredient has an amount needed for the recipe. The ingredient_amount table has a recipe_id, an ingredient_id, and an amount.
When creating a new recipe, I want to be able to create these recipe/ingredient associations in the same place. In the end, I'm going to build an AJAX autocompleter for the ingredients. For now, as a baby step, I'd like to just assume the ingredient exists, and take care of checking once I've got this part down.
So, how can I make new.html.erb for recipes do this? How can I extend the form for more than one ingredient?
As it stands now, after going through http://weblog.rubyonrails.org/2009/1/26/nested-model-forms
I still can't get any fields to add ingredients. The current code is below.
class Recipe < ActiveRecord::Base
has_many :ingredient_amounts
has_many :ingredients, :through => :ingredient_amounts
accepts_nested_attributes_for :ingredient_amounts, :allow_destroy => true
end
class IngredientAmount < ActiveRecord::Base
belongs_to :ingredient
belongs_to :recipe
end
class Ingredient < ActiveRecord::Base
has_many :ingredient_amounts
has_many :recipes :through => :ingredient_amounts
end
Here's new.html.erb as I have it currently:
<h1>New recipe</h1>
<% form_for #recipe do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :instructions %><br />
<%= f.text_area :instructions %>
</p>
<p>
<%= f.label :numberOfServings %><br />
<%= f.text_field :numberOfServings %>
</p>
<p>
<%= f.label :prepTime %><br />
<%= f.text_field :prepTime %>
</p>
<p>
<% f.fields_for :ingredient_amounts do |ingredient_form| %>
<%= ingredient_form.label :ingredient_formedient_id, 'Ingredient' %>
<%= ingredient_form.collection_select :ingredient_id, Ingredient.all, :id, :name, :prompt => "Select an Ingredient"%>
<%= ingredient_form.text_field :amount %>
<% unless ingredient_form.object.new_record? %>
<%= ingredient_form.label :_delete, 'Remove:' %>
<%= ingredient_form.check_box :_delete %>
<% end %>
</p>
<% end %>
<p>
<%= f.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', recipes_path %>
The important bits of the recipe controller:
def new
#recipe = Recipe.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #recipe }
end
end
def create
#recipe = Recipe.new(params[:recipe])
respond_to do |format|
if #recipe.save
flash[:notice] = 'Recipe was successfully created.'
format.html { redirect_to(#recipe) }
format.xml { render :xml => #recipe, :status => :created, :location => #recipe }
else
format.html { render :action => "new" }
format.xml { render :xml => #recipe.errors, :status => :unprocessable_entity }
end
end
end
And... I have no idea where to start in the ingredient_amounts controller.
This was my very first stab, and I'm pretty sure it's not so close :)
def new
#recipe = Recipe.find(params[:recipe_id])
#ingredient = Ingredient.find(params[:ingredient_id])
#ingredient_amount = Recipe.ingredient_amounts.build
end
Thanks for the help!
I believe what you are looking for is 'Nested Model Forms'.
Try this link: http://weblog.rubyonrails.org/2009/1/26/nested-model-forms
It's hard to know what to search for when you dont really know the terminology to begin with :)