I'm currently working on a small school project that utilizes Ruby on Rails and I'm having some trouble getting my self-referential associations working correctly.
Context
The intended functionality of my web app is for users to post houses/apartments for other users to search through and rent. Since I'm having issues with a specific association, I'm working with a completely stripped down version that only has two models, User and Lease.
What I'm Trying to Accomplish
Ideally, when a person first registers on the site, a User object is created to hold their information such as email and password. A User can then either post a listing or search through listings.
Once a post has been created and another user decides to rent the posted house, a Lease object is created, which holds the ID of the posting User as well as the ID of the renting user, aliased as "landlord_id" and "tenant_id" respectively.
A User should now be identified as either a User, Landlord or a Tenant (or both Landlord and Tenant) based on whether there are any Lease objects with their ID as either a Landlord or a Tenant. This identification will be used to determine whether the User can access other areas of the site.
userFoo.leases
This should give me a list of all Lease objects with which the User's ID is associated, regardless of whether it's as a Landlord or Tenant.
userFoo.tenants
This should give me a list of any User object whose ID is associated with the ID of userFoo as a Tenant through Lease, and the inverse if I ask for landlords.
The Code
User Class
class User < ApplicationRecord
has_many :tenants, class_name: "Lease", foreign_key: "landlord_id"
has_many :landlords, class_name: "Lease", foreign_key: "tenant_id"
end
Lease Class
class Lease < ApplicationRecord
belongs_to :landlord, class_name: "User"
belongs_to :tenant, class_name: "User"
end
Users Table Migration
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
t.string :email
t.string :password_digest
t.timestamps
end
end
end
Leases Table Migration
class CreateLeases < ActiveRecord::Migration[6.0]
def change
create_table :leases do |t|
t.references :landlord, null: false, foreign_key: {to_table: :users}
t.references :tenant, null: false, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Database Schema
ActiveRecord::Schema.define(version: 2020_10_18_005954) do
create_table "leases", force: :cascade do |t|
t.integer "landlord_id", null: false
t.integer "tenant_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["landlord_id"], name: "index_leases_on_landlord_id"
t.index ["tenant_id"], name: "index_leases_on_tenant_id"
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "leases", "users", column: "landlord_id"
add_foreign_key "leases", "users", column: "tenant_id"
end
What's Wrong?
userFoo.leases
Normally a User would have_many leases by having their ID associated with a lease as "user_id." However, since I'm using "tenant_id" and "landlord_id", this command fails because it can't find "user_id" in the Leases table.
userFoo.tenants
This command gives me a list of all Lease objects where userFoo's ID is associated as "landlord_id" instead of all User objects associated with userFoo's ID as tenants. To retrieve a tenant as is, I have to use the command:
userFoo.tenants.first.tenant
Conclusion I am having a bit of a hard time understanding these deeper, more complex associations, and I've spent some time trying to find a detailed reference on has_many that covers all the arguments, but all I can really find are small blog posts that reference the "Employees" and "Managers" example on guides.rubyonrails.com . I think one problem is that I'm not sure I'm correctly reflecting my model associations in my table schema.
I'm more than happy to teach myself if someone can point me in the right direction. I'm also open to alternative solutions but only if I can't get the functionality I want out of this setup, because my instructor specifically asked me to try it this way
Thanks in advance for any help! It's much appreciated.
as per your requirement you can try like this:
# app/models/user.rb
class User < ApplicationRecord
has_many :owned_properties, class_name: "Property", foreign_key: "landlord_id"
has_many :rented_properties, class_name: "Property", foreign_key: "tenant_id"
end
Here I have declared two associations with same table but different foreign keys.
# app/models/property.rb
class Property < ApplicationRecord
belongs_to :landlord, class_name: "User"
belongs_to :tenant, class_name: "User"
end
Here I have taken one table by using this user can post one property where landlord is the owner of a house and later you can add tenant who is taking rent to one property.
# db/migrations/20201018054951_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false, index: true
t.string :password_digest, null: false
t.timestamps
end
end
end
Above is your users table migration.
# db/migrations/20201018055351_create_properties.rb
class CreateProperties < ActiveRecord::Migration[6.0]
def change
create_table :properties do |t|
t.references :landlord, foreign_key: {to_table: :users}, null: false
t.references :tenant, foreign_key: {to_table: :users}
t.timestamps
end
end
end
Above is your properties table migration.
# db/schema.rb
ActiveRecord::Schema.define(version: 2020_10_18_055351) do
create_table "properties", force: :cascade do |t|
t.bigint "landlord_id", null: false
t.bigint "tenant_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["landlord_id"], name: "index_properties_on_landlord_id"
t.index ["tenant_id"], name: "index_properties_on_tenant_id"
end
create_table "users", force: :cascade do |t|
t.string "name", null: false
t.string "email", null: false
t.string "password_digest", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email"
end
add_foreign_key "properties", "users", column: "landlord_id"
add_foreign_key "properties", "users", column: "tenant_id"
end
If you want to fetch all the owned properties of a user, use user.owned_properties
.
If you want to fetch all rented properties of a user, use user.rented_properties
.
^^ Here both the cases you'll get objects of Property
class.
If you want to get landlord of a property, use property.landlord
.
If you want to get tenant of a property, use property.tenant
.
^^ Here both the cases you'll get objects of User
class.
If you want you can add other attributes like: name
, price
, etc to properties table.
I think, this will help you. Thanks :) Happy Coding :)