Rails has_many有一个整数主键和一个字符串外键

时间:2014-10-05 20:03:19

标签: ruby-on-rails postgresql foreign-keys nosql

我有三个导轨对象:UserDemoUserStatsUserDemoUser都有许多与之关联的统计信息。 UserStats表存储在Postgresql上(使用ActiveRecord)。 DemoUser存储在redis中。 DemoUser的id是(随机)字符串。 User的id是(标准轨道)递增整数。

stats表格的user_id列可以包含User ID或DemoUser ID。因此,user_id列是一个字符串,而不是整数。

从随机字符串转换为整数并不是一种简单的方法,但是将整数id转换为字符串(42 -> "42")是一种非常简单的方法。这些ID 保证不会重叠(不会有User个实例与DemoUser具有相同ID。

我有一些管理这些统计数据的代码。我希望能够传递some_user个实例(可以是DemoUserUser)然后能够使用id获取Stats,更新它们等。能够为has_many模型定义User也很高兴,所以我可以做user.stats

但是,像user.stats这样的操作会创建一个类似

的查询

SELECT "stats".* FROM "stats" WHERE "stats"."user_id" = 42

然后以PG::UndefinedFunction: ERROR: operator does not exist: character varying = integer

打破

有没有办法让数据库(Postgresql)或Rails在JOIN上自动转换id?(从整数到字符串的转换应该很简单,例如{{1 }})

编辑:更新问题,尽量让事情变得清晰。很高兴接受编辑或回答问题以澄清任何事情。

5 个答案:

答案 0 :(得分:1)

您无法在没有内置相等运算符的两种类型之间定义外键。

正确的解决方案是将字符串列更改为整数。


在你的情况下,你可以=创建一个用户定义的varchar = string运算符,但这会在数据库的其他地方产生混乱的副作用;例如,它会允许虚假代码,如:

SELECT 2014-01-02 = '2014-01-02'

运行没有错误。所以我不打算给你这么做的代码。如果您真的觉得这是唯一的解决方案(我认为这可能不正确),请参阅CREATE OPERATORCREATE FUNCTION

答案 1 :(得分:1)

一种选择是在user_id表中添加单独的demo_user_idstats列。 user_id将是一个整数,您可以将其用作PostgreSQL中users表的外键,demo_user_id将是一个链接到Redis数据库的字符串。如果您想正确处理数据库,可以使用真正的FK将stats.user_id链接到users.id以确保参照完整性,并且包含CHECK约束以确保{{1}中的一个}和stats.user_id为NULL:

stats.demo_user_id

当然,你必须对ActiveRecord进行一些正确约束你的数据库,AR不相信像FK和CHECK这样的花哨的东西,即使它们是数据完整性所必需的。您必须手动控制check (user_id is null <> demo_user_id is null) ,但是某些定期扫描以确保它们与Redis中的值相关联将是一个好主意。

现在,您的demo_user_id可以使用与User列的标准关联来查找统计信息,而stats.user_id可以使用DemoUser

答案 2 :(得分:0)

暂时,我的解决方案&#39;不要在Rails中使用has_many,但如果需要,我可以在模型中定义一些辅助函数。 e.g。

class User < ActiveRecord::Base
  # ...
  def stats
    Stats.where(user_id: self.id.to_s) 
  end
  # ...
end

另外,我会定义一些帮助范围来帮助实施to_s翻译

class Stats < ActiveRecord::Base
  scope :for_user_id, -> (id) { where(user_id: id.to_s) }
  # ...
end

这应该允许像

这样的调用

user.statsStats.for_user_id(user.id)

答案 3 :(得分:0)

我想我之前误解了你的问题的一个细节,因为它被埋没在评论中。

(我强烈建议编辑您的问题,以便在评论显示问题中存在令人困惑/不完整的内容时澄清要点)。

您似乎需要从整数列到字符串列的外键,因为字符串列可能是整数,或者可能是某些不相关的字符串。 那是为什么你不能把它变成一个整数列 - 它不一定是有效的数字值,它可能是来自不同系统的文本密钥。

在这种情况下,典型的解决方案是使用合成主键和两个UNIQUE约束,一个用于来自每个系统的键,另外一个CHECK约束阻止两者设置。 E.g。

CREATE TABLE my_referenced_table (
   id serial,
   system1_key integer,
   system2_key varchar,
   CONSTRAINT exactly_one_key_must_be_set 
     CHECK (system1_key IS NULL != system2_key IS NULL),
   UNIQUE(system1_key),
   UNIQUE(system2_key),
   PRIMARY KEY (id),
   ... other values ...
);

然后,您可以从整数键表中引用system1_key的外键。

它并不完美,因为它不会阻止相同的值出现在两个不同的行中,一个用于system1_key,另一个用于system2_key

所以另一种选择可能是:

CREATE TABLE my_referenced_table (
   the_key varchar primary key,
   the_key_ifinteger integer,
   CONSTRAINT integerkey_must_equal_key_if_set
     CHECK (the_key_ifinteger IS NULL OR (the_key_ifinteger::varchar = the_key)),
   UNIQUE(the_key_ifinteger),
   ... other values ...
);

CREATE OR REPLACE FUNCTION my_referenced_table_copy_int_key()
RETURNS trigger LANGUAGE plpgsql STRICT
AS $$
BEGIN
  IF NEW.the_key ~ '^[\d]+$' THEN
     NEW.the_key_ifinteger := CAST(NEW.the_key AS integer);
  END IF;
  RETURN NEW;
END;
$$;

CREATE TRIGGER copy_int_key
BEFORE INSERT OR UPDATE ON my_referenced_table
FOR EACH ROW EXECUTE PROCEDURE my_referenced_table_copy_int_key();

如果它是一个整数,则复制整数值,因此你可以引用它。

总而言之,我认为整个想法有点不确定。

答案 4 :(得分:0)

我想我可能有一个解决你的问题的方法,但也许不是一个更好的问题:

class User < ActiveRecord::Base

  has_many :stats, primary_key: "id_s"

  def id_s
    read_attribute(:id).to_s
  end

end

仍然使用第二个虚拟列,但使用Rails关联可能更方便,并且与数据库无关。