侧边栏壁纸
  • 累计撰写 207 篇文章
  • 累计创建 58 个标签
  • 累计收到 5 条评论

Rails 学习笔记 05: 关联

barwe
2021-09-17 / 0 评论 / 0 点赞 / 369 阅读 / 8,727 字
温馨提示:
本文最后更新于 2022-07-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Active Record 关联

关联的类型

Rails 有六种关联:

  • belongs_to
  • has_one
  • has_many
  • has_many :through
  • has_one :through
  • has_and_belongs_to_many

belongs_to

声明一对一的关系,当前模型的实例属于另一个模型的实例,例如订单属于顾客可定义为:

class Order < ActiveRecord::Base
    belongs_to :customer
end

一个订单只会属于一个顾客,因此belongs_to声明中只能用单数形式
使用了belongs_to声明的 orders 数据表中会出现一个:customer_id字段。

has_one

声明一对一的关系,通常与belongs_to搭配使用。

class Order < ActiveRecord::Base
    belongs_to :customer
end

class Customer < ActiveRecord::Base
    has_one :order
end

一个顾客只拥有一个订单,customers 数据表中不会多出额外的字段。

一对一关系的两个模型在逻辑上存在着主从关系:"主"拥有(has_one)"从","从"属于(belongs_to)"主"。

has_many

声明一对多的关系,通常与belongs_to搭配使用。
表示当前模型的实例拥有零个或者多个其他模型的实例。

class Order < ActiveRecord::Base
    belongs_to :customer
end

class Customer < ActiveRecord::Base
    has_many :orders
end

has_one不同,使用has_many声明时要使用复数形式

has_many :through

借助中间模型,建立两个模型之间多对多的关联。

例如

class Physician < ActiveRecord::Base
    has_many :appointments
    has_many :patients, through: :appointments
end

class Appointment < ActiveRecord::Base
    belongs_to :physician
    belongs_to :patient
end

class Patient < ActiveRecord::Base
    has_many :appointments
    has_many :physicians, through: :appointments
end

又例如文档-章节-段落之间的关联:

class Document < ActiveRecord::Base
    has_many :sections
    has_many :paragraphs, through: :sections
end

class Section < ActiveRecord::Base
    belongs_to :document
    has_many :paragraphs
end

class Paragraph < ActiveRecord::Base
    belongs_to :section
end

通过document.paragraphs可直接访问文档的所有段落。

has_and_belongs_to_many

直接建立两个模型之间的多对多关系,例如装配体-零件:

class Assembly < ActiveRecord::Base
    has_and_belongs_to_many :parts
end

class Part < ActiveRecord::Base
    has_and_belongs_to_many :assemblies
end

使用 belongs_to 还是 has_one

"主"拥有(has_one)"从","从"属于(belongs_to)"主"。
外键会放在使用belongs_to声明的模型中。

使用 has_many :through 还是 has_and_belongs_to_many

不管哪种方式,数据库都会建立第三个中间数据表,只是has_many :through为这个中间数据表建立了模型,而has_and_belongs_to_many不会。

使用哪一种方式取决于这个中间数据表是否有必要作为独立实体存在,使用has_and_belongs_to_many关联时需要在数据库中手动建立中间数据表。

多态关联

当前模型同时属于其它多个模型,可以为每个关联的模型声明一个belongs_to,也可以使用多态关联

class Picture < ActiveRecord::Base
    belongs_to :imageable, ploymorphic: true
end

class Employee < ActiveRecord::Base
    has_many :pictures, as: :imageable
end

class Product < ActiveRecord::Base
    has_many :pictures, as: :imageable
end

自连接

模型和自己建立关联。例如在雇员信息表中,每个雇员可能属于上级,也可能拥有下级:

class Employee < ActiveRecord::Base
    has_many :subordinates, 
    	class_name: "Employee", 
    	foreign_key: "manager_id"
    belongs_to :manager,
    	class_name: "Employee"
end

如此,employee.subordinatesemloyee.manager可用。

小技巧和注意事项

缓存控制

通过关联取回的数据都会使用缓存,例如

customer.orders # retrieve orders from the databasecustomer.orders # retrieve orders from the cachedcustomer.orders.size # use the cached

调用关联方法时传入true可重载缓存:

customer.orders.empty? # from the cachecustomer.orders(true).empty? # from the database

避免命名冲突

关联的名字要避开ActiveRecord::Base自动添加的实例方法,例如attributes, connection等。

更新模式

模式数据表,Rails中建立的关联并不会改变数据库的结构,因此与关联相关的数据表结构需要我们手动修改。不同关联对应的修改可能不同,例如外键关联需要在声明belongs_to的模型对应的数据表中添加外键字段;声明has_and_belongs_to_many的两个模型需要建立额外的中间数据表(连接数据表)。

