many-to-many with has_many :through association nested form - ruby-on-rails

student.rb
class Student < ActiveRecord::Base
has_many :enrollments
has_many :courses, through: :enrollments
accepts_nested_attributes_for :enrollments
end
enrollment.rb
class Enrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
course.rb
class Course < ActiveRecord::Base
has_many :enrollments
has_many :students, through: :enrollments
end
enrollments_controller.rb
class EnrollmentsController < ApplicationController
def new
#current_student = current_user.student
#enrollments = #current_student.enrollments.build
end
def create
current_student = current_user.student
#enrollments = current_student.enrollments.build(enrollment_params)
if #enrollments.save
flash[:success] = "You have successfully enrolled."
redirect_to new_enrollment_path
else
# fail
flash[:danger] = "Please try again."
redirect_to new_enrollment_path
end
end
private
def enrollment_params
params.require(:enrollment).permit(:student_id, :course_id)
end
end
enrollment/new.html.erb
<%= nested_form_for(#current_student, html: { class: 'form-horizontal' }) do |f| %>
<%= f.fields_for :enrollments do |e| %>
<div class="form-group">
<%= e.label :course_id, for: :course_id, class: 'col-xs-3 control-label' %>
<div class="col-xs-9">
<%= e.collection_select :course_id, Course.all, :id, :name, {prompt: "Select your Course"}, {class: 'form-control'} %>
</div>
</div>
<% end %>
<%= f.link_to_add 'Add Course', :enrollments, class: "col-xs-9 col-xs-offset-3" %>
<div class="form-group">
<div class="col-xs-9 col-xs-offset-3">
<%= f.submit "Enroll Now", class: 'btn btn-primary' %>
</div>
</div>
<% end %>
By referering to :
many-to-many: has_many :through association form with data assigned to linking model create form view
Intention: Create the Enrollments with the existing courses and students.
Current implementation of enrollment/new.html.erb will show all of the existing enrollments on the form which is not the desired presentation view.
I wish to create a blank form for creating enrollments.
How can I do that?

By just adding a line, "!e.object.persisted?" it solves the problem.
enrollment/new.html.erb
<%= f.fields_for :enrollments do |e| %>
<!-- -->
<% if !e.object.persisted? %>
<div class="form-group">
<%= e.label :course_id, for: :course_id, class: 'col-xs-3 control-label' %>
<div class="col-xs-9">
<%= e.collection_select :course_id, Course.all, :id, :name, {prompt: "Select your Course"}, {class: 'form-control'} %>
</div>
</div>
<% end %>
<% end %>

Related

Validation failed: Stocks product must exist, Stocks location must exist

I have three models -> locations, products and stocks.
Stocks is a join table of locations and products.
When creating a new product i used fields_for to show locations and even though i got it to work, for some reason now it does not seem to work anymore and it gives me the above error.
<div class="input-field">
<%= f.label :product_name %>
<%= f.text_field :name, autofocus: true %>
</div>
<div class="input-field">
<%= f.label :price %>
<%= f.text_field :price, autofocus: true %>
</div>
<% if !#edit %>
<%= f.fields_for :stocks do |ff| %>
<div class="input-field margin-top x-4">
<%= ff.collection_select :location_id, Location.all, :id, :structured_location , {:prompt => "Please Select Locations for Product"}, {multiple: true} %>
<%= ff.label :locations %>
</div>
<div class="input-field">
<%= ff.label :quantity %>
<%= ff.text_field :quantity %>
</div>
<div class="input-field">
<%= ff.label :threshold_quantity %>
<%= ff.text_field :threshold_quantity %>
</div>
<% end %>
<% else %>
<%= collection_select :product, :location_ids, Location.all, :id, :structured_location , {:prompt => "Please Select Locations for Product"}, {multiple: true} %>
<% end %>
<div class="row margin-top x-4">
<div class="col s12 center-align">
<%= f.submit "#{current_page?(new_product_path) ? "Create Product" : "Update Product"}", class: "btn wave-effect pink darken-1 btn-large" %>
</div>
</div>
controller
class ProductsController < ApplicationController
helper_method :sort_column, :sort_direction
def index
#products = Product.order(sort_column + " " + sort_direction)
end
def new
#product = Product.new
#product.stocks.build
end
def create
#product = Product.new(product_params)
if #product.save!
flash[:notice] = "Successfully saved..."
redirect_to products_path
else
flash[:alert] = "Something went wrong, please check the values you entered"
redirect_to :back
end
end
private
def product_params
params.require(:product).permit(:name,:price, location_ids: [], stocks_attributes: [:id, :quantity, :threshold_quantity, location_id: []])
end
end
product model
class Product < ApplicationRecord
has_many :stocks, dependent: :destroy
has_many :locations, :through => :stocks
accepts_nested_attributes_for :stocks
end
parameters in rails console
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"l1BFhrdyB2QMO5k3+60GNiPphFfF+DXDGPbUU3V2Op2aekObjgIe13k8uoedmDIEZgIeXPZUeS/0VxQXkKa1Uw==",
"product"=>{"name"=>"Soap", "price"=>"10", "location_ids"=>["", "1",
"2"], "stocks_attributes"=>{"0"=>{"quantity"=>"100",
"threshold_quantity"=>"100"}}}, "commit"=>"Create Product"}
After hours of searching i stumbled upon this post
BigBinary
it seems that Rails 5 made belongs_to relationship IDs required by default thats why i had validations failed and i couldn't find anything about it.
simply adding optional: true in my stock model worked!
class Stock < ApplicationRecord
belongs_to :location, optional: true
belongs_to :product, optional: true
accepts_nested_attributes_for :product, allow_destroy: true
accepts_nested_attributes_for :location, allow_destroy: true
end

