在插件中使用 HasCustomFields

我有一个插件中的模型。该模型是 Pfaffmananger::Server,对应的表是 pfaffmanager_servers。出于某些原因,我想为该模型添加自定义字段(我预计会有一些扩展服务器数据的需求,这些需求可能仅适用于少数实例,而我不希望模型中包含大量大部分未使用的字段)。

以下是创建该表的迁移代码:

class CreatePfaffmanagerServerCustomField < ActiveRecord::Migration[6.0]
  def change
    create_table :pfaffmanager_server_custom_fields do |t|
      t.integer :server_id, null: false
      t.string :name, limit: 256, null: false
      t.text :value
      t.timestamps null: false
    end
    add_index :pfaffmanager_server_custom_fields, [:server_id, :name]
  end
end

如果我将字段名改为 pfaffmanager_server_id 而不是 server_id,迁移会失败,因为它找不到 server_id;但是,当字段名使用 pfaffmanager_server_id 时,在创建自定义字段后尝试保存服务器,我会收到以下错误:

  Pfaffmanager::ServerCustomField Load (0.4ms)  SELECT "pfaffmanager_server_custom_fields".* FROM "pfaffmanager_server_custom_fields" WHERE "pfaffmanager_server_custom_fields"."server_id" = 1
   (0.2ms)  ROLLBACK
PG::UndefinedColumn: ERROR:  column "pfaffmanager_server_id" of relation "pfaffmanager_server_custom_fields" does not exist
LINE 1: INSERT INTO pfaffmanager_server_custom_fields (pfaffmanager_...

因此,如果迁移中定义的字段是 pfaffmanager_server_id,迁移会失败;如果字段名是 server_id,保存操作会失败。

我正在查看 concerns/has_custom_fields.rb 中的 save_custom_fields 方法,但无法完全确定是否有办法对其进行重写。

除了将整个结构重构为使用 server 而不是 pfaffmanager_server 之外,还有其他可行的解决方案吗?

你是否也将 add_index 调用更新为指向 pfaffmanager_server_id

2 个赞

非常感谢你,David!

是的,这可能就是问题所在。我想我本该把这条错误信息也包含进来。而且,我可以让迁移在任一字段下都能工作(不过,如果字段名是 pfaffmanager_server_id,索引名称就会超过 63 个字符,所以我必须为索引使用不同的名称)。

让我再试一次。

字段名为 server_id

class CreatePfaffmanagerServerCustomField < ActiveRecord::Migration[6.0]
  def change
    create_table :pfaffmanager_server_custom_fields do |t|
      t.integer :server_id, null: false
      t.string :name, limit: 256, null: false
      t.text :value
      t.timestamps null: false
    end
    add_index :pfaffmanager_server_custom_fields, [:server_id, :name]
  end
end

保存时失败,报错如下:

 Pfaffmanager::ServerCustomField Load (3.8ms)  SELECT "pfaffmanager_server_custom_fields".* FROM "pfaffmanager_server_custom_fields" WHERE "pfaffmanager_server_custom_fields"."server_id" = 1
   (1.4ms)  ROLLBACK
PG::UndefinedColumn: ERROR:  column "pfaffmanager_server_id" of relation "pfaffmanager_server_custom_fields" does not exist
LINE 1: INSERT INTO pfaffmanager_server_custom_fields (pfaffmanager_...
                                                       ^

看来它在查找 pfaffmanager_server_id。现在,让我们改用 pfaffmanager_server_id

字段名为 pfaffmanager_server_id

这是迁移代码:

class CreatePfaffmanagerServerCustomField < ActiveRecord::Migration[6.0]
  def change
    create_table :pfaffmanager_server_custom_fields do |t|
      t.integer :pfaffmanager_server_id, null: false
      t.string :name, limit: 256, null: false
      t.text :value
      t.timestamps null: false
    end
    add_index :pfaffmanager_server_custom_fields,
      [:pfaffmanager_server_id, :name],
      name: 'index_pfaffmanager_server_custom_fields_on_server_id_and_name'

  end
end

当我尝试访问 s.custom_fields 时发生了以下情况(上面的代码在访问时能返回 nil,但直到我尝试保存自定义字段时才失败)。我在这里调用了 register_custom_field_type

 pry(main)> s.custom_fields
   (1.1ms)  SELECT "pfaffmanager_server_custom_fields"."name", "pfaffmanager_server_custom_fields"."value" FROM "pfaffmanager_server_custom_fields" WHERE "pfaffmanager_server_custom_fields"."server_id" = 1 ORDER BY id asc
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR:  column pfaffmanager_server_custom_fields.server_id does not exist
LINE 1: ...e" FROM "pfaffmanager_server_custom_fields" WHERE "pfaffmana...
                                                             ^

from /home/pfaffman/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/rack-mini-profiler-2.3.0/lib/patches/db/pg.rb:69:in `exec_params'
Caused by PG::UndefinedColumn: ERROR:  column pfaffmanager_server_custom_fields.server_id does not exist
LINE 1: ...e" FROM "pfaffmanager_server_custom_fields" WHERE "pfaffmana...
                                                             ^

现在它又在查找 server_id 了。

看来 custom_fields 想要一种字段名,而 save_custom_fields 却想要另一种。

它并不在意索引名称是什么,对吧?

1 个赞

我想不是的,没错。

我认为第二种方法(使用 pfaffman_server_id 作为列名)可能是应该追求的目标。

我在想覆盖这个方法是否会有帮助:

你可以在控制台中检查当前值,例如:

Server.new.custom_fields_fk

如果结果是 server_id,我建议在你的 Server 模型中覆盖该方法:

class Server < ...
  def custom_fields_fk
    "pfaffman_server_id"
  end
end
1 个赞

或者也许是这一段::thinking:

1 个赞

非常感谢!所以,如果我使用 pfaffmanager_serfver_id,那么 Server.new.custom_fields_fk 就是 pfaffmanager_server_id。也许我应该使用 server_id,然后按照你上面的建议进行覆盖。

看起来 refresh_custom_fields_from_db 正在尝试使用错误的名称。我还不太清楚 _custom_fields.order... 具体在做什么。

我打算试试使用 server_id 看看会发生什么,以及我是否随后可以覆盖 custom_fields_fk

1 个赞

哈哈!你成功了!非常感谢。我把它改成了 server_id,然后在 server.rb 模型中做了如下修改:

    def custom_fields_fk
      @custom_fields_fk ||= "server_id"
    end

只要这仅针对当前模型生效,而不会破坏 user_custom_field 及其相关功能,我想我就可以准备再次在 Best way to enforce permissions--controller or constraint? 上折磨自己了。之后我还可以添加路由,用来为新自定义字段填充……一些内容。

真的无法用言语表达我的感激之情。你很可能帮我节省了一整天的时间。我欠你一杯 :beer:

1 个赞

太好了!:tada: 如果你能想到让它更加通用的方法,我们非常欢迎提交 PR。

1 个赞

如果我有新的想法,我会告知您或提交一个 PR,但我认为一个想要使用自定义字段的插件已经相当边缘化了。

1 个赞

OM_fk_G.

custom_fields_fk 看起来太蠢了,以至于我以为它是我想删除的东西,并且给它起了个蠢名字,这样我以后就知道可以安全地删除它了。现在是时候了,所以我删了它。然后我的 specs 就失败了。

代码里甚至还有一条带有指向这个主题链接的注释。

1 个赞