2011-07-12 20 views
5

我正在尋找一個解決以下問題:我有一個由可更新的數據庫視圖通過的ActiveRecord-JDBC支持(在DB2中的ActiveRecord實體適應寶石)。該視圖包含從其他列計算出來的一列,並且是「只讀」的:您無法以任何有效方式設置該列。當爲此實體創建新記錄時,應該設置該字段而不是。但是,默認情況下,ActiveRecord會將其設置爲「默認」(NULL),該值將被數據庫拒絕。忽略「只讀」列在創建和紅寶石更新的ActiveRecord

attr_readonly是不是一個解決方案,因爲只有排除從更新一列,而不是從創建。

attr_ignore,如由「林肯寶石實現,不是解決辦法或者,因爲這時該字段被完全忽略。但是,該列仍然需要閱讀並且可以訪問。它甚至被用作關係的一部分。

有辦法阻止你設置一個ActiveRecord實體的特定屬性,但通常不會阻止包含在創建或更新語句屬性

有誰知道是否有一個辦法ActiveRecord將列指定爲'從未設置此字段'?

更新,響應Arsen7: 我已經嘗試使用after_initialize鉤從一個新創建的實體刪除屬性,因此不包括在建的SQL。這樣做的麻煩在於該屬性被完全刪除並且根本不可用,與上述「igonre_attr」情況非常相似。由於緩存,這不是微不足道的,並且需要額外的邏輯來強制重新加載這些特定表的實體。除了使用after_initialize之外,通過重寫create來添加'重新加載'也許可以實現。

(正如Arsen7指出的,我忘了說我在ActiveRecord的3.0.9)

我的解決方案

由於我的實體已經從ActiveRecord::Base一個子類繼承,我已經選擇添加before_createafter_create掛鉤。在before_create鉤子中,我從實例的@attributes中刪除了「計算」列。在after_create鉤子中,我再次添加它們並從數據庫中讀取'calculated'列的值,以將它們設置爲它們接收到的值。

增加這樣的鉤子幾乎是相同的覆蓋創造,所以我認爲Arsen7的答案是正確的。

回答

2

恐怕ActiveRecord沒有準備好您需要的用例。 (順便說一句:你使用哪個版本的AR?)

但我相信你可能會應用兩種可能的解決方法。

首先,是要覆蓋模型的「create」方法,執行一些其他的SQL,在最壞的情況下手動準備。我認爲,需要被覆蓋的實際功能本身並不是「創造」,而是看着你能找到的那個來源。

其他解決方案,我相信,一個更優雅的一個,將在數據庫中創建觸發器。我更喜歡PostgreSQL世界,我將使用'CREATE RULE',但是看DB2文檔,我發現在DB2中有'INSTEAD OF' triggers。我希望這可能會有所幫助。

+0

如果你想改寫「創造」,我不建議去除對象的屬性,我只是建議不指定列創建(和執行)的SQL。但觸發器呢?它看起來是最優雅的解決方案 - 您的查詢將被數據庫重寫 - 您可以從最終查詢中刪除該只讀列。 – Arsen7

+0

我喜歡觸發器的想法,但缺點是計算字段需要在'源'字段之一更改時更新。本質上,計算的字段與其他字段上的多個觸發器相同。因此,它比多個觸發器稍微簡單一些。至於修改「創建」(也許是'更新'):我更喜歡上面修改構建SQL的方法,因爲後者更深入ActiveRecord的碗中。更高級別的更改通常更容易理解。但是,我仍然必須這樣做,而且我可能會錯誤的:) – Confusion

2

我已經在我的模型重寫的ActiveRecord :: Base的#arel_attributes達到同樣的效果:

Class Model < ActiveRecord::Base 
    @@skip_attrs = [:attr1, :attr2]  

    def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) 
    skip_attrs = @@skip_attrs.map { |attr| [self.class.arel_table[attr] } 
    attrs = super(include_primary_key, include_readonly_attributes, attribute_names) 
    attrs.delete_if {|key, value| skip_attrs.include?(key) }   
    end 
end 

的@@ skip_attrs數組中的屬性將通過ActiveRecord的兩個INSERT和UPDATE語句被忽略,因爲它們都依賴於arel_attributes_values來返回模型的屬性列表。

更好的解決方案是:ActiveRecord :: Base#arel_attributes上的補丁以及類似於'attr_readonly'的'attr_ignore'宏。

歡呼

+0

我認爲這個選項,我同意它也解決了這個問題。我對它的主要保留意見是,如果該方法在未來版本的ActiveRecord中發生變化,可能會導致難以發現的錯誤,我不會感到驚訝。 – Confusion

+0

是的,你說得對。在中期最好向AR的作者提交一個請求來考慮這樣的特徵。上面的這個只是一個'補丁',不過是一個'補丁'。 – evital

0

我知道這是很老了,但我一直在用這同一個問題掙扎。我有一個帶有觸發器的數據庫,該數據庫根據密鑰中的最大值計算索引值。我也想阻止任何在AR中設置值的能力,因爲它可能會在插入行時拋棄應用的索引。

CREATE TRIGGER incr_col_idx 
AFTER INSERT ON fl_format_columns 
FOR EACH ROW 
BEGIN UPDATE fl_format_columns 
SET idx = (SELECT coalesce(max(idx),-1) + 1 
      FROM fl_format_columns 
      WHERE fl_file_format_id = new.fl_file_format_id) 
WHERE fl_file_format_id = new.fl_file_format_id AND name = new.name; 
END; 

我試過了各種各樣的東西,但它總是回來直接覆蓋setter。

# @raise ArgumentError when an attempt is made to set a value that is calculated in db 
def idx=(o) 
    raise ArgumentError,'the value of idx is set by the db. attempts to set value is not allowed.' unless o.nil? 
end 

這將需要捕捉異常,而不是詢問錯誤陣列,但是這是我結束了。它通過以下檢查:

context 'column index' do 
    it 'should prevent idx from being set' do 
    expect{FL_Format_Column.create(fl_file_format_id:-1,name:'test idx',idx:0)}.to raise_error(ArgumentError) 
    end 
    it 'should calculate idx relative to zero' do 
    x = FL_Format_Column.create(fl_file_format_id:-1,name:'test_idx_nil') 
    expect(x.errors[:idx].any?).to be false 
    expect(FL_Format_Column.last.idx).to be > -1 
    end 
end