hardow2011
hardow2011

Reputation: 59

accepts_nested_attributes_for is contradicting the relational database logic in Rails

Ruby on Rails model logic conflicts with the relational database logic.

In Ruby on Rails, the model possessing the belongs_to, will have the foreign key in the database. So, the model:

class Student < ApplicationRecord
    belongs_to :university
end

class University < ApplicationRecord
    has_many :university
end

Will have the migration file:

class CreateStudents < ActiveRecord::Migration[6.0]
    t.string :name
    t.references :university, foreign_key: true
end

class CreateUniversities < ActiveRecord::Migration[6.0]
    t.string :name
end

And, logically, the student will have the University foreign_key in the database, like so:

Student
-id int (PK)
-name varchar
-University_id int (FK)

University
-id int (PK)
-name varchar

So good so far

The problem arises when I want accepts_nested_attributes_for in the student model for referencing a University in the _form.html.erb. In order to use it, the host model, in this case Student, must have a has_one instead of a belongs_to for the referencing table. So it would become:

class Student < ApplicationRecord
    has_one :university
    accepts_nested_attributes_for :university
end

Notice how, in order to use accepts_nested_attributes_for for referencing the university, the Student model MUST replace its belongs_to :university for a has_one :university, and University would have a belongs_to :student, instead of a has_many :student.

So in the migration file, Student would lose its reference to University, and the latter would have a reference to Student instead, like so:

class CreateStudents < ActiveRecord::Migration[6.0]
    t.string :name
end

class CreateUniversities < ActiveRecord::Migration[6.0]
    t.string :name
    t.references :student, foreign_key: true
end

And the database would forcefully be:

Student
-id int (PK)
-name varchar

University
-id int (PK)
-name varchar
-Student_id int (FK)

Which is wrong, because there should be a one to many relationship from University to Student, not the other way around.

So, is there a way to use accepts_nested_attributes_for, without messing up the relational database logic by having to forcefully inverse the relations in the models ?

Upvotes: 0

Views: 65

Answers (2)

hardow2011
hardow2011

Reputation: 59

I see that I have REALLY made a blunder trying to explain my doubts (it is my first question, I have learned from my mistake).

The ultimate goal was to know:

Can I use accepts_nested_attributes_for when there is a belongs_to instead of a has_one or has_many ?

So, the answer is yes:

class Student < ApplicationRecord
    belongs_to :university
    accepts_nested_attributes_for :university
end

In the student controller, there should be a build_:

  def new
    @student = Student.new
    @student.build_university
  end

I REALLY, REALLY, REALLY mistyped my question, and I apologize for it.

Thanks for all your answers.

Upvotes: 0

ekr990011
ekr990011

Reputation: 744

accepts_nested_attributes_forNested attributes allow you to save attributes on associated records through the parent.

So you might be having a slight mix up about which one you want to be the parent. If you make the student the parent with nested attributes for the university you would not be referencing the university but essentially creating a new university record or updating an existing one with the nested attributes you used in your student form.

One-to-one

Consider a Member model that has one Avatar:

class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar
end

Enabling nested attributes on a one-to-one association allows you to create the member and avatar in one go:

params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'

So you would only be updating/creating new University records for each student you used those nested attributes on.

Here is for one-to-many:

One-to-many

Consider a member that has a number of posts:

class Member < ActiveRecord::Base
  has_many :posts
  accepts_nested_attributes_for :posts
end

You can now set or update attributes on the associated posts through an attribute hash for a member: include the key :posts_attributes with an array of hashes of post attributes as a value.

For each hash that does not have an id key a new record will be instantiated, unless the hash also contains a _destroy key that evaluates to true.

params = { member: {
  name: 'joe', posts_attributes: [
    { title: 'Kari, the awesome Ruby documentation browser!' },
    { title: 'The egalitarian assumption of the modern citizen' },
    { title: '', _destroy: '1' } # this will be ignored
  ]
}}

member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'

You can also see that in the one to many example above. Are you looking to change/create a University record when you post a Student record?

If you are just looking for a reference you essentially have that in your first example and could just change your routes up a bit to have the student referenced by the university.

Then once you have the routes nested you can just do a little work to the form partial. Here is a guy doing just that

So maybe if I am way off here describe why when you create/update a Student you want to create/update a University. If that is what you want to do.

This would helps others with your context a little more and might help others understand your intent with the nested attributes.

Add your form partial for example and explain your goal.

EDIT: As a punt you can maybe look at has and belongs to many association. This tutorial talks about nested attributes with a many to many association. But since I'm not certain exactly what your after it may or may not help.

Another punt sounds like you want maybe a student's university record to change when you update them. So you would have the student belong to the university and the university to has many students but also has many university records and the student to has many/has one university record and the record to belong to both. aka: this

Then you could have the student have accepts nested attributes for the university record/s.

Upvotes: 1

Related Questions