Rails отношения ActiveRecord, STI и наследование
Итак, прошло много лет с тех пор, как я написал любой код ruby, и мой дизайн может быть неправильным. Имея это в виду, я пишу небольшую утилиту для клонирования сущностей проекта в TargetProcess через REST. Целевой процесс имеет модель данных, которая допускает несколько типов родительских: дочерних отношений:
project:epic:feature:user_story
project:feature:user_story
project:user_story
Однако все сущности практически идентичны с точки зрения структуры данных, поэтому представляется целесообразным использовать STI и модели для определения отношений и наследования. Я создал новое приложение Rails только с этими моделями, чтобы проверить ошибку, которую я получаю при попытке связать Epic с Feature:
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 ответа
ОК, я получил это почти случайно! Я добавил столбцы xxx_id в таблицу target_process_entities. Я предполагал, что таблицы ИППП смогут отвечать на определения отношений, хотя мое понимание внутренней работы ИППП и отношений в лучшем случае ржавое и неполное...
Я могу ошибаться, но похоже, что ваша таблица 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