2014-12-31 83 views
1

我使用readonly?函數標記我的Invoice已發送後不可變;對於InvoiceLine s,我只是將readonly?函數代入Invoice繞過只讀?當保存ActiveRecord

一個簡單的例子:

class Invoice < ActiveRecord::Base 
    has_many :invoice_lines 
    def readonly?; self.invoice_sent? end 
end 

def InvoiceLine < ActiveRecord::Base 
    def readonly?; self.invoice.readonly? end 
end 

這個偉大的工程,只是在一個特定的情況下我想更新InvoiceLine不管readonly?屬性。

有沒有辦法做到這一點?

我試過使用save(validate: false),但這沒有效果。我看着在AR源persistence.rb,這似乎只是做:

def create_or_update 
    raise ReadOnlyRecord if readonly? 
    ... 
end 

是否有避免這種明顯的方法是什麼?

A(有點髒)的解決方法,我可能會在Python做:

original = line.readonly? 
line.readonly? = lambda: false 
line.save() 
line.readonly? = original 

但是,這並不在Ruby中工作,因爲功能不是第一類對象...

+0

'Proc'和'lambda'是Ruby中的第一類,如果這可以幫助您解決「有點髒」的解決方法。雖然可能與Python有不同的屬性。 –

+0

@EricPlaton問題是我不能分配給'line.readonly?'; 'line.readonly? = - > {}'是一個語法錯誤(由於'?',但即使沒有,我會調用'line.readonly ='方法,這與我想要的非常不同)。 – Carpetsmoker

回答

1

這裏是一個答案,但我不喜歡它。我會建議對設計思考三次:如果你使這個數據不可變,並且你確實需要改變它,那麼可能會有設計問題。如果ORM和數據存儲區「不同」,則不要讓任何頭痛。


一種方法是使用元編程設施。說你要的invoice_line1item_num更改爲123,你可以入手:

invoice_line1.instance_variable_set(:@item_num, 123) 

注意,上面不會有直接的ActiveRecord模型的屬性工作,所以就需要進行調整。但是,我真的會建議重新考慮設計,而不是陷入黑魔法之中。

2

我遇到了一個類似的問題,只有一個只讀字段,並使用update_all解決了它。

它需要一個ActiveRecord::Relation所以它會是這樣的......

Invoice.where(id: id).update_all("field1 = 'value1', field2 = 'value2'")

3

你可以很容易在實例化對象重新定義一個方法,但語法定義,而不是分配。例如。進行更改,需要一個調整到一個只讀否則對象架構的時候,我已經知道使用這種形式:

line = InvoiceLine.last 
def line.readonly?; false; end 

的Et瞧,狀態重寫!實際發生的是對象本徵類中的readonly?方法的定義,而不是其類。儘管如此,這實際上是在物體的內臟中咕嚕咕嚕;在模式改變之外,這是一種嚴重的代碼異味。

所以最好在你的模型代碼中明確地表達需求。你可以這樣寫:

class InvoiceLine < ActiveRecord::Base 
    attr_accessor :force_writeable 

    def readonly? 
    invoice.readonly? unless force_writeable 
    end 
end 

,因爲這樣的客戶端代碼可以說

line.force_writable = true 
line.update(description: "new narrative line") 

我還是真的不喜歡它,因爲它仍然允許外部代碼來決定的內部行爲,並離開對象其他代碼可能會跳過狀態更改。這裏有一個稍微更安全,更rubyish變種:然後

class InvoiceLine < ActiveRecord::Base 
    def force_update(&block) 
    saved_force_update = @_force_update 
    @_force_update = true 
    result = yield 
    @_force_update = saved_force_update 
    result 
    end 

    def readonly? 
    invoice.readonly? unless @_force_update 
    end 
end 

客戶端代碼可以這樣寫:

line.force_update do 
    line.update(description: "new description") 
end 

最後,這可能是最精密的機制,可以僅允許某些屬性改變。你可以這樣做,在一個before_save回調,並拋出一個異常,但我很喜歡用這個驗證依賴於ActiveRecord的髒屬性模塊:

class InvoiceLine < ActiveRecord::Base 
    validate :readonly_policy 

    def readonly_policy 
    if invoice.readonly? 
     (changed - ["description", "amount"]).each do |attr| 
     errors.add(attr, "is a read-only attribute") 
     end 
    end 
    end 
end 

我喜歡這個有很多;它將所有的領域知識放入模型中,它使用支持和內置的機制,不需要任何猴子修補或元編程,併爲您提供可以傳播回視圖的好的錯誤消息。

相關問題