单表继承(STI)问题

Posted

技术标签:

【中文标题】单表继承(STI)问题【英文标题】:Single Table Inheritance(STI) Problems 【发布时间】:2012-10-31 19:53:40 【问题描述】:

我是 Rails 新手,在我的数据库中指定几个实体和关系时遇到了一些问题。

让我们假设以下实体。

赛车实体

迁移文件如下所示:

class CreateRacers < ActiveRecord::Migration
    def self.up
        create_table :racers, options: "ENGINE=InnoDB" do |t|
        t.string :email, limit: 60, null: false
        end
        add_index :racers, :email, unique: true
        execute "ALTER TABLE racers MODIFY id INT UNSIGNED AUTO_INCREMENT;"
    end

    def self.down
        drop_table :racers
    end
end

模型文件如下所示:

class Racer < ActiveRecord::Base
    attr_accessible :email
    validates_uniqueness_of :email

    before_save  |racer| racer.email = email.downcase 

    # Email validation
    VALID_EMAIL_REGEX = /([\w+.]+)@[a-z\d\-.]+\.[a-z]+\z/i
    validates :email, presence: true, length: maximum: 60, format:  with: VALID_EMAIL_REGEX 

    has_many :tracks, dependent: :delete_all
end

追踪实体

迁移文件如下所示:

class CreateTracks < ActiveRecord::Migration
    def self.up
        create_table :tracks, options: "ENGINE=InnoDB" do |t|
            t.column :user_id, 'integer unsigned', null: false
            t.string :description, limit: 250, null: false
            t.string :image, null: true
        end
        execute "ALTER TABLE tracks MODIFY id INT UNSIGNED AUTO_INCREMENT;"
        add_foreign_key :tracks, :racers
    end

    def self.down
        remove_foreign_key :tracks, :racers
        drop_table :tracks
    end
end

模型文件如下所示:

class Track < ActiveRecord::Base
    attr_accessible :description, :image

    validates :description, presence: true, length: maximum: 250

    belongs_to :racer
    validates_presence_of :racer_id
    has_many :stops, dependent: :delete_all
end

停止实体

迁移文件如下所示:

class CreateStops < ActiveRecord::Migration
    def self.up
        create_table :stops, options: "ENGINE=InnoDB" do |t|
            t.column :track_id, 'integer unsigned', null: false
            t.column :coordinates, :point, null: false
            t.string :name, limit: 30, null: true
            t.column :sequence_order, 'integer unsigned', null: false
        end
        execute "ALTER TABLE stops MODIFY id INT UNSIGNED AUTO_INCREMENT;"
        add_foreign_key :stops, :tracks
    end

    def self.down
        remove_foreign_key :stops, :tracks
        drop_table :stops
    end
end

模型文件如下所示:

class Stop < ActiveRecord::Base
    attr_accessible :coordinates, :name, :sequence_order

    validates :name, presence: true, length: maximum: 30
    validates :coordinates, presence: true
    validates :spot_sequence_order, presence: true

    belongs_to :track
    validates_presence_of :track_id
    has_one :challenge, dependent: :delete_all
end

Challenge、Puzzle、Quiz、QuizOption 实体(问题所在)

已经看到上面的 Stop 实体 has_one 挑战,我希望 Challenge 是一个测验 或拼图挑战 belongs_to 停止。到目前为止,我有以下迁移:

class CreatePuzzles < ActiveRecord::Migration
    def self.up
        create_table :puzzles, options: "ENGINE=InnoDB" do |t|
            t.string :image_path, null: false
            t.int :ver_split, null: false, default: 4
            t.int :hor_split, null: false, default: 4
        end
        execute "ALTER TABLE puzzlies MODIFY id INT UNSIGNED AUTO_INCREMENT;"
    end

    def self.down
        drop_table :quizzes
    end
end
class CreateQuizzes < ActiveRecord::Migration
    def self.up
        create_table :quizzes, options: "ENGINE=InnoDB" do |t|
            t.string :question, null: false
        end
        execute "ALTER TABLE quizzes MODIFY id INT UNSIGNED AUTO_INCREMENT;"
    end

    def self.down
        drop_table :quizzes
    end
end

还有以下型号

class Puzzle < ActiveRecord::Base
    attr_accessor :image_path, :ver_split, hor_split

    validates :image_path, presence: true, allow_blank: false
    validates :ver_split, allow_blank: false
    validates :hor_split, allow_blank: false

    belongs_to :stop
end
class Quiz < ActiveRecord::Base
    attr_accessor :question

    validates :question, presence: true, length:  maximum: 255 , allow_blank: false

    belongs_to :spot
    has_many :quiz_options
end

测验有几个答案,其中一个或多个是正确的。

