Rails polymorphic relations - ruby-on-rails

I'm trying to use polymorphic relationship with rails 5.
I have difficulties to figure out how to finish my relations.
I have users, who can take reservations for hotels, restaurants, etc.
My purpose is to get hotels name and reservations when calling /users/{id} through API.
Here is my User model :
class User < ApplicationRecord
has_many :reservations, as: :reservable
has_many :hotels, through: :reservations
end
My Hotel model :
class Hotel < ApplicationRecord
has_many :reservations, as: :reservable
belongs_to :users, through: :reservations
end
My Reservation model :
class Reservation < ApplicationRecord
belongs_to :reservable, polymorphic: true
belongs_to :users
belongs_to :hotels
end
Migrations :
User :
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.timestamps
end
end
end
Reservations :
class CreateReservations < ActiveRecord::Migration[5.0]
def change
create_table :reservations do |t|
t.belongs_to :hotel, index: true
t.belongs_to :user, index: true
t.datetime :date_from
t.datetime :date_to
t.timestamps
end
end
end
class ReservationPolymorphism < ActiveRecord::Migration[5.0]
def change
rename_column :reservations, :hotel_id, :reservable_id
change_column :reservations, :reservable_id, polymorphic: true
add_column :reservations, :reservable_type, :string
end
end
Hotel :
class CreateHotels < ActiveRecord::Migration[5.0]
def change
create_table :hotels do |t|
t.string :name
t.string :address
t.string :postal_code
t.string :town
t.timestamps
end
end
end
I just have 1 line in my reservations table :
mysql> select * from reservations;
+----+---------------+-----------------+---------+---------------------+---------------------+---------------------+---------------------+
| id | reservable_id | reservable_type | user_id | date_from | date_to | created_at | updated_at |
+----+---------------+-----------------+---------+---------------------+---------------------+---------------------+---------------------+
| 1 | 1 | Hotel | 1 | 2017-01-12 00:00:00 | 2017-01-15 00:00:00 | 2016-10-19 09:18:01 | 2016-10-19 09:18:01 |
+----+---------------+-----------------+---------+---------------------+---------------------+---------------------+---------------------+
I have no result when using the API.
Here is what I get, using Rails console :
2.2.3 :001 > thomas = User.find(1)
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, first_name: "Thomas", last_name: "Dupont", email: "thomas.dupont#yopmail.com", created_at: "2016-10-18 21:12:12", updated_at: "2016-10-18 21:12:12">
2.2.3 :003 > thomas.reservations
Reservation Load (0.3ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`reservable_id` = 1 AND `reservations`.`reservable_type` = 'User'
2.2.3 :005 > thomas.hotels
NameError: uninitialized constant User::Hotels
So I can see I make basic mistake with rails relations and polymorphism but I really can't find out where I'm wrong.
I think I made a mistake.
I assumed Polymorphism could be use to load other models and their tables (like "morphTo" with eloquent / laravel), whereas it's just to load a model which has no data representation (as described in this post : https://robots.thoughtbot.com/using-polymorphism-to-make-a-better-activity-feed-in-rails )

I guess on viewing your code, you have written extra associations on top of the polymorphic association,
To create a polymorphic association, just the following code is enough. No need to pass extra association inside the model. Please refactor your code as follows,
User model
class User < ApplicationRecord
has_many :reservations, as: :reservable
end
Hotel model :
class Hotel < ApplicationRecord
has_many :reservations, as: :reservable
end
Reservation model :
class Reservation < ApplicationRecord
belongs_to :reservable, polymorphic: true
end
And in the migration file for reservations, add the reservable as below
t.references :reservable, polymorphic: true

Ok i sorted this out :
This is the reservation model :
class Reservation < ApplicationRecord
belongs_to :reservable, polymorphic: true
end
This is the hotel model :
class Hotel < ApplicationRecord
end
And the user model :
class User < ApplicationRecord
has_many :reservations
end
And how I got a results in rails console :
Running via Spring preloader in process 87452
Loading development environment (Rails 5.0.0.1)
2.2.3 :001 > u = User.find(1)
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, first_name: "Thomas", last_name: "Dupont", email: "thomas.dupont#yopmail.com", created_at: "2016-10-18 21:12:12", updated_at: "2016-10-18 21:12:12">
2.2.3 :002 > u.reservations
Reservation Load (0.3ms) SELECT `reservations`.* FROM `reservations` WHERE `reservations`.`user_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy [#<Reservation id: 1, reservable_id: 1, reservable_type: "Hotel", user_id: 1, date_from: "2017-01-12 00:00:00", date_to: "2017-01-15 00:00:00", created_at: "2016-10-19 09:18:01", updated_at: "2016-10-19 09:18:01">]>
2.2.3 :003 > u.reservations.first.reservable
Hotel Load (0.3ms) SELECT `hotels`.* FROM `hotels` WHERE `hotels`.`id` = 1 LIMIT 1
=> #<Hotel id: 1, name: "HYATT REGENCY HOTEL", address: "3 Place du Général Kœnig", postal_code: "75017", town: "PARIS", created_at: "2016-10-19 08:36:55", updated_at: "2016-10-19 08:36:55">

Related

How do you set up MTI in Rails with a polymorphic belongs_to association?

In an effort to create a Short, Self Contained, Correct (Compilable), Example, imagine that I want to do the following.
I have a blog website. There are two types of posts, TextPost and LinkPost. There are also two types of users, User and Guest. I would like to implement Multiple Table Inheritance with TextPost and LinkPost, by which I mean (hopefully I'm using the term correctly):
At the model level, I will have Post, TextPost and LinkPost. TextPost and LinkPost will inherit from Post.
At the database level, I will have tables for the "leaf" models of TextPost and LinkPost, but not for Post.
Each type of Post can belong to either a User or a Guest. So we have a polymorphic belongs_to situation.
My question is how to accomplish these goals.
I tried the following, but it doesn't work.
class Post < ApplicationRecord
self.abstract_class = true
belongs_to :author, polymorphic: true # user or guest
validates :title, :author_id, :author_type, presence: true
end
class TextPost < Post
validates :content, presence: :true
end
class LinkPost < Post
validates :url, presence: :true
end
class User < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
validates :name, presence: true
end
class Guest < ApplicationRecord
has_many :text_posts, as: :author
has_many :link_posts, as: :author
end
class CreateTextPosts < ActiveRecord::Migration[6.1]
def change
create_table :text_posts do |t|
t.string :title
t.string :content
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateLinkPosts < ActiveRecord::Migration[6.1]
def change
create_table :link_posts do |t|
t.string :title
t.string :url
t.references :author, polymorphic: true
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :name
t.timestamps
end
end
end
class CreateGuests < ActiveRecord::Migration[6.1]
def change
create_table :guests do |t|
t.timestamps
end
end
end
Console output:
:001 > user = User.create(name: 'alice')
(1.6ms) SELECT sqlite_version(*)
TRANSACTION (0.1ms) begin transaction
TRANSACTION (0.1ms) SAVEPOINT active_record_1
User Create (1.2ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "alice"], ["created_at", "2021-06-11 23:33:38.445387"], ["updated_at", "2021-06-11 23:33:38.445387"]]
TRANSACTION (0.2ms) RELEASE SAVEPOINT active_record_1
:002'> text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type:
'user')
Traceback (most recent call last):
1: from (irb):2:in `<main>'
NameError (wrong constant name user)
The names of constants look like the names of local variables, except that they begin with a capital letter.
All the built-in classes, along with the classes you define, have a corresponding global constant with the same name as the class called class name.
So in your case, when you define User class, there's a constant class name: User, but not user, that why the error NameError (wrong constant name user) is raised.
try text_post = TextPost.create(title: 'foo', content: 'lorem ipsum', author_id: 1, author_type: 'User')

Adding one-to-many association on namespaced models

I'm trying to set up a one-to-many relationship between two namespaced models, and despite my best efforts can't catch where my problem is.
Here is my migration:
class AddAssociations < ActiveRecord::Migration[5.1]
def change
add_belongs_to :admin_plans, :academy_courses, foreign_key: true, index: true
add_reference :academy_courses, :admin_plans, foreign_key: true, index: true
end
end
Admin::Plan model:
class Admin::Plan < ApplicationRecord
belongs_to :course, class_name: 'Academy::Course', optional: true, foreign_key: :academy_courses_id
end
Academy::Course model:
class Academy::Course < ApplicationRecord
has_many :plans, class_name: 'Admin::Plan, foreign_key: :admin_plans_id
end
Here's what happens when I try to get all plans for a course:
irb(main):001:0> c = Academy::Course.new
=> #<Academy::Course id: nil, title: nil, description: nil, user_id: nil, created_at: nil, updated_at: nil, admin_user_plans_id: nil, admin_plans_id: nil>
irb(main):004:0> c.plans
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):005:0> c.plans.all
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column admin_plans.course_id does not exist)
LINE 1: SELECT "admin_plans".* FROM "admin_plans" WHERE "admin_plan...
^
: SELECT "admin_plans".* FROM "admin_plans" WHERE "admin_plans"."course_id" = $1 LIMIT $2
irb(main):007:0> Admin::Plan.last
Admin::Plan Load (1.0ms) SELECT "admin_plans".* FROM "admin_plans" ORDER BY "admin_plans"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<Admin::Plan id: 2, name: "MOMLab", price: 15, created_at: "2019-02-06 15:05:43", updated_at: "2019-02-07 07:12:26", academy_courses_id: nil>
I'd really appreciate ideas of where this admin_plans.course_id is coming from, since I declared that the foreign key is :academy_courses_id.
Thanks :)
After a lot of banging my head on the wall and trying every method on Google, I settled for this:
Migration:
class AddCourseIdToAcademyLessons < ActiveRecord::Migration[5.1]
def change
add_column :admin_plans, :course_id, :integer, foreign_key: true, index: true
end
end
Admin::Plan class:
class Admin::Plan < ApplicationRecord
belongs_to :course, class_name: 'Academy::Course', optional: true
end
Academy::Course class:
class Academy::Course < ApplicationRecord
has_many :plans, class_name: 'Admin::Plan'
end

