Есть ли простой способ сделать модель Rails ActiveRecord доступной только для чтения? - PullRequest
55 голосов
/ 13 апреля 2011

Я хочу иметь возможность создать запись в БД, но затем запретить Rails вносить изменения с этого момента. Я понимаю, что изменения по-прежнему возможны на уровне БД.

Я полагаю, что attr_readonly делает то, что я хочу на уровне атрибутов, но я не хочу указывать поля вручную ... Я бы предпочел более подход белого списка.

Кроме того, я знаю, что есть опция: read_only для ассоциаций, но я не хочу ограничивать «только чтение» объекта тем, был ли он получен через ассоциацию или нет.

Наконец, я хочу иметь возможность уничтожать записи, поэтому такие вещи, как: зависимый =>: уничтожить работы в ассоциациях.

Итак, подведем итог: 1) разрешить создание записей, 2) разрешить удаление записей и 3) предотвратить изменение записей, которые были сохранены.

Ответы [ 8 ]

74 голосов
/ 13 апреля 2011

Глядя на ActiveRecord::Persistence, все в итоге вызывает create_or_update за кадром.

def create_or_update
  raise ReadOnlyRecord if readonly?
  result = new_record? ? create : update
  result != false
end

Так! Просто:

def readonly?
  !new_record?
end
47 голосов
/ 04 июня 2013

Я нашел более краткое решение, которое использует обратный вызов after_initialize:

class Post < ActiveRecord::Base
  after_initialize :readonly!
end
25 голосов
/ 13 апреля 2011

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

Однако, если вам нужен доступ на уровне модели, вы можете добавить к конкретной модели следующее:

 def readonly?
    true
  end

  def before_destroy
    raise ActiveRecord::ReadOnlyRecord
  end
16 голосов
/ 13 апреля 2011

Этот пост в блоге по-прежнему действителен: http://ariejan.net/2008/08/17/activerecord-read-only-models/

По сути, вы можете положиться на проверку ActiveRecord, если добавите метод:

def readonly?
  true
end
5 голосов
/ 28 апреля 2015

TL; DR для OP

class YourModel < ActiveRecord::Base
  before_save { false } # prevent create & update, allows destroy

  # ... 
end

Обычно

  • Чтобы предотвратить только создание: before_create { false }
  • Чтобы предотвратить только обновления: before_update { false }
  • Для предотвращения уничтожения только: before_destroy { false } # does not prevent delete

См. Также: http://guides.rubyonrails.org/active_record_callbacks.html

2 голосов
/ 13 января 2015

Это кажется довольно эффективным и, вероятно, немного излишним, но для моего случая я действительно хочу быть уверенным, что мое приложение никогда не создаст, не сохранит, не обновит или не уничтожит какие-либо записи в модели.

module ReadOnlyModel
  def readonly?() true end
  def create_or_update() raise ActiveRecord::ReadOnlyRecord end
  before_create { raise ActiveRecord::ReadOnlyRecord }
  before_destroy { raise ActiveRecord::ReadOnlyRecord }
  before_save { raise ActiveRecord::ReadOnlyRecord }
  before_update { raise ActiveRecord::ReadOnlyRecord }
end

class MyModel < ActiveRecord::Base
  include ReadOnlyModel
  # ...
end

Поскольку ОП попросил иметь возможность создавать и уничтожать, но не сохранять или обновлять, я считаю, что это будет работать

module SaveAndDestroyOnlyModel
  before_save { raise ActiveRecord::ReadOnlyRecord }
  before_update { raise ActiveRecord::ReadOnlyRecord }
end

class MyModel < ActiveRecord::Base
  include SaveAndDestroyOnlyModel
  # ...
end

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

0 голосов
/ 12 декабря 2018

В поисках способа достижения того же элемента управления, предложенного @Nate (избегая любого вида создания / обновления / удаления), но используя его только в определенных частях моего приложения и для всех моделей одновременно, я создал этот Ruby * 1001. * уточнение

module ReadOnlyRailsMode
  CLASS_METHODS    = ActiveRecord::Base.methods
    .select { |m| m =~ /(update|create|destroy|delete|save)[^\?]*$/ }

  INSTANCE_METHODS = ActiveRecord::Base.instance_methods
    .select { |m| m =~ /(update|create|destroy|delete|save)[^\?]*$/ }

  refine ActiveRecord::Base.singleton_class do
    CLASS_METHODS.each do |m|
      define_method(m) do |*args|
        raise ActiveRecord::ReadOnlyRecord
      end
    end
  end

  refine ActiveRecord::Base do
    def readonly?; true; end

    INSTANCE_METHODS.each do |m|
      define_method(m) do |*args|
        raise ActiveRecord::ReadOnlyRecord
      end
    end
  end
end

И использовать его только в определенной части кода:

class MyCoolMailerPreview < ActionMailer::Preview
  using ReadOnlyRailsMode 
end

(Это реальный вариант использования, я искал способ избежать людей, создающих и редактирующих реальные записи из ActionMailer :: Previews, потому что я хочу разрешить предварительный просмотр в производстве, но если по ошибке кто-то создает предварительный просмотр, который изменяет реальные данные, это стало бы хаосом).

Код немного некрасиво переопределяет все методы (create, create !, и т. Д.), Потому что цель состоит в том, чтобы изменить поведение всех моделей, и обратные вызовы типа "before_create" не могут использоваться для этой цели, поскольку быть локально только для «использования» области, изменяя все приложение.

Этот подход работает для меня, я могу явно заблокировать все эти методы для всех моделей только в одном классе и не связываться с остальной частью приложения. К сожалению, до сих пор уточнения не применялись к подклассам, поэтому в моем случае я не смог заблокировать все вставки по умолчанию в родительский класс (ActionMailer :: Preview), что было моей первоначальной целью, но блокировка для каждого класса хорошая отправная точка.

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

0 голосов
/ 08 декабря 2015

Пользовательский валидатор может сделать это:

validate :nothing_changed, unless: :new_record? # make immutable

...

def nothing_changed
  errors.add(:base, "Record is read-only") if self.changed?
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...