Rails行为不端,发布了Postgres SERIAL NOT NULL列

时间:2010-10-13 00:14:40

标签: ruby-on-rails postgresql ruby-on-rails-3 plpgsql

我正在开发一个带有PostgreSQL 8.4数据库后端的(当前)Rails 2.3.x应用程序。在我的Rails应用程序中,我有一个对应于数据库表的模型,该数据库表有两列数据类型SERIAL并设置为NOT NULL。我将这些列中的一个设置为Rails中的主键和PostgreSQL约束。

表格定义:

CREATE TABLE problem_table
(
  col1 serial NOT NULL,
  col2 serial NOT NULL,
  other_col1 character varying,
  other_col2 character varying,
  ...,
  CONSTRAINT problem_table_pkey PRIMARY KEY (col1)
);

模型类定义:

class ModelClass1 < ActiveRecord::Base
  self.table_name = 'problem_table'
  self.primary_key = 'col1'
end

我的问题是关于非主键SERIAL NOT NULL列。当我尝试执行Rails ActiveRecord :: Base#create时,Rails正确地没有为主键SERIAL NOT NULL列设置一个值,而是为另一个设置了一个NULL值的列值,这导致PostgreSQL抱怨NOT NULL列被设置为NULL。

我告诉Rails做什么:

ModelClass1.create(
  other_col1: 'normal'
  other_col2: 'data',
  ...
);

Rails告诉PostgreSQL:

INSERT INTO problem_table (
  col2, 
  other_col1, 
  other_col2,
  ...
) VALUES (
  NULL,
  'normal',
  'data',
  ...
);

我的问题是,如何让Rails停止为此列传递NULL并且不传递任何内容,让DEFAULT nextval(my_seq)接管?或者,如果不可能,我怎么能告诉PostgreSQL在传递时忽略这个NULL值和/或认识到它与'设为DEFAULT'相同?

我会尝试只修补Rails 2.3.x ActiveRecord内部版本,但我知道如果我这样做,那么在转换到Rails 3时我会被搞砸。

我已经考虑过尝试使用PL / pgSQL触发器来修复问题,但我无法弄清楚如何告诉PostgreSQL PL / pgSQL“取消定义”NEW.col2值或者说NEW.col2 := DEFAULT(不起作用)。

感谢答案和/或建议!

3 个答案:

答案 0 :(得分:4)

更简单的方法是定义自己的序列,并在ActiveRecord回调中使用Postgres自己的nextval()nextval()处理一个步骤推进序列,并在一个原子操作中返回下一个值。

在迁移中:

def self.up
    execute "CREATE SEQUENCE myseq"
end

def self.down
    execute "DROP SEQUENCE myseq"
end    

在模型中:

before_save :set_column_from_sequence, :on => :create

def set_column_from_sequence
    self.mycolumn = self.class.connection.select_value("SELECT nextval('myseq')")
end

答案 1 :(得分:0)

不确定精确的PL / pgSQL语法(我的PostgreSQL安装在家里所以我不能玩它)但在Oracle PL / SQL中我会做类似的事情

CREATE OR REPLACE TRIGGER MYSCHEMA.PROBLEM_TABLE_BI
  BEFORE INSERT ON MYSCHEMA.PROBLEM_TABLE
  REFERENCING NEW AS NEW
  FOR EACH ROW
BEGIN
  IF :NEW.COL2 IS NULL THEN
    :NEW.COL2 := MY_SEQ.NEXT_VAL;
  END IF;
END PROBLEM_TABLE_BI;

PL / pgSQL应该是类似的。

希望这有帮助。

答案 2 :(得分:0)

找到有用的东西。我可能需要在很多表中使用不同的序列来增加第二个SERIAL列。下面是一个解决方案,其中任何数量的表只需要1个触发函数,其中col1由唯一序列递增。

CREATE OR REPLACE FUNCTION fn_set_col1_as_nextval_sequence_if_null()
  RETURNS trigger AS
$BODY$
  BEGIN
    IF NEW.col1 IS NULL THEN
      SELECT nextval(TG_ARGV[0]) INTO NEW.col1;
    END IF;
    RETURN NEW;
  END;
$BODY$
  LANGUAGE 'plpgsql';

CREATE TRIGGER trg_set_col1_as_nextval_sequence_on_problem_table_create
BEFORE INSERT
ON problem_table
FOR EACH ROW
EXECUTE PROCEDURE fn_set_col1_as_nextval_sequence_if_null('problem_table_col1_seq');

如果可能的话,我仍然想弄清楚如何修复2.3.x和3.0的Rails行为,但这将同时起作用。