First time trying to use a foreign key other than model_id in Rails

So I know usually, a belongs_to associates models together based on the id column, but in my case I want to associate it by the token column instead.
For example:
#app/models/user.rb
class User < ApplicationRecord
has_many :test_results
end
and
#app/models/test_result.rb
class TestResult < ApplicationRecord
belongs_to :user
end
The User model has a column named token, and so when I create a new entry in TestResult that has the same token as what appears in User, I want the TestResult to be associated to that User.
I tried this in the model form:
#app/models/test_result.rb
class TestResult < ApplicationRecord
belongs_to :user, foreign_key: "token"
end
but when I go create a new test result, I can see that ActiveRecord is still looking for the id field that matches, instead of token.
2.5.1 :001 > TestResult.create(token: "Hello")
(6.2ms) SET NAMES utf8, ##SESSION.sql_mode = CONCAT(CONCAT(##sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), ##SESSION.sql_auto_is_null = 0, ##SESSION.wait_timeout = 2147483
(0.2ms) BEGIN
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 0 LIMIT 1
(0.2ms) ROLLBACK
=> #<TestResult id: nil, token: "Hello", created_at: nil, updated_at: nil>
Here's what my migration files look like:
class TestResults < ActiveRecord::Migration[5.1]
def change
create_table :test_results do |t|
t.string :token
t.timestamps
end
end
end
and
class User < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.belongs_to :company, foreign_key: true
t.belongs_to :platform, foreign_key: true
t.string :token
t.timestamps
end
end
end
I know I am using the foreign_key wrong, but I'm not sure how.
Not sure if this is the best solution, but just adding the primary_key function to the TestResult model in my case worked.
#app/models/test_result.rb
class TestResult < ApplicationRecord
belongs_to :user, foreign_key: "token", primary_key: "token"
end

