nested attributes with has many through - ruby-on-rails

I am getting unpermitted params when I am passing values from UI. The association is many to many between models.
class User < ApplicationRecord
has_many :user_posts
has_many :posts, through: :user_posts
end
class Post < ApplicationRecord
has_many :user_posts
has_many :users, through: :user_posts
accepts_nested_attributes_for :user_posts
end
class UserPost < ApplicationRecord
belongs_to :user
belongs_to :post
accepts_nested_attributes_for :user
end
posts/_form.html.erb
<%= form_with(model: post) do |form| %>
<% if post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% post.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%#= form.label :name %>
<%#= form.text_field :name %>
</div>
<div class="field">
<%= form.fields_for :user_posts do |f| %>
<%= f.collection_select :user_id, User.all, :id, :username, {include_blank: false, include_hidden: false }, {:multiple => true, :class=>""} %>
<% end %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy ]
# GET /posts or /posts.json
def index
#posts = Post.all
end
# GET /posts/1 or /posts/1.json
def show
end
# GET /posts/new
def new
#post = Post.new
#post.user_posts.build
end
# GET /posts/1/edit
def edit
end
# POST /posts or /posts.json
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: "Post was successfully created." }
format.json { render :show, status: :created, location: #post }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /posts/1 or /posts/1.json
def update
respond_to do |format|
if #post.update(post_params)
format.html { redirect_to #post, notice: "Post was successfully updated." }
format.json { render :show, status: :ok, location: #post }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1 or /posts/1.json
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url, notice: "Post was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Only allow a list of trusted parameters through.
def post_params
params.require(:post).permit(:username, user_posts_attributes: [:user_id])
end
end
When I run the below line it gives unpermitted params
#post = Post.new(post_params)
Unpermitted parameter: :user_id
Schema
\d users
Table "public.users"
Column | Type | Collation | Nullable | Default
------------+--------------------------------+-----------+----------+-----------------------------------
id | bigint | | not null | nextval('users_id_seq'::regclass)
username | character varying | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
\d posts
Table "public.posts"
Column | Type | Collation | Nullable | Default
------------+--------------------------------+-----------+----------+-----------------------------------
id | bigint | | not null | nextval('posts_id_seq'::regclass)
name | character varying | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
\d user_posts
Table "public.user_posts"
Column | Type | Collation | Nullable | Default
------------+--------------------------------+-----------+----------+----------------------------------------
id | bigint | | not null | nextval('user_posts_id_seq'::regclass)
user_id | bigint | | not null |
post_id | bigint | | not null |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
Am I missing something crucial? Any help is appreciated
rails - 6.1
ruby - 3.0

You do not need or even want to use nested attributes here.
If you just want to associate existing records you just need a select for the user_ids attribute:
<%= form_with(model: post) do |form| %>
...
<div class="field">
<%= form.label :user_ids, 'Select the users' %>
<%= f.collection_select :user_ids, User.all, :id, :username, { include_blank: false, include_hidden: false }, { multiple: true, class: ""} %>
</div>
...
<% end %>
These setters and getters are created by has_many :users, through: :user_posts.
And to whitelist the post[user_ids] parameter as an array:
def post_params
params.require(:post).permit(
:foo,
:bar,
:baz,
user_ids: []
)
end
As you can see you don't need to explitly deal with the join table either as user_ids= will do all the work for you.
Creating join table entities with nested attributes is only necissary if the join table actually contains additional data that must be input by the user.

Related

Rails .build is not building has_many :options

I have a Poll app with 3 models.
Poll.rb
class poll < ApplicationRecord
validates_presence_of :user, :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
Question.rb
class Question < ApplicationRecord
validates_presence_of :poll_id, :question_id, :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
Option.rb
class Option < ApplicationRecord
validates_presence_of :question_id, :title
belongs_to :question
belongs_to :poll
end
I want the question form to have a field for adding options so I've added this to the question _form.
<%= form.fields_for :option do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
I can now see an option block which is good. Although I wish to have 3 possbile options so in the questions_controller.rb I've added the following:
def new
#question = #poll.questions.build
3.times { #question.options.build } # 3 different options
end
Despite this I'm only seeing one option block instead of the 3. Why is this the case and how do i fix? Additionally I'm not seeing new entries into the options postgresql table.
Full questions_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll
# GET /questions or /questions.json
def index
#questions = Question.all
end
# GET /questions/1 or /questions/1.json
def show
end
# GET /questions/new
def new
# #question = Question.new
#question = #poll.questions.build
3.times { #question.options.build } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /questions or /questions.json
def create
#question = Question.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to polls_question_url(#question), notice: "Question was successfully created." }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to polls_question_url(#question), notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/1.json
def destroy
poll_id = Question.find_by(params[:poll_id])
session[:return_to] ||= request.referer
#question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
params.require(:question).permit(:poll_id, :question_type, :title, :description, :randomize_selection, :voter_abstain, { option_attributes: [:question_id, :poll_id, :party_id, :title, :description] } )
end
def set_poll
#poll = poll.find_by(params[:poll_id])
end
end
routes.rb
resources :users do
resources :polls
end
resource :polls do
resources :questions
end
resource :questions do
resources :options
end
Edit:
Here is my questions form partial.
_form.html.erb
<%= form_with(model: [#Poll, question] ) do |form| %>
<% if question.errors.any? %>
<div style="color: red">
<h2><%= pluralize(question.errors.count, "error") %> prohibited this question from being saved:</h2>
<ul>
<% question.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.hidden_field :poll_id %>
</div>
<div>
<%= form.label :question_type, style: "display: block" %>
<%= form.text_field :question_type %>
</div>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :randomize_selection, style: "display: block" %>
<%= form.check_box :randomize_selection %>
</div>
<div>
<%= form.label :voter_abstain, style: "display: block" %>
<%= form.check_box :voter_abstain %>
</div>
<div>
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
Here is the poll's show where I am rendering the forms.
show.html.erb
<p style="color: green"><%= notice %></p>
<p>
<strong>Poll Title:</strong>
<%= #poll.title %>
<%= render #poll %>
</p>
<div>
<%= link_to "Edit this poll", edit_user_poll_path(#poll) %> |
<%= link_to "Back to polls", user_polls_path %> |
<%= link_to "Destroy this poll", user_poll_path(#poll), method: :delete %>
</div>
<% if #poll.questions.any? %>
<hr>
<h2>Questions:</h2>
<%= render #poll.questions %>
<% end %>
<hr>
<h2>Add a new Question:</h2>
<%= render "questions/form", question: #poll.questions.build %>
The argument you pass to fields_for has to match the name of the assocation on the model:
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
Pay very careful attention to plurization in Rails. Its a huge part of getting Convention over Configuration to work for you instead of against you.
However there are a quite a few other problems with this code.
Constants should always be CamelCase or UPPERCASE in Ruby - you need to change class poll to class Poll and fix all the references to the class. This isn't just a matter of style since the interpreter treats identifiers that start with an uppercase letter completely differently.
You're not nesting it properly. You have a nested route but you're still treating it like a non-nested resource in your controller and docstrings.
You're passing the parent id in your params whitelist. :poll_id and :question_id should not be whitelisted. Do not pass the parent id with a hidden input. The question id is assigned by Rails - you should not trust the user to pass it.
The option should not need a poll_id. Use an indirect has_one assocation to go up the tree. This could cause a edge case where a question and its options belong to different polls.
First lets fix the models:
class Poll < ApplicationRecord
# belongs_to assocations are required by default
# adding validations will just cause duplicate error messages
validates_presence_of :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
class Question < ApplicationRecord
validates_presence_of :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
class Option < ApplicationRecord
validates_presence_of :title
belongs_to :question
has_one :poll, through: :question
end
Then I would recommend that you use shallow nesting
resource :polls do
resources :questions, shallow: true
end
This creates the questions member routes (show, edit, delete) without the /polls/:poll_id prefix while the collection routes (index, create, new) are nested.
And that you set controller up as:
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll, only: %i[ new create index ]
# GET /polls/1/questions or /polls/1/questions.json
def index
#questions = #poll.questions.all
end
# GET /questions/1 or /polls/1/questions/1.json
def show
end
# GET /polls/1/questions/new
def new
# build is just an alias of new for legacy compatibility with Rails 2...
# its about time that we ditch it
#question = #poll.questions.new
3.times { #question.options.new } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /polls/1/questions or /polls/1/questions.json
def create
#question = #poll.questions.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: "Question was successfully created." }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to #question, notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/2.json
def destroy
session[:return_to] ||= request.referer
#question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Questions.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
# do not write this in a single unreadable line
params.require(:question).permit(
:question_type,
:title,
:description,
:randomize_selection,
:voter_abstain,
# do not wrap hash arguments in brackets
# as it will break if/when the `permit` method is changed to use real keyword arguments
# for has_many assocations the key naming convention is also plural_attributes
options_attributes: [
:party_id,
:title,
:description
]
)
end
def set_poll
#poll = Poll.find_by(params[:poll_id])
end
end
The key difference here is that you should look up the poll by the parameter in the URL for the nested routes and create the question off the poll instance (which sets poll_id).
Added:
You're not actually using the model you initialized in your controller. If you want to render the form from a completely different action you need to initialize the instance variable there:
class PollsController < ApplicationController
def show
#question = #poll.questions.new
3.times { #question.options.new } # 5 different options ???
end
# ...
end
<%= render "questions/form", question: #question %>
And in your partial you have a sneaky little bug. Ruby is case sensitive so #poll and #Poll are actually different variables.
irb(main):049:0> #foo = "bar" => "bar"
irb(main):050:0> #Foo
=> nil
Since instance variables are auto-vivified you're just get an unexpected nil instead of an error. What you actually want is:
<%= form_with(model: [#poll, question] ) do |form| %>

view one has_many record in rails

Restaurant Load (1.5ms) SELECT * FROM "restaurants" INNER JOIN "restaurant_branches" ON "restaurant_branches"."restaurant_id" = "restaurants"."restaurant_id"
+----------+---------+---------+---------+----------+---------+----------+---------+---------+---------+---------+---------+---------+---------+---------+-------+
| resta... | res_... | res_... | crea... | updat... | user_id | resta... | addr... | addr... | addr... | addr... | addr... | addr... | numb... | numb... | email |
+----------+---------+---------+---------+----------+---------+----------+---------+---------+---------+---------+---------+---------+---------+---------+-------+
| 27 | DOGG... | WE S... | 2014... | 2014-... | 4 | 28 | 405 ... | | CHICAGO | IL | 60666 | USA | | | |
| 27 | DOGG... | WE S... | 2014... | 2014-... | 4 | 29 | 111 ... | | CHICAGO | IL | 60661 | USA | | | |
+----------+---------+---------+---------+----------+---------+----------+---------+---------+---------+---------+---------+---------+---------+---------+-------+
As you can see I have a model restaurants and restaurant_branches. How would I approach if I want to open a restaurant record and ONLY ONE branch? Because right now, my form displays all the branches. How will I open this on my index.html.erb file so that my show.html.erb file will only be one restaurant_branches? Thank you for any help.
<h1>
<strong>Restaurant Name:</strong>
<%= #restaurant.res_name %>
</h1>
<p>
<strong>Restaurant Description:</strong>
<%= #restaurant.res_description %>
</p>
<ol class="restaurant_branch_fields">
<% #restaurant.restaurant_branches.each do |f| %>
<li>
<p><strong>Address</strong><%= f.set_address %></p>
<p><strong>Contact Info</strong><%= f.set_contact_info %></p>
</li>
<% end %>
</ol>
<p>
<%= link_to "Edit", edit_restaurant_path(#restaurant) %>
<%= link_to "Destroy", #restaurant, :confirm => 'Are you sure?', :method => :delete %>
<%= link_to "View All", restaurants_path %>
</p>
class RestaurantsController < ApplicationController
before_filter :authenticate_user!, except: [:index, :show]
def index
#restaurant = Restaurant.get_list
end
def show
##restaurant = Restaurant.select('*').joins(:restaurant_branches).find_by(:restaurant_id => params[:id], :restaurant_branches => {:restaurant_branch_id => params[:id]})
##restaurant = Restaurant.joins(:restaurant_branches).find_by(:restaurant_id => params[:id], :restaurant_branches => {:restaurant_branch_id => params[:restaurant_branch_id]})
#restaurant = Restaurant.find(params[:id])
end
def new
#restaurant = Restaurant.new
end
def create
if user_signed_in?
#restaurant = Restaurant.new(restaurant_params)
if #restaurant.save
redirect_to #restaurant
else
render 'new'
end
else
redirect_to new_owner_session_path
end
end
def edit
#restaurant = Restaurant.find(params[:id])
end
def update
#restaurant = Restaurant.find(params[:id])
if #restaurant.update_attributes(restaurant_params)
flash.now[:notice] = "You have successfully updated #{#restaurant.res_name.titleize}."
redirect_to #restaurant
else
render 'edit'
end
end
def destroy
#restaurant = Restaurant.find(params[:id])
#restaurant.destroy
flash.now[:notice] = "#{#restaurant.res_name.titleize} has been deleted!"
redirect_to restaurants_url
end
private
def restaurant_params
params.require(:restaurant).permit(:res_name, :res_description, restaurant_branches_attributes: [ :id, :address_line1, :address_line2, :address_line3, :address_line4, :address_line5, :address_line6, :number_phone, :number_fax, :email, :_destroy, pictures_attributes: [ :id, :name, :image, :_destroy] ] ).merge(user_id: current_user.id)
end
end
Rails.application.routes.draw do
devise_for :users, path_names: {sign_in: "login", sign_out: "logout"}
resources :restaurants do
resources :restaurant_branches, shallow: true
end
root to: 'restaurants#index'
end
Any help is appreciated.
Your page displays all the branches because of this iteration:
<% #restaurant.restaurant_branches.each do |f| %>
#restaurant.restaurant_branches contains all the branches, and .each will loop through them.
If you need to access only the first of those branches you can use this code in your controller's view action
#branch = #restaurant.restaurant_branches.first
Then in your view:
<p><strong>Address</strong><%= #branch.address %></p>
<p><strong>Contact Info</strong><%= #branch.contact_info %></p>
If you need the last branch you could have use in your controller #branch = #restaurant.restaurant_branches.last
If you need a specific id of a branch you could have use in your controller #branch = #restaurant.restaurant_branches.find(123)
If you really want that each restaurant can have only one branch then I suggest you go with has_one relationship instead of has_many.
class Restaurant < ActiveRecord::Base
has_one :restaurant_branch
end
class RestaurantBranch < ActiveRecord::Base
belongs_to :restaurant
end
Of course, you have to update your controller and views a bit as you will reference the restaurant_branch like this
#restaurant.restaurant_branch #without 'es'
More information: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_one
Regards

HABTM Association Build

I need propagate this values in :departaments_products table: , but I received the error:
I'm using Rails 4
NoMethodError in Products#new
undefined method `departament_id' for #<Product:0x007f916d35d648>
view.html.erb:
<%= form_for(#product) do |f| %>
<% if #product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% #product.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price %>
</div>
<%= f.collection_select(:departament_id, Departament.all, :id, :name, {:include_blank => true}) %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Products_controller:
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
#products = Product.all
end
# GET /products/1
# GET /products/1.json
def show
#product = Product.find( params[:id] )
end
# GET /products/new
def new
#product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Produto criado com sucesso' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: #product }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:name, :price)
end
end
Models:
class Departament < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :departaments
end
Migration:
class AddProductsAndDepartaments < ActiveRecord::Migration
def change
create_table :departaments_products do |t|
t.references :product, :departament
end
end
end
As its a HABTM association, logically you should be selecting multiple departament_ids for a single product. That said, you should include multiple: true option in the collection_select for departament_ids (Notice departament_ids in plural) in your view code:
<%= f.collection_select(:departament_ids, Departament.all, :id, :name, {include_blank: true}, {multiple: true}) %>
Currently, you are accessing it as departament_id (Notice singular) BUT as per HABTM association you get a method named departament_ids (Notice plural) and NOT departament_id which is why you receive error as NoMethodError in Products#new undefined method 'departament_id'
Once you are done with this change, you need to permit the departament_ids field in ProductsController as below:
def product_params
params.require(:product).permit(:name, :price, :departament_ids => [])
end
:departament_ids => [] is used because multiple selection is allowed for departament_ids and so you would receive it as an Array in params hash upon form submission.
Try departament_ids
For has_many => departament_ids
For has_one => departament_id

nested form has many through rails4

I'm not getting the expected results, the Skills are not getting saved. I want to allow each person to have say a primary skill ONLY 1.
DB
# Table name: people
#
# id :integer not null, primary key
# first_name :string(255)
# last_name :string(255)
# headline :string(255)
# description :string(255)
# user_id :integer
# created_at :datetime
# updated_at :datetime
#
# == Schema Information
#
# Table name: skills
#
# id :integer not null, primary key
# title :string(255)
# description :text
# created_at :datetime
# updated_at :datetime
#
# == Schema Information
#
# Table name: entity_skills
#
# id :integer not null, primary key
# skill_id :integer
# person_id :integer
# created_at :datetime
# updated_at :datetime
#
Models
class Person < ActiveRecord::Base
has_many :entity_skills
has_many :skills, through: :entity_skills, foreign_key: "person_id"
accepts_nested_attributes_for :skills
end
class Skill < ActiveRecord::Base
has_many :entity_skills
has_many :people, through: :entity_skills, foreign_key: "person_id"
end
class EntitySkill < ActiveRecord::Base
belongs_to :person
belongs_to :skill
end
Controller
def new
#person = Person.new
#all_skills = Skill.all
#entity_skills = #person.skills.build
end
def edit
#all_skills = Skill.all
#entity_skills = #person.entity_skills.build
end
def create
#person = Person.new(person_params)
respond_to do |format|
if #person.save
format.html { redirect_to #person, notice: 'Person was successfully created.' }
format.json { render action: 'show', status: :created, location: #person }
else
format.html { render action: 'new' }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
def person_params
params.require(:person).permit(:first_name, :last_name, :headline, :description, :user_id, :employer_id, skills_attributes: [:id])
end
Form
<%= form_for(#person) do |f| %>
....
....
<h2>Skills</h2>
<%= f.fields_for #entity_skills do |es| %>
<%= es.label "All Skills" %>
<%= collection_select(:skills, :id, #all_skills, :id, :title) %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Nothing is being saved for the skills, Can someone explain what is going on?
DB
# == Schema Information
#
# Table name: entity_skills
#
# id :integer not null, primary key
# skill_id :integer
# person_id :integer
# created_at :datetime
# updated_at :datetime
# position :integer
#
Controller
def new
#person = Person.new
#all_skills = Skill.all
5.times {#person.entity_skills.build}
end
def create
#person = Person.new(person_params)
respond_to do |format|
if #person.save
format.html { redirect_to #person, notice: 'Person was successfully created.' }
format.json { render action: 'show', status: :created, location: #person }
else
format.html { render action: 'new' }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
def person_params
params.require(:person).permit(:first_name, :last_name, :headline, :description, :user_id, :employer_id, :entity_skills_attributes => [:skill_id, :position, :person_id, :id])
end
Form
<h2>Skills</h2>
<% i=0 %>
<%= f.fields_for :entity_skills do |builder| %>
<% i+=1 %>
<%= render 'shared/skills_fields', :f => builder, :i=> i %>
<% end %>
shared/skills_fields
<div class="field">
<%= f.label "All Skills" %>
<%= f.collection_select(:skill_id, Skill.all, :id, :title) %>
<%= f.hidden_field :position, :value=>i %>
<%= f.hidden_field :person_id, :value=>#person.id %>
</div>
I had a few things funky, params were accepted but not assigned correctly to the form, also made mistake of using :skills in the permitted params instead of :entity_skills_attributes => [:skill_id, :position]
To work with updates all I had to do was
def edit
#entity_skills= #person.entity_skills
end
I will use the position to reorder skills in case anyone wonders ;-)
Finally solved
You cant save a has_many through without constructing the join records.
Perhaps checkout this tutorial by Dave Shepard which runs through the things you need to do with a full example.

Rails has_one association setup

Hi I'm new to rails and have been going in circles with this has_one association for hours. I have Products and Skins and when I create a new product via the form I'd like to use a select box to choose a skin to associate with the product.
I want to then render the product with a haml file in /skin/templates directory after having saved the name of the haml file in the templates column of the skins table.
The current error I'm getting is:
undefined method `template' for nil:NilClass
for this line in the controller:
render "/skins/templates/#{#product.skin.template}"
However I've tried other various configurations using skin_id as well and haven't been able to get past this.
Here's the code:
products_controller.rb
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
if request.path != product_path(#product)
redirect_to #product, status: :moved_permanently
else
render "/skins/templates/#{#product.skin.template}"
end
end
def new
#product = Product.new
respond_to do |format|
format.html # new.html.haml
format.json { render json: #product }
end
end
def create
#product = Product.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
format.html { render action: "new" }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
end
product.rb
class Product < ActiveRecord::Base
attr_accessible :name, :skin
has_one :skin
end
skin.rb
class Skin < ActiveRecord::Base
attr_accessible :product, :name, :template
belongs_to :product
end
_form.html.haml
= form_for #product do |f|
- if #product.errors.any?
#error_explanation
%h1= "#{pluralize(#product.errors.count, "error")} prohibited this product from being saved:"
%ul
- #product.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :name
= f.text_field :name
= f.select :skin_id, Skin.all.collect{|t| [t.name, t.id]}
.actions
= f.submit 'Save'
products table
id | name | created_at | updated_at
----+--------------+--------------------------------------------------------
1 | test | 2013-03-30 18:01:42.102505 | 2013-03-30 18:01:42.102505
skins table
id | name | template | created_at | updated_at | product_id
----+---------+----------+----------------------------+----------------------------+------------
1 | Product | product | 2013-03-30 20:13:26.374145 | 2013-03-30 20:13:26.374145 |
product_id in skin record is empty... but looks like your product should "belongs_to" skin
1) add skin_id to your products table and remove product_id from skins table
2) change Product model
class Product < ActiveRecord::Base
attr_accessible :name, :sku, :skin_id
belongs_to :skin
validates_presence_of :skin #add validation
end
3) Skin model
class Skin < ActiveRecord::Base
attr_accessible :name, :template
has_many :products
end

Resources