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
Related
I wonder is it ok to have like 2 associations with the same table. For example:
class CreateTeams < ActiveRecord::Migration[6.0]
def change
create_table :teams do |t|
t.string :name, null: false
t.references :manager, foreign_key: { to_table: 'users' }
end
end
end
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.references :team
end
end
end
class Team < ApplicationRecord
has_many :users
belongs_to :manager, class_name: 'User', foreign_key: 'manager_id'
end
class User < ApplicationRecord
belongs_to :team
has_one :child_team, class_name: 'Team' # bad name, but just for example
end
Or it would be better to create a join table with team_id, user_id, and member_type?
Ruby/Rails versions do not matter but let's assume Ruby is 2.7.0 and Rails is 6.0.0
From a technical point of view - that's perfectly fine, but be careful with possible foreign key loop in the future.
This is more a question of architecture and your predictions of how system will evolve. A many-to-many relation with a explicit join model is more flexible. For example:
does manager always belong to the team? with a join table it's easier to fetch "all users from the team, no matter the role" or "all the teams a person has relation to, also no matter the role"
if there will be other roles or multiple people at same position - join table will also come handy
What you have seems fine.
Though a join table would be more flexible to provide more roles. This also avoids having a circular dependency in setting up teams and users.
class CreateTeams < ActiveRecord::Migration[6.0]
def change
create_table :teams do |t|
t.string :name, null: false
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.timestamps
end
end
end
class CreateTeamMembers < ActiveRecord::Migration[6.0]
def change
create_table :team_members do |t|
t.belongs_to :user, null: false, foreign_key: true
t.belongs_to :team, null: false, foreign_key: true
t.integer :role, null: false
t.timestamps
# Enforce one manager per team
t.index [:team_id],
name: :one_manager,
unique: true,
where: "role = 0"
end
end
end
class TeamMember < ApplicationRecord
enum role: { manager: 0, player: 1, fan: 2 }
belongs_to :user
belongs_to :team
end
class Team < ApplicationRecord
has_many :users, through: :team_members
has_many :team_members, dependent: :destroy
has_one :manager, -> { where(role: :manager) }, class_name: "TeamMember"
has_many :players, -> { where(role: :player) }, class_name: "TeamMember"
has_many :fans, -> { where(role: :fan) }, class_name: "TeamMember"
end
class User < ApplicationRecord
has_many :team_memberships, dependent: :destroy, class_name: "TeamMember"
has_many :teams, through: :team_memberships
end
You could even potentially take advantage of single table inheritance to differentiate your users by their role.
This is something you could migrate to later if necessary.
I am using PostgreSQL as my db in rails. I have a User model and a Product model. I am trying to make a Transaction model where I want to reference user twice, once as buyer and once as seller. I generated the models as suggested in this post (answer by toasterlovin)
Write a migration with reference to a model twice
However it gives me an error when I use PostgreSQL (with SQLite it worked fine). I get the following error. What can I do to resolve this?
{StandardError: An error has occurred, this and all later migrations canceled:
PG::UndefinedTable: ERROR: relation "buyers" does not exist
: ALTER TABLE "transactions" ADD CONSTRAINT "fk_rails_0b24a7fcc3"
FOREIGN KEY ("buyer_id")
REFERENCES "buyers" ("id")
C:/Users/Powerhouse/Desktop/CodingDojo/ruby/rails/demo/db/migrate/20161024032156_create_transactions.rb:3:in change'
C:inmigrate'
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: relation "buyers" does not exist
: ALTER TABLE "transactions" ADD CONSTRAINT "fk_rails_0b24a7fcc3"
FOREIGN KEY ("buyer_id")
REFERENCES "buyers" ("id")
C:/Users/Powerhouse/Desktop/CodingDojo/ruby/rails/demo/db/migrate/20161024032156_create_transactions.rb:3:in change'
C:inmigrate'
PG::UndefinedTable: ERROR: relation "buyers" does not exist
C:/Users/Powerhouse/Desktop/CodingDojo/ruby/rails/demo/db/migrate/20161024032156_create_transactions.rb:3:in change'
C:inmigrate'
Tasks: TOP => db:migrate
(See full trace by running task with --trace) }
Models
class User < ActiveRecord::Base
has_many :products
has_many :sales, :class_name => 'Transaction', :foreign_key => 'seller_id'
has_many :purchases, :class_name => 'Transaction', :foreign_key => 'buyer_id
end
Product Model
class Product < ActiveRecord::Base
belongs_to :user
end
Transaction model
class Transaction < ActiveRecord::Base
belongs_to :buyer, :class_name => 'User'
belongs_to :seller, :class_name => 'User'
belongs_to :product
end
Migration files
User
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.timestamps null: false
end
end
end
Product
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.references :user, index: true, foreign_key: true
t.string :name
t.timestamps null: false
end
end
end
Transaction
class CreateTransactions < ActiveRecord::Migration
def change
create_table :transactions do |t|
t.references :buyer, index: true, foreign_key: true
t.references :seller, index: true, foreign_key: true
t.references :product, index: true, foreign_key: true
t.timestamps null: false
end
end
end
This is due to PostgreSql not understanding how to make the custom association foreign keys.
Try changing the migration file to,
class CreateTransactions < ActiveRecord::Migration
def change
create_table :transactions do |t|
t.integer :buyer_id
t.integer :seller_id
t.integer :product_id
t.timestamps null: false
end
add_index(:transactions, :buyer_id)
add_index(:transactions, :seller_id)
add_index(:transactions, :product_id)
add_foreign_key :transactions, :users, column: :buyer_id
add_foreign_key :transactions, :users, column: :seller_id
end
end
It automatically links buyer_id to user, taking the power of
belongs_to :buyer, :class_name => 'User'
The above line makes the buyer_id as foreign key.
You can also try,
class CreateTransactions < ActiveRecord::Migration
def change
create_table :transactions do |t|
t.references :buyer, index: true
t.references :seller, index: true
t.references :product, index: true
t.timestamps null: false
end
add_foreign_key :transactions, :users, column: :buyer_id
add_foreign_key :transactions, :users, column: :seller_id
end
end
Your problem is that when ActiveRecord see this:
t.references :buyer, index: true, foreign_key: true
it tries to create a foreign key inside the database (because foreign_key: true) but the naming conventions tell AR that it should be linking to the buyers table (because you're referencing :buyer).
Just like you specify class_name: in your model:
belongs_to :buyer, :class_name => 'User'
to tell AR not use the conventional Buyer name you can override the convention in your migration by modifying your :foreign_key option:
t.references :buyer, index: true, foreign_key: { to_table: :users }
to explicitly name the table. Similarly for your other reference that doesn't use the conventional naming scheme:
t.references :seller, index: true, foreign_key: { to_table: :users }
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'
I'm trying to solve an issue.
I have this class
class UserWebexClassroom < ActiveRecord::Base
include PGEnum
belongs_to :attendee, class_name: :User, foreign_key: :user_id, inverse_of: :attendee_classrooms
belongs_to :classroom, class_name: :WebexClassroom, primary_key: :external_id, foreign_key: :webex_classroom_external_id, inverse_of: :attendee_classrooms
has_one :webex, through: :classroom
validates_inclusion_of :status, in: statuses.values
end
UserWebexClassroom
=> UserWebexClassroom(id: integer, user_id: integer, webex_classroom_external_id: integer, status: enum)
This is a system to track the presence of some people in some Webex Sessions.
I'm trying to find the most efficient way to find the istances of this class passing the webex and the user.
Is there an active record method to find by two different relations?
I'm not so sure, but I guess you could do:
UserWebexClassroom.joins(:webex).where(webex: {id: webex}, user: user)
This is assuming the name of your webex table is webex, however if it's webexes, you should rather have:
UserWebexClassroom.joins(:webex).where(webexes: {id: webex}, user: user)
First, you need to have associations in every model set up correctly.
class WebexClassroom < ActiveRecord::Base
has_many :attendee_classrooms, class_name: :UserWebexClassroom
# this line is very important, or the query won't be done with correct join conditions
has_one :webex, primary_key: :external_id, foreign_key: :webex_classroom_external_id
end
class Webex < ActiveRecord::Base
belongs_to :webex_classroom, primary_key: :external_id, foreign_key: :webex_classroom_external_id
end
Then you could query to get all user_id for one webex with id: 1
[58] pry(main)> UserWebexClassroom.includes("webex").where("webexes.id = ?", 1).references("webexes")
SQL (0.2ms) SELECT "user_webex_classrooms"."id" AS t0_r0, "user_webex_classrooms"."user_id" AS t0_r1, "user_webex_classrooms"."webex_classroom_external_id" AS t0_r2, "webexes"."id" AS t1_r0, "webexes"."webex_classroom_external_id" AS t1_r1 FROM "user_webex_classrooms" LEFT OUTER JOIN "webex_classrooms" ON "webex_classrooms"."external_id" = "user_webex_classrooms"."webex_classroom_external_id" LEFT OUTER JOIN "webexes" ON "webexes"."webex_classroom_external_id" = "webex_classrooms"."external_id" WHERE (webexes.id = 1)
=> [#<UserWebexClassroom:0x007fdea4643720 id: 1, user_id: 2, webex_classroom_external_id: 1>, #<UserWebexClassroom:0x007fdea4641628 id: 2, user_id: 4, webex_classroom_external_id: 1>]
The answer by #oreoluwa won't work because joins assume you are using the simple association, without through option, without customized foregien keys. If you would like to do with joins, you have to specify the join conditions explicitly.
UPDATE:
Here are my schema and other two models:
# db/schema.rb
ActiveRecord::Schema.define(version: 20160619045919) do
create_table "user_webex_classrooms", force: :cascade do |t|
t.integer "user_id"
t.integer "webex_classroom_external_id"
end
create_table "users", force: :cascade do |t|
end
create_table "webex_classrooms", primary_key: "external_id", force: :cascade do |t|
end
create_table "webexes", force: :cascade do |t|
t.integer "webex_classroom_external_id"
end
end
# app/models/user.rb
class User < ActiveRecord::Base
has_many :attendee_classrooms, class_name: :UserWebexClassroom
end
# app/models/user_webex_classroom.rb
class UserWebexClassroom < ActiveRecord::Base
belongs_to :attendee, class_name: :User, foreign_key: :user_id, inverse_of: :attendee_classrooms
belongs_to :classroom, class_name: :WebexClassroom, primary_key: :external_id, foreign_key: :webex_classroom_external_id, inverse_of: :attendee_classrooms
has_one :webex, through: :classroom
end
I have model User, then
has_many :estates, dependent: :destroy
Model Estate with polymorphic association
has_one :location, as: :locatable, dependent: :destroy
in Location migration, i have
class CreateLocations < ActiveRecord::Migration
def change
create_table :locations do |t|
t.string :address
t.decimal :lat, {:precision=>10, :scale=>6}
t.decimal :lng, {:precision=>10, :scale=>6}
t.references :locatable, polymorphic: true, index: true
How i can filter or find Users from Location.address?
I tried
User.where(estates: { location: { address: "London" } })
and have this error
SQLite3::SQLException: no such column: estates.locatable_id:
SELECT "users".* FROM "users" WHERE "estates"."locatable_id" = '-
--
:address: !ruby/object:Arel::Nodes::BindParam {}'
Maybe something like
User.includes(:estates).where
thanks in advance!
I think what you're looking for is using a join to find users.
User.joins(estates: :location).where(locations: {address: "London"})
This tells your sql call to match users to estates and estates to locations and to select only the users where the location is "London".