same foreign key used two times in a table

I have the followings tables
break_points
id: integer
break_point_name: string
schedules
id: integer
departure: int -> break_point_id
arrival:int -> break_point_id
departure_date:date
arrival_date: date
Both departure and arrival are breakpoints .
Then Will I to create an association * to * instead?
1.9.3-p547 :004 > s=Schedule.find(1)
Schedule Load (0.1ms) SELECT "schedules".* FROM "schedules" WHERE "schedules"."id" = ? LIMIT 1 [["id", 1]]
=> #<Schedule id: 1, departure_id: 1, departure_date: "2015-01-05", departure_time: 28800, arrival_id: 11, arrival_date: "2015-01-06", arrival_departure: 3600, bus_company_id: 1, created_at: "2014-11-08 22:55:00", updated_at: "2014-11-08 22:55:00">
1.9.3-p547 :005 > s.departure_break_points
BreakPoint Load (0.3ms) SELECT "break_points".* FROM "break_points" WHERE "break_points"."departure_id" = 1
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: break_points.departure_id: SELECT "break_points".* FROM "break_points" WHERE "break_points"."departure_id" = 1
from /home/fernando/.rvm/gems/ruby-1.9.3-p547#ticket_master/gems/sqlite3-1.3.10/lib/sqlite3/database.rb:91:in `initialize'
from /home/fernando/.rvm/gems/ruby-1.9.3-p547#ticket_master/gems/sqlite3-1.3.10/lib/sqlite3/database.rb:91:in `new'
from /home/fernando/.rvm/gems/ruby-1.9.3-p547#ticket_master/gems/sqlite3-1.3.10/lib/sqlite3/database.rb:91:in `prepare'
these are my migrations that generate the tables of the database
class CreateSchedules < ActiveRecord::Migration
def change
create_table :schedules do |t|
t.integer :departure_id
t.date :departure_date
t.time :departure_time
t.integer :arrival_id
t.date :arrival_date
t.time :arrival_departure
t.integer :bus_company_id
t.timestamps
end
end
end
class CreateBreakPoints < ActiveRecord::Migration
def change
create_table :break_points do |t|
t.string :city
t.integer :province_id
t.timestamps
end
end
end
and these are my models
These are my models
class BreakPoint < ActiveRecord::Base
attr_accessible :break_point_name, :city
belongs_to :province
end
class Schedule < ActiveRecord::Base
has_many :departure_break_points,class_name: "BreakPoint", :foreign_key => 'departure_id', :dependent => :destroy
has_many :arrival_break_points, class_name: "BreakPoint", :foreign_key => 'arrival_id', :dependent => :destroy
end
Add two relations in your model , but specify the foreign key
has_many :departure_break_points,class_name: "BreakPoint", :foreign_key => 'departure' :dependent => :destroy
has_many :arrival_break_points, class_name: "BreakPoint", :foreign_key => 'arrival', :dependent => :destroy

