2009-11-19 8 views

答えて

8

@readonlyを(メソッド内で)trueに設定することで、AR :: Bオブジェクト全体をフリーズできますが、すべての属性がロックアウトされます。

私がお勧めする方法は、スーパーに渡す前に、現在の状態をチェックする属性のsetterメソッドを定義することである:

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

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

[EDIT]または属性の大量のために:

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 

他の人と共有するためにプラグインを使用することを唱えてくれる人はどれですか?disable_attributes :author, :content, :comments, :when => :published?

+0

感謝。多数の属性については、これらのセッターをロックする必要のある属性の配列を渡して上書きするためにクラス評価を行うことができますか? –

+0

正確に。私はclass_evalとして書くべきかどうか議論しましたが、読みやすくするためにそれに反対しました。私は他人のためにそれをタックします。 –

3

特定の状態になっている場合は、妥当性検査で属性の変更をブロックします。あなたは、バリデーションに直接物事をハードコードすることができます。しかし、私は、ホワイトリスト(状態で変更できる属性のリスト)またはブラックリスト(状態で変更できない属性のリスト)を定義する定数を使用するほうが、少し頑強なアプローチを選択します。

両方のアプローチの例を示します。それぞれのアプローチでは、現在の状態または新しい状態を文字列として返す、状態メソッドがモデル内にあることを前提としています。

ホワイトリスト方式

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 

ブラックリスト方式

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

私が投稿した後、私の解決策は本書と本質的に似ていることが分かった。私は私がもっと明示的だと信じていますが、これは宣言的であるため、これも良いことです。 –

+0

EmFi - 応答していただきありがとうございます。これは私のRubyの知識のために少し進んでいますが、単なるアンパサンドを含む2行の行については、これが&&の意味でない限り、何が起こっているのか正確には分かりません - 実際にはこれをテストするエラーが発生しています: & 'for {}:ハッシュ –

+1

&は配列の交差演算子です。 http://ruby-doc.org/core/classes/Array.html#M002212配列Aと配列Bは、両方の配列に共通の要素を返します。 A&B == Aの場合、Aのすべての要素はBに入っています。また、複数回コピーして貼り付けたタイプミスのためにエラーが発生しています。変更のすべてのインスタンスが変更されているはずです。私はこれを反映するためにソリューションを更新しました – EmFi

14

編集検証エラーで編集しないでくださいどの属性:

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 

ティsは2.236で導入されたActiveRecord::Dirty拡張を使用します。

+0

フィードバックFrançoisのおかげで、これはこれを行うための別の素晴らしい方法のように見えます。 –

0

特定の状態が単にpersisted?の場合は、attr_readonlyが最適です。

attr_readonly(*attributes)公共

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

テストする(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 
関連する問題