Module: TransientRecord::Context

Defined in:
lib/transient_record.rb

Overview

A module for creating Transient Record contexts.

A context is a Ruby module (created via Module.new) and extended with Context. This means instance methods below should be called as module methods on a context, not as instance methods.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.create(base_class) ⇒ Module

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates a context corresponding to the specified base class.

Parameters:

  • base_class (Class)

    Active Record class to use to connect to the database and as a base class for models.

Returns:

  • (Module)

    context module used as a namespace for models



117
118
119
120
121
122
123
# File 'lib/transient_record.rb', line 117

def self.create base_class
  Module.new do
    extend Context
    @base_class       = base_class
    @transient_tables = []
  end
end

Instance Method Details

#cleanupnil

Drops transient tables and models.

Calling this method removes all models and drops all tables created within this context. Instead of calling this method, you usually should TransientRecord.cleanup to cleanup all contexts.

Calling this method does the following:

  1. Remove all models defined via #define_model.

  2. Drop all tables created via #create_table.

  3. Run garbage collection to ensure model classes are truly removed. This may be needed in some versions of Active Record.

Returns:

  • (nil)


222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/transient_record.rb', line 222

def cleanup
  constants.each { |name| remove_const name }

  tables_to_remove = @transient_tables
  drop_attempts = tables_to_remove.count * (1 + tables_to_remove.count) / 2

  drop_attempts.times do
    table = tables_to_remove.pop
    break if table.nil?

    begin
      @base_class.connection.drop_table table, force: :cascade, if_exists: true
    rescue ActiveRecord::InvalidForeignKey, ActiveRecord::StatementInvalid
      # ActiveRecord::StatementInvalid is raised by MySQL when attempting to
      # drop a table that has foreign keys referring to it.
      tables_to_remove.unshift(table)
    end
  end

  if !@transient_tables.empty?
    raise Error.new(<<~ERROR)
      The following transient tables could not be removed: #{@transient_tables.join(', ')}.
    ERROR
  end

  GC.start

  nil
end

#create_table(table_name, options = {}) {|table| ... } ⇒ ModelDefinitionProxy

Creates a transient table.

This method can be considered to be a wrapper around #create_table in Active Record, as it forwards its arguments and the block.

Transient tables are not made temporary in the database (in other words, they are not created using CREATE TEMPORARY TABLE), because temporary tables are treated differently by Active Record. For example, they aren’t listed by #tables. If a temporary table is needed then pass temporary: true via options, which Active Record will recognized out of the box.

Transient tables must be dropped explicitly by calling TransientRecord.cleanup or #cleanup.

Parameters:

  • table_name (String, Symbol)

    name of the table to create.

  • options (Hash) (defaults to: {})

    options to use during table creation; they are forwarded as is to create_table in Active Record.

Yields:

  • (table)

    table definition block forwarded to create_table in Active Record.

Returns:

  • (ModelDefinitionProxy)

See Also:



150
151
152
153
154
155
156
157
# File 'lib/transient_record.rb', line 150

def create_table table_name, options = {}, &block
  table_name = table_name.to_sym
  @transient_tables << table_name

  @base_class.connection.create_table table_name, **options, &block

  ModelDefinitionProxy.new self, table_name
end

#define_model(model_name, base_class = nil) { ... } ⇒ nil

Defines a transient Active Record model.

Calling this method is roughly equivalent to defining a class inheriting from the class the context corresponds to and with class body defined by the block passed to the method.

The base class can be customized by passing in a second argument, but it must be a subclass of the context’s base class.

Transient models must be removed explicitly by calling TransientRecord.cleanup or #cleanup.

Examples:

Primary = TransientRecord.context_for ApplicationRecord

# The following method call ...
Primary.define_model(:User) do
  validates :email, presence: true
end

# ... is roughly equivalent to this class definition.
class Primary::User < ApplicationRecord
  validates :email, presence: true
end

Parameters:

  • model_name (String, Symbol)

    name of model to define.

  • base_class (Class) (defaults to: nil)

    base class the model should inherit from

Yields:

  • class definition

Returns:

  • (nil)


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/transient_record.rb', line 191

def define_model model_name, base_class = nil, &block
  base_class ||= @base_class

  if base_class > @base_class
    raise Error.new(<<~ERROR)
      #{model_name} base class is #{base_class.name} but it must be a descendant of #{@base_class.name}
    ERROR
  end

  klass = Class.new base_class
  const_set model_name, klass

  klass.class_eval(&block) if block_given?

  nil
end