Module: Fixtury::MutationObserver

Defined in:
lib/fixtury/mutation_observer.rb

Overview

The mutation observer class is responsible for tracking the isolation level of resources as they are created and updated. If a resource is created in one isolation level, but updated in another, the mutation observer will raise an error. If Rails is present, the Railtie will hook into ActiveRecord to automatically report these changes to the MutationObserver

Defined Under Namespace

Modules: ActiveRecordHooks

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.current_executionObject (readonly)

Returns the value of attribute current_execution.



35
36
37
# File 'lib/fixtury/mutation_observer.rb', line 35

def current_execution
  @current_execution
end

Class Method Details

.current_definitionFixtury::Definition?

The definition associated with the current execution.

Returns:

  • (Fixtury::Definition, nil)

    The definition associated with the current execution, or nil if there is no current execution.



71
72
73
# File 'lib/fixtury/mutation_observer.rb', line 71

def current_definition
  current_execution&.definition
end

.current_isolation_keyString?

The isolation key of the current definition associated with the current execution.

Returns:

  • (String, nil)

    The isolation key of the current definition, or nil if there is no current definition.



64
65
66
# File 'lib/fixtury/mutation_observer.rb', line 64

def current_isolation_key
  current_definition&.isolation_key
end

.log(msg, level: ::Fixtury::LOG_LEVEL_DEBUG) ⇒ Object



37
38
39
# File 'lib/fixtury/mutation_observer.rb', line 37

def log(msg, level: ::Fixtury::LOG_LEVEL_DEBUG)
  ::Fixtury.log(msg, name: "mutation_observer", level: level)
end

.normalized_locator_key(obj) ⇒ String?

Since there may be inheritance at play, we use the base class to consolidate ensure the same db record always produces the same locator key by using the base class to generate the locator key.

Parameters:

  • obj (ActiveRecord::Base)

    The object to generate a locator key for.

Returns:

  • (String, nil)

    The locator key for the given object, or nil if there is no current execution.



81
82
83
84
85
86
87
# File 'lib/fixtury/mutation_observer.rb', line 81

def normalized_locator_key(obj)
  return nil unless current_execution

  pk = obj.class.primary_key
  delegate_object = obj.class.base_class.new(pk => obj.read_attribute(pk))
  current_execution.store.locator.dump(delegate_object, context: "<mutation_observer>")
end

.observe(execution) {|void| ... } ⇒ Object

Observe mutation activity while the given block is executed.

Parameters:

  • execution (Fixtury::Execution)

    The execution that is currently being observed.

Yields:

  • (void)

    The block to execute while observing the given execution.



53
54
55
56
57
58
59
# File 'lib/fixtury/mutation_observer.rb', line 53

def observe(execution)
  prev_execution = current_execution
  @current_execution = execution
  yield
ensure
  @current_execution = prev_execution
end

.on_record_create(obj) ⇒ void

This method returns an undefined value.

When a record is created we assign ownership to the current isolation key, if present.

Parameters:

  • obj (ActiveRecord::Base)

    The record that was created.



93
94
95
96
97
98
99
# File 'lib/fixtury/mutation_observer.rb', line 93

def on_record_create(obj)
  locator_key = normalized_locator_key(obj)
  return unless locator_key

  log("Setting isolation level of #{locator_key.inspect} to #{current_isolation_key.inspect} via #{current_definition.inspect}")
  owners[locator_key] = current_isolation_key
end

.on_record_update(obj, changes) ⇒ void

This method returns an undefined value.

When a record is updated we check to see if the reported owner matches the current isolation key. If it doesn’t, we raise an error.

Parameters:

  • obj (ActiveRecord::Base)

    The record that was updated.

  • changes (Hash)

    The changes that were made to the record.

Raises:



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/fixtury/mutation_observer.rb', line 108

def on_record_update(obj, changes)
  return if changes.blank?

  locator_key = normalized_locator_key(obj)
  log("verifying record update for #{locator_key}")

  actual_owner = reported_owner(locator_key)
  return unless actual_owner

  if current_isolation_key.nil?
    log("Allowing update to #{locator_key.inspect} because there is no registered owner.")
    return
  end

  if actual_owner == current_isolation_key
    log("Allowing update to #{locator_key.inspect} in the #{actual_owner.inspect} isolation level via #{current_definition.inspect}.")
    return
  end

  raise Errors::IsolatedMutationError,  "Cannot modify #{locator_key.inspect}. Owned by: #{actual_owner.inspect}. Modified by: #{current_isolation_key.inspect}. Requested changes: #{changes.inspect}"
end

.ownersObject



41
42
43
# File 'lib/fixtury/mutation_observer.rb', line 41

def owners
  @owners ||= {}
end

.reported_owner(locator_key) ⇒ Object



45
46
47
# File 'lib/fixtury/mutation_observer.rb', line 45

def reported_owner(locator_key)
  owners[locator_key]
end