class CreateQuizOptions < ActiveRecord::Migration
    def self.up
        create_table :quiz_options do |t|
            t.column :quiz_id, 'integer unsigned', null: false
            t.string :option, null: false
            t.boolean :is_correct, null: false, default: false
        end
        add_foreign_key :quiz_options, :quizzes
        execute "ALTER TABLE quiz_options MODIFY id INT UNSIGNED AUTO_INCREMENT;"
    end

    def self.down
        remove_foreign_key :quiz_options, :quizzes
        drop_table :quiz_options
    end
end
class QuizOption < ActiveRecord::Base
    attr_accessor :option, :is_correct

    validates :option, presence: true, length:  maximum: 255 
    validates_inclusion_of :is_correct, in: [true,false]

    belongs_to :quiz
    validates_presence_of :quiz_id
end

问题

我应该如何指定我的迁移、模型和控制器来实现这个目标?

我找到了几个 STI 和多态关联的示例,但我不知道应该应用哪一个以及如何将它们应用到这个案例中。

首先我尝试使用 STI 并在 Challenge 表中声明所有必要的字段,然后 QuizPuzzle 模型继承自挑战模型。问题是我不知道把 has_many :quiz_options 放在哪里。

然后我尝试使用 here 和 here 解释的多态关联,但老实说,我无法理解如何使其适应这种特定情况。

提前致谢。

编辑:我忘了说我使用的是 mysql。我还有一些 gem 来管理空间数据类型(rgeo、activerecord-mysql2spatial-adapter)和foreign_keys(foreigner)。

【问题讨论】:

【参考方案1】:

好的,我想我通过应用单表继承解决了我自己的问题。

结果并不漂亮,但它有效,由于缺乏其他解决方案的适当信息,我现在会坚持下去。

让我们回到我的例子。

RacerTrackStop 实体保持原样,没有任何变化。

Challenge 迁移将如下所示:

class CreateChallenges < ActiveRecord::Migration
    def self.up
        create_table :challenges, options: "ENGINE=InnoDB" do |t|
            t.column :stop_id, 'integer unsigned', null: false
            t.string :type
            # Common attributes to all challenges
            t.string :description, null: false
            # Puzzle attributes
            t.string :image_path
            t.int :vert_split
            t.int :hor_split
            # Quiz attributes
            t.string :question
        end
        add_foreign_key :challenges, :stops
        execute "ALTER TABLE quizzes MODIFY id INT UNSIGNED AUTO_INCREMENT;"
    end

    def self.down
        remove_foreign_key :challenges, :stops
        drop_table :quizzes
    end
end

在这个表格中,我只是添加了所有必要的字段来指定测验谜题

现在模型将如下所示:

class Challenge < ActiveRecord::Base
    validates_presence_of :spot_id, :description

    attr_accessible :description, :image_path

    # Validate description attribute
    validates :description, length:  maximum: 255 , allow_blank: false
    # Validates image type
    VALID_IMAGE_TYPE_REGEX = /^[\/]?([\w]+[\/])*([\w]+\.(png|jpg))$/
    validates :image_path, length:  maximum: 255 , allow_blank: true, format:  with: VALID_IMAGE_TYPE_REGEX 

    # Validates type attribute
    validates_inclusion_of :type, in: %w( Quiz Puzzle )

    # Associations
    belongs_to :stop
    has_many :quiz_options
end
class Puzzle < Challenge
    validates_presence_of :image_path
    attr_accessor :image_path, :ver_split, hor_split

    validates :image_path, allow_blank: false
    validates :ver_split, allow_blank: false
    validates :hor_split, allow_blank: false
end
class Quiz < Challenge
    validates_presence_of :question
    attr_accessor :question

    validates :question, length:  maximum: 255 , allow_blank: false
end

现在完成所有工作,QuizOption 迁移和模型将如下所示:

class CreateQuizOptions < ActiveRecord::Migration
    def self.up
        create_table :quiz_options do |t|
            t.column :challenge_id, 'integer unsigned', null: false
            t.string :option, null: false
            t.boolean :is_correct, null: false, default: false
        end
        add_foreign_key :quiz_options, :challenges
        execute "ALTER TABLE quiz_options MODIFY id INT UNSIGNED AUTO_INCREMENT;"
    end

    def self.down
        remove_foreign_key :quiz_options, :challenges
        drop_table :quiz_options
    end
end
class QuizOption < ActiveRecord::Base
    validates_presence_of :challenge_id, :option
    attr_accessor :option, :is_correct

    validates :option, length:  maximum: 255 
    validates_inclusion_of :is_correct, in: [true,false]

    belongs_to :challenge
end

这样一切都按我的意愿进行。

【讨论】:

以上是关于单表继承(STI)问题的主要内容,如果未能解决你的问题,请参考以下文章

核心数据:继承、STI 还是其他?

获取 STI 中的记录以获取 rails 4 中的空 has_many 记录

通过rails中关联模型的子类查询

单表继承 活动管理模型映射问题 rails 4

推进单表继承问题

Prisma 中是不是支持单表继承?