web-dev-qa-db-fra.com

Existe-t-il un moyen simple de rendre un modèle Rails ActiveRecord en lecture seule?

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. 

55
brettish

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
73
scragz

J'ai trouvé une solution plus concise, qui utilise le callback after_initialize:

class Post < ActiveRecord::Base
  after_initialize :readonly!
end
46
kwarrick

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
25
Mike Lewis

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
16
apneadiving

TL; DR pour OP

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

  # ... 
end

Généralement

  • Pour empêcher crée seulement: before_create { false } 
  • Pour empêcher les mises à jour uniquement: before_update { false }
  • Pour éviter de détruire seulement: before_destroy { false } # does not prevent delete

Voir aussi: http://guides.rubyonrails.org/active_record_callbacks.html

4
Barry

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.

2
Nate

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
0
Burnzoire

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.

0
Everton J. Carpes