I'm working on my first Rails project, and I have the following model relationship:
class Profile < ActiveRecord::Base
belongs_to :identifiable, polymorphic: true
accepts_nested_attributes_for :students
class Student < ActiveRecord::Base
has_one :profile, as: :identifiable
attr_accessible :profile
The associated controllers are:
class StudentsController < ApplicationController
def new
#student = Student.new
end
def create
#student = Student.new(params[:student])
if #student.save
redirect_to root_path
else
render 'new'
end
end
end
And
class ProfilesController < ApplicationController
def new
#profile = Profile.new
end
def create
#profile = Profile.new(params[:profile])
#profile.save
end
end
What I'm trying to do is create a new Student with the following form, which is in students\new.html.erb:
<h1>Create a new Student Account</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#student) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.fields_for :profile, #profile do |builder| %>
<%= builder.label :name %>
<%= builder.text_field :name %>
<%= builder.label :email %>
<%= builder.text_field :email %>
<%= builder.label :password %>
<%= builder.password_field :password %>
<%= builder.label :password_confirmation, "Confirmation" %>
<%= builder.password_field :password_confirmation %>
<% end %>
</div>
</div>
<p><%= f.submit "Submit", class: "btn btn-large btn-primary" %></p>
<% end %>
I'm getting the following error message when I try to submit the form: No association found for name 'students'. Has it been defined yet? What am I doing wrong? Thanks in advance.
In order for a model to accept nested attributes for another model, the association to the other model needs to be declared. In Profile you have accepts_nested_attributes_for :students, but there is no corresponding association defined (e.g. has_many :students), which is why you're getting that particular error. In your case, this association wouldn't be correct, however.
Usually, if model A accepts nested attributes for model B, either A has_many B or A has_one B. In your case, you have A belongs_to B. A better design would be
class Profile < ActiveRecord::Base
belongs_to :identifiable, polymorphic: true
class Student < ActiveRecord::Base
attr_accessible :profile_attributes
has_one :profile, as: :identifiable
accepts_nested_attributes_for :profile
Should your student be singular? ie: accepts_nested_attributes_for :student
Edit: Also, your Student should accept nested attributes for a Profile, if the Student has_one profile, and the Student form contains the fields_for call (I think...)
Related
I'm currently working on a form to edit a user's information, and part of the form is to check off their roles that they have.
roles are stored in their own table that has the role type and the user's ID.
What I'm looking to do is put the checkboxes for the 3 types of roles, and the user can check off which roles they are supposed to have.
Essentially the form should look like this:
The problem is I can't figure out how to set this up with the form builder. I have accepts_nested_attributes_for :roles set on user, but I'm not sure how this would work with fields_for.
Any ideas?
You don't need nested attributes to simply assign associations to a record.
To start with you want to alter your tables and associations to create a normalization table:
class User < ApplicationRecord
has_many :user_roles, dependent: :destroy
has_many :roles, through: :user_roles
end
# remove user_id from the roles table
class Role < ApplicationRecord
validates_uniqueness_of :name
has_many :user_roles, dependent: :destroy
has_many :users, through: :user_roles
end
# rails g model user_role user:references role:references
class UserRole < ApplicationRecord
validates_uniqueness_of :role_id, scope: :user_id
belongs_to :user
belongs_to :role
end
This means the definition name of a role is only defined once in the roles table instead of being duplicated for every role applied to a user. And to assign the role to a user you're just adding a role id and user id in a join table which can be indexed much more efficiently.
Since we don't need to set that duplicate name column for every user role we can just use role_ids= to set the roles of a user with a checkbox.
<%= form_for(#user) do |f| %>
<%= f.collection_check_boxes(:role_ids, Role.all, :id, :name) %>
...
<% end %>
class UsersController < ApplicationController
# ...
def create
#user = User.new(user_params)
# ...
end
# ...
private
def user_params
params.require(:user)
.permit(:foo, :bar, role_ids: [])
end
end
https://guides.rubyonrails.org/form_helpers.html#nested-forms
The example given there is :
<%= form_for #person do |f| %>
Addresses:
<ul>
<%= f.fields_for :addresses do |addresses_form| %>
<li>
<%= addresses_form.label :kind %>
<%= addresses_form.text_field :kind %>
<%= addresses_form.label :street %>
<%= addresses_form.text_field :street %>
...
</li>
<% end %>
</ul>
<% end %>
Adapting that to have I'm assuming you've setup your models:
<%= form_for #user do |f| %>
<%= f.input :first_name %>
<%= f.input :last_name %>
Role:
<ul>
<%= f.fields_for :roles do |role_f| %>
<li>
<%= role_f.check_box :it %>
<%= role_f.check_box :accounting %>
<%= role_f.check_box :sales%>
</li>
<% end %>
</ul>
<% end %>
From there you can see how the params come through and create the roles as needed
I have a form I would like to create that populates the corresponding model form for, and a fields_for that populates a has many through table.
The plan.rb model:
class Plan < ApplicationRecord
has_many :plan_materials
has_many :materials, through: :plan_materials
accepts_nested_attributes_for :materials
end
The materials.rb model:
class Material < ApplicationRecord
has_many :plan_materials
has_many :plans, through: :plan_materials
end
And the PlanMaterial model:
class PlanMaterial < ApplicationRecord
belongs_to :plan
belongs_to :material
end
This is what I have in the plan form:
<%= form_for #plan do |form| %>
<div class="form-group">
<%= form.label :name %>
<%= form.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :description %>
<%= form.text_field :description, class: 'form-control' %>
</div>
<%= form.fields_for :materials, plan.materials.build do |material_fields| %>
<%= material_fields.text_field :title %>
<% end %>
<%= form.submit %>
<% end %>
I have created fields_for forms before but never trying to input the information for the form being created, and the id of the material they are selecting, in a new table from that same form.
I am creating this through a plan.materials.build which I realize could possibly be the wrong way to go about doing this because I don't think it would be building that in the plan_materials table.
class PlansController < ApplicationController
def index
#plans = Plan.all
end
def new
#plan = Plan.new
#plan.materials.build
end
def create
#plan = Plan.new(plan_params)
respond_to do |format|
if #plan.save
format.html { redirect_to plan_path, notice: 'Plan has been created' }
else
format.html { render :new, notice: 'There was an error saving your plan' }
end
end
end
private
def plan_params
params.require(:plan).permit(:name, :description, :grade_id, :subject_id, :unit_id, materials_attributes: [:title])
end
end
So, just to recap, I would like to create a plan, and be able to add materials to that plan. I need to have that plan created in the plans table, along with the id of the plan that was created and the id of the material that was added in that form. How do I go about doing this?
I have a rails4 app. At the moment my collection select only works if I select only one option. Below is my working code. I only have product form. Industry model is populated with seeds.rb. IndustryProduct is only use to connect the other 2 models.
I'd like to know what I have to change in the code to be able to choose more.
I saw some working examples with multiple: true option like (https://www.youtube.com/watch?v=ZNrNGTe2Zqk at 10:20) but in this case the UI is kinda ugly + couldn't pull it off with any of the sample codes. Is there an other solution like having more boxes with one option chosen instead of one box with multiple options?
models:
class Product < ActiveRecord::Base
belongs_to :user
has_many :industry_products
has_many :industries, through: :industry_products
has_many :product_features
accepts_nested_attributes_for :industry_products, allow_destroy: true
accepts_nested_attributes_for :product_features
validates_associated :industry_products
validates_associated :product_features
end
class Industry < ActiveRecord::Base
has_many :industry_products
has_many :products, through: :industry_products
accepts_nested_attributes_for :industry_products
end
class IndustryProduct < ActiveRecord::Base
belongs_to :product
belongs_to :industry
end
_form.html.erb
<%= form_for #product do |f| %>
<%= render 'layouts/error_messages', object: f.object %>
......
<%= f.fields_for :industry_products do |p| %>
<%= p.collection_select :industry_id, Industry.all, :id, :name %>
<% end %>
<%= f.fields_for :product_features do |p| %>
<%= p.text_field :feature, placeholder: "add a feature", class: "form-control" %>
<% end %>
<%= f.submit class: "btn btn-primary" %>
<% end %>
products controller
def new
#product = Product.new
#product.industry_products.build
#product.product_features.build
end
def create
#product = current_user.products.new(product_params)
if #product.save
redirect_to #product
else
render action: :new
end
end
......
def product_params
params.require(:product).permit(....., industry_products_attributes: [:id, :industry_id, :_destroy], industries_attributes: [:id, :name], product_features_attributes: [:feature])
end
Firstly, you could fix your first collection select by using it to set the industry_ids for the #product:
<%= form_for #product do |f| %>
<%= f.collection_select :industry_ids, Industry.all, :id, :name %>
<% end %>
This will allow you to set the collection_singular_ids method, which exists for all has_many associations.
You'd have to back it up in the params method:
#app/controllers/products_controller.rb
....
def product_params
params.require(:product).permit(.... industry_ids: [])
end
A lot more succinct than using nested attributes.
To get that "multiple" selection, you'll want to use the following:
<%= f.collection_select :industry_ids, Industry.all, :id, :name, {}, { multiple: true } %>
Tested & working
--
You may also want to look at collection_check_boxes:
<%= f.collection_check_boxes :industry_ids, Industry.all, :id, :name %>
I GOT TWO PROBLEMS:
-I'm stuck with creating a project which includes nested attributes for :position
-I got it nearly working for editing the project details plus the :position attribute, but the fields_for :assigned_projects ads all the fields for all users who are assigned to the project.
I have 3 Models:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
attr_accessible :name, :controlled, :currency, :creator_id, :assigned_projects
accepts_nested_attributes_for :assigned_projects#, :allow_destroy => true
end
class AssignedProject < ActiveRecord::Base
belongs_to :user, class_name: "User"
belongs_to :project, class_name: "Project"
attr_accessible :project_id, :user_id, :position, :project, :user, :user_attributes
accepts_nested_attributes_for :user
end
Each User can create a Project and is the Projects.creator
Each Project has_many Users through the join model Assigned_Project
Each User can have a different position in the project, so I want to save the :position in the join model AssignedProject.
if a user creates a Project, he should be able to edit the project attributes PLUS the :position attribute of the new join model.
Now the Form field for New.Project and Edit.Project
/project/new.htm.erb
<%= form_for( setup_new_project(#project) ) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.submit "Add Project", class: "" %>
<% end %>
/project/edit.htm.erb
<%= form_for( setup_project(current_project) ) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.submit "Update Project", class: "" %>
<% end %>
And I have the following setup methods as described in this article:
http://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
module FormHelper
def setup_project(project)
project.assigned_projects ||= AssignedProject.new
project
end
def setup_new_project(project)
project.assigned_project = AssignedProject.new
project
end
end
I hope the problem is clear enough.
for creating a new project the current error message is:
undefined method `assigned_project='
15: <%= render 'shared/user_sidebar_menu' %>
16:
17: <div class="span4 offset1">
18: <%= form_for( setup_new_project(#project) ) do |f| %>
19:
20: <%= render 'shared/error_messages', object: f.object %>
21:
UPDATE: added projects_controller.rb
projects_controller.rb
class ProjectsController < ApplicationController
def new
#project = Project.new
end
def create
#project = Project.new(params[:project])
#project.creator = current_user
if #project.save
current_user.assigned_projects.create(project: #project)
redirect_to current_user
else
render 'new'
end
end
end
Update setup_new_project method as below:
def setup_new_project(project)
project.assigned_projects.build ## Updated this line
project
end
Use project.assigned_projects(Notice plural) instead of project.assigned_project(Notice singular WRONG).
User and AssignedProject model are in a 1-M relationship. So, you get dynamic method assigned_projects=(Notice plural), you are getting error as you called assigned_project=(Notice singular) which does not exist.
UPDATE
undefined method each for <AssignedProject:0x007ff7aa55b528>
Use project.assigned_projects.build instead of project.assigned_project = AssignedProject.new.
UPDATE 2
You are approaching this incorrectly. The form helpers are totally not required. All you need to do is update the new and create actions as below:
def new
#project = Project.new
#project.assigned_projects.build
end
and update the form_for in both new and edit view's as below:
<%= form_for(#project) do |f| %>
I'm trying to save the comment text section of a comment and it won't save for some reason.
I'm checking my server outputs, and the comment attribute is set, but when actually saved, it turns out as NIL.
Originally it has "comment"=>{"comment"=>"hello dude"}, "commit"=>"Sbmit Comment"
But in the saving it saves NIL.
Here's my form for comments
<div class="container">
<% if user_signed_in? %>
<%= form_for([#answer, #comment]) do |f| %>
<p>
<%= f.label :comment %>
<%= f.text_area :comment, :cols => "50", :rows => "30"%>
</p>
<p>
<%= f.submit "Submit Comment" %>
</p>
<% end %>
<% else %>
<p> <em>You must be signed in to comment</em> </p>
<% end %>
</div>
Here's my comments controller
class CommentsController < ApplicationController
def create
#answer = Answer.find(params[:answer_id])
#comment = #answer.comments.new(params[:comments])
#comment.writer = current_user.username
#comment.save
redirect_to question_path(#answer)
end
end
And here's my model.
class Comment < ActiveRecord::Base
belongs_to :answer
attr_accessible :anonymous, :comment, :writer, :votes
end
Heres my answers model
class Answer < ActiveRecord::Base
has_many :comments, dependent: :destroy
belongs_to :question
attr_accessible :anonymous, :answer, :commenter, :votes, :comments_attributes
end
Any ideas?
EDIT: I've used params[:comment], however, it says I cannot mass assign attributes to answer, even though answer has attr_accessible: :comments_attributes
In this line #comment = #answer.comments.new(params[:comments]) change :comments to :comment
You are trying to save with params[:comments] in your controller but what is being passed would respond to params[:comment]