Hi I`m learning has_many :through and I have a association like this.
student:
has_many :subjects, through: :participations
has_many :participations
subject:
has_many :students, through: :participations
has_many :participations
belongs_to :student
participation:
belongs_to :student
belongs_to :subject
The student subjects are updated through checkboxes in update view:
= f.association :subjecs, label_method: :title, value_method: :id, label: 'Subjects', as: :check_boxes
And I went so far :( My student have subjects id, but it can`t get them since no participation is created.
My update action:
def create
student = Student.new(student_params)
if student.save
redirect_to students_path
else
render 'edit'
end
end
My question is when should I create participation object, and where is the appropriate place for the function ?
When you have a has_many relationship, you actually get a bunch of methods which can help you.
One of these is association_singular_ids- which if you populate it correctly, will automatically create the associative data you need.
The way to do this will be to use the following:
#app/views/students/new.html.erb
<%= form_for #student do |f| %>
<%= f.collection_check_boxes :subject_ids, Subject.all, :id, :name %>
<%= f.submit %>
<% end %>
I know you're using f.association (which is built through simple_form) - you'll be much better suited to using collection_check_boxes (it even explains an example of what you're having problems with).
You shouldn't need to pass the params or anything - and because your participations model acts as a join, it should be populated automatically if you use the above code.
HABTM
You may also wish to look at has_and_belongs_to_many:
#app/models/student.rb
class Student < ActiveRecord::Base
has_and_belongs_to_many :subjects
end
#app/models/subject.rb
class Subject < ActiveRecord::Base
has_and_belongs_to_many :students
end
#join table - students_subjects
This is often preferred over has_many :through because it requires less maintenance (as described in the link above).
Nested Attributes
Finally, to give you some more perspective, you'll need to know about the nested attributes aspect of Rails.
I originally thought your answer would be that you're not using accepts_nested_attributes_for, but I don't think so now. Nonetheless, you'll still gain benefit from knowing about it.
--
One of the reasons you'd use has_many through would be to populate the join model with other attributes. HABTM does not allow this; because has_many :through has a join model (in your case participations), it allows you to add extra attributes into it.
As such, if you're looking to change any of those attributes, you'll need to pass them through your various models:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :participations
has_many :subjects, through: :participations
accepts_nested_attributes_for :participations
end
#app/models/participation.rb
class Participation < ActiveRecord::Base
belongs_to :student
belongs_to :subject
accepts_nested_attributes_for :student
end
#app/models/subject.rb
class Subject< ActiveRecord::Base
has_many :participations
has_many :students, through: :participations
end
This will allow you to use the following:
#app/controllers/students_controller.rb
class StudentsController < ApplicationController
def new
#student = Student.new
#student.participations.build
end
def create
#student = Student.new student_params
#student.save
end
private
def student_params
params.require(:student).permit(:student, :params, participations_attributes: [subject_attributes:[]])
end
end
This should allow you to use the following:
#app/views/students/new.html.erb
<%= form_for #student do |f| %>
<%= f.fields_for :participations do |p| %>
<%= p.text_field :name %>
<%= p.collection_check_boxes :subject_id, Subject.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
My problem was that I didn`t permited the chenge od subject_ids in the student controller:
params.require(:student).permit(:first_name, :last_name, subject_ids: [])
Related
I am attempting to create a method by which a user may attach related records to an existing records similar to how a user may follow other users. However, when the method is called, a relationship can only be made between the record and itself. I have based my code on a follower/following model and I believe the issue is arising because the method is unable to differentiate between the current record and the record being selected to create a relationship with. Any ideas how this may be addressed? Relevant code is below...
Model
class Ingref < ApplicationRecord
has_many :active_relationships, class_name: "Ingrelationship",
foreign_key: "child_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Ingrelationship",
foreign_key: "parent_id",
dependent: :destroy
has_many :children, through: :active_relationships, source: :parent
has_many :parents, through: :passive_relationships, source: :child
# Follows a user.
def follow(other_ingref)
children << other_ingref
end
# Unfollows a user.
def unfollow(other_ingref)
children.delete(other_ingref)
end
# Returns true if the current user is following the other user.
def following?(other_ingref)
children.include?(other_ingref)
end
end
Relationship Controller
class IngrelationshipsController < ApplicationController
before_action :set_search
def create
ingref = Ingref.find(params[:parent_id])
ingref.follow(ingref)
redirect_to ingref
end
def destroy
ingref = Ingrelationship.find(params[:id]).parent
#ingref.unfollow(ingref)
redirect_to ingref
end
end
Relationship Model
class Ingrelationship < ApplicationRecord
belongs_to :child, class_name: "Ingref"
belongs_to :parent, class_name: "Ingref"
end
Form
<% Ingref.find_each do |ingref| %>
<div class="col-md-2">
<div class="caption">
<h3 class="title" style="font-size: 14px;"> <%= ingref.name %> </h3>
<%= form_for(#ingref.active_relationships.build) do |f| %>
<div><%= hidden_field_tag :parent_id, ingref.id %></div>
<%= f.submit "Follow" %>
<% end %>
</div>
</div>
<% end %>
I found that the issue outlined above arose due to a child not being defined in the child-parent relationship between two records in the self referential relationship. by adding the following lines in the form and controller respectively I was able to define this variable and create the desired relationships.
Form
<%= hidden_field_tag :child_id, #ingref.id %>
Controller
current_ingref = Ingref.find(params[:child_id])
I'm having some trouble to implement a nested form with a has_one :through association.
Models
# model: member.rb
belongs_to :user
has_one :academic
# model: user.rb
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member, reject_if: :all_blank
accepts_nested_attributes_for :academic, reject_if: :all_blank
# model: academic.rb
belongs_to :member
belongs_to :user
Controller
# users_controller.rb
def new
#user = User.new
#user.build_member
#user.build_academic
end
I also have tried with:
#user.member.build_academic
View
# new.html.erb
<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |ff| %>
<%= ff.text_field :email %>
# member belongs to user so I can call a fields_for
<% ff.fields_for :member do |f| %>
<%= f.text_field :name %>
# this part is not shown. What is wrong with my association?
<% f.fields_for :academic do |a| %>
<%= a.text_field :major %>
<% end %>
<% end %>
<% end %>
I've taken a look into the Rails documentation. The first fields_for is shown in the page (:member), but the second one (:academic), which has the has_one :through association, is not shown in the page.
Any help will be appreciated. Thank you.
Through
If you want to build your data through a relation, you have to pass the associated data as it's constructed:
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
has_one :academic
accepts_nested_attributes_for :academic
end
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic, through: :member
accepts_nested_attributes_for :member
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :member
belongs_to :user
end
This will allow you to do the following:
#app/controllers/members_controller.rb
class MembersController < ApplicationController
def new
#member = Member.new
#member.build_member.build_academic
end
def create
#member = Member.new member_params
#member.save
end
private
def member_params
params.require(:member).permit(:x, :y, :z, academic_attributes: [:some, :attributes, member_attributes:[...]])
end
end
This would permit the following:
#app/views/users/new.html.erb
<%= form_for #user do |f| %>
<%= f.fields_for :member do |m| %>
<%= f.text_field :name %>
...
<%= m.fields_for :academic do |a| %>
<% a.text_field :name %>
...
<% end %>
<% end %>
<%= f.submit %>
<% end %>
This works to build a new member object, and a new academic object from the user. Although not strictly what you're asking, it looks like it could benefit you in some form.
Associations
If you want to do what you're asking (IE build_member and build_academic exclusively), you'll need to get rid of the has_one :through relationship...
#app/models/user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :academics, through: :memberships
end
#app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :academic
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
The problem is you're basically trying to build a relationship for a direct association (member) and an indirect relationship (academic).
If you want to build both exclusively, you have to make them have a direct association with your main model. The above should allow you to do the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.members.build.academics.build
end
end
Much like my top example, this will pass nested data through your form - if you wanted to have it completely exclusively, do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :member
has_one :academic
has_and_belongs_to_many :academics
end
#app/models/member.rb
class Member < ActiveRecord::Base
belongs_to :user
end
#app/models/academic.rb
class Academic < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :users
end
This will allow the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.build_member
#user.build_academic
end
def create
#user = User.new user_params
#user.save
end
private
def user_params
params.require(:user).permit(:user, :params, member_attributes: [], academic_attributes:[])
end
end
For future reference or for anyone that has the same problem, I found a way around to fix this problem, in case you have same structure.
I could fix this by going to the view, before the form, and writing:
<% resource.member.build_academic %>
In my case resource is User, set by devise, a Rails gem used for authentication.
And you don't need to reference any :through or whatsoever in your model.
It is not the most efficient way, but I haven't found any other solution. Hope it helps.
I'm trying to build a form for a model with a join-table containing references to itself.
I'll give you a quick example of what i'm trying to achive:
Lets say i have the subject physics. To learn physics you will need to know basic math, i.e physics is dependent on math. Any subject should be able to have multiple dependencies both ways.
What i'm having problems with is submitting this relationship through a form.
My code:
Model:
class Subject < ActiveRecord::Base
has_many :needs, foreign_key: :target_id
has_many :subjects, through: :needs
accepts_nested_attributes_for :needs,:subjects,allow_destroy: true
end
class Need < ActiveRecord::Base
belongs_to :target, class_name: :subject
belongs_to :prerequisite, class_name: :subject
end
Controller:
class SubjectsController < ApplicationController
def create
#subject = Subject.new secure_params
if #subject.save
redirect_to root_path, success: 'Subject created'
else
render :new
end
end
def new
#subject = Subject.new
end
private
def secure_params
params.require(:subject).permit(:name, :content, needs_attributes: [:target,:prerequisite])
end
end
Form:
<div class="row">
<div class="col-md-8">
<%= simple_nested_form_for #subject do |f| %>
<%= f.input :name %>
<%= f.input :content %>
<%= f.fields_for :needs do |d| %>
<%=d.association :prerequisite %>
<% end %>
<%= f.link_to_add "Add a prerequisite", :needs %>
<%= f.submit class: 'btn btn-primary btn-lg' %>
<% end %>
</div>
</div>
Using this approach i get "uninitialized constant Need::subject" on entering the "new" action.
Any ideas on mistakes in my approach or code will be greatly appreciated.
Edit: adding join table
class CreateNeeds < ActiveRecord::Migration
def change
create_table :needs do |t|
t.references :target
t.references :prerequisite
end
end
end
Edit2: working code (only changed parts)
Model
class Subject < ActiveRecord::Base
has_many :needs, foreign_key: :target_id
has_many :prerequisites, class_name: "Subject",through: :needs, source: :prerequisites
accepts_nested_attributes_for :needs,:prerequisites,allow_destroy: true
end
class Need < ActiveRecord::Base
belongs_to :target, class_name: "Subject"
belongs_to :prerequisite, class_name: "Subject"
end
Controller
def secure_params
params.require(:subject).permit(:name, :content, needs_attributes: [:prerequisite_id])
end
end
Like i said in my comment, focus on making sure all the associations are hooked up properly before you worry about forms. The console, or automated tests, are both a good place to test this.
I think it maybe should be like so:
class Subject < ActiveRecord::Base
has_many :needs, as: :target
has_many :subjects, :class_name: "Subject", through: :needs, source: :prerequisite
accepts_nested_attributes_for :needs,:subjects,allow_destroy: true
end
#assuming you have fields target_id and prerequisite_id in this table
class Need < ActiveRecord::Base
belongs_to :target, class_name: :subject
belongs_to :prerequisite, class_name: :subject
end
I think the association name "subjects" is potentially confusing as it will return the subjects that this subject requires. This isn't immediately obvious to the reader, which could include you in the future.
At some point you may want to list the subjects that require this subject. It may be worth differentiating between these now, so you can clearly tell the difference. You could do this like so:
class Subject < ActiveRecord::Base
has_many :required_needs, as: :target
has_many :required_by_needs, as: :prerequisite
has_many :required_subjects, :class_name: "Subject", through: :required_needs, source: :prerequisite
has_many :required_by_subjects, :class_name: "Subject", through: :required_by_needs, source: :target
accepts_nested_attributes_for :required_needs, :required_by_needs, :required_subjects, :required_by_subjects, allow_destroy: true
end
I am working on a Rails application and currently I have 2 models - Subjects and Lessons.
A Subject has 3 different types of lessons - Lecture, Tutorial and Laboratory. I modelled such that there are 3 has_one to the Lesson model.
Right now, I am trying to create a nested form for subjects and lessons but the lecture, tutorial and laboratory being saved was always the first form that was rendered.
i.e. I have 3 nested forms separately for Lecture, Tutorial and Laboratory but the Lecture, Tutorial and Laboratory that was saved was always the one that was first built. In my codes the lecture was first built so the attributes for tutorial and laboratory would follow the one that I have filled in for my lecture.
I am not sure where I have went wrong or even if having multiple has_one relationship works in this case so any advice would be appreciated.
The related codes are as follows:
The subject model
class Subject < ActiveRecord::Base
has_one :lecture, :class_name => "Lesson"
has_one :laboratory,:class_name => "Lesson"
has_one :tutorial, :class_name => "Lesson"
accepts_nested_attributes_for :lecture
accepts_nested_attributes_for :laboratory
accepts_nested_attributes_for :tutorial
end
The lesson model
class Lesson < ActiveRecord::Base
belongs_to :subject
end
The Subject and lesson nested form
<%= form_for(#subject_list) do |f| %>
<div class="field">
<%= f.label :subject_code %><br />
<%= f.text_field :subject_code %>
</div>
<div>
<%= f.fields_for :lecture do |lecture| %>
<%= render "lecture_fields", :f => lecture %>
<% end %>
</div>
<div>
<%= f.fields_for :tutorial do |tutorial| %>
<%= render "tutorial_fields", :f => tutorial %>
<% end %>
</div>
<div>
<%= f.fields_for :laboratory do |laboratory| %>
<%= render "laboratory_fields", :f => laboratory %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The new action in the subject controller
def new
#subject = Subject.new
lecture = #subject.build_lecture
laboratory = #subject.build_laboratory
tutorial = #subject.build_tutorial
respond_to do |format|
format.html # new.html.erb
format.json { render json: #subject }
end
end
I would appreciate if someone could help me out in identifying where I have went wrong. If in the case that I should not be creating such multiple relationships, I would like to have some advice on how could I actually render out 3 forms with a default field indicating the lesson type.
I'm not really sure if that works, but my advise is to use AR inheritance
class Lesson < ActiveRecord::Base
end
class LectureLesson < Lesson
belongs_to :subject
end
class LaboratyLesson < Lesson
belongs_to :subject
end
class TutorialLesson < Lesson
belongs_to :subject
end
class Subject
has_one :lecture_lesson
has_one :laboratory_lesson
has_one :tutorial_lesson
accepts_nested_attributes_for :lecture_lesson
accepts_nested_attributes_for :laboratory_lesson
accepts_nested_attributes_for :tutorial_lesson
end
Migration
class LessonsAndSubjects < ActiveRecord::Migration
def up
remove_column :subjects, :lesson_id
add_column :subjects, :lecture_lesson_id, :integer
add_column :subjects, :laboratory_lesson_id, :integer
add_column :subjects, :tutorial_lesson_id, :integer
add_column :lessons, :type, :string
add_index :subjects, :lecture_lesson_id
add_index :subjects, :laboratory_lesson_id
add_index :subjects, :tutorial_lesson_id
end
def down
remove_column :subjects, :lecture_lesson_id
remove_column :subjects, :laboratory_lesson_id
remove_column :subjects, :tutorial_lesson_id
remove_column :lessons, :type
add_column :subjects, :lesson_id, :integer
end
end
it makes more sense and it may be fix you issue with nested attributes
Actually from the answer from rorra one point is missing, you need to add a polymorphic association for each "children" to not incurr in query problems
class Lesson < ActiveRecord::Base
belongs_to :subject
end
class LectureLesson < Lesson
belongs_to :polymorphic_lecture_lesson, polymorphic: true
end
class Subject
has_one :lesson
has_one :lecture_lesson, as: :polymorphic_lecture_lesson
accepts_nested_attributes_for :lesson
accepts_nested_attributes_for :lecture_lesson
end
in migration you have then to add
add_column :lessons, :polymorphic_lecture_lesson_id, :integer, index: true
add_column :lessons, :polymorphic_lecture_lesson_type, :integer, index: true
Interestingly, I interpreted this question much differently than the other answers seem to have.
If you are looking to have 2 has_one to a single model/table, then one can do the following:
Given a Person has one best pet and one worst pet, which are represented by the same Pet table/model that has an attribute to differentiate between the two...
class Person < ApplicationRecord
has_one :best_pet, -> { where(pet_type: "best") }, class_name: "Pet"
has_one :worst_pet, -> { where(pet_type: "worst") }, class_name: "Pet"
...
end
class Pet < ApplicationRecord
belongs_to :person
validates_presence_of :pet_type
validates_uniqueness_of :pet_type, scope: :person_id
...
end
Now, whether or not this is good database design is up for debate.
I have these models:
class Order < ActiveRecord::Base
has_many :order_lines
has_many :prizes, :through => :order_lines
accepts_nested_attributes_for :order_lines
end
class Prize < ActiveRecord::Base
has_many :order_lines
end
class OrderLine < ActiveRecord::Base
belongs_to :order
belongs_to :prize
end
I would like a nested form in the order form that displays every prize with a text box next to it where the user can enter pieces (eg. the amount to order). When the form is submitted the create action should create the order_lines accordingly. I can't find a solution anywhere.
First of all in the Order model use accepts_nested_attributes_for :prizes, instead of what you have.
After that it's easy, just add a form in the view (there's no additional step controller)
<%= form_for #order do |order_form| %>
..
<%= order_form.fields_for :prizes do |prizes_form| %>
<%= prizes_form.text_field :piece %>
..
<% end %>
..
<% end %>
This is straight from the documentation... You should definitely check that first.