Class: Idempo::ActiveRecordBackend

Inherits:
Object
  • Object
show all
Defined in:
lib/idempo/active_record_backend.rb

Overview

This backend currently only works with mysql2 since it uses advisory locks

Defined Under Namespace

Classes: MysqlLock, PostgresLock, Store

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeActiveRecordBackend

Returns a new instance of ActiveRecordBackend.



61
62
63
64
# File 'lib/idempo/active_record_backend.rb', line 61

def initialize
  require "active_record"
  @memory_lock = Idempo::MemoryLock.new
end

Class Method Details

.create_table(via_migration) ⇒ Object



3
4
5
6
7
8
9
10
# File 'lib/idempo/active_record_backend.rb', line 3

def self.create_table(via_migration)
  via_migration.create_table "idempo_responses", charset: "utf8mb4", collation: "utf8mb4_unicode_ci" do |t|
    t.string :idempotent_request_key, index: {unique: true}, null: false
    t.datetime :expire_at, index: true, null: false # Needs an index for cleanup
    t.binary :idempotent_response_payload, limit: Idempo::SAVED_RESPONSE_BODY_SIZE_LIMIT
    t.timestamps
  end
end

Instance Method Details

#modelObject

Allows the model to be defined lazily without having to require active_record when this module gets loaded



67
68
69
70
71
# File 'lib/idempo/active_record_backend.rb', line 67

def model
  @model_class ||= Class.new(ActiveRecord::Base) do
    self.table_name = "idempo_responses"
  end
end

#prune!Object

Deletes expired cached Idempo responses from the database, in batches



97
98
99
# File 'lib/idempo/active_record_backend.rb', line 97

def prune!
  model.where("expire_at < ?", Time.now).in_batches.delete_all
end

#with_idempotency_key(request_key) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/idempo/active_record_backend.rb', line 73

def with_idempotency_key(request_key)
  # We need to use an in-memory lock because database advisory locks are
  # reentrant. Both Postgres and MySQL allow multiple acquisitions of the
  # same advisory lock within the same connection - in most Rails/Rack apps
  # this translates to "within the same thread". This means that if one
  # elects to use a non-threading webserver (like Falcon), or tests Idempo
  # within the same thread (like we do), they won't get advisory locking
  # for concurrent requests. Therefore a staged lock is required. First we apply
  # the memory lock (for same thread on this process/multiple threads on this
  # process) and then once we have that - the DB lock.
  @memory_lock.with(request_key) do
    db_safe_key = Digest::SHA1.base64digest(request_key)
    database_lock = lock_implementation_for_connection(model.connection)
    raise Idempo::ConcurrentRequest unless database_lock.acquire(model.connection, request_key)

    begin
      yield(Store.new(db_safe_key, model))
    ensure
      database_lock.release(model.connection, request_key)
    end
  end
end