【问题标题】:Rails: Self-referential associations have my head spinningRails:自引用关联让我头晕目眩
【发布时间】:2020-10-18 03:01:51
【问题描述】:

我目前正在开展一个使用 Ruby on Rails 的小型学校项目,但在让我的自引用关联正常工作时遇到了一些问题。

上下文

我的网络应用程序的预期功能是让用户发布房屋/公寓供其他用户搜索和出租。由于我遇到了特定关联的问题,我正在使用一个完全精简的版本,它只有两个模型,用户和租赁。

我正在努力实现的目标

理想情况下,当一个人第一次在网站上注册时,会创建一个 User 对象来保存他们的信息,例如电子邮件和密码。然后,用户可以发布列表或搜索列表。

一旦创建了帖子并且另一个用户决定租用发布的房屋,就会创建一个 Lease 对象,其中包含发布用户的 ID 以及租赁用户的 ID,别名为“landlord_id”和“租户id”。

现在应根据是否存在任何 ID 为业主或租户的租赁对象,将用户标识为用户、业主或租户(或业主和租户)。此标识将用于确定用户是否可以访问网站的其他区域。

userFoo.leases

这应该给我一个与用户 ID 相关联的所有租赁对象的列表,无论它是作为房东还是租户。

userFoo.tenants

这应该给我一个任何用户对象的列表,其 ID 与作为租户的 userFoo 的 ID 通过租赁相关联,如果我要求房东,则相反。

守则

用户类

class User < ApplicationRecord
  has_many :tenants, class_name: "Lease", foreign_key: "landlord_id"
  has_many :landlords, class_name: "Lease", foreign_key: "tenant_id"
end

租赁类

class Lease < ApplicationRecord
  belongs_to :landlord, class_name: "User"
  belongs_to :tenant, class_name: "User"
end

用户表迁移

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

租赁表迁移

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

数据库架构

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

怎么了?

userFoo.leases

通常情况下,用户将通过将其 ID 与租约关联为“user_id”来拥有_many 个租约。但是,由于我使用的是“tenant_id”和“landlord_id”,因此该命令会失败,因为它在 Leases 表中找不到“user_id”。

userFoo.tenants

此命令为我提供了一个列表,其中列出了 userFoo 的 ID 关联为“landlord_id”的所有 Lease 对象,而不是与 userFoo 的 ID 关联的所有用户对象作为租户。要按原样检索租户,我必须使用以下命令:

userFoo.tenants.first.tenant

结论 我有点难以理解这些更深入、更复杂的关联,我花了一些时间试图找到关于 has_many 的详细参考,涵盖所有论点,但我真正能找到的只是参考的小型博客文章guides.rubyonrails.com 上的“员工”和“经理”示例。我认为一个问题是我不确定我是否在表架构中正确反映了我的模型关联。

如果有人能指出我正确的方向,我非常乐意自学。我也对其他解决方案持开放态度,但前提是我无法从此设置中获得我想要的功能,因为我的导师特别要求我以这种方式尝试

提前感谢您的帮助!非常感谢。

【问题讨论】:

标签: ruby-on-rails associations self-join


【解决方案1】:

根据您的要求,您可以这样尝试:

# 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

这里我声明了两个具有同一个表但外键不同的关联。

# app/models/property.rb

class Property < ApplicationRecord
  belongs_to :landlord, class_name: "User"
  belongs_to :tenant, class_name: "User"
end

这里我已经使用了一张桌子,这个用户可以发布一个房产,其中房东是房子的所有者,然后你可以添加正在收租的租户到一个房产。

# 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

以上是您的用户表迁移。

# 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

以上是您的属性表迁移。

# 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

如果要获取用户的所有拥有属性,请使用user.owned_properties

如果您想获取用户的所有租用属性,请使用user.rented_properties

^^ 在这两种情况下,您都会得到Property 类的对象。

如果您想获得房产的房东,请使用property.landlord

如果您想获得物业的租户,请使用property.tenant

^^ 在这两种情况下,您都将获得User 类的对象。

如果您愿意,可以在属性表中添加其他属性,例如:nameprice 等。

我想,这会对你有所帮助。谢谢 :) 快乐编码 :)

【讨论】:

  • 非常感谢!我看到我犯的错误是我试图在 Rails 文档中应用单向所有权示例,其中 Employee1 可以属于 Employee2 并使 Employee2 成为经理,并使其双向工作。在我的应用程序中,用户不应该属于其他用户,因为实际上是财产以及拥有或租用它的人将用户的角色定义为房东或租户。因此,即使我的代码有效,您的示例也更准确地代表了现实世界的关联。再次感谢您的帮助!
  • 这也允许我添加 "has_many :tenants, through: :owned_properties" 和相反的用户模型,这样我就可以做 "userFoo.tenants" 并且它有效!
猜你喜欢
  • 1970-01-01
  • 2014-11-29
  • 2014-05-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多