Parent model Object ID nil when updating child model in form - ruby-on-rails

I am trying to save parent models ID and it child attributes using a join table council_history, for some reason it seems to be not saving the parent models ID in the join table but saves child attributes ID, when I try to do something similar with a many to many association everything works fine and parent object is saved, I am not sure if its my associations that are wrong, have been wrestling with multiple combinations, any help would be appreciated.
Started PUT "/properties/34/build/council" for 127.0.0.1 at 2013-08-21 00:44:37 +0100
Processing by Properties::BuildController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+nfsCrRvTUrgYrdHix0v2QGaDJj25j4X1sh8oHVpkgs=", "property"=> {"council_history_attributes"=>{"property_id"=>"34", "council_id"=>"1"}}, "commit"=>"Create Council history", "property_id"=>"34", "id"=>"council"}
Started GET "/properties/34/build/confirmed" for 127.0.0.1 at 2013-08-21 00:44:37 +0100
Processing by Properties::BuildController#show as HTML
Parameters: {"property_id"=>"34", "id"=>"confirmed"}
Property Load (0.2ms) SELECT "properties".* FROM "properties" WHERE "properties"."id" = ? LIMIT 1 [["id", "34"]]
CouncilHistory Load (0.3ms) SELECT "council_histories".* FROM "council_histories" WHERE "council_histories"."property_id" = 34 LIMIT 1
(0.1ms) begin transaction
**(0.5ms) UPDATE "council_histories" SET "property_id" = NULL, "updated_at" = '2013-08-20 23:44:37.220619' WHERE "council_histories"."id" = 18**
(1.5ms) commit transaction
Tenant Load (0.3ms) SELECT "tenants".* FROM "tenants" WHERE "tenants"."property_id" = 34
Rendered properties/build/confirmed.html.erb within layouts/application (2.1ms)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
(0.4ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = 3 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)))
Property.rb
class Property < ActiveRecord::Base
attr_accessible :name, :council_history_attributes, :council,
:property_id, :council_id, :status, :council_history_id
belongs_to :user
has_one :council_history
accepts_nested_attributes_for :council_history
has_one :council, through: :council_history, :foreign_key => :council_id
accepts_nested_attributes_for :council
Council.rb
class Council < ActiveRecord::Base
attr_accessible :CouncilEmail, :name, :CouncilTel, :council_histories_attributes
has_many :council_histories
has_many :properties, :through => :council_histories, :foreign_key => :property_id
end
Council_history.rb -- Join Table
class CouncilHistory < ActiveRecord::Base
attr_accessible :council_id, :property_id, :vacant
belongs_to :council
belongs_to :property
end
Nested Controller
class Properties::BuildController < ApplicationController
include Wicked::Wizard
steps :council, :confirmed
def show
#property = Property.find(params[:property_id])
#property.build_council_history do |council_history|
#council = council_history.build_council
end
render_wizard
end
View Form
<%= simple_form_for #property, url: wizard_path, :method => 'put' do |f| %>
<%= f.simple_fields_for :council_history do |builder| %>
<%= builder.input :property_id, as: :hidden, input_html: { value: #property.id } %>
<%= builder.input :council_id, :collection => Council.all %>
<%= builder.submit %>
<% end %>
<% end %>

If you will ask another questions on the future, make sure to AVOID putting authenticity_token in the question ...

Related

Rails not displaying saving Model property using update_attributes

I have a Project model. I recently added project_name to that model. When I enter a name into the field and create a new Project however, it doesn't save the new attribute.
Projects Controller:
def create
if project.update_attributes(project_params)
flash[:success] = 'Project was successfully created'
redirect_to project_url(project)
else
render :new
end
end
def project_params
params.require(:project).permit(:description, :project_name, :customer_number, :sales_number, :sales_id, :customer_id, :discount, :date_wanted, :price,
items_attributes: [:id, :name, :unit, :quantity, :price, :_destroy])
end
Project Model:
include AASM
STEP_FORWARD_STATE = {
'quotes' => :proposal,
'proposals' => :ordered,
'orders' => :active,
'in_process' => :close
}
STEP_BACK_STATE = {
'proposals' => :quoted,
'orders' => :proposal,
'in_process' => :ordered,
'closed' => :active
}
belongs_to :customer
belongs_to :sales, class_name: 'User'
has_many :curtains, dependent: :destroy
has_many :versions, class_name: 'ProjectVersion', dependent: :destroy
has_many :tasks, dependent: :destroy
has_many :drape_tasks, class_name: 'DrapeTask', dependent: :destroy
has_many :trough_tasks, class_name: 'TroughTask', dependent: :destroy
has_many :items, dependent: :destroy
accepts_nested_attributes_for :items, :allow_destroy => true
delegate :email, :first_name, :full_name, :city_address, :name_for_select,
:contry_address, :address_for_email, :phone_o,
to: :customer, prefix: true, :allow_nil => true
delegate :name_for_select, :address_for_email, :email, :city_address, :full_name, :rep_number, :first_title,
to: :sales, prefix: true, :allow_nil => true
validates :customer, :sales, presence: true
attr_accessor :update_event
Terminal Log During the action:
Started POST "/projects" for ::1 at 2016-06-08 15:54:57 -0400
Processing by ProjectsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+vpCG6JPLLSBtntKhudPTopzq/MH0YlnjlfnFSsOVYnMfRY888LafcReDF41GIxb14q6DLdkbz/AwXEQi+iF4w==", "commit"=>"Create Project", "name"=>"TEST DAMMIT", "customer_autocomplete"=>"test#test.com", "project"=>{"customer_id"=>"1", "sales_id"=>"1", "discount"=>""}, "sales_autocomplete"=>"dzk#dzk", "description_autocomplete"=>"DID IT WORK"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Profile Load (1.8ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."person_id" = $1 AND "profiles"."person_type" = $2 LIMIT 1 [["person_id", 1], ["person_type", "User"]]
Project Load (1.5ms) SELECT "projects".* FROM "projects" WHERE "projects"."id" IS NULL ORDER BY "projects"."id" ASC LIMIT 1
(0.2ms) BEGIN
Customer Load (0.5ms) SELECT "customers".* FROM "customers" WHERE "customers"."id" = $1 LIMIT 1 [["id", 1]]
Profile Load (1.2ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."person_id" = $1 AND "profiles"."person_type" = $2 LIMIT 1 [["person_id", 1], ["person_type", "Customer"]]
User Load (1.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
CACHE (0.0ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."person_id" = $1 AND "profiles"."person_type" = $2 LIMIT 1 [["person_id", 1], ["person_type", "User"]]
SQL (0.8ms) INSERT INTO "projects" ("state", "sales_id", "customer_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["state", "quotes"], ["sales_id", 1], ["customer_id", 1], ["created_at", "2016-06-08 19:54:57.746357"], ["updated_at", "2016-06-08 19:54:57.746357"]]
Item Load (0.2ms) SELECT "items".* FROM "items" WHERE "items"."project_id" = $1 [["project_id", 13]]
Curtain Load (0.2ms) SELECT "curtains".* FROM "curtains" WHERE "curtains"."project_id" = $1 [["project_id", 13]]
(1.2ms) COMMIT
Redirected to http://localhost:3000/projects/13
EDIT (June 9th):
Thanks for the help so far, #taryn-east .
(Also added my project_params above for clarification)
Here's the form I used for project_name:
.form-group
= label_tag 'name', 'Project Name', class: 'control-label'
.controls
= text_field_tag 'name', project.project_name, class: 'form-control', autocomplete: :off
For comparison, here's a form used for a working parameter of the model:
.form-group
= label_tag 'customer_autocomplete', 'Customer', class: 'control-label'
.controls
= text_field_tag 'customer_autocomplete', project.customer_name_for_select, class: 'form-control customer-autocomplete', autocomplete: :off
= f.hidden_field :customer_id
Clearly, f.hidden_field :[parameter] was missing. I added it to my project_name field, leaving it looking like so:
.form-group
= label_tag 'name', 'Project Name', class: 'control-label'
.controls
= text_field_tag 'name', project.project_name, class: 'form-control', autocomplete: :off
= f.hidden_field :project_name
Which now leaves us with a Terminal log of:
Started POST "/projects" for ::1 at 2016-06-09 12:11:42 -0400
Processing by ProjectsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"BqEZsyQK+uLoeWewYC6smeC6CX0EGLcZgUQlmAyjtLqOmAb3v+2pw6VJ0hoBnISkGegz3fCAMT4+UPMVh28l8Q==", "commit"=>"Create Project", "project_name"=>"TEST PLS DEALER", "project"=>{"project_name"=>"", "customer_id"=>"1", "sales_id"=>"1", "discount"=>"", "description"=>""}, "customer_autocomplete"=>"test#test.com", "sales_autocomplete"=>"dzk#dzk", "description_autocomplete"=>"WORK PLS ONE TIME EHHHH"}
This is promising, as adding
f.hidden_field :project_name
Add project_name to the Project hash passed in the Create action like so:
"project"=>{"project_name"=>"", "customer_id"=>"1", "sales_id"=>"1", "discount"=>"", "description"=>""}
However, you can see that the actual content of the parameter remains outside the field it is supposed to reside in. I did some digging and noticed the difference between my field and the previous working fields: the working ones had class: "[:parameter]-autocomplete" in them. I added the appropriate class to mine, and it still didn't work. Elsewhere in the app, there's a Coffeescript:
$(document).on 'focus', '.customer-autocomplete', ->
$(#).autocomplete
source: "/customers",
dataType: 'JSON',
minLength: 2,
select: (event, ui) ->
$('#project_customer_id').val ui.item.customer_id
$('#sales_autocomplete').val ui.item.sales_data
$('#project_sales_id').val ui.item.sales_id
$(document).on 'focus', '.sales-autocomplete', ->
$(#).autocomplete
source: "/sales",
dataType: 'JSON',
minLength: 2,
select: (event, ui) ->
$('#project_sales_id').val ui.item.sales_id
I'm not familiar with Coffeescript, but it seems like this is what's auto-completing the hidden fields with class: "[:parameter]-autocomplete". How do I replicate this for my added fields?
EDIT: (June 10th)
I took your advice and simplified the migrations (definitely a good call :) ) but it still seems to be uncooperative.
New Params:
def project_params
params.require(:project).permit(:description, :name, :customer_number, :sales_number, :sales_id, :customer_id, :discount, :date_wanted, :price,
items_attributes: [:id, :name, :unit, :quantity, :price, :_destroy])
end
New Form:
.form-group
= label_tag 'name', 'Project Name', class: 'control-label'
.controls
= text_field_tag 'name', project.name, class: 'form-control name-autocomplete', autocomplete: :off
= f.hidden_field :name
New Show:
Project Name:
= project.name
Terminal Log from attempt to make a Project with the above:
Started POST "/projects" for ::1 at 2016-06-10 10:30:30 -0400
Processing by ProjectsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"iMt4rowEbO1UGzjwfl1XkraTbktC+5+/MiNzYvU/uxAA8mfqF+M/zBkrjVof73+vT8FU67ZjGZiNN6XvfvMqWw==", "commit"=>"Create Project", "name"=>"TEST PROJECT NAME", "project"=>{"name"=>"", "customer_id"=>"1", "sales_id"=>"1", "discount"=>"", "description"=>""}, "customer_autocomplete"=>"test#test.com", "sales_autocomplete"=>"dzk#dzk", "description_autocomplete"=>"TEST PROJECT DESCRIPTION"}
I'm having the same issue with project.description as well, as you can see; is it possible the 'project' variable is defined somewhere else in the app that doesn't include these new params? It's not throwing a red screen at any point in the process, either. Thanks again for all the help so far #taryn :)
If you look at the output in the server window it shows you the problem:
"name"=>"TEST DAMMIT", "customer_autocomplete"=>"test#test.com",
"project"=>{"customer_id"=>"1", "sales_id"=>"1", "discount"=>""}
That name field isn't part of the project and thus won't get picked up as an attribute of project.
What you'd expect to see coming through from params would be this:
"customer_autocomplete"=>"test#test.com",
"project"=>{"name"=>"TEST DAMMIT", "customer_id"=>"1", "sales_id"=>"1", "discount"=>""}
This means that something is slightly wrong with your form. Check that your name fields is done the same way as the other project fields
Edit:
In your form, the attributes is named name:
text_field_tag 'name',
but in the rest of your code it's expecting the attribute to be named project_name
params.require(:project).permit(:description, :project_name
You have to actually name the field the same as what you use int he code :) eg
text_field_tag 'project_name',
though TBH I'd seriously recommend just calling it name (and rewriting the migrations etc) if you can because project.project_name is a bit redundant ;)

Has_many association mapped to different model

I want users to be able to select the languages they speak. I have setup the associations, the table attributes and the part of the form. When I select a language and submit the form I go to the rails console and do a u.languages but I get an empty array back: => []
Here are the logs when I submit the form:
Started POST "/update_user_via_user" for 127.0.0.1 at 2016-03-18 13:26:03 +0200
ActiveRecord::SchemaMigration Load (0.4ms) SELECT "schema_migrations".* FROM "schema_migrations"
Processing by UsersController#update_user_via_user as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"CB1Qca0VrBcap9qO6VpKfoi2dG8GNG+tGGNDgCnFEv4E=", "user"=>{ "fullname"=>"John Doe", "languages"=>["", "1", "2"]}, "commit"=>"Save"}
User Load (28.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3 ORDER BY "users"."id" ASC LIMIT 1
Unpermitted parameters: languages
(0.1ms) begin transaction
(0.1ms) commit transaction
Redirected to http://127.0.0.1:3000/setup_profile
Completed 302 Found in 163ms (ActiveRecord: 29.5ms)
Now if you look closely on the loogs you will see "Unpermitted parameters: languages".
In my users_controller I have the followings:
def user_params
params.require(:user).permit(:languages, :fullname)
end
and the custom action:
def update_user_via_user
if current_user.update(user_params)
flash.notice = "Your profile was sent for moderation. We will moderate it asap!"
else
flash.alert = "Something went wrong! Please try again."
end
redirect_to root_path
end
Some other references: (my question at the end)
schema.rb:
languages table:
create_table "languages", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
users table:
t.string "languages"
language.rb model:
class Language < ActiveRecord::Base
belongs_to :user
end
and user.rb model:
class User < ActiveRecord::Base
has_many :languages
end
The view:
<%= f.label :languages %>
<%= f.select :languages, Language.all.map{ |l| [l.name, "#{l.id}"] }, {}, { :multiple => true } %>
I am not sure why "languages" is not permitted and also if my concept of code is correct
class Language < ActiveRecord::Base
belongs_to :user
end
This would setup a one two many relation between users and languages which is not what you want. What you want is a many to many relation:
Users can speak many languages
Languages have many speakers (users)
So to keep track of this we need a third table:
language_users is what is known as a join table. You can actually name the table whatever you want - calling it a + _ + bs is just a convention.
We also need to setup our models to use the join table.
class User < ActiveRecord::Base
has_many :language_users
has_many :languages, through: :language_users
end
class Language < ActiveRecord::Base
has_many :language_users
has_many :users, through: :language_users
end
# this is a join model that links User & Language
class LanguageUser < ActiveRecord::Base
belongs_to :user
belongs_to :language
end
To create a form element where users can select languages you would use:
<%= f.collection_select(:languages_ids, Language.all, :id, :name, multiple: true) %>
Or:
<%= f.collection_check_boxes(:languages_ids, Language.all, :id, :name) %>
languages_ids is a special accessor that ActiveRecord creates for has_many associations that lets you set multiple associations at one by passing an array of ids.
I was thinking Deep's answer was the right one, based on this exchange.
I fired up my console and did this:
irb(main):001:0> x = ActionController::Parameters.new(user: {fullname: "John Doe", languages: ['','1','2']})
=> {"user"=>{"fullname"=>"John Doe", "languages"=>["", "1", "2"]}}
irb(main):002:0> y = x.require(:user).permit(:fullname, :languages => [])
=> {"fullname"=>"John Doe", "languages"=>["", "1", "2"]}
irb(main):003:0> y[:languages]
=> ["", "1", "2"]
So, hm.
What's the error message you're getting now? Same as original?

Rails nested_form for a recursive model association

It's based on two models: Formula and Operand. A formula can have many Operands. Each operand can have many or none operands.
I've established a polymorphic association in which Operand belongs to operation and also have many operands as operator. Formula have many operands as operation itself.
I'm trying to build a nested_form that can handle this, but i'm stuck. I'm using simple_nested_form. Any help would be appreciated.
class Formula < ActiveRecord::Base
validates_presence_of :name, :client_id
belongs_to :client
has_many :operands, as: :operation, dependent: :destroy
accepts_nested_attributes_for :operands, allow_destroy: true
end
class Operand < ActiveRecord::Base
validates_presence_of :operator
belongs_to :operation, polymorphic: true
has_many :operands, as: :operation, dependent: :destroy
accepts_nested_attributes_for :operands, allow_destroy: true
OPERATOR_TYPES = ["+", "-", "*", "/"]
end
UPDATE
Based in models above, I add in formulas_controller:
def new
#formula.operands.build
end
In the formula _form, I add fields_for :operands
= simple_nested_form_for #formula, :html => { :class => 'form-vertical' } do |f|
= f.error_notification
.form-inputs
.row
.span3= f.input :name
.well
= f.fields_for :operands
= f.link_to_add t('helpers.links.add'), :operands
And then, the partial _operands_fields, also rendering fields_for :operands
.well
.row
.span2= f.input :operator, collection: Operand::OPERATOR_TYPES
.span3= f.input :numeric_operand
.span3= f.link_to_remove t('helpers.links.remove')
.well
= f.fields_for :operands
= f.link_to_add t('helpers.links.add'), :operands
I think this should work, but it seems that when the formula form is rendered, it includes the fields_for the formula operand. Then, inside this partial, instead of including "Add", it renders another operand fields, and so on, ending in an infinite loop such this:
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
Client Load (0.2ms) SELECT "clients".* FROM "clients" WHERE "clients"."id" = ? LIMIT 1 [["id", 1]]
Rendered formulas/_operand_fields.html.haml (48.4ms)
Rendered formulas/_operand_fields.html.haml (9.0ms)
Rendered formulas/_operand_fields.html.haml (7.5ms)
Rendered formulas/_operand_fields.html.haml (9.1ms)
Rendered formulas/_operand_fields.html.haml (8.8ms)
Rendered formulas/_operand_fields.html.haml (9.2ms)
Rendered formulas/_operand_fields.html.haml (9.3ms)
Rendered formulas/_operand_fields.html.haml (9.5ms)
Rendered formulas/_operand_fields.html.haml (17.7ms)
Rendered formulas/_operand_fields.html.haml (52.4ms)
Rendered formulas/_operand_fields.html.haml (10.4ms)
Rendered formulas/_operand_fields.html.haml (11.3ms)
Any ideas in how to solve this problem?

Implement a "Add to collection" in rails 4

I'm working on a app where users can find recipes made by chefs. I have implemented already a "like/dislike" fonction but now I would like to give to the user the ability to save a recipe in a collection.
The user can create many collections, so when he want to save a recipe in a collection, he should be able to see his current collections or create a new one.
Here are my models:
class User < ActiveRecord::Base
has_many :collections
accepts_nested_attributes_for :collections
end
class Collection < ActiveRecord::Base
has_many :recipe_collections
has_many :recipes, through: :recipe_collections
end
class Recipe < ActiveRecord::Base
has_many :recipe_collections
end
class RecipeCollection < ActiveRecord::Base
belongs_to :recipe
belongs_to :collection
end
my recipe_collections_controller.rb
class RecipeCollectionsController < ApplicationController
before_action :set_recipe, only: [:create]
before_action :set_user, only: [:create]
def create
#recipe_collection = #recipe.recipe_collections.build(recipe_collection_params)
if current_user
#recipe_collection.save
respond_to do |format|
format.html {redirect_to :back }
end
else
respond_to do |format|
format.html { render 'recipes/show' }
end
end
end
private
def set_user
#user = current_user
end
def set_recipe
#recipe = Recipe.find(params[:recipe_id])
end
def recipe_collection_params
params.require(:recipe_collection).permit(:collection_id, :recipe_id, collection_attributes: [:id], recipe_attributes: [:id])
end
end
In the recipe show, I have a render, show.html.erb:
<%= render "recipe_collections/form", collection: #recipe_collection || #recipe.recipe_collections.build%>
my partial _form.html.erb
<%= simple_form_for ([ collection.recipe, collection ]) do |form| %>
<%= form.association :collection, as: :check_boxes %>
<%= form.button :submit %>
<% end %>
my routes.rb
resources :recipes, :concerns => :paginatable do
member do
get "like", to: "recipes#upvote"
get "dislike", to: "recipes#downvote"
end
resources :reviews, only: :create
resources :recipe_collections
end
What is the problem:
1: in my show I have a checkbox form, but it don't display the current user collections but all.
2: when I submit the form, it don't save the recipe in the collection that I chose.
edit:
here are an exemple of my logs when I submit the form.
Started POST "/recipes/66/recipe_collections" for ::1 at 2015-04-06 23:58:06 +0200
Processing by RecipeCollectionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GhMIJs4nNmwsLpzBj4l5ta/OW6fN9dfuzBBnciJsjEa0YlfjQMQYDEmwhcXb9++oEmS36yEbgICNsSdbLgiigA==", "recipe_collection"=>{"collection_id"=>["24", ""]}, "commit"=>"Créer un(e) Recipe collection", "recipe_id"=>"66"}
(0.4ms) SELECT COUNT(*) FROM "recipes"
(0.2ms) SELECT COUNT(*) FROM "publishers"
(0.2ms) SELECT COUNT(*) FROM "votes"
Recipe Load (0.2ms) SELECT "recipes".* FROM "recipes" WHERE "recipes"."id" = $1 LIMIT 1 [["id", 66]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["id", 5]]
Unpermitted parameter: collection_id
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO "recipe_collections" ("recipe_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["recipe_id", 66], ["created_at", "2015-04-06 21:58:06.566948"], ["updated_at", "2015-04-06 21:58:06.566948"]]
(0.4ms) COMMIT
Redirected to http://localhost:3000/recipes/66
Completed 302 Found in 11ms (ActiveRecord: 2.0ms)
Its a bit confusing whether you are setting up a relationship between recipes and collections as many-to-many or one-to-many. Your text implies a collection has many recipes and a recipe belongs to one collection. While your code, by using a join table and stating a recipe has many recipe-collections implies you are setting up a many-to-many relationship.
In other words you are a bit mixed up...
If a recipe belongs to a collection. Get rid of the join table. Recipe belongs_to :collection, while Collection has_many :recipes. Get rid of the other relationship stuff in those two models. In your migration table for Recipe, make sure there is a t.belongs_to :collection to set up the foreign key.
If a recipe has and belongs to many collections. Keep the join table, but name it collections_recipes as Rails can only find it if both sides are pluralized and in alphabetical order. Recipe has_and_belongs_to_many :collections, Collection has_and_belong_to_many :recipes. Drop the has_many :recipe_collections and the through: bit, do not replace either, Rails does this for you. Check your join table in your migration has t.belongs_to :recipe and :collection
Hope this helps.

Nested forms in Rails using has_many :through

I am having trouble figuring out how to make a nested form using a has_many :through relationship. I used this Railscast and I took at look at this tutorial and lots of the questions on Stack Overflow and elsewhere around Google.
I'm trying to make a way to create tags via the articles form. My code has gone through lots of iterations based on information from lots of different sources and none of them have worked, but right now I have
A class for articles
class Article < ActiveRecord::Base
attr_accessible :content, :heading, :image, :tag_ids, :tags, :tag_name, :tag_attributes
belongs_to :user
has_many :comments, :dependent => :destroy
has_many :article_tags
has_many :tags, :through => :article_tags
accepts_nested_attributes_for :tags, :reject_if => proc { |attributes| attributes['tag_name'].blank? }
...
end
A class for tags
class Tag < ActiveRecord::Base
attr_accessible :tag_name
has_many :article_tags
has_many :articles, :through => :article_tags
end
A class for article_tags
class ArticleTag < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
The New in my articles_controller.rb is like this:
def new
#article = Article.new
#tags = Tag.find(:all)
article_tag = #article.article_tags.build()
#article_tags = #article.tags.all
#article.article_tags.build.build_tag
3.times do
article_tag = #article.article_tags.build()
end
end
And my form for articles is currently like this (I have gone back and forth between nesting the fields_for :tags inside the fields_for :article_tags or just letting them be on their own):
<%= form_for #article , :html => { :multipart => true } do |f| %>
...excerpted...
<%= f.fields_for :article_tags do |t| %>
<%= t.fields_for :tags do |ta| %>
<%= ta.label :tag_name, "Tag name" %>
<%= ta.text_field :tag_name %>
<% end %>
<% end %>
I realize this is probably messy; I'm pretty new at this and I'm trying to figure it out. Do I have to add anything to the articles_controller create? Is it something to do with the attr_accessible? Or should I do something completely different?
EDIT:
Here are the request parameters after making the change suggested by Hck and creating a new article, selecting an existing tag with tag_id 3 and trying to also create a new tag at the same time:
Started POST "/articles" for 127.0.0.1 at 2011-08-10 19:05:46 +1000
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"5CQuV4RWfFZD1uDjv1DrZbIe+GB/sDQ6yiAETZutmZ4=", "article"=>{"heading"=>"Test heading", "content"=>"Test Content", "tag_ids"=>["3"], "article_tags"=>{"tags"=>{"tag_name"=>"Test tag"}}}, "commit"=>"Submit"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
WARNING: Can't mass-assign protected attributes: article_tags
Tag Load (0.4ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = 3 LIMIT 1
AREL (0.4ms) INSERT INTO "articles" ("content", "user_id", "created_at", "updated_at", "heading", "image_file_name", "image_content_type", "image_file_size") VALUES ('Test Content', 1, '2011-08-10 09:05:46.228951', '2011-08-10 09:05:46.228951', 'Test heading', NULL, NULL, NULL)
AREL (0.2ms) INSERT INTO "article_tags" ("article_id", "tag_id", "created_at", "updated_at") VALUES (88, 3, '2011-08-10 09:05:46.243076', '2011-08-10 09:05:46.243076')
[paperclip] Saving attachments.
Redirected to [localhost]
Completed 302 Found in 212ms
And if I add :article_tags to the attr_accessible for Article and try again, I get:
Started POST "/articles" for 127.0.0.1 at 2011-08-10 19:11:49 +1000
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"5CQuV4RWfFZD1uDjv1DrZbIe+GB/sDQ6yiAETZutmZ4=", "article"=>{"heading"=>"Test heading", "content"=>"Test content", "tag_ids"=>["3"], "article_tags"=>{"tags"=>{"tag_name"=>"Test tag "}}}, "commit"=>"Submit"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 LIMIT 1
Tag Load (0.4ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = 3 LIMIT 1
Completed in 119ms
ActiveRecord::AssociationTypeMismatch (ArticleTag(#2165285820) expected, got Array(#2151973780)):
app/controllers/articles_controller.rb:32:in `create'
Try to replace #article.article_tags.build.build_tag with #article.tags.build in your controller`s action.
I don't think you have to nest the article tags in it too. Article tags is just an association between the articles and tags. You can simply create the new tag within the articles because you already associated with them. I believe it is from the magic of the "accepts_nested_attributes". try this.
<%= form_for #article , :html => { :multipart => true } do |f| %>
...excerpted...
<%= f.fields_for :tags, Tag.new do |t| %>
<%= t.label :tag_name, "Tag name" %>
<%= t.text_field :name %>
<% end %>
<% end %>
Also, you should try to mass assign it instead of saving every attribute piece by piece by using private params. I had a nested forms problem before too, so you can take a look at how I wrote my code:
Cannot save record to database RAILS nested forms
The only thing I left out there was the private params section, which I recommended you to do.
private
def venue_params
params.require(:venue).permit(:name, :address, :discount, :latitude, :longitude, :tags_attributes =>[:name],:tag_ids => [])
end
I also wrote a blog post about nested forms, so you can take a look at it too
http://minling.github.io/

Resources