2012-04-26 51 views
2

在我的Rails 3.1.3應用程序中,我有subscriptions表;必須對此表上的操作進行跟蹤,這對計費至關重要。由於數據庫可以以多種方式訪問​​(API,控制檯,客戶端應用程序),所以簡單的ActiveRecord回調或觀察者不足以確保記錄表上的所有事務。因此,我在subscriptions表上創建了一個數據庫觸發器,可以隨時在「日誌」表中插入一條記錄進行更改。爲了做到這一點,我正在使用像這樣的Rails遷移:在Rails應用中更改PostgreSQL表格禁用觸發器

def up 
    execute <<-SQL 
    CREATE OR REPLACE FUNCTION FUNCTION_Event_Type() 
    RETURNS TRIGGER 
    AS 
    $TRIGGER_Event_Type$ 
     BEGIN 
     IF (TG_OP = 'DELETE') THEN 
       INSERT INTO subscription_log (company_id, product_id, old_package_id, new_package_id, trigger_type, updated_at, created_at) 
       SELECT OLD.company_id, OLD.product_id, OLD.package_id, NULL, 'Delete', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP; 
       RETURN OLD; 
      ELSIF (TG_OP = 'UPDATE') THEN 
       INSERT INTO subscription_log (company_id, product_id, old_package_id, new_package_id, trigger_type, updated_at, created_at) 
       SELECT OLD.company_id, OLD.product_id, OLD.package_id, NEW.package_id, 'Update', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP; 
       RETURN NEW; 
      ELSIF (TG_OP = 'INSERT') THEN 
       INSERT INTO subscription_log (company_id, product_id, old_package_id, new_package_id, trigger_type, updated_at, created_at) 
       SELECT NEW.company_id, NEW.product_id, NULL, NEW.package_id, 'Insert', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP; 
       RETURN NEW; 
      END IF; 
     RETURN NULL; 
     END; 
    $TRIGGER_Event_Type$ 
    LANGUAGE plpgsql; 

    CREATE TRIGGER TRIGGER_Event_Type 
    AFTER INSERT OR UPDATE OR DELETE ON subscriptions 
     FOR EACH ROW EXECUTE PROCEDURE FUNCTION_Event_Type(); 
SQL 
end 

觸發器工作正常,並根據需要記錄日誌。我有rspec測試檢查記錄是否插入「日誌記錄」表中隨時可以在subscriptions表上完成的事情。不過,我仍然在應用程序的工作,而不得不添加一列subscriptions表使用Rails遷移:

def change 
    add_column :subscriptions, :description, :text 
end 

後我跑遷移,我的測試,檢查觸發器的功能是這樣的:

lambda do 
    FactoryGirl.create(:subscription) 
    end.should change(SubscriptionLog, :count).by(1) 

開始失敗。

更新:開發數據庫添加一列後仍然有觸發器。測試數據庫運行遷移後失去觸發器,添加一列...奇怪

問題:
是否改變表殺死觸發器?如果確實如此,確保觸發持續的方式是什麼?

+1

你究竟是如何「向列表添加一列」?請顯示代碼。列名也是相關的。它是如何「開始失敗」的?任何錯誤消息?觸發器仍然存在? – 2012-04-26 15:57:02

+0

只有你有這個問題,因爲你有一個糟糕的架構。您的應用程序,控制檯等應該使用api(可能是rails應用程序),並且會有業務規則生存的唯一位置。不在數據庫中。 – 2012-04-26 16:51:21

+2

我不同意@ismaelga。如果多層可以訪問數據庫,則數據庫中的觸發器是最安全的方法。業務規則只在應用程序 - 這本質上是不安全的。 – 2012-04-26 17:11:20

回答

2

是否改變表格殺死觸發器?

回答提問的問題:,一般不會。添加列不應該混淆觸發器 - 只要它不會間接影響觸發器函數或WHEN condition of the trigger(PostgreSQL 9.0或更高版本)中的SQL語句。


如果您通過刪除/重新創建表,然後,當然,你必須重新創建觸發器添加一列了。觸發器功能不會被刪除,但觸發器是。你能檢查觸發器是否仍然存在嗎?


你有沒有目標列表INSERT命令。這是一種常見的腳弓,當基礎表稍後改變時容易破損。除特設的SQL命令外,總是提供目標列表

相反的:

INSERT INTO subscription_log 
SELECT OLD.company_id, OLD.product_id, OLD.package_id, ... ; 

務必:

 
INSERT INTO subscription_log (col1, col2, col3, ...) 
SELECT OLD.company_id, OLD.product_id, OLD.package_id, ... ; 

Details about INSERT in the manual.

+0

對不起,我確實有他們,但我沒有發佈它,因爲我試圖保持短的問題。我將用完整的代碼更新這個問題。同樣,觸發器可以正常工作,但在表上修改後會停止工作。 – 2012-04-26 15:49:44

+0

非常感謝您的幫助。觸發器仍然存在,我更新了問題。 – 2012-04-26 17:20:15

+1

顯然,只有測試數據庫受到影響。這很奇怪,因爲我在開發和測試中運行相同的遷移。 – 2012-04-26 17:40:55

3

最後,我想它了。在向subscriptions表添加一列後,我跑rake db:test:prepare(別問我爲什麼)。顯然,「轉儲當前模式,放棄測試數據庫,然後從轉儲重建測試模式」由於觸發器是通過執行SQL(而不是rails helpers)創建的,因此它從未在模式文件中反映出來。因此,它從未被rake db:test:prepare重新創建解決方案:下降測試數據庫並再次運行所有遷移...並且再也不運行rake db:test:prepare。希望有人認爲它有用。

+0

這裏有相同的問題,但我們不是負責創建觸發器的人。你有沒有找到一種方法來跳過db:test:prepare? – Dogweather 2013-09-30 19:08:18

+0

我找到了一個'$ bundle exec rake db:crete_trigger RAILS_ENV = test'的方法,但這種方法很奇怪。我正在尋找更進一步的解決方案。 – itsnikolay 2013-12-30 17:42:51