在 Rails 管理中配置正确的连接列
Configuring the proper join column in Rails Admin
我有两个模型,它们通过 has_and_belongs_to_many
关系相互关联。
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands
end
假设两个 table 都有一个 id 字段,并且那个阶段有一个 stage_name
字段。
它们通过名为 bands_stages
的 table 相互关联,其架构类似于:
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
我打算使用 Rails Admin 来允许我们修改 Stage
上的某些字段,但每次运行时,我都会收到 SQL 错误:
column stages.id
does not exist
似乎 Rails 管理员默认选择了错误的加入列。我如何通知 Rails 管理员我希望它加入我加入 table 中实际 存在 的列?
请注意,我实际上无法使用 stages
table 中的 ID。目的是在任何给定时间只存在十个阶段,用阶段编号表示,但每个乐队都可以访问每个阶段。由于 ID 会自动递增,因此利用更具体的 :stage_number
字段似乎更安全、更明确。
Edit - 我做了 mis-understand 主键位,我想我的愿望是告诉 Rails 使用不同的属性作为 PK,这应该比 re-purposing auto-increment-by-default PK ID 问题更少。在这种情况下,Stage
模型应包括 self.primary_key = "stage_number"
,以及与 HABTM 相关的此答案底部的其余详细信息。当然 has-many-through 仍然是我的首选解决方案。
我认为模型和方法存在比 Rails 管理员更大的问题。
如果我明白你想做什么,那么你还需要关闭 auto-increment stages
table 中的主键以保存任意数字(表示阶段编号)作为主键 ID。它可能会很快结束,所以我建议不要这样做。
如果数据真正是静态的(曾经有 10 个阶段),您甚至可以将其作为常量保留在 Band
模型中并完全废弃 Stage
(除非还有更多),例如
class Band < ActiveRecord::Base
POSSIBLE_STAGES = [1, 2, ...]
validates :stage, inclusion: { in: POSSIBLE_STAGES, message: "%{value} is not a stage we know of!" }
end
对于 table-based 方法,我建议 has-many-through,它会在未来为您省去很多痛苦(即使您不需要在联接上添加其他属性 table,像嵌套形式这样的东西比在 HABTM 中更容易处理)。像这样:
class Band < ActiveRecord::Base
has_many :events
has_many :stages, through :events
# band details go into this model
end
class Event < ActiveRecord::Base
belongs_to :band
belongs_to :stage
# you could later add attributes here, such as date/time of event, used_capacity, attendee rating, and
# even validations such as no more than X bands on any given stage at the same time etc.
end
class Stage < ActiveRecord::Base
has_many :events
has_many :bands, through :events
# stage number/details go into this model
end
迁移看起来像这样:
create_table :bands do |t|
t.string :bandname
# whatever else
end
create_table :events do |t|
t.belongs_to :band
t.belongs_to :stage
# you could add attributes here as well, e.g. t.integer :used_capacity
end
create_table :stages do |t|
t.integer :number
t.integer :total_capacity
# whatever else
end
如您所见,这里根本没有触及主键 ID,我总是会避免将业务数据存储在 Rails' 和任何类型的数据库管道(我认为 ID 是,它们在那里确保关系数据库中数据的 relation/integrity,以及到 ActiveRecord 的良好且一致的映射 - 所有业务数据都应该在实际属性中,而不是用于连接模型的管道)。
如果你仍然想要 HABTM 和 re-purposing 的主要 ID,那么我认为 Stage
应该包含一个 foreign_key
声明到 "advertise" 本身到 bands_stages
加入 table 作为具有自定义键名称(仅在 bands_stages
中),同时将 association_foreign_key
保留在 Band
端以显示您要在加入中查询的内容 table 到达彼岸。 stages
table 仍然会使用 id
作为它的主键,你只想用 t.integer :id, :options => 'PRIMARY KEY'
之类的东西关闭 auto-increment (可能依赖关于数据库风格 - 我再次建议不要这样做)。
您的模型将如下所示:
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: "stage_number"
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: "stage_number"
end
bands
和 bands_stages
之间的联系是 bands.id = bands_stages.band_id
,为此会找到许多 bands_stages.stage_number
,并且每个都将连接到 stage
通过 bands_stages.stage_number = stages.id
(其中 stages.id
已 re-purposed 表示未来可能面临危险的业务数据)。
我确定这不是 rails 管理员的问题,而是 habtm 协会的问题。
要使 habtm 使用 sql 中的右列,必须为阶段模型指定主键,并为关联指定外键。
这是使其正常工作的唯一方法。
class Stage < ActiveRecord::Base
self.primary_key = "stage_number"
has_and_belongs_to_many :bands, foreign_key: :stage_number
end
但我认为最好的方法是使用联合模型和 has_many/belongs_to 因为对于 has_many/belongs_to 可以通过 :primary_key 选项将任何列设置为主键。
class BandStageLink < ActiveRecord::Base
self.table_name = "bands_stages"
belongs_to :band
belongs_to :stage, foreign_key: :stage_number, primary_key: :stage_number
end
class Band < ActiveRecord::Base
has_many :band_stage_links
has_many :stages, through: :band_stage_links, foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_many :band_stage_links, primary_key: :stage_number, foreign_key: :stage_number
has_many :bands, through: :band_stage_links
end
更新: 请注意,在这种情况下,仍然不需要为阶段 table 指定任何主键。例如我的迁移是:
class CreateStageBandTables < ActiveRecord::Migration
def change
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
create_table :bands do |t|
t.string :name
end
create_table :stages, id: false do |t|
t.integer :stage_number
t.string :name
end
end
end
我针对 rails 4.2.5 测试了这两种情况,一切正常。
将 association_foreign_key
值更改为字符串而不是符号。
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: 'stage_number'
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: 'stage_number'
end
我有两个模型,它们通过 has_and_belongs_to_many
关系相互关联。
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands
end
假设两个 table 都有一个 id 字段,并且那个阶段有一个 stage_name
字段。
它们通过名为 bands_stages
的 table 相互关联,其架构类似于:
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
我打算使用 Rails Admin 来允许我们修改 Stage
上的某些字段,但每次运行时,我都会收到 SQL 错误:
column
stages.id
does not exist
似乎 Rails 管理员默认选择了错误的加入列。我如何通知 Rails 管理员我希望它加入我加入 table 中实际 存在 的列?
请注意,我实际上无法使用 stages
table 中的 ID。目的是在任何给定时间只存在十个阶段,用阶段编号表示,但每个乐队都可以访问每个阶段。由于 ID 会自动递增,因此利用更具体的 :stage_number
字段似乎更安全、更明确。
Edit - 我做了 mis-understand 主键位,我想我的愿望是告诉 Rails 使用不同的属性作为 PK,这应该比 re-purposing auto-increment-by-default PK ID 问题更少。在这种情况下,Stage
模型应包括 self.primary_key = "stage_number"
,以及与 HABTM 相关的此答案底部的其余详细信息。当然 has-many-through 仍然是我的首选解决方案。
我认为模型和方法存在比 Rails 管理员更大的问题。
如果我明白你想做什么,那么你还需要关闭 auto-increment stages
table 中的主键以保存任意数字(表示阶段编号)作为主键 ID。它可能会很快结束,所以我建议不要这样做。
如果数据真正是静态的(曾经有 10 个阶段),您甚至可以将其作为常量保留在 Band
模型中并完全废弃 Stage
(除非还有更多),例如
class Band < ActiveRecord::Base
POSSIBLE_STAGES = [1, 2, ...]
validates :stage, inclusion: { in: POSSIBLE_STAGES, message: "%{value} is not a stage we know of!" }
end
对于 table-based 方法,我建议 has-many-through,它会在未来为您省去很多痛苦(即使您不需要在联接上添加其他属性 table,像嵌套形式这样的东西比在 HABTM 中更容易处理)。像这样:
class Band < ActiveRecord::Base
has_many :events
has_many :stages, through :events
# band details go into this model
end
class Event < ActiveRecord::Base
belongs_to :band
belongs_to :stage
# you could later add attributes here, such as date/time of event, used_capacity, attendee rating, and
# even validations such as no more than X bands on any given stage at the same time etc.
end
class Stage < ActiveRecord::Base
has_many :events
has_many :bands, through :events
# stage number/details go into this model
end
迁移看起来像这样:
create_table :bands do |t|
t.string :bandname
# whatever else
end
create_table :events do |t|
t.belongs_to :band
t.belongs_to :stage
# you could add attributes here as well, e.g. t.integer :used_capacity
end
create_table :stages do |t|
t.integer :number
t.integer :total_capacity
# whatever else
end
如您所见,这里根本没有触及主键 ID,我总是会避免将业务数据存储在 Rails' 和任何类型的数据库管道(我认为 ID 是,它们在那里确保关系数据库中数据的 relation/integrity,以及到 ActiveRecord 的良好且一致的映射 - 所有业务数据都应该在实际属性中,而不是用于连接模型的管道)。
如果你仍然想要 HABTM 和 re-purposing 的主要 ID,那么我认为 Stage
应该包含一个 foreign_key
声明到 "advertise" 本身到 bands_stages
加入 table 作为具有自定义键名称(仅在 bands_stages
中),同时将 association_foreign_key
保留在 Band
端以显示您要在加入中查询的内容 table 到达彼岸。 stages
table 仍然会使用 id
作为它的主键,你只想用 t.integer :id, :options => 'PRIMARY KEY'
之类的东西关闭 auto-increment (可能依赖关于数据库风格 - 我再次建议不要这样做)。
您的模型将如下所示:
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: "stage_number"
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: "stage_number"
end
bands
和 bands_stages
之间的联系是 bands.id = bands_stages.band_id
,为此会找到许多 bands_stages.stage_number
,并且每个都将连接到 stage
通过 bands_stages.stage_number = stages.id
(其中 stages.id
已 re-purposed 表示未来可能面临危险的业务数据)。
我确定这不是 rails 管理员的问题,而是 habtm 协会的问题。 要使 habtm 使用 sql 中的右列,必须为阶段模型指定主键,并为关联指定外键。 这是使其正常工作的唯一方法。
class Stage < ActiveRecord::Base
self.primary_key = "stage_number"
has_and_belongs_to_many :bands, foreign_key: :stage_number
end
但我认为最好的方法是使用联合模型和 has_many/belongs_to 因为对于 has_many/belongs_to 可以通过 :primary_key 选项将任何列设置为主键。
class BandStageLink < ActiveRecord::Base
self.table_name = "bands_stages"
belongs_to :band
belongs_to :stage, foreign_key: :stage_number, primary_key: :stage_number
end
class Band < ActiveRecord::Base
has_many :band_stage_links
has_many :stages, through: :band_stage_links, foreign_key: :stage_number
end
class Stage < ActiveRecord::Base
has_many :band_stage_links, primary_key: :stage_number, foreign_key: :stage_number
has_many :bands, through: :band_stage_links
end
更新: 请注意,在这种情况下,仍然不需要为阶段 table 指定任何主键。例如我的迁移是:
class CreateStageBandTables < ActiveRecord::Migration
def change
create_table :bands_stages, id: false do |t|
t.integer :band_id
t.integer :stage_number
end
create_table :bands do |t|
t.string :name
end
create_table :stages, id: false do |t|
t.integer :stage_number
t.string :name
end
end
end
我针对 rails 4.2.5 测试了这两种情况,一切正常。
将 association_foreign_key
值更改为字符串而不是符号。
class Band < ActiveRecord::Base
has_and_belongs_to_many :stages, association_foreign_key: 'stage_number'
end
class Stage < ActiveRecord::Base
has_and_belongs_to_many :bands, foreign_key: 'stage_number'
end