在rails迁移中添加sqlite3触发器

时间:2017-10-12 07:12:43

标签: ruby-on-rails ruby activerecord sqlite

我有这两次迁移:

class CreateBreakdowns < ActiveRecord::Migration[5.1]
  def change
    create_table :breakdowns do |t|
      t.date :date
      t.string :content
      t.decimal :amount
      t.timestamps
    end
  end
end

class CreateDailyTotals < ActiveRecord::Migration[5.1]
 def change
    create_table :daily_totals do |t|
      t.date      :date
      t.decimal   :total
      t.timestamps
    end
  end
end

而且,我想在迁移中添加以下触发器:

CREATE TRIGGER update_total AFTER INSERT ON breakdowns
    BEGIN
      UPDATE daily_totals
      SET total = (SELECT SUM(amount) FROM breakdowns WHERE New.date = date)
      WHERE New.date = date;
    END;

目的是当用户将数据插入故障表时,然后执行触发器来更新daily_totals。

我的问题是我必须添加哪个迁移文件才能添加此触发器?并且,如果我将此触发器添加到迁移文件中,它是否会起作用?

我用我的测试数据库尝试了触发器并且它可以工作,它只是不使用rails。我再次使用sqlite3,而不是mySQL或Oracle SQL。

谢谢!

1 个答案:

答案 0 :(得分:1)

您可以在迁移中执行原始SQL:

class CreateUpdateTotalTrigger < ActiveRecord::Migration[5.1]
  def change
    execute <<-SQL
      CREATE TRIGGER update_total AFTER INSERT ON breakdowns
      BEGIN
        UPDATE daily_totals
        SET total = (SELECT SUM(amount) FROM breakdowns WHERE New.date = date)
        WHERE New.date = date;
      END;
    SQL
  end
end

我对PostgreSQL中的程序有类似的用例,但不支持。

我创建了一个StoreProcedure问题:

# app/procedures/concerns/stored_procedure.rb
module StoredProcedure
  extend ActiveSupport::Concern

  included do
    self.abstract_class = true
  end

  module ClassMethods
    # without return value
    def execute_sp(sql, *bindings)
      perform_sp(:execute, sql, *bindings)
    end

    # select many return values
    def fetch_sp(sql, *bindings)
      perform_sp(:select_all, sql, *bindings)
    end

    # select single return value
    def fetch_sp_val(sql, *bindings)
      perform_sp(:select_value, sql, *bindings)
    end

    protected

    def perform_sp(method, sql, *bindings)
      sql = send(:sanitize_sql_array, bindings.unshift(sql)) if bindings.any?
      connection.send(method, sql)
    end
  end
end

然后我为存储过程创建了一个ActiveRecord类:

# app/procedures/sp_active_record.rb
class SpActiveRecord < ActiveRecord::Base
  self.abstract_class = true

  class << self
    def drop_function(function_name, *types)
      connection.execute("DROP FUNCTION IF EXISTS #{function_name}(#{types ? types.join(', ') : nil});")
    end

    def create_function
      connection.execute(minify("CREATE OR REPLACE FUNCTION #{yield};"))
    end

    private

    def minify(string)
      string.tr("\n", ' ').gsub(/\s+/, ' ')
    end
  end
end

最后,我为每个程序创建一个类:

class SpYoutubeChannel < SpActiveRecord
  include StoredProcedure
  class << self
    def show_interactions_by(interval, start_date = nil, end_date = nil)
      fetch_sp(minify(interactions_statement), interval, start_date, end_date).map { |r| OpenStruct.new(r) }
    end

    def interactions_statement
      <<-SQL
      ----
      SQL
    end
  end
end

我可以在迁移中使用我的助手

class CreateYoutubeInteractionsF < ActiveRecord::Migration[5.0]
  def up
    SpActiveRecord.drop_function(:youtube_interactions_f, 'int', 'date', 'date')
    SpActiveRecord.create_function do
      <<-SQL
      youtube_interactions_f(
        interval_size int, start_period date, end_period date
      )
      ---
      SQL
    end
  end

  def down
    SpActiveRecord.drop_function(:youtube_interactions_f, 'int', 'date', 'date')
  end
end

对于触发器你绝对可以做同样的事情!