Je veux pouvoir créer un enregistrement dans la base de données mais empêcher ensuite Rails d’apporter des modifications à partir de ce moment. Je comprends que des changements seront toujours possibles au niveau de la base de données.
Je pense que attr_read ne fait que ce que je veux au niveau des attributs, mais je ne veux pas avoir à spécifier manuellement les champs ... Je préférerais plutôt une approche de liste blanche.
De plus, je sais qu'il existe une option: read_only pour les associations, mais je ne veux pas limiter le "readonlyness" de l'objet à s'il a été récupéré via une association ou non.
Enfin, je veux pouvoir tout de même détruire un enregistrement de manière à inclure des éléments tels que: dépendants =>: détruire des œuvres dans les associations.
Ainsi, pour résumer: 1) autoriser la création d'enregistrements, 2) autoriser la suppression d'enregistrements et 3) empêcher la modification d'enregistrements persistants.
En regardant ActiveRecord::Persistence
, tout finit par appeler create_or_update
en coulisse.
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
result != false
end
Alors! Juste:
def readonly?
!new_record?
end
J'ai trouvé une solution plus concise, qui utilise le callback after_initialize
:
class Post < ActiveRecord::Base
after_initialize :readonly!
end
Pourquoi ne pas simplement créer un utilisateur sur la base de données ayant un accès en lecture seule et laisser Rails utiliser ce compte.
Toutefois, si vous souhaitez accéder au niveau du modèle, vous pouvez ajouter les éléments suivants à un modèle spécifique:
def readonly?
true
end
def before_destroy
raise ActiveRecord::ReadOnlyRecord
end
Cet article de blog est toujours valable: http://ariejan.net/2008/08/17/activerecord-read-only-models/
Fondamentalement, vous pouvez compter sur la validation d'ActiveRecord si vous ajoutez une méthode:
def readonly?
true
end
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
Voir aussi: http://guides.rubyonrails.org/active_record_callbacks.html
Cela semble être assez efficace et probablement un peu excessif, mais pour mon cas, je veux vraiment être sûr que mon application ne créera jamais, ne sauvegardera, ne mettra pas à jour ou ne détruira aucun enregistrement du modèle.
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
Puisque OP a demandé à pouvoir créer et détruire, mais pas enregistrer ni mettre à jour, je crois que cela fonctionnera
module SaveAndDestroyOnlyModel
before_save { raise ActiveRecord::ReadOnlyRecord }
before_update { raise ActiveRecord::ReadOnlyRecord }
end
class MyModel < ActiveRecord::Base
include SaveAndDestroyOnlyModel
# ...
end
Non exactement la bonne exception, mais assez proche, je pense.
Un validateur personnalisé peut faire ceci:
validate :nothing_changed, unless: :new_record? # make immutable
...
def nothing_changed
errors.add(:base, "Record is read-only") if self.changed?
end
Vous recherchez un moyen d'obtenir le même contrôle proposé par @Nate (en évitant toute forme de création/mise à jour/suppression) mais en l'utilisant uniquement dans des parties spécifiques de mon application et pour tous les modèles à la fois, j'ai créé ce Ruby raffinement :
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
Et pour ne l'utiliser que dans une partie spécifique du code:
class MyCoolMailerPreview < ActionMailer::Preview
using ReadOnlyRailsMode
end
(Ceci est un cas d'utilisation réel, je cherchais un moyen d'éviter que des personnes créent et modifient des enregistrements réels à partir de ActionMailer :: Previews, car je souhaite autoriser les aperçus en production, mais si par erreur quelqu'un crée un aperçu qui modifie les données réelles, cela deviendrait un chaos).
Le code est un peu moche en redéfinissant toutes les méthodes (create, create !, etc.) car le but est de changer le comportement de tous les modèles, et des rappels tels que "before_create" ne peuvent pas être utilisés à cette fin car ils ne seraient pas localement uniquement. à la portée "using", en changeant l'application entière.
Cette approche fonctionne pour moi, je peux explicitement bloquer toutes ces méthodes pour tous les modèles dans une seule classe et ne pas déranger le reste de l'application. Malheureusement, jusqu'à présent, les raffinements ne s'appliquaient pas aux sous-classes. Par conséquent, dans mon cas, je n'ai pas été en mesure de bloquer toutes les insertions dans la classe parent (ActionMailer :: Preview), ce qui était mon objectif initial, mais le blocage par classe est un bon point de départ.
Mon application nécessite d'affiner toutes les méthodes, mais le contrôle peut être effectué uniquement pour les méthodes intéressantes telles que détruire ou mettre à jour, ce qui peut fonctionner dans tous les cas, y compris celui de la question d'origine.