2009-11-19 11 views

risposta

8

È possibile bloccare un intero oggetto AR :: B impostando @readonly su true (in un metodo), ma questo bloccherà tutti gli attributi.

Il modo in cui mi sento di raccomandare è di definire i metodi attributo setter che controllano lo stato attuale prima di passare a super:

class Post < ActiveRecord::Base 
    def author=(author) 
    super unless self.published? 
    end 

    def content=(content) 
    super unless self.published? 
    end 
end 

[EDIT] O per una grande quantità di attributi:

class Post < ActiveRecord::Base 
    %w(author content comments others).each do |method| 
    class_eval <<-"end_eval", binding, __FILE__, __LINE__ 
     def #{method}=(val) 
     super unless self.published? 
     end 
    end_eval 
    end 
end 

Che ovviamente vorrei inserire in un plug-in per condividere con altri, e aggiungere un bel DSL per l'accesso come: disable_attributes :author, :content, :comments, :when => :published?

+0

Grazie per il feedback Colin. Per un gran numero di attributi suppongo che potrei fare un'eval class per sovrascrivere questi setter passando una serie di attributi che devono essere bloccati, eh? –

+0

Precisamente. Ho discusso se scriverlo come un class_eval, ma ho deciso contro la leggibilità. Lo affronterò anche se per gli altri. –

3

Si potrebbe aggiungere ac convalida di ustom per bloccare le modifiche agli attributi se ti trovi in ​​un determinato stato. È possibile codificare direttamente le cose direttamente nella convalida. Ma preferisco l'approccio un po 'più robusto usando le costanti che definiscono una lista bianca (lista di attributi che possono cambiare in uno stato) o una lista nera (lista di attributi che non possono cambiare in uno stato).

Ecco un esempio di entrambi gli approcci. Ogni approccio presuppone che ci sia un metodo di stato nel modello che restituisce lo stato corrente/nuovo come una stringa.

White List Approccio

WhiteListStateLockMap = { 
    "state_1" => [ 
    "first_attribute_allowed_to_change_in_state_1", 
    "second_attribute_allowed_to_change_in_state_1", 
    ... 
    ], 
    "state_2" => [ 
    "first_attribute_allowed_to_change_in_state_2", 
    "second_attribute_allowed_to_change_in_state_2", 
    ... 
    ], 
    ... 
} 

validates :state_lock 

def state_lock 
    # ensure that all changed elements are on the white list for this state. 
    unless changed & WhiteListStateLockMap[state] == changed 
    # add an error for each changed attribute absent from the white list for this state. 
    (changed - WhiteListStateLockMap[state]).each do |attr| 
     errors.add attr, "Locked while #{state}" 
    end 
    end 
end 

approccio nero Lista

BlackListStateLockMap = { 
    "state_1" => [ 
    "first_attribute_not_allowed_to_change_in_state_1, 
    "second_attribute_not_allowed_to_change_in_state_1, 
    ... 
    ], 
    "state_2" => [ 
    "first_attribute_not_allowed_to_change_in_state_2", 
    "second_attribute_not_allowed_to_change_in_state_2", 
    ... 
    ], 
    ... 
} 

validates :state_lock 

def state_lock 
    # ensure that no changed attributes are on the black list for this state. 
    unless (changed & BlackListStateLockMap[state]).empty? 
    # add an error for all changed attributes on the black list for this state. 
    (BlackListStateLockMap[state] & changed).each do |attr| 
     errors.add attr, "Locked while #{state}" 
    end 
    end 
end 
+0

Dopo aver pubblicato, mi sono reso conto che la mia soluzione era essenzialmente simile a questa, tranne che scritta in modo diverso. Credo che il mio sia più esplicito, ma anche questo è positivo, in quanto è più dichiarativo. –

+0

EmFi - grazie per la risposta.Questo è un po 'avanzato per la mia conoscenza di Ruby, in termini di due linee che coinvolgono la singola e commerciale, non sono sicuro di cosa stia succedendo lì, a meno che questo non sia inteso come un && - In realtà sto ricevendo un errore testando questo: metodo indefinito ' & 'per {}: Hash –

+1

& è l'operatore di intersezione per gli array. http://ruby-doc.org/core/classes/Array.html#M002212. Array A & Array B restituisce gli elementi comuni a entrambi gli array. Se A & B == A allora tutti gli elementi di A sono in B. Inoltre stai ricevendo l'errore a causa di un mio errore tipografico che è stato copiato e incollato più volte. Tutte le istanze di modifiche dovrebbero essere cambiate. Ho aggiornato la soluzione per riflettere questo – EmFi

14

Editing attributi che non deve essere modificato è un errore di convalida:

class Post < ActiveRecord::Base 
    validate :lock_down_attributes_when_published 

    private 

    def lock_down_attributes_when_published 
    return unless published? 

    message = "must not change when published" 
    errors.add(:title, message) if title_changed? 
    errors.add(:published_at, message) if published_at_changed? 
    end 
end 

Thi s usa le estensioni ActiveRecord::Dirty introdotte in 2.2 circa.

+0

Grazie per il feedback François, questo sembra un altro ottimo modo per farlo. –

0

Se lo stato particolare è semplicemente persisted?, quindi attr_readonly è l'opzione migliore.

attr_readonly(*attributes)pubblico

Attributes listed as readonly will be used to create a new record but update operations will ignore these fields.

Per testare (per gentile concessione di THAiSi):

class MyModel < ActiveRecord::Base 
    attr_readonly :important_type_thingie 
end 

#RSpec 
describe MyModel do 
its('class.readonly_attributes') { should include "important_type_thingie" } 

it "should not update the thingie" do 
    m = create :my_model, :important_type_thingie => 'foo' 
    m.update_attributes :important_type_thingie => 'bar' 
    m.reload.important_type_thingie.should eql 'foo' 
end 
end 
Problemi correlati