Rails плохо себя ведет с колонкой Postgres SERIAL NOT NULL - PullRequest
4 голосов
/ 13 октября 2010

Я разрабатываю (в настоящее время) приложение Rails 2.3.x с базой данных PostgreSQL 8.4. В моем приложении на 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 при его передаче и / или признать, что оно совпадает с 'set as DEFAULT'?

Я бы попытался просто пропатчить внутренние компоненты Rails 2.3.x ActiveRecord, но я знаю, что если бы я это сделал, то был бы не уверен, когда дело доходит до перехода на Rails 3.

Я пытался исправить положение с помощью триггера PL / pgSQL ПЕРЕД ВСТАВКОЙ, но не могу понять, как сказать PostgreSQL с PL / pgSQL «отменить определение» значения NEW.col2 или сказать NEW.col2 : = ПО УМОЛЧАНИЮ (что не работает).

Ответы и / или предложения приветствуются!

Ответы [ 3 ]

4 голосов
/ 24 ноября 2010

Самый простой способ сделать это, вероятно, определить собственную последовательность и использовать собственный nextval() Postgres внутри обратного вызова ActiveRecord. 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
0 голосов
/ 13 октября 2010

Нашел что-то, что работает. Мне потенциально нужен этот второй столбец SERIAL в довольно многих таблицах, использующих различные последовательности для приращения. Ниже приведено решение, в котором для любого количества таблиц, содержащих столбец 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');

Я все еще хотел бы выяснить, как исправить поведение Rails для 2.3.x и 3.0, если это возможно, но пока это будет работать.

0 голосов
/ 13 октября 2010

Не уверен насчет точного синтаксиса 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 должен быть похожим.

Надеюсь, это поможет.

...