I have model called Section, and I want section to be able to have many Sections - so a Self Joins seems like a place to start.
I setup my code like this:
Model file
class Section < ApplicationRecord
belongs_to :offer
has_many :offer_items
# Self joins:
has_many :child_sections, class_name: "Section", foreign_key: "parent_id"
belongs_to :parent_section, class_name: "Section", optional: true
end
Migration file
class CreateSections < ActiveRecord::Migration[5.0]
def change
create_table :sections do |t|
t.string :name
t.references :offer, foreign_key: true
t.references :parent_section, foreign_key: true
t.timestamps
end
end
end
As you can see I set belongs_to :parent_section as optional, as no every section should has its parent.
When I print attribute_names of my Section model it says:
=> ["id", "name", "offer_id", "parent_section_id", "created_at", "updated_at"]
Trying to retrieve child_sections gets me an error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: sections.parent_id: SELECT "sections".* FROM "sections" WHERE "sections"."parent_id" = ?
Where did I make my mistake?
You do not have parent_id in your sections
change this:
has_many :child_sections, class_name: 'Section', foreign_key: 'parent_id'
on this:
has_many :child_sections, class_name: 'Section', foreign_key: 'parent_section_id'
Related
I'm building a dating app, and I'm creating a table (and model) for when users rate each other (like/dislike). I'm supporting multiple genders, so I don't want to simply have columns for "man's ID" and "woman's ID". However, that means I simply have two users IDs, and need to search both for the current user's ID.
I've managed to get the migration to work, but not to get the actual class to work. I can't figure out how to tell Rails to search both columns.
Ratings migration:
class CreateRatings < ActiveRecord::Migration[6.0]
def change
create_table :ratings do |t|
t.references :event, null: false, foreign_key: true
t.references :user1, index: true, null: false, foreign_key: {to_table: :users}
t.references :user2, index: true, null: false, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Ratings model:
class Rating < ApplicationRecord
belongs_to :event
belongs_to :user1, class_name: "User"
belongs_to :user2, class_name: "User"
end
When I try it in irb, I get:
irb(main):003:0> User.first.ratings
User Load (3.0ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column ratings.user_id does not exist)
LINE 1: SELECT "ratings".* FROM "ratings" WHERE "ratings"."user_id" ...
^
HINT: Perhaps you meant to reference the column "ratings.user1_id" or the column "ratings.user2_id".
irb(main):004:0>
Does this just need to be left out of Rails association-land? e.g. Should I instead define a method in the User class to get records matching Ratings.user1=User.id OR Ratings.user2=User.id?
I'm using a Postgres database, if that allows any cool solutions.
Users migration:
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
<snip>
t.timestamps
end
add_index :users, :email, unique: true
end
end
Users model
class User < ApplicationRecord
has_many :ratings
end
If you do not specify a foreign key, a default user_id will be looked for, but it does not exist, hence the error message you are getting. So you can do:
class Rating < ApplicationRecord
belongs_to :event
belongs_to :user1, class_name: "User", :foreign_key => 'user1'
belongs_to :user2, class_name: "User", :foreign_key => 'user2'
end
class User < ApplicationRecord
has_many :ratings1, foreign_key: :user1, class_name: 'Rating'
has_many :ratings2, foreign_key: :user2, class_name: 'Rating'
end
Then you can do
user.ratings1.first and user.ratings2.first
To display both, you need a method like:
def all_user_ratings
Rating.where("user1 = ? OR user2 = ?", user.id, user.id)
end
I have a Player class that I want to have high_school_team and club_team properties. So then I figure Player will have high_school_team_id and club_team_id properties that point to the corresponding team. I try to do this in the following migration, but it doesn't work.
class CreatePlayers < ActiveRecord::Migration[6.0]
def change
create_table :players do |t|
t.string :first_name
t.string :middle_name
t.string :last_name
t.decimal :height
t.decimal :weight
t.date :birthday
t.references :team, :high_school_team, foreign_key: true
t.references :team, :club_team, foreign_key: true
t.decimal :gpa
t.string :class_year
t.string :intended_major
t.string :email
t.string :phone_number
t.text :notes
t.timestamps
end
end
end
It gives the following error:
code/scout-db [master●] » rails db:migrate
== 20191218003854 CreatePlayers: migrating ====================================
-- create_table(:players)
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
you can't define an already defined column 'team_id'.
/Library/Ruby/Gems/2.6.0/gems/activerecord-6.0.2.1/lib/active_record/connection_adapters/abstract/schema_definitions.rb:372:in `column'
...
HighSchoolTeam and ClubTeam are models that do single table inheritance with Team.
I don't see why I'm getting the error. The docs seem to say that the first argument for t.referenes is table_name and the second ref_name. :team is the name of the table and I want the references to be high_school_team_id and club_team_id.
When I switch the order of the arguments to t.references, it still doesn't work. Somehow it gives the same error: you can't define an already defined column 'team_id'..
It looks you you are confusing SchemaStatement#add_reference and TableDefinition#references which have completely different signatures.
If you want to setup a foreign key column where the table can't be derived from the name of the column (the first argument) you just pass foreign_key: { to_table: :teams}.
class CreatePlayers < ActiveRecord::Migration[6.0]
def change
create_table :players do |t|
t.references :high_school_team, foreign_key: { to_table: :teams}
t.references :club_team, foreign_key: { to_table: :teams}
end
end
end
t.references :high_school_team, index: true as recommended by the other answers is NOT equivilent. That just adds a index to the column but no foreign key constraint.
You can then setup the assocations on Player as:
class Player < ApplicationRecord
belongs_to :high_school_team, class_name: 'Team'
belongs_to :club_team, class_name: 'Team'
end
You can't use a single has_many :players assocation on the other end though as the foreign key column can be either players.high_school_team_id or players.club_team_id.
class Team
has_many :high_school_team_players,
foreign_key: :high_school_team_id,
class_name: 'Player'
has_many :club_team_players,
foreign_key: :club_team_id,
class_name: 'Player'
end
But really a better alternative in the first place would have been to setup a join table:
class Player
has_many :placements
has_one :high_school_team,
through: :placements,
source: :team
class_name: 'Team'
has_one :club_team,
through: :placements,
source: :team
class_name: 'Team'
end
class Placement
belongs_to :player
belongs_to :team
end
class Team
has_many :placements
has_many :players, through: :placements
end
The doc you mentioned talks about the case when you need to add reference to an existing table.
For adding a refernence to a new table:
t.references :team, :high_school_team, foreign_key: true
This piece of code is wrong. Instead, it should be
t.references :high_school_team, foreign_key: {to_table: :teams}
to_table is needed to add database referential integrity
So your migration will be like this:
class CreatePlayers < ActiveRecord::Migration[6.0]
def change
create_table :players do |t|
....
t.references :high_school_team, foreign_key: {to_table: :teams}
t.references :club_team, foreign_key: {to_table: :teams}
....
end
end
end
I am trying to create self join in rails.
class ItemGroup < ApplicationRecord
has_many :children, class_name: "ItemGroup", foreign_key: "parent_id"
belongs_to :parent, class_name: "ItemGroup"
end
This is my code but when ever i try to add new record it doesn't saves it. So what is the problem in this code can any one help me or give me idea about self join.
class CreateItemGroups < ActiveRecord::Migration[5.0]
def change
create_table :item_groups do |t|
t.string :name
t.integer :parent_id
t.text :description
t.references :parent, index: true
t.timestamps
end
end
end
And this is my migration file.
Rails 5 makes belongs_to association required by default. To disable it you need to add optional: true to belongs_to
belongs_to :parent, class_name: "ItemGroup", optional: true
I have these two models:
module Studying
class Student < ApplicationRecord
has_and_belongs_to_many :instructors,
class_name: 'Studying::Instructor',
foreign_key: 'studying_student_id',
association_foreign_key: 'studying_instructor_id'
end
end
module Studying
class Instructor < ApplicationRecord
has_and_belongs_to_many :students,
class_name: 'Studying::Student',
foreign_key: 'studying_instructor_id',
association_foreign_key: 'studying_student_id'
end
end
And for join_table I have generated migration:
def change
create_table :studying_instructors_students, id: false do |t|
t.belongs_to :studying_instructor, index: { name: 'index_instructors_students_on_studying_instructor_id' }
t.belongs_to :studying_student, index: { name: 'index_instructors_students_on_studying_student_id' }
end
end
So all is working fine, but the point is, that my senior comrade tells me that I should not use in models things such as:
foreign_key: 'studying_instructor_id',
and
association_foreign_key: 'studying_student_id'
but instead of these I should use:
foreign_key: 'instructor_id',
and
association_foreign_key: 'student_id'
and in the same way in the first model because this is against the convention.
I do not know how I can do this in this models and in tables (how you already understand tables names in db: studying_instructors and studying_students).
Any advice please ?
You can create relation table this way
def change
create_table :studying_instructors_students, id: false do |t|
t.integer :instructor_id, index: true
t.integer :student_id, index: true
end
end
and then you can use foreign_key: 'instructor_id', and association_foreign_key: 'student_id'
If I get this wrong way, please, feel free to address additional questions.
I would do something like this:
class Studying::Student < ApplicationRecord
has_many :student_instructors, class_name: 'Studying::StudentInstructor'
has_many :instructors, through: :student_instructors, class_name: 'Studying::Instructor'
end
class Studying::Instructor < ApplicationRecord
has_many :student_instructors, class_name: 'Studying::StudentInstructor'
has_many :students, through: :student_instructors, class_name: 'Studying::Instructor'
end
class Studying::StudentInstructor < ApplicationRecord
belongs_to :student, class_name: 'Studying::Student'
belongs_to :instructor, class_name: 'Studying::Instructor'
end
I have three tables here. One for students, second for instructors and third one is a junction table (student_instructors).
Migration for studnt_instructor will look something like this
def change
create_table :student_instuctors, id: false do |t|
t.integer :instructor_id, index: true
t.integer :student_id, index: true
end
end
I have not verified the syntax but this is more of logic explanation.
I hope this helps.
I have a class:
class Route < ActiveRecord::Base
belongs_to :origin, class_name: 'Airport'
belongs_to :destination, class_name: 'Airport'
belongs_to :key_transfer_country, class_name: 'Country'
end
with the corresponding entry in schema.rb of
create_table "routes", force: true do |t|
t.integer "origin_id"
t.integer "destination_id"
t.integer "key_transfer_country_id"
t.datetime "created_at"
t.datetime "updated_at"
end
My Airport model includes:
has_many :from_routes, class_name: 'Route', foreign_key: 'destination_id'
has_many :to_routes, class_name: 'Route', foreign_key: 'origin_id'
and my Country model includes:
has_many :transfers, class_name: 'Route', foreign_key: 'key_transfer_country_id'
My problem comes when I try to create a route. Having set up origin, destination and transfer (which can be nil), I have:
route = Route.find_or_create_by(
origin: origin,
destination: destination,
key_transfer_country: transfer
)
but I get an SQL error
no such column: routes.key_transfer_country: SELECT "routes".* FROM "routes" WHERE "routes"."origin_id" = 6658 AND "routes"."destination_id" = 8025 AND "routes"."key_transfer_country" IS NULL LIMIT 1 (ActiveRecord::StatementInvalid)
So for some reason, Rails has appended _id to origin and destination (as I expected), but, for some reason I don't understand, it hasn't appended it to key_transfer_country, so the SELECT is failing.
Can anybody help me work out why?
try to add the foreign_key to your relationship :key_transfer_country, like this
class Route < ActiveRecord::Base
belongs_to :origin, class_name: 'Airport'
belongs_to :destination, class_name: 'Airport'
belongs_to :key_transfer_country, class_name: 'Country', foreign_key: 'key_transfer_country_id'
end
This is because the name of the foreign key can not be assumed for some reason by the length of the field name. Let's try.
Sorry for my english