Есть ли простой способ сделать модель Rails ActiveRecord доступной только для чтения?
Я хочу иметь возможность создать запись в БД, а затем не позволять Rails делать изменения с этой точки. Я понимаю, что изменения будут по-прежнему возможны на уровне БД.
Я верю, что attr_readonly делает то, что я хочу, на уровне атрибута, но я не хочу вручную указывать поля... Я предпочел бы иметь более белый подход.
Кроме того, я знаю, что для ассоциаций есть опция: read_only, но я не хочу ограничивать "readonlyness" объекта, если он был получен через ассоциацию или нет.
Наконец, я хочу, чтобы все еще можно было уничтожить запись, поэтому такие вещи, как: dependent = > : destroy, работают в ассоциациях.
Итак, суммируем: 1) разрешаем создание записей, 2) разрешаем удаление записей и 3) предотвращаем изменение сохраненных записей.
Ответы
Ответ 1
Глядя на 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
Ответ 2
Я нашел более сжатое решение, которое использует обратный вызов after_initialize
:
class Post < ActiveRecord::Base
after_initialize :readonly!
end
Ответ 3
Почему бы просто не создать пользователя в базе данных, имеющей доступ только для чтения, и использовать рельсы для этой учетной записи.
Однако, если вам нужен доступ к уровню модели, вы можете добавить следующее к определенной модели:
def readonly?
true
end
def before_destroy
raise ActiveRecord::ReadOnlyRecord
end
Ответ 4
Этот пост в блоге по-прежнему действителен: http://ariejan.net/2008/08/17/activerecord-read-only-models/
В основном вы можете положиться на проверку ActiveRecord, если добавить метод:
def readonly?
true
end
Ответ 5
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
Ответ 6
Это кажется довольно эффективным и, вероятно, немного переборщием, но для моего случая я действительно хочу быть уверенным, что мое приложение никогда не будет создавать, сохранять, обновлять или уничтожать любые записи в модели, когда-либо.
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
Поскольку OP попросил, чтобы иметь возможность создавать и уничтожать, но не сохранять или обновлять, я считаю, что это сработает
module SaveAndDestroyOnlyModel
before_save { raise ActiveRecord::ReadOnlyRecord }
before_update { raise ActiveRecord::ReadOnlyRecord }
end
class MyModel < ActiveRecord::Base
include SaveAndDestroyOnlyModel
# ...
end
Не совсем правильное исключение, но достаточно близко, я думаю.
Ответ 7
Пользовательский валидатор может сделать это:
validate :nothing_changed, unless: :new_record? # make immutable
...
def nothing_changed
errors.add(:base, "Record is read-only") if self.changed?
end
Ответ 8
В поисках способа достижения того же элемента управления, предложенного @Nate (избегая любого вида создания/обновления/удаления), но используя его только в определенных частях моего приложения и для всех моделей одновременно, я создал это уточнение Ruby:
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), что было моей первоначальной целью, но блокировка для каждого класса хорошая отправная точка.
Мое приложение требует уточнения всех методов, но контроль может быть выполнен только для интересных методов, таких как уничтожение или обновление, и они могут работать во всех случаях, в том числе из исходного вопроса.