Rails ActiveRecord关系,STI和继承

时间:2015-10-02 15:22:25

标签: activerecord sti

好的,自从我编写了任何ruby代码以来已经好几年了,我的设计可能不正确。考虑到这一点,我正在编写一个小实用程序,通过REST克隆TargetProcess中的项目实体。 Target Process有一个数据模型,允许几种类型的父:子关系:

project:epic:feature:user_story
project:feature:user_story
project:user_story

但是,从数据结构的角度来看,所有实体几乎完全相同,因此使用STI并使用模型来定义关系和继承似乎是有意义的。我创建了一个新的Rails应用程序,只使用这些模型来验证我在尝试将Epic与功能关联时遇到的错误:

ActiveModel::MissingAttributeError: can't write unknown attribute `epic_id`

以下是模型:

class TargetProcessEntity < ActiveRecord::Base
end

class Project < TargetProcessEntity
  has_many :epics
  has_many :features
  has_many :user_stories
end

class Project < TargetProcessEntity
  has_many :epics
  has_many :features
end

class Epic < TargetProcessEntity
  belongs_to :project
  has_many   :features
end

class Feature < TargetProcessEntity
  belongs_to :project
  belongs_to :epic
  has_many   :user_stories
end

class UserStory < TargetProcessEntity
  belongs_to :feature
  belongs_to :project
end

这是架构:

ActiveRecord::Schema.define(version: 20150929122254) do

  create_table "epics", force: :cascade do |t|
    t.datetime "created_at",               null: false
    t.datetime "updated_at",               null: false
    t.integer  "target_process_entity_id"
    t.integer  "project_id"
  end

  add_index "epics", ["project_id"], name: "index_epics_on_project_id"
  add_index "epics", ["target_process_entity_id"], name: "index_epics_on_target_process_entity_id"

  create_table "features", force: :cascade do |t|
    t.integer "project_id"
    t.integer "epic_id"
    t.integer "target_process_entity_id"
  end

  add_index "features", ["epic_id"], name: "index_features_on_epic_id"
  add_index "features", ["project_id"], name: "index_features_on_project_id"
  add_index "features", ["target_process_entity_id"], name: "index_features_on_target_process_entity_id"

  create_table "projects", force: :cascade do |t|
    t.datetime "created_at",               null: false
    t.datetime "updated_at",               null: false
    t.integer  "target_process_entity_id"
  end

  add_index "projects", ["id"], name: "index_projects_on_id"
  add_index "projects", ["target_process_entity_id"], name: "index_projects_on_target_process_entity_id"

  create_table "target_process_entities", force: :cascade do |t|
    t.string   "type",             null: false
    t.string   "name"
    t.text     "description"
    t.integer  "source_remote_id"
    t.float    "numeric_priority"
    t.integer  "owner"
    t.datetime "created_at",       null: false
    t.datetime "updated_at",       null: false
    t.integer  "cloned_remote_id"
    t.string   "resource_type"
    t.integer  "project_id"
  end

  create_table "user_stories", force: :cascade do |t|
    t.integer  "project_id"
    t.integer  "feature_id"
    t.datetime "created_at",               null: false
    t.datetime "updated_at",               null: false
    t.integer  "target_process_entity_id"
  end

  add_index "user_stories", ["feature_id"], name: "index_user_stories_on_feature_id"
  add_index "user_stories", ["project_id"], name: "index_user_stories_on_project_id"
  add_index "user_stories", ["target_process_entity_id"], name: "index_user_stories_on_target_process_entity_id"

end

虽然Epic和Feature都有project_id,但Feature的实例没有epic_id属性;试图为剧情分配史诗爆炸:

[20] pry(main)> epic = Epic.new
=> #<Epic:0x007fcab6c80590
 id: nil,
 type: "Epic",
 name: nil,
 description: nil,
 source_remote_id: nil,
 numeric_priority: nil,
 owner: nil,
 created_at: nil,
 updated_at: nil,
 cloned_remote_id: nil,
 resource_type: "Epic",
 project_id: nil>
[21] pry(main)> feature = Feature.new
=> #<Feature:0x007fcab6d3ba48
 id: nil,
 type: "Feature",
 name: nil,
 description: nil,
 source_remote_id: nil,
 numeric_priority: nil,
 owner: nil,
 created_at: nil,
 updated_at: nil,
 cloned_remote_id: nil,
 resource_type: "Feature",
 project_id: nil>
[22] pry(main)> epic.save
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "target_process_entities" ("type", "resource_type", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["type", "Epic"], ["resource_type", "Epic"], ["created_at", "2015-10-02 15:18:13.351578"], ["updated_at", "2015-10-02 15:18:13.351578"]]
   (4.6ms)  commit transaction
=> true
[23] pry(main)> feature.epic = epic
ActiveModel::MissingAttributeError: can't write unknown attribute `epic_id`
from /Users/kcallahan/.rbenv/versions/2.0.0-p647/lib/ruby/gems/2.0.0/gems/activerecord-4.2.4/lib/active_record/attribute.rb:138:in `with_value_from_database'
[24] pry(main)> 

我意识到我要么做错了,要么做出糟糕的设计决定;任何意见都非常受欢迎,因为我几乎没有找到任何相关的东西,而且几天来我一直在抨击它!

2 个答案:

答案 0 :(得分:0)

好的,我差点错了!我将xxx_id列添加到target_process_entities表中。我认为STI表能够响应关系定义,尽管我对STI和关系的内部运作的理解充满了生气和不完整......

答案 1 :(得分:-1)

我可能错了,但看起来你的Feature表是Project和Epic之间多对多关系的连接表。

如果是这种情况,您的模型可能看起来像这样

class Project < TargetProcessEntity
  has_many :features
  has_many :epics, through: :features
end

class Epic < TargetProcessEntity
  has_many :features
  has_many :projects, through: :features
end

class Feature < TargetProcessEntity
  belongs_to :project
  belongs_to :epic
  has_many   :user_stories
end

如果您使用相同名称

,则隐含来源

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html