【问题标题】:Adding a sqlite3 trigger in Ruby on rails migration在 Ruby on rails 迁移中添加 sqlite3 触发器
【发布时间】:2017-10-12 07:12:42
【问题描述】:

我有这两个迁移:

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。

谢谢!

【问题讨论】:

  • 我要问,在普通的 ruby​​ 中不这样做吗?如果您使用的是更容易实现并且正确完成后可以提高性能的 Rails

标签: ruby-on-rails ruby activerecord sqlite


【解决方案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 中不受支持的过程有类似的用例。

我创建了一个存储过程关注点:

# 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

您绝对可以为触发器做同样的事情!

【讨论】:

  • 感谢您的回答!那么,我必须仅为触发器创建单独的迁移吗?我不能在 daily_totals 迁移中添加触发代码吗?
  • 我会说是的(def up; end 用于创建,def down; end 用于删除它)。如果您使用多个触发器,您可能想要创建一个复杂的体系结构,就像我为过程所做的那样
  • 这回答了你的问题吗?
  • 绝对!我还没有机会尝试代码,但我明白了。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-12
  • 2015-04-09
  • 2019-12-24
  • 2013-11-03
相关资源
最近更新 更多