has_and_belongs_to_many关联下,最好通过:join_table选项指定连接数据表的名称。

控制关联的作用域

发生关联的模型必须位于同一个作用域(module)内,例如

module MyApplication    module Business        class Supplier < ActiveRecord::Base            has_one :account        end                class Account < ActiveRecord::Base            belongs_to :supplier        end    endend

Supplier模型和Account模型同属于一个作用域(MyApplication::Business)内。

关联处于不同作用域的模型需要显式指定模型所属的作用域,例如

module MyApplication    module Business        class Supplier < ActiveRecord::Base            has_one :account, class_name: "MyApplication::Billing::Account"        end    end        module Billing        class Account < ActiveRecord::Base            belongs_to :supplier, class_name: "MyApplication::Business::Supplier"        end    endend

使用:class_name选项指定关联的模型的完整名称。

双向关联

对某个数据对象的直接引用和作为关联对象的间接引用,逻辑上是一个东西,但是在内存中是两块区域,修改一个并不会立即同步到另一个,例如在“一个customer拥有多个orders,一个order只属于一个customer”的关联中

customer = Customer.firstorder = customer.orders.first

从数据库取回数据时,customer.nameorder.customer.name是一致的。如果此时修改customer.nameorder.customer.name其中一个的值,它们就不相等了。这是因为它们两个虽然逻辑上表示一个东西,但是在内存中却属于两个区域。

使用:inverse_of同步关联两端的操作。

class Customer < ActiveRecord::Base    has_many :orders, inverse_of: :customerendclass Order < ActiveRecord::Base    belongs_to :customer, inverse_of: :ordersend

:inverse_of的限制:

  • 不能和:through一起使用:不能跨模型同步,这是个很复杂的事情
  • 不能和:polymorphic一起使用:多态关联涉及的模型是复杂的、不确定的
  • 不能和:as选项一起使用
  • 在 belongs_to 关联中,会忽略 has_many 关联的 inverse_of 选项:没看懂介个

关联详解

belongs_to 关联详解

belongs_to 关联声明了当前模型与关联的模型是一对一的关系,即当前模型中包含外键。

belongs_to 关联添加的方法

class Order < ActiveRecord::Base    belongs_to :customerend

order会自动获得五个相关的方法:

  • order.customer(force_reload = false)返回关联对象或者nil
  • order.customer=(another_customer)
  • order.build_customer(attributes = {})构建关联对象,不存入数据库
  • order.create_customer(attributes = {})创建关联对象,进行验证,存入数据库
  • order.create_customer!(attributes = {})验证失败时抛出异常

belongs_to 方法的选项

:auto_save

保存父对象时自动保存所有子对象。

:class_name

无法从关联的名字获取关联模型时,使用:class_name选项指定关联模型的位置。例如

  • 关联模型与当前模型属于不同的作用域;
  • 关联模型的类名与关联的名字不一致;
  • ……
:counter_cache

缓存关联对象拥有当前模型实例的数目,在顾客-订单模型中

class Customer < ActiveRecord::Base    has_many :ordersendclass Order < ActiveRecord::Base    belongs_to :customer, counter_cache: trueend

然后就可以提升访问customer.orders.size的效率。

但是,模型的定义不会影响模式的表达,因此我们还需要在 customers 数据表中添加 orders_count 字段。该名称为默认名称,如果使用其他字段名,只需要将该字段名显式指定给:counter_cache选项即可,例如

class Order < ActiveRecord::Base  belongs_to :customer, counter_cache: :count_of_ordersend
:dependent

当 belongs_to 和 has_one 搭配使用时,表示两个模型是一对一关系。设置 dependent 选项可以在删除一个对象时,也删除关联对象。注意不要在 belongs_to - has_many 关联中使用这个选项。

:foreign_key

外键字段名,默认是关联名加_id后缀。

class Order < ActiveRecord::Base    belongs_to :customer, class_name: "Patron", foreign_key: "patron_id"end

模型是模型,模式是模式。外键字段需要在迁移中手动添加。

:inverse_of

双向关联。

class Customer < ActiveRecord::Base    has_many _orders, inverse_of: trueendclass Order < ActiveRecord::Base    belongs_to :customer, inverse_of: trueend
:polymorphic
:touch

意为“摸一下”。当保存或者删除当前对象时,更新关联对象的updated_at字段。

class Order < ActiveRecord::Base    belongs_to :customer, touch: trueend

或者显示指定更新哪个时间戳字段:

class Order < ActiveRecord::Base    belongs_to :customer, touch: orders_updated_atend
:validate

保存对象时,是否自动验证关联对象,默认为false

belongs_to 的作用域

略。

检查关联对象是否存在

检查订单的关联顾客是否存在:

order.customer.nil?

has_one 关联详解

has_one 关联添加的方法

class Supplier < ActiveRecord::Base    has_one :accountend