has_many association using foreign_key option seem not to work?

I'm new to Rails and I'm having an issue with models using "different" primary/foreign key naming conventions than supported by Rails. (OK, I think this MIGHT be the problem)
So these are my 2 models:
class Project < ActiveRecord::Base
self.primary_key = "PROJECT_ID"
has_many :employees, :foreign_key => "PROJECT_ID"
end
class Employee < ActiveRecord::Base
self.primary_key = "EMPLOYEE_ID"
belongs_to :project, :primary_key => "PROJECT_ID"
end
And this is what's driving me nuts:
> p = Project.find(2)
Project Load (0.2ms) SELECT "projects".* FROM "projects" WHERE "projects"."PROJECT_ID" = ? LIMIT 1 [[nil, 2]]
=> #<Project project_id: 2, name: "Project 2", created_at: "2013-08-18 21:26:33.538007", updated_at: "2013-08-18 21:26:33.538007">
> p.employees.inspect
Employee Load (0.2ms) SELECT "employees".* FROM "employees" WHERE "employees"."PROJECT_ID" = ? **[[nil, nil]]**
=> "#<ActiveRecord::Associations::CollectionProxy []>"
For some reason I don't receive the employees with project_id = 2. It seems that the ? gets substituted with nil.
It works the other way round, check this out
> e = Employee.find_by_project_id(2)
Employee Load (0.2ms) SELECT "employees".* FROM "employees" WHERE "employees"."project_id" = 2 LIMIT 1
=> #<Employee employee_id: 2, first_name: "Will", last_name: "Smith", project_id: 2, created_at: "2013-08-18 21:21:47.884919", updated_at: "2013-08-18 21:22:48.263970">
> e.project.inspect
Project Load (0.2ms) SELECT "projects".* FROM "projects" WHERE "projects"."PROJECT_ID" = ? ORDER BY "projects"."PROJECT_ID" ASC LIMIT 1 [[nil, 2]]
=> "#<Project project_id: 2, name: \"Project 2\", created_at: \"2013-08-18 21:26:33.538007\", updated_at: \"2013-08-18 21:26:33.538007\">"
What am I missing?
Try this:
class Project < ActiveRecord::Base
self.primary_key = "PROJECT_ID"
has_many :employees
end
class Employee < ActiveRecord::Base
attr_accessible :project
self.primary_key = "EMPLOYEE_ID"
belongs_to :project
end
Try to avoid upper case column names at all cost.
for the record here my schema.rb
create_table "employees", :primary_key => "EMPLOYEE_ID", :force => true do |t|
t.integer "project_id"
end
create_table "projects", :primary_key => "PROJECT_ID", :force => true do |t|
end
Try the following:
class Project < ActiveRecord::Base
self.primary_key = "PROJECT_ID"
has_many :employees, :foreign_key => "PROJECT_ID", :primary_key => "PROJECT_ID"
end
class Employee < ActiveRecord::Base
self.primary_key = "EMPLOYEE_ID"
belongs_to :project, :primary_key => "PROJECT_ID", :foreign_key => "PROJECT_ID"
end
If you use uppercased field names (i am not sure based on your question) then make sure you always use uppercased names (e.g. find_by_PROJECT_ID). Rails and ActiveRecord are case sensitive.

Resources