Active Record关联

在开发中常常会涉及到多个模型进行关联的操作.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: '小明'})     # 为王老师创建一个学生
# 其他的查询方法可以参考上面提到的关联相关操作

你可能感兴趣的:(Active Record关联)