Class: Fixtury::Store

Inherits:
Object
  • Object
show all
Defined in:
lib/fixtury/store.rb

Overview

A store is a container for built fixture references. It is responsible for loading and caching fixtures based on a schema and a locator.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(schema: nil) ⇒ Store

Returns a new instance of Store.



17
18
19
20
21
# File 'lib/fixtury/store.rb', line 17

def initialize(schema: nil)
  @schema = schema || ::Fixtury.schema
  @locator = ::Fixtury::Locator.from(::Fixtury.configuration.locator_backend)
  self.references = ::Fixtury.configuration.stored_references
end

Instance Attribute Details

#locatorObject (readonly)

Returns the value of attribute locator.



13
14
15
# File 'lib/fixtury/store.rb', line 13

def locator
  @locator
end

#schemaObject (readonly)

Returns the value of attribute schema.



14
15
16
# File 'lib/fixtury/store.rb', line 14

def schema
  @schema
end

Instance Method Details

#clear_stale_references!void

This method returns an undefined value.

Clear any references that are beyond their ttl or are no longer recognizable by the locator.



60
61
62
63
64
65
66
# File 'lib/fixtury/store.rb', line 60

def clear_stale_references!
  references.delete_if do |name, ref|
    stale = reference_stale?(ref)
    log("expiring #{name}", level: LOG_LEVEL_DEBUG) if stale
    stale
  end
end

#get(search) ⇒ Object Also known as: []

Fetch a fixture by name. This will load the fixture if it has not been loaded yet. If a definition contains an isolation key, all fixtures with the same isolation key will be loaded.

Parameters:

  • search (String)

    The name of the fixture to search for.

Returns:

  • (Object)

    The loaded fixture.

Raises:



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/fixtury/store.rb', line 115

def get(search)
  log("getting #{search} relative to #{schema.pathname}", level: LOG_LEVEL_DEBUG)

  # Find the definition.
  dfn = schema.get!(search)
  raise ArgumentError, "#{search.inspect} must refer to a definition" unless dfn.acts_like?(:fixtury_definition)

  pathname = dfn.pathname
  isokey = dfn.isolation_key

  # Ensure that if we're part of an isolation group, we load all the fixtures in that group.
  maybe_load_isolation_dependencies(isokey)

  # See if we already hold a reference to the fixture.
  ref = references[pathname]

  # If the reference is a placeholder, we have a circular dependency.
  if ref&.holder?
    raise Errors::CircularDependencyError, pathname
  end

  # If the reference is stale, we should refresh it.
  # We do so by clearing it from the store and setting the reference to nil.
  if ref && reference_stale?(ref)
    log("refreshing #{pathname}", level: LOG_LEVEL_DEBUG)
    clear_reference(pathname)
    ref = nil
  end

  value = nil

  if ref
    log("hit #{pathname}", level: LOG_LEVEL_ALL)
    value = locator.load(ref.locator_key)
    if value.nil?
      clear_reference(pathname)
      ref = nil
      log("missing #{pathname}", level: LOG_LEVEL_ALL)
    end
  end

  if value.nil?
    # set the references to a holder value so any recursive behavior ends up hitting a circular dependency error if the same fixture load is attempted
    references[pathname] = ::Fixtury::Reference.holder(pathname)

    begin
      executor = ::Fixtury::DefinitionExecutor.new(store: self, definition: dfn)
      output = executor.call
      value = output.value
    rescue StandardError
      clear_reference(pathname)
      raise
    end

    log("store #{pathname}", level: LOG_LEVEL_DEBUG)

    locator_key = locator.dump(output.value, context: pathname)
    reference_opts = {}
    reference_opts.merge!(output.)
    reference_opts[:isolation_key] = isokey unless isokey == pathname
    references[pathname] = ::Fixtury::Reference.new(
      pathname,
      locator_key,
      **reference_opts
    )
  end

  value
end

#inspectString

Summarize the current state of the store.

Returns:

  • (String)


47
48
49
50
51
52
53
54
55
# File 'lib/fixtury/store.rb', line 47

def inspect
  parts = []
  parts << "schema: #{schema.inspect}"
  parts << "locator: #{locator.inspect}"
  parts << "ttl: #{ttl.inspect}" if ttl
  parts << "references: #{references.size}"

  "#{self.class}(#{parts.join(", ")})"
end

#load_all(schema = self.schema) ⇒ void

This method returns an undefined value.

Load all fixtures in the target schema, defaulting to the store’s schema. This will load all fixtures in the schema and any child schemas.

Parameters:

  • schema (Fixtury::Schema) (defaults to: self.schema)

    The schema to load, defaults to the store’s schema.



73
74
75
76
77
78
# File 'lib/fixtury/store.rb', line 73

def load_all(schema = self.schema)
  schema.children.each_value do |item|
    get(item.pathname) if item.acts_like?(:fixtury_definition)
    load_all(item) if item.acts_like?(:fixtury_schema)
  end
end

#loaded?(search) ⇒ TrueClass, FalseClass

Is a fixture for the given search already loaded?

Parameters:

  • search (String)

    The name of the fixture to search for.

Returns:

  • (TrueClass, FalseClass)

    ‘true` if the fixture is loaded, `false` otherwise.



98
99
100
101
102
103
104
# File 'lib/fixtury/store.rb', line 98

def loaded?(search)
  dfn = schema.get!(search)
  ref = references[dfn.pathname]
  result = ref&.real?
  log(result ? "hit #{dfn.pathname}" : "miss #{dfn.pathname}", level: LOG_LEVEL_ALL)
  result
end

#loaded_isolation_keysObject



33
34
35
36
# File 'lib/fixtury/store.rb', line 33

def loaded_isolation_keys
  @loaded_isolation_keys ||= ::Concurrent::ThreadLocalVar.new({})
  @loaded_isolation_keys.value
end

#referencesObject



23
24
25
26
# File 'lib/fixtury/store.rb', line 23

def references
  @references ||= ::Concurrent::ThreadLocalVar.new({})
  @references.value
end

#references=(value) ⇒ Object



28
29
30
31
# File 'lib/fixtury/store.rb', line 28

def references=(value)
  references.clear
  @references.value = value
end

#resetObject

Empty the store of any references and loaded isolation keys.



39
40
41
42
# File 'lib/fixtury/store.rb', line 39

def reset
  references.clear
  loaded_isolation_keys.clear
end

#with_relative_schema(schema) {|void| ... } ⇒ Object

Temporarily set a contextual schema to use for loading fixtures. This is useful when evaluating dependencies of a definition while still storing the results.

Parameters:

Yields:

  • (void)

    The block to execute with the given schema.

Returns:

  • (Object)

    The result of the block



86
87
88
89
90
91
92
# File 'lib/fixtury/store.rb', line 86

def with_relative_schema(schema)
  prior = @schema
  @schema = schema
  yield
ensure
  @schema = prior
end