在开发中常常会涉及到多个模型进行关联的操作.Rails支持六种关联:
belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many
为了后续的内容分析,事先创建以下模型
rails new shop-app # 创建项目
rails g model product # 产品模型
rails g model category # 产品分类模型
rails g model product_detail # 商品详细参数,商品信息的补充
rails g model teacher # 老师信息
rails g model student # 学生信息
belongs_to关联
belongs_to关联声明两个模型的一对一关联,声明所在的模型属于另一个模型.如果商品信息和商品分类信息的关系,商品属于商品分类.
class Product < ApplicationRecord
belongs_to :category
end
模型迁移代码做修改
class CreateProducts < ActiveRecord::Migration[5.1]
def change
create_table :products do |t|
t.string :name
t.belongs_to :category, index: true
t.string :description
t.decimal :price, :precision => 18, :scale => 2
t.timestamps
end
end
end
使用数据库工具打开刚才创建的表可以发现在products表中会生成一个字段category_id,并且做了外键关联,关联categories表.
rails c # 打开rails终端,创建一些数据做测试
c = Category.create(name: '3C') # 创建分类
p = Product.new
p.category_id = c.id
p.name = 'iphone X'
p.description = '最新款的iphone'
p.save
# 通过上述代码的操作可以看到一条3C分类下的产品被保存如数据库
first_p = Product.first
first_p.category # 获取分类信息
first_p.category.name # 获取分类的名字
# 通过设置belongs_to关联之后 可以直接在查询的结果上调用关联模型的信息
# 在设置belongs_to之后 rails为模型自动添加了一些方法
# association
# association=(associate)
# build_association(attributes = {})
# create_association(attributes = {})
# create_association!(attributes = {})
# 这五个方法中的 association 要替换成传给 belongs_to 方法的第一个参数.
# Product模型的每一个实例都获得了如下方法
# @product = Product.first
# @product.category # 返回关联的对象,如果不存在返回nil
# @product.category= # 赋值关联对象
# @product.build_category= # 根据传递的参数设置一个新的关联对象,不存数据库
# @product.create_category= # 创建新的关联对象,保存.
# @product.create_category!= # 创建新的关联对象,保存.报错是抛出ActiveRecord::RecordInvalid 异常
在model中设置belongs_to的时候,可以设置以下可选参数
:autosave # 自动保存,设为 true,保存对象时,会自动所有关联对象
:class_name # 指定关联模型的名字(关联模型的名字无法从关联名称获取)
:counter_cache # 计数缓存,需要结合has_many使用, 如统计某一分类的产品数量
:dependent # 对象销毁后如何处理关联信息
:destroy:也销毁关联的对象
:delete_all:直接从数据库中删除关联的对象(不执行回调)
:nullify:把外键设为 NULL(不执行回调)
:restrict_with_exception:如果有关联的记录,抛出异常
:restrict_with_error:如果有关联的对象,为属主添加一个错误
注意:使用has_many关联后不要使用此属性
:foreign_key # 设置使用的外键名
:primary_key # 设置主键名(不使用默认的id作为主键)
:inverse_of # 指定 belongs_to 关联另一端的 has_many 和 has_one 关联名
:polymorphic # 选项为 true 时,表明这是个多态关联
:touch # 设为true,保存或销毁对象时,关联对象的updated_at会设置成当前时间
:validate # 是否验证关联对象
:optional # optional 选项设为 true,不会验证关联对象是否存在
has_one关联
has_one关联建立两个模型的一对一关联,声明当前模型包含一个另一个模型.如商品信息和商品详情内容(product_detail作为商品product内容的补充信息),一个商品信息包含一个详情信息.
class Product < ApplicationRecord
belongs_to :category
has_one :product_detail
end
class ProductDetail < ApplicationRecord
end
模型迁移代码做修改
class CreateProductDetails < ActiveRecord::Migration[5.1]
def change
create_table :product_details do |t|
t.belongs_to, :product, index: true
t.text, :content
t.text, :specification
t.timestamps
end
end
end
使用has_one的时候,迁移代码中需要在关联模型的迁移中做修改
@product = Product.first
@product.create_product_detail!(content: '商品详情') # 创建详情信息
# 创建has_one 关联后,声明所在的类自动获得了五个关联相关的方法
# association
# association=(associate)
# build_association(attributes = {})
# create_association(attributes = {})
# create_association!(attributes = {})
# 这五个方法中的 association 要替换成传给has_one方法的第一个参数.用法不再详述,和上面的belongs_to类似.
在model中设置has_one的时候,可以设置以下可选参数
:as # 选项表明这是多态关联
:autosave # 同上
:class_name # 同上
:dependent # 同上
:foreign_key # 同上
:inverse_of # 同上
:primary_key # 同上
:source # 指定has_one :through关联的源关联名称
:source_type # 指定通过多态关联处理 has_one :through 关联的源关联类型
:through # 指定用于执行查询的联结模型
:validate # 同上
has_one: through关联
has_one :through 关联建立两个模型之间的一对一关系.表示通过一个第三方模型建立一个一对一的关联.如:每一个商品有详情信息,详情有主图信息,可以为此建立模型
class Product < ApplicationRecord
belongs_to :category
has_one :product_detail
has_one :product_cover_img, through: :product_detail
end
class ProductDetail < ApplicationRecord
belongs_to :product
has_one :product_cover_img
end
class ProductCoverImg < ApplicationRecord
belongs_to :product
end
模型迁移代码做修改
class CreateProductDetails < ActiveRecord::Migration[5.1]
def change
create_table :product_details do |t|
t.belongs_to :product
t.text :content
t.text :specification
t.timestamps
end
end
end
class CreateProductCoverImgs < ActiveRecord::Migration[5.1]
def change
create_table :product_cover_imgs do |t|
t.belongs_to :product_detail, index: true
t.string :path
t.string :name
t.string :title
t.timestamps
end
end
end
访问相关属性
@product = Product.first # 单条商品信息
@product.product_detail # 商品详情
@product.product_detail.product_cover_img # 商品主图信息
# 创建一条记录,比较长...
@product.product_detail.create_product_cover_img(name: '主图', path: '/uploads/1.png')
has_many 关联
has_many 关联建立两个模型之间的一对多关联,在belongs_to的另一端使用,has_many表示实例包含有零个或者多个其他模型.比如起初创建的分类模型可以包含很多产品数据.
修改相关的模型
class Category < ApplicationRecord
has_many :products # 此处为复数形式
end
因为已经在product的模型迁移部分做过设置,此处不需要再做操作
访问相关属性和方法
@category = Category.first
@products = @category.products # 获取分类下的所有产品信息
@products.size # 获取产品的数量
@category.products.create({name: '华为mate10'}) # 分类下创建产品
# 创建has_many关联之后,将获得以下方法
# collection # 返回所有关联对象,不存在返回空数组
# collection<<(object,...) # 想数组中添加对象,如:向分类下添加产品信息,修改商品外键
# collection.delete(object, ) # 从关联对象中删除数据,设置外键为null,如果设置了dependent: :destroy或删除对象
# collection.destroy(object, ...) # 从关联的数组中删除对象
# collection=(objects) # 设置数组只包含指定对象
# collection_singular_ids # 或有所有的id数组
# collection_singular_ids=(ids) # 设置关联数组只包含指定id的子集
# collection.clear # 根据dependent设置的依赖删除对象,不设置默认为设置外键为null
# collection.empty? # 判断是否为空,返回bool值
# collection.size # 关联对象的数量
# collection.find(...) # 查找集合中的对象 根据id
# collection.where(...) # 根据条件查找集合中的对象
# collection.exists?(...) # 判断结合中是否存在符合条件的对象
# collection.build(attributes = {}, ...) # 构建对象,但不保存
# collection.create(attributes = {}) # 创建对象
# collection.create!(attributes = {}) # 创建对象,出错是返回错误信息 ActiveRecord::RecordInvalid 异常
# 方法中的 collection 要替换成传给 has_many 方法的第一个参数
在model中设置has_many的时候,可以设置以下可选参数
:as # 表明这是多态关联
:autosave # 是否自动保存
:class_name # 指定关联模型的名字(关联模型的名字无法从关联名称获取)
:counter_cache # 计数缓存列
:dependent # 设置销毁的时候怎么处理关联对象
:destroy:# 销毁所有关联的对象
:delete_all:# 直接把所有关联的对象从数据库中删除
:nullify:# 把外键设为 NULL
:restrict_with_exception:# 有关联的对象时抛出异常
:restrict_with_error:# 有关联的对象时,向属主添加一个错误
:foreign_key # 设置外键名
:inverse_of # 设置belongs_to的关联名
:primary_key # 设置主键名
:source # has_many :through时使用
:source_type # has_many :through 时使用
:through # 创建关联的第三个桥接模型
:validate # 保存时是否验证关联对象,默认为true
counter_cache计数缓存
设置了belongs_to和has_many关联之后,会设计到计数统计的问题.比如:商品分类Category中按照分类信息统计商品数量
# 只需要在产品模型中添加可选参数counter_cache:true
# 然后在catrgory的迁移代码中添加一列products_count
# 当每一个产品的分类id值发生更改或者有新产品新增之后Category中的products_count会自动更新
class Product < ApplicationRecord
belongs_to :category, counter_cache: true
has_one :product_detail
has_one :product_cover_img, through: :product_detail
end
class CreateCategories < ActiveRecord::Migration[5.1]
def change
create_table :categories do |t|
t.string :name
t.integer :products_count # 计数缓存字段(关联模型的复数形式_count命名)
t.timestamps
end
end
end
@c = Category.first
@c.products_count # 获取当前分类下的产品数量
has_many :through 关联
has_many :through 关联用于建立两个模型之间的多对多关联.通过一个产品规格信息来进行描述:
一个产品可以包含很多个规格,每一个规格又可以包含很多个数据值:
产品,包含:尺码、颜色、容量
容量:32G、64G、128G
屏幕尺寸:10寸、12寸
class Product < ApplicationRecord
belongs_to :category
has_one :product_detail
has_one :product_cover_img, through: :product_detail
has_many :standard, through: :standard_vals
has_many :standard_vals
end
# 产品的规格信息
class Standard < ApplicationRecord
has_many :standard_vals
has_many :products, through: :standard_vals
end
# 产品的规格值
class StandardVal < ApplicationRecord
belongs_to :product
belongs_to :standard
end
数据库迁移做修改
# 创建规格信息
class CreateStandards < ActiveRecord::Migration[5.1]
def change
create_table :standards do |t|
t.string :name
t.timestamps
end
end
end
# 创建规格值, 外键关联规格和产品表
class CreateStandardVals < ActiveRecord::Migration[5.1]
def change
create_table :standard_vals do |t|
t.belongs_to :product
t.belongs_to :standard
t.string :name
t.timestamps
end
end
end
通过以上的关键操作之后,可以通过代码创建一些对象做一个测试
Standard.create(name: '容量')
Standard.create(name: '屏幕尺寸')
@category = Category.first
@product = @category.products.create(name: '华为 Mate10')
@product.standard_vals # 获取所有的规格属性信息
@product.standard_vals.create({name: '64G', standard:Standard.first}) # 创建一个规格属性值
@product.standard_vals.create({name: '32G', standard:Standard.first})
@product.standard_vals.create({name: '128G', standard:Standard.first})
@product.standard_vals.create({name: '10寸', standard:Standard.last})
@product.standard_vals.create({name: '12寸', standard:Standard.last})
has_and_belongs_to_many 关联
has_and_belongs_to_many直接建立两个模型的多对多关联,不借助第三个模型.比如:老师和学生的关系,一个老师可以教很多个学生,每一个学生又可以有很多个老师
# 通过join_table指定关联表的名字
class Student < ApplicationRecord
has_and_belongs_to_many :teachers, join_table: :teachers_students
end
class Teacher < ApplicationRecord
has_and_belongs_to_many :students, join_table: :teachers_students
end
数据库迁移代码
class CreateTeachers < ActiveRecord::Migration[5.1]
def change
create_table :teachers do |t|
t.string :name
t.timestamps
end
end
end
class CreateStudents < ActiveRecord::Migration[5.1]
def change
create_table :students do |t|
t.string :name
t.timestamps
end
# 为了省事直接把关联表的创建放在这里
create_table :teachers_students do |t|
t.belongs_to :teacher, index: true # 关联teachers
t.belongs_to :student, index: true # 关联students
end
end
end
# 测试代码
w = Teacher.create(name: '王老师')
w.students.create({name: '小红'}) # 为王老师创建一个学生
w.students.create({name: '小明'}) # 为王老师创建一个学生
# 其他的查询方法可以参考上面提到的关联相关操作