实例 supplier 获得的方法有:

  • supplier.account(force_reload = false) -> Account | nil
  • supplier.account=another_account
  • supplier.build_account(attributes = {}) -> Account
  • supplier.create_account(attributes = {}) -> Account
  • supplier.create_account!(attributes = {}) -> Account

has_one 方法的选项

很多与 belongs_to 的选项相似。

:as

多态关联中使用。

:autosave
:class_name
:dependent

在供应商-账户模型中,设置供应商 has_one 的dependent选项,以表明在处理供应商对象时如何处理账户对象:

  • :destroy销毁该供应商拥有的账户对象和数据库记录
  • :delete直接删除供应商拥有的账户的数据库记录,因此不会触发关联对象的回调
  • :nullify将账户对应的数据库记录的外键改为 NULL,不会触发回调
  • :restrict_with_exception存在关联账户时抛出异常
  • :restrict_with_error存在关联账户时发生错误

如果关联模型外键有 NOT NULL 约束,则:nillify失败,关联销毁失败。

:foreign_key
:inverse_of
:primary_key
:source
:source_type
:through
:validate

has_one 的作用域

检查关联的对象是否存在

supplier.account.nil?

什么时候保存对象

将账户对象赋值给供应商对象的关联时,会自动保存供应商对象,因为账户对象需要供应商作为外键,而稳定的外键需要将供应商保存到数据库才能生成。如果替换供应商的关联账户,所有与替换相关的对象都会保存,因为外键需要更新。

如果供应商对象没有被保存,与之关联的账户对象也不会被保存。

has_many 关联详解

当前对象拥有多个关联对象,外键保存在关联对象中。

has_many 关联添加的方法

class Customer < ActiveRecord::Base    has_many :ordersend

customer 对象自动获得了 16 个相关的方法:

  1. .orders(force_reload = false) -> Order[]
  2. .orders << order1, ...将添加的 orders 的外键设为 customer 的主键
  3. .orders.delete(order1, ...)删除多个 orders,将它们的外键设为 NULL
  4. .orders.destroy(order1, ...)删除多个 orders 对象和数据库记录
  5. .orders = another_orders
  6. .order_ids关联的 orders 的 ID 列表
  7. .order_ids = ids
  8. .orders.clear
  9. .orders.empty?
  10. orders.size
  11. orders.find(...)
  12. orders.where(...)惰性加载
  13. orders.exist?(...)根据条件检查关联对象数组中是否存在符合条件的对象
  14. orders.build(attributes = {}, ...)初始化关联对象,但不会自动保存
  15. orders.create(attributes = {}, ...) 初始化关联对象,会自动保存对象
  16. orders.create!(attributes = {}, ...)

has_many 方法的选项

  • :as多态关联
  • :autosave
  • :class_name
  • :dependent
    • :destroy
    • :delete_all从数据库中直接删除所有关联对象的记录,不回调
    • :nullify设置外键为 NULL,不回调
    • :restrict_with_exception
    • :restrict_with_error
  • :foreign_key
  • :inverse_of
  • :primary_key关联对象的外键应该保存当前对象的哪一个字段,默认是id字段
  • :source搭配 through 使用
  • :source_type搭配 through 和多态关联使用
  • :through
  • :validate

has_many 的作用域

高级用法,自定义 has_many 查询关联对象的方式,使用作用域代码块指定选项。

where

关联对象必须满足的条件

class Customer < ActiveRecord::Base    # customer.confirmed_orders返回所有经过确认的订单    has_many :confirmed_orders, -> { where confirmed: true }, class_name: "Order"end
extending
group

结合 through 进行分组查询

class Customer < ActiveRecord::Base    # customer.line_items返回与customer关联的所有orders关联的商品???    has_many line_items, -> { group 'orders.id' }, through: :ordersend
inlcudes

按需加载间接关联

class Customer < ActiveRecord::Base    # customer.orders.line_items可访问了    has_many :orders, -> { inlcudes :line_items }end
limit

限制通过关联获取的对象的数量

class Customer < ActiveRecord::Base    has_many :recent_orders, -> { order('order_date desc').limit(100) }, class_name: "Order"
offset

通过关联获取对象时跳过前面一部分

class Customer < ActiveRecord::Base    has_many :orders, -> { offset 10 }end
order

关联对象的顺序

class Customer < ActiveRecord::Base    has_many :orders, -> { order "date_confirmed DESC" }end
readonly

关联对象只读。

class Customer < ActiveRecord::Base    has_many :orders, -> { readonly }end
select

选择关联对象的哪些字段,默认全部,注意选中关联对象的主键和外键。

distinct

搭配 through 使用,确保间接关联的对象没有重复值。

class Person < ActiveRecord::Base    has_many :readings    has_many :posts, -> { distinct }, through: :readings

什么时候保存对象

……

0

评论区