【问题标题】:Configuring the proper join column in Rails Admin在 Rails Admin 中配置正确的连接列
【发布时间】:2016-01-28 11:42:31
【问题描述】:

我有两个模型,它们通过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

假设两个表都有一个 id 字段,并且那个阶段有一个stage_name 字段。

它们通过一个名为bands_stages 的表相互关联,其架构类似于:

create_table :bands_stages, id: false do |t|
    t.integer :band_id
    t.integer :stage_number
end

我的意图是使用 Rails Admin 来允许我们修改 Stage 上的某些字段,但每次运行时,我都会收到 SQL 错误:

stages.id 列不存在

Rails 管理员似乎默认选择了错误的列加入。我如何通知 Rails 管理员我希望它加入我的联接表中实际存在的列?

请注意,我实际上无法使用 stages 表中的 ID。其目的是在任何给定时间仅存在十个阶段,由阶段编号表示,但每个乐队都可以访问每个阶段。由于 ID 会自动递增,因此使用更具体的 :stage_number 字段的意图似乎更安全、更明确。

【问题讨论】:

  • 您看到的错误意味着您在stages 上没有id 列。您是否也create_table ... id: false 参加了阶段?有什么理由不将stage_id 添加到bands_groups,然后从那里引用stage_number?这将使您的关联不那么老套。
  • @JoshBrody:不使用ID是有原因的;作为商业规则,只有 10 个阶段将永远存在。但是,预计每个乐队都可以访问每个阶段。 ID 不会给我我需要的东西,尤其是在测试时,因为我会通过阶段编号来引用阶段。

标签: ruby ruby-on-rails-4 rails-admin


【解决方案1】:

我确定这不是 rails admin 的问题,而是 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

更新:请注意,在这种情况下,仍然不需要为阶段表指定任何主键。例如我的迁移是:

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 测试了这两种情况,一切正常。

【讨论】:

  • 我有点惊讶它是如此简单。谢谢。
【解决方案2】:

编辑 - 我确实误解了主键位,我认为希望告诉 Rails 使用不同的属性作为 PK,这应该比重新利用自动增量更容易- 默认情况下 PK ID。在这种情况下,Stage 模型应包括self.primary_key = "stage_number",以及与 HABTM 相关的答案底部的其余详细信息。当然 has-many-through 仍然是我首选的解决方案。

我认为模型和方法存在比 Rails Admin 更大的问题。

如果我理解您要执行的操作,那么您还需要关闭 stages 表中主键的自动增量,以将任意数字(表示阶段编号)作为主键 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

对于基于表的方法,我建议使用 has-many-through,它会在将来为您节省很多痛苦(即使您不需要连接表上的其他属性,例如嵌套表单比在 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,它们在那里确保关系数据库中数据的关系/完整性,以及与 ActiveRecord 的良好且一致的映射 - 所有业务数据都应除此之外,在实际属性中,而不是用于连接模型的管道)。

如果您仍然想要 HABTM 并重新调整主 ID 的用途,那么我认为 Stage 应该包含一个 foreign_key 语句来“宣传”自己到 bands_stages 连接表作为具有自定义键名(在 @ 987654332@ 仅),同时将association_foreign_key 保留在Band 端以显示要在连接表中查询的内容以到达另一端。 stages 表仍会使用 id 作为其主键,但您只想使用 t.integer :id, :options =&gt; 'PRIMARY 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

bandsbands_stages 之间的连接将是 bands.id = bands_stages.band_id,为此会找到许多 bands_stages.stage_number,并且每个都将通过 bands_stages.stage_number = stages.id 连接到 stage(其中 stages.id 已重新- 旨在代表未来可能面临风险的业务数据)。

【讨论】:

    【解决方案3】:

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-09
      • 2013-08-14
      • 2019-07-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多