重学rails: association 关联 basic

表与表关系 basic
更新于: 2024-10-20 23:16:47

关联

  • 了解各种类型的关联。
  • 声明 Active Record 模型之间的关联。
  • 为您的模型选择正确的关联类型。
  • 使用单表继承。
  • 设置和使用委托类型。

过与 VS 现在

操作没有关联有关联
创建 Migratoin
class CreateAuthors < ActiveRecord::Migration[8.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end

    create_table :books do |t|
      t.references :author
      t.datetime :published_at
      t.timestamps
    end
  end
end
# 在原来的基础上,使用此代码: 
rails generate migration AddAuthorToBooks author:references

此次迁移将author_id在数据库中添加列并设置外键关系,确保您的模型和数据库保持同步。

要了解有关不同类型关联的更多信息,您可以阅读本指南的下一部分。接下来,您将找到一些使用关联的技巧和窍门。最后,还有关于 Rails 中关联方法和选项的完整参考。
Model
class Author < ApplicationRecord
# 没有操作
end

class Book < ApplicationRecord
# 没有操作
end
class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end

class Book < ApplicationRecord
  belongs_to :author
end
删除操作
@book = Book.create(author_id: @author.id, published_at: Time.now)
@books = Book.where(author_id: @author.id)

# 删除 books
@books.each do |book|
  book.destroy
end
# 删除 author(与此书有关的)
@author.destroy
@book = @author.books.create(published_at: Time.now)
@author.destroy

我的准备

rails g model Author name:string
rails g model Book title:string published_at:date author:references

六种关联

Rails 支持六种类型的关联,每种类型都有特定的用例。

belongs_to

  • 声明模型的每个实例都“属于”另一个模型的一个实例
  • 例如,如果您的应用程序包含作者和书籍,并且每本书都可以分配给一个作者
class Book < ApplicationRecord
  belongs_to :author
end

optional: true 的含义

这确保了即使 optional: true允许author_id为 NULL,当它不为 NULL 时,它仍然必须引用作者表中的有效记录。

class Book < ApplicationRecord
  belongs_to :author, optional: true
end

添加的方法belongs_to

class Book < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :books
  validates :name, presence: true
end

该模型的实例Book将具有以下方法:

  • author
  • author=
  • build_author
  • create_author
  • create_author!
  • reload_author
  • reset_author
  • author_changed?
  • author_previously_changed?
初始化新的has_one或belongs_to关联时,必须使用build_前缀来构建关联,而不是 association.build用于has_many或 has_and_belongs_to_many关联的方法。要创建关联,请使用create_前缀。

对上面那句话的理解:参考这里

aric: 其实还不是特别理解这一句话。

在 Rails 中,当处理 `has_one` 或 `belongs_to` 关联时,使用 `build_` 前缀来构建关联对象,意味着创建一个新的、尚未保存到数据库的关联对象。例如,如果一个 `User` 模型 `has_one :profile`,你可以使用 `build_profile` 来构建一个 `Profile` 对象,但这个对象还没有保存到数据库中。

### `build_` 和 `association.build` 的区别
- `build_` 前缀(例如 `build_profile`)主要用于 `has_one` 和 `belongs_to` 关联。
- 对于 `has_many` 或 `has_and_belongs_to_many` 关联,你会使用 `association.build` 方法。例如,`user.posts.build` 会为 `has_many :posts` 关联创建一个新帖子,但同样不会保存到数据库。

#### 示例:
```ruby
# has_one or belongs_to
user = User.find(1)
profile = user.build_profile(name: "John Doe")  # 构建 profile,但不保存到数据库

# has_many
post = user.posts.build(title: "New Post")      # 构建 post,但不保存到数据库
```

### 使用 `create_` 前缀创建关联
当你希望在创建关联的同时立即保存到数据库时,可以使用 `create_` 前缀。
- 对于 `has_one` 或 `belongs_to` 关联,使用 `create_profile`,它不仅会构建 `Profile`,还会将其保存。
- 对于 `has_many` 关联,使用 `association.create`,如 `user.posts.create`,同样会立即创建并保存关联对象。

#### 示例:
```ruby
# has_one or belongs_to
profile = user.create_profile(name: "John Doe")  # 创建并保存 profile 到数据库

# has_many
post = user.posts.create(title: "New Post")      # 创建并保存 post 到数据库
```

### 总结:
- `build_` 前缀用于 `has_one` 和 `belongs_to` 关联,只构建但不保存对象。
- `association.build` 用于 `has_many` 和 `has_and_belongs_to_many` 关联,也只构建对象。
- `create_` 前缀用于所有关联类型,同时构建并保存对象到数据库。

检查关联变更

不要model.association_changed?与 混淆model.association.changed?。前者检查关联是否已被新记录替换,而后者跟踪关联属性的变化。

@book.author # => #<Author author_number: 123, author_name: "John Doe">
@book.author_changed? # => false
@book.author_previously_changed? # => false

@book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith">
@book.author_changed? # => true

@book.save!
@book.author_changed? # => false
@book.author_previously_changed? # => true

创建实例

  • 创建 book: 单数 create_author/build_author
  • 创建 author: author.books.create 添加book到 author 管理;其实就是 push/add
错误信息
# 使用 build_author/create_author
book = Book.create(title: 'Harry')
book.build_author(name: 'aric')
book.save

# 使用 create 添加 books
author = Author.find(1)

# 错误信息
author = Author.new
author.save
author.errors.full_messages

has_one: 一对一

关联has_one表示另一个模型引用了此模型。可以通过此关联获取该模型。

例如,如果应用程序中的每个供应商只有一个帐户,则可以像这样声明供应商模型:

belongs_to: 都含有 xx_id 关联的

class Supplier < ApplicationRecord
  has_one :account
end

class Account < ApplicationRecord
  validates :terms, presence: true
  belongs_to :supplier
end

delete vs destroy

delete: 只会删除,但会跳过 callback

destroy: 删除,并执行正常的 callback

has_many :through 多对多

关联has_many :through通常用于与另一个模型建立多对多关系。此关联表示声明模型可以通过第三个模型与另一个模型的零个或多个实例匹配

例如,假设有一家医疗机构,患者可以预约看医生。相关关联声明可能如下所示:

对象关系图
class Physician < ApplicationRecord
  has_many :appointments
  has_many :patients, through: :appointments
end

class Appointment < ApplicationRecord
  belongs_to :physician
  belongs_to :patient
end

class Patient < ApplicationRecord
  has_many :appointments
  has_many :physicians, through: :appointments
end

回调

关联回调与普通回调类似,但它们是由与 Active Record 对象关联的集合的生命周期中的事件触发的。有四种可用的关联回调:

  • before_add
  • after_add
  • before_remove
  • after_remove