I'm attempting to use global ids to specify polymorphic associations. However, when I want to reference the object through the association, no object is found.
Consider the following models:
class Person < ActiveRecord::Base
has_many :optionees, as: :entity
belongs_to :company
end
and
class Optionee < ActiveRecord::Base
belongs_to :entity, polymorphic: true
belongs_to :option
end
If I seed the above as follows:
Person.create(fname: "John", lname:"Smith", email:"john.smith#email.com", telephone:"555-555-5555", street:"333 Street St", city:"Salt Lake City", state:"UT", zip:"99999", company_id:"1")
(Giving the Person an ID of 1)
and
Optionee.create total_shares: "332", exercise_price: "33.22" option_id: "1" entity_id: "gid://legal/Person/1"
(Giving the Optionee an ID of 1)
I am unable to look up the person with the following command:
Optionee.find(1).entity
Instead it gives me a nill result.
I realize that the traditional way to do polymorphic associations is to provide the model name AND id; is there somthing I'm missing to do this with global ids? Many thanks in advance.
You need to add entity_type filed in Optionee table as well. And if person id is 1, then entity_id is equals to 1:-
Optionee.create total_shares: "332", exercise_price: "33.22" option_id: "1" entity_id: "1", entity_type: "Person"
Or you can create record for person in optionee table by doing this:-
#person = Person.find(params[:id])
#person.optionees.create(params)
It will set entity_id and entity_type for person. By doing this, no need to set entity_id and entity_type explicitly.
Related
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 ?
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.
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.
I'm trying to play around with association tables as well as single table inheritance ('STI'). I have a Man, Woman, and a Relationship model. The Relationship model has a type column, so that I can use STI. I also created a Friend model that inherits from Relationship, as Friend will be one type of relationship.
Man.rb
attr_accessible :name
has_many :relationships
has_many :women, :through => :relationships
Woman.rb
attr_accessible :name
has_many :relationships
has_many :men, :through => :relationships
In the meetup model, I also wish to keep track of when and where the date took place.
Relationship.rb
attr_accessible :type
belongs_to :woman
belongs_to :man
Friend.rb
class Friend < Relationship
end
However, when I try to create a relationship with type friend, I get the warning message that the subclass doesn't exist. In the console, I'm doing this
sarah = Woman.create!(name: 'Sarah')
jim = Man.create!(name: 'Jim')
jim.relationships.build(type: 'Friend', woman_id: 1)
=> #<Relationship id: nil, type: "Friend", man_id: 1, woman_id: 1, created_at: nil, updated_at: nil>
jim.save!
but then when I try to pull up the relationships for jim, I get
>> jim.relationships
Relationship Load (0.3ms) SELECT "relationships".* FROM "relationships" WHERE "relationships"."man_id" = 1
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'friends'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite Relationship.inheritance_column to use another column for that information.
The same is true when I try to create the relationship with 'friends' instead of 'Friend'
jim.relationships.build(type: 'friends', woman_id: 1)
can you explain what I'm doing wrong?
I didn't create a table in the db for friends. I thought everything could be stored in the Relationship model. Since this is just a play-around app, I only assigned the type attribute for the Relationship model.
i think the type value needs to be be lower case and singular - friend ?
but really you probably want to use the objects and let rails handle that for you
sarah = Woman.create!(name: 'Sarah')
jim = Man.create!(name: 'Jim')
Friend.create!(women: sarah, man: jim)
the Man Women models don't make sense to me - they are both a Person and a relationship should just have 2 people, regardless of gender
In my project, a user has a hotel, and each hotel has room types associated with it. Briefly, the models are
class User < ActiveRecord::Base
has_many :hotels
class Hotel < ActiveRecord::Base
belongs_to :user
has_many :roomtypes
class Roomtype < ActiveRecord::Base
attr_accessible :maxguests, :name
belongs_to :hotel
If I do the commands:
#user = User.find(1)
#user.hotels.find(1).roomtypes.build(name: "something", maxguests: "2")
The console returns:
#<Roomtype id: nil, name: "something", maxguests: 2, created_at: nil, updated_at: nil, hotel_id: 1>
For some reason, the Roomtype id and the timestamps are nil. Any ideas why?
You have built the roomtype (which creates a new instance of the object), but you not saved it yet. You have to explicitly save roomtype for it to go into the db (and thus get an id).
Instead of build use create or create! to accomplish this with a single method call
Replace build with create and you'll have your persisted object with id and timestamp.
Would not make sense to have this before.
I have a model called "EmployeeRecord" and it has a field called username, There is another model called "Employee" with a field called username.
I want to create an association between the 2 models so when I do:
record = EmployeeRecord.find(1)
record.employee // returns Employee instance
I was thinking I'd just need to do this, but apparently it doesn't work:
class EmployeeRecord < ActiveRecord::Base
has_one :employee, :foreign_key: username
end
Assume I can't add an employee_id field to EmployeeRecord. I scoured the Rails tutorials.. and recall wanting to know how to do this months ago.. but those dang Rails tutorials glided over this.. I remember.. it made me very very angry hehe
Any idea?
You'll also need to specify the primary_key used for the association, or else it's defaulted to "id". Your statement actually says "Search for a field username in table employee that is equal to my id field". What you actually want is "Search for a field username in table employee that is equal to my username field"
This should do the trick :
class EmployeeRecord < ActiveRecord::Base
has_one :employee, :foreign_key => username, :primary_key => :username
end
But hey... Why don't you use ids?
Add the foreign key option in the belongs_to method in your Employee model.
class Employee < ActiveRecord::Base
belongs_to :employee_record, foreign_key: username
end
The has_one or has_many is the parent, so it doesn't store the foreign key value/column. That's what the child does that has the belong_to side of the relationship.
What is the difference between a belongs_to and a has_one?
Reading the Ruby on Rails guide hasn't helped me.
They essentially do the same thing, the only difference is what side of the relationship you are on. If a User has a Profile, then in the User class you'd have has_one :profile and in the Profile class you'd have belongs_to :user. To determine who "has" the other object, look at where the foreign key is. We can say that a User "has" a Profile because the profiles table has a user_id column. If there was a column called profile_id on the users table, however, we would say that a Profile has a User, and the belongs_to/has_one locations would be swapped.
here is a more detailed explanation.
It's about where the foreign key sits.
class Foo < AR:Base
end
If foo belongs_to :bar, then the foos table has a bar_id column
If foo has_one :bar, then the bars table has a foo_id column
On the conceptual level, if your class A has a has_one relationship with class B then class A is the parent of class B hence your class B will have a belongs_to relationship with class A since it is the child of class A.
Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class declaring the belongs_to relationship.
class User < ActiveRecord::Base
# I reference an account.
belongs_to :account
end
class Account < ActiveRecord::Base
# One user references me.
has_one :user
end
The tables for these classes could look something like:
CREATE TABLE users (
id int(11) NOT NULL auto_increment,
account_id int(11) default NULL,
name varchar default NULL,
PRIMARY KEY (id)
)
CREATE TABLE accounts (
id int(11) NOT NULL auto_increment,
name varchar default NULL,
PRIMARY KEY (id)
)
has_one and belongs_to generally are same in a sense that they point to the other related model. belongs_to make sure that this model has the foreign_key defined.
has_one makes sure that the other model has_foreign key defined.
To be more specific, there are two sides of relationship, one is the Owner and other is Belongings. If only has_one is defined we can get its Belongings but cannot get the Owner from the belongings. To trace the Owner we need to define the belongs_to as well in the belonging model.
One additional thing that I want to add is, suppose we have the following models association.
class Author < ApplicationRecord
has_many :books
end
If we only write the above association, then we can get all books of a particular author with
#books = #author.books
but, for a particular book, we can't get the corresponding author with
#author = #book.author
To make the above code work we need to add an association to the Book model as well, like this
class Book < ApplicationRecord
belongs_to :author
end
This will add method 'author' to the Book model. For mode details see guides
has_one
This method should only be used if the other class contains the foreign key.
belongs_to
This method should only be used if the current class contains the foreign key.
From a simplicity standpoint, belongs_to is better than has_one because in has_one, you would have to add the following constraints to the model and table that has the foreign key to enforce the has_one relationship:
validates :foreign_key, presence: true, uniqueness: true
add a database unique index on the foreign key.