Rails 5 ActiveRecord::RecordInvalid (Validation failed), but I have no validations

I'm trying to implement a has_many :through many to many form, but I'm having an issue submitting to the database. I have no field validations, which tells me it's complaining about the structure of the parameters hash more than anything.
The error is:
ActiveRecord::RecordInvalid (Validation failed: Expense expense categories expense must exist):
The parameter hash looks like this:
Parameters: {"utf8"=>"✓", "expense"=>{"date"=>"2006/12/12", "amount"=>"234", "check_number"=>"234", "debit"=>"0", "notes"=>"234", "expense_expense_categories_attributes"=>{"1464029611137"=>{"amount"=>"234", "expense_category_id"=>"1", "_destroy"=>"false"}}}, "commit"=>"Create Expense"}
One thing I notice is that it's not adding the :expense_id value into the junction table. This should be done by the accepts_nested_attributes_for mechanism but it's not. I'm starting to think this an issue with Rails 5 because I've had similar relationships and forms structured like this that works fine. Do you guys see anything I'm missing?
here's my controller:
def create
#expense = Expense.new(expense_params)
respond_to do |format|
if #expense.save!
#expenses = Expense.all.paginate(:page => params[:page], :per_page => 9).order("created_at DESC")
format.html { redirect_to #expense, notice: 'Expense was successfully created.' }
format.js {}
format.json { render json: #expense, status: :created, location: #expense }
else
#expenses = Expense.all.paginate(:page => params[:page], :per_page => 9).order("created_at DESC")
format.html { render action: "index" }
format.js {}
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def expense_params
params.require(:expense).permit(:id, :date, :amount, :check_number, :debit, :notes, :amount, :expense_expense_categories_attributes => [:id, :amount, :expense_id , :expense_category_id, :_destroy])
end
Here are my models:
Expense
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
end
ExpenseCategory:
class ExpenseCategory < ApplicationRecord
has_many :expense_expense_categories
has_many :expenses, through: :expense_expense_categories
end
ExpenseExpenseCategory
class ExpenseExpenseCategory < ApplicationRecord
monetize :amount_cents
belongs_to :expense
belongs_to :expense_category
accepts_nested_attributes_for :expense_category
end
_form.html.erb:
<%= form_for #expense, html: { :class => "ui form segment" }, :remote => true do |f|%>
<div class="field">
<%= f.label :date%>
<div class="ui small input">
<%= f.date_field :date %>
</div>
</div>
<div class="field">
<%= f.label :amount %>
<div class="ui small input">
<%= f.text_field :amount %>
</div>
</div>
<div class="field">
<%= f.label :check_number %>
<div class="ui small input">
<%= f.text_field :check_number %>
</div>
</div>
<div class="field">
<%= f.label :debit %>
<div class="ui small input">
<%= f.check_box :debit %>
</div>
</div>
<div class="field">
<%= f.label :notes %>
<div class="ui small input">
<%= f.text_area :notes %>
</div>
</div>
<%= f.fields_for :expense_expense_categories do |builders| %>
<%= render 'expense_expense_category_fields', :f => builders %>
<% end %>
<%= link_to_add_fields "Add Category", f, :expense_expense_categories %>
<%= f.submit class: "ui blue button" %>
expense_expense_category_fields.htnl.erb
<div class="field">
<%=f.label :amount%>
<%= f.text_field :amount %>
</div>
<div class="field">
<%=f.label :category%>
<%= f.select :expense_category_id, ExpenseCategory.all.collect { |p| [p.category, p.id] } %>
</div>
<%= f.hidden_field :_destroy %>
<%= link_to "Remove option", "#", :class => "remove_expense_expense_categories" %>
Here is the form data from the browser being submitted:
utf8:✓
expense[date]:2016-05-12
expense[amount]:23
expense[check_number]:23
expense[debit]:0
expense[notes]:
expense[expense_expense_categories_attributes][1464030986149][amount]:23
expense[expense_expense_categories_attributes][1464030986149][expense_category_id]:1
expense[expense_expense_categories_attributes][1464030986149][_destroy]:false
expense[expense_expense_categories_attributes][1464030991099][amount]:43
expense[expense_expense_categories_attributes][1464030991099][expense_category_id]:10
expense[expense_expense_categories_attributes][1464030991099][_destroy]:false
commit:Create Expense
This is because Rails is attempting to create ExpenseExpenseCategory before the expense has been created. You must define; :inverse_of on the associations.
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories, inverse_of: :expense
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
end
class ExpenseCategory < ApplicationRecord
has_many :expense_expense_categories, inverse_of: :expense_category
has_many :expenses, through: :expense_expense_categories
end
class ExpenseExpenseCategory < ApplicationRecord
monetize :amount_cents
belongs_to :expense, inverse_of: :expense_expense_categories
belongs_to :expense_category, inverse_of: :expense_expense_categories
accepts_nested_attributes_for :expense_category
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
Please check this link
https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html
inverse_of didn't worked for me, I have to use optional: true in belongs_to association.

Rails 4.0 nested object forms not rendered

I have two models in my app: "WorkPost" and "Contacts".
WorkPost
class WorkPost < ActiveRecord::Base
has_one :contacts
end
Contacts
class Contacts < ActiveRecord::Base
belongs_to :work_post
end
In my controller's new method I do:
def new
#work_post = WorkPost.new
#work_post.contacts
end
And in view I create form:
<%= form_for(#work_post) do |f| %>
<div class="field">
<%= f.label 'Vacation' %><br>
<%= f.text_field :post_title, :placeholder => 'Vacation here' %>
</div>
<div class="field">
<%= f.label 'Vacation description' %><br>
<%= f.text_area :post_body, :placeholder => 'Vacation description here' %>
</div>
<% f.fields_for :contacts do |cf| %>
<div class="field">
<%= cf.label 'Email' %><br>
<%= cf.text_field :emails, :placeholder => 'Email here' %>
</div>
<% end %>
<div class="actions">
<%= f.submit "Post vacation", :class => 'btn_act' %>
</div>
<% end %>
But it seems like line <% f.fields_for :contacts do |cf| %> doesn't work.
Everything is rendered fine but email field.What I am doing wrong?
The problem is with this line
<% f.fields_for :contacts do |cf| %>
which should be
<%= f.fields_for :contact do |cf| %>
Also, the class name for the model and the association name for has_one/belongs_to should be singular.
#work_post.rb
class WorkPost < ActiveRecord::Base
has_one :contact #should be singular
end
#contact.rb
class Contact < ActiveRecord::Base #should be singular
belongs_to :work_post
end
Also, notice the change :contacts to :contact, as it is a has_one association.
Update:
Also, try the below changes
Include accepts_nested_attributes_for :contact in work_post.rb model
#work_post.rb
class WorkPost < ActiveRecord::Base
has_one :contact
accepts_nested_attributes_for :contact
end
Change the new method to below
def new
#work_post = WorkPost.new
#work_post.build_contact
end

'Can't mass-assign protected attributes' when implementing Multiple Table Inheritance with nested forms

HI I am trying to implement the MTI in my application. I have a Person Model and 2 models inheriting from it: Client and TeamMember. When creating a Team Member I want to save to to database vallues for both person (first and last name, email etc) and team member(experience level, type of team, if lead or not). I am using the nested attributes form so in my team member form I am nesting the person fields. Unfortunatellly I am getting "Can't mass-assign protected attributes: person" error when trying to save. Can anyone tell me how this can be solved? Thanks!
Models:
UPDATED TeamMember class but still the same error
also tried people_attributes and persons_attributes and none of these worked
class TeamMember < ActiveRecord::Base
has_many :project_team_members
has_many :projects, through: :project_team_members
has_one :person, as: :profile, dependent: :destroy
accepts_nested_attributes_for :person
attr_accessible :person_attributes, :experience_level, :lead, :qualification, :team
end
class Person < ActiveRecord::Base
belongs_to :company
belongs_to :profile, polymorphic: true
attr_accessible :email, :first_name, :last_name, :phone_number, :profile_id, :profile_type
end
Controller as follows:
class TeamMembersController < ApplicationController
def create
person = Person.create! { |p| p.profile = TeamMember.create!(params[:team_member]) }
redirect_to root_url
end
and the view:
<%= form_for(#team_member) do |f| %>
<%= f.fields_for :person do |ff| %>
<div>
<%= ff.label :first_name %>
<%= ff.text_field :first_name %>
</div>
<div>
<%= ff.label :last_name %>
<%= ff.text_field :last_name %>
</div>
<div>
<%= ff.label :phone_number %>
<%= ff.text_field :phone_number %>
</div>
<div>
<%= ff.label :email %>
<%= ff.text_field :email %>
</div>
<div>
<%= ff.label :company_id %>
<%= ff.text_field :company_id %>
</div>
<% end %>
<div class="field">
<%= f.label :team %><br />
<%= f.text_field :team %>
</div>
<div class="field">
<%= f.label :experience_level %><br />
<%= f.text_field :experience_level %>
</div>
<div class="field">
<%= f.label :qualification %><br />
<%= f.text_field :qualification %>
</div>
<div class="field">
<%= f.label :lead %><br />
<%= f.check_box :lead %>
</div>
<div class="actions">
<%= f.submit %>
</div>
UPDATED TeamMembersController (Solution thanks to the courtesy of Tiago)
def new
#team_member = TeamMember.new
#team_member.build_person
respond_to do |format|
format.html # new.html.erb
format.json { render json: #team_member }
end
end
def create
#team_member = TeamMember.create!(params[:team_member])
redirect_to root_url
end
To mass assign attributes in a nested form, you'll need to specify:
class TeamMember < ActiveRecord::Base
has_many :project_team_members
has_many :projects, through: :project_team_members
has_one :person, as: :profile, dependent: :destroy
:experience_level, :lead, :qualification, :team #what is this line doing??
accepts_nested_attributes_for :person
attr_accessible :person_attributes
end
EDIT:
In the action called before the form you need to build person. Like:
#team_member = TeamMember.new
#team_member.build_person
Then you'll have one person (non-persisted) associated with #team_member.

Nested attributes form for model which belongs_to few models

I have few models - User, Teacher and TeacherLeader.
class User < ActiveRecord::Base
attr_accessible ...,
:teacher_attributes
has_one :teacher
has_one :teacher_leader
accepts_nested_attributes_for :teacher_leader
end
class Teacher < ActiveRecord::Base
belongs_to :user
has_one :teacher_leader
end
class TeacherLeader < ActiveRecord::Base
belongs_to :user
belongs_to :teacher
end
I would like to fill TeacherLeader via nested attributes. So, i do such things in controller:
class TeacherLeadersController < ApplicationController
...
def new
#user = User.new
#teacher_leader = #user.build_teacher_leader
#teachers_collection = Teacher.all.collect do |t|
[ "#{t.teacher_last_name} #{t.teacher_first_name} #{t.teacher_middle_name}", t.id ]
end
#choosen_teacher = #teachers_collection.first.last unless #teachers_collection.empty?
end
end
And also have such view (new.html.erb):
<%= form_for #user, :url => teacher_leaders_url, :html => {:class => "form-horizontal"} do |f| %>
<%= field_set_tag do %>
<% f.fields_for :teacher_leader do |tl| %>
<div class="control-group">
<%= tl.label :teacher_id, "Teacher names", :class => "control-label" %>
<div class="controls">
<%= select_tag( :teacher_id,
options_for_select( #teachers_collection, #choosen_teacher )) %>
</div>
</div>
<% end %>
<% end %>
...
<%= f.submit "Create", :class => "btn btn-large btn-success" %>
<% end %>
Problem is that select form here does NOT appear. Why? Do i do something wrong?
<%= f.fields_for :teacher_leader do |tl| %>

Resources