美文网首页ruby on rails
Active Record关联

Active Record关联

作者: 李傲娢 | 来源:发表于2018-04-07 14:43 被阅读65次

    在开发中常常会涉及到多个模型进行关联的操作.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关联

        本文链接:https://www.haomeiwen.com/subject/zrathftx.html