Игнорировать столбец «только для чтения» при создании и обновлении в Ruby ActiveRecord - PullRequest
5 голосов
/ 12 июля 2011

Я ищу решение следующей проблемы: у меня есть сущность ActiveRecord, которая поддерживается обновляемым представлением базы данных (в DB2 через гем activerecord-jdbc-adapter).Это представление содержит один столбец, который рассчитывается из других столбцов и является «только для чтения»: вы не можете установить этот столбец любым допустимым способом.Когда для этого объекта создается новая запись, это поле должно быть установлено , а не .Однако по умолчанию ActiveRecord устанавливает значение по умолчанию (NULL), которое отклоняется базой данных.

attr_readonly не является решением, поскольку исключает только столбец из обновлений, а не из созданий..

attr_ignore, например, реализованный гемом 'lincoln', также не является решением, поскольку тогда поле полностью игнорируется.Тем не менее, столбец все еще должен быть прочитан и быть доступным.На самом деле он даже используется как часть отношения.

Существуют способы запретить вам устанавливать определенный атрибут сущности ActiveRecord, но это обычно не препятствует включению этого атрибута в операторы create или update

Кто-нибудь знает, есть ли способ в ActiveRecord указать столбец как «никогда не устанавливать это поле»?

Обновление в ответ на Arsen7: я пытался использовать хук after_initialize дляудалите атрибут из вновь созданной сущности, чтобы он не был включен в составленный SQL.Проблема в том, что атрибут полностью удален и больше не доступен, в значительной степени идентичен описанной выше ситуации 'igonre_attr'.Из-за кэширования обойти это не тривиально и потребуется дополнительная логика для принудительной перезагрузки сущностей этих конкретных таблиц.Этого, вероятно, можно достичь, переопределив create для добавления «перезагрузки», в дополнение к использованию after_initialize.

(Как указал Arsen7, я забыл упомянуть, что я на ActiveRecord 3.0.9)

Мое решение

Поскольку мои сущности уже наследуются от подкласса ActiveRecord::Base, я решил добавить before_create и after_create хуки.В хуке before_create я удаляю «вычисленные» столбцы из @attributes экземпляра.В хуке after_create я снова добавляю их и считываю значения «вычисленных» столбцов из базы данных, чтобы установить их на значения, которые они получили.

Добавление таких хуков почти идентично переопределению create, поэтому я считаю ответ Arsen7 правильным.

Ответы [ 3 ]

2 голосов
/ 15 июля 2011

Я достиг того же результата, переопределив ActiveRecord :: Base # arel_attributes в моей модели:

Class Model < ActiveRecord::Base
  @@skip_attrs = [:attr1, :attr2]     

  def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
    skip_attrs = @@skip_attrs.map { |attr| [self.class.arel_table[attr] }
    attrs = super(include_primary_key, include_readonly_attributes, attribute_names)
    attrs.delete_if {|key, value| skip_attrs.include?(key) }         
  end 
end

Атрибуты в массиве @@ skip_attrs будут игнорироваться ActiveRecord как для операторов вставки, так и для операторов обновления, так как они оба полагаются на arel_attributes_values ​​для возврата списка атрибутов модели.

Лучшим решением было бы исправление: ActiveRecord :: Base # arel_attributes вместе с макросом attr_ignore, похожим на attr_readonly.

ура

2 голосов
/ 13 июля 2011

Боюсь, ActiveRecord не подготовлен для нужного вам варианта использования. (Кстати: какую версию AR вы используете?)

Но я считаю, что вы можете применить два возможных обходных пути.

Первый - переписать метод 'create' вашей модели, выполнив какой-то другой SQL, подготовленный вручную в худшем случае. Я полагаю, что реальной функцией, которую нужно будет перезаписать, будет не само «создание», а, взглянув на источники, вы сможете найти такую.

Другим решением, и я считаю, более элегантным, было бы создание триггера в базе данных. Я больше в мире PostgreSQL, где я бы использовал 'CREATE RULE', но, глядя на документацию DB2, я вижу, что в DB2 есть триггеры 'INSTEAD OF' . Я надеюсь, что это может быть полезно.

0 голосов
/ 19 января 2017

Я знаю, что это очень старо, но я боролся с этой самой проблемой. У меня есть база данных с триггером, который вычисляет значение индекса на основе максимального значения в ключе. Я также хочу предотвратить возможность установки значения в AR, поскольку оно может отбросить индекс, применяемый при вставке строк.

CREATE TRIGGER incr_col_idx
AFTER INSERT ON fl_format_columns
FOR EACH ROW
BEGIN UPDATE fl_format_columns
SET idx = (SELECT coalesce(max(idx),-1) + 1
           FROM fl_format_columns
           WHERE fl_file_format_id = new.fl_file_format_id)
WHERE fl_file_format_id = new.fl_file_format_id AND name = new.name;
END;

Я пробовал разные вещи, но всегда возвращался к переопределению сеттера напрямую.

# @raise ArgumentError when an attempt is made to set a value that is calculated in db
def idx=(o)
  raise ArgumentError,'the value of idx is set by the db. attempts to set value is not allowed.' unless o.nil?
end

Это потребует перехвата исключения, а не опроса массива ошибок, но на этом я и остановился. Он проходит следующую проверку:

context 'column index' do
  it 'should prevent idx from being set' do
    expect{FL_Format_Column.create(fl_file_format_id:-1,name:'test idx',idx:0)}.to raise_error(ArgumentError)
  end
  it 'should calculate idx relative to zero' do
    x = FL_Format_Column.create(fl_file_format_id:-1,name:'test_idx_nil')
    expect(x.errors[:idx].any?).to be false
    expect(FL_Format_Column.last.idx).to be > -1
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...