fixture_kit 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e0215c05b7885a41bf05f4ecd197b5df0d2bec66a8994b9ffe3c5310523d7c2
4
- data.tar.gz: 41f8326c8e2b24094d977127da376efcec101471d61a3da009cbb61a8683cce8
3
+ metadata.gz: 311fd09f37153fae9d8a09e3e41357c38f18e1e343794a758ae943dfdca3c40a
4
+ data.tar.gz: ee4257fc3c840f809a25204f300c766f872688c5c7417dc22155f001ba424882
5
5
  SHA512:
6
- metadata.gz: 5315136c139de91a4d6a7bf325fdd9710a0583928b1f0dccdee0d8584afc9be050083ee174a0ddc3d538650d8509e4fd764731c4e547d5f9dacc2651690a454a
7
- data.tar.gz: f7212cae51606a7e9ef8c80f7c27d9a95e6091b02f0873d2786241780bbd2a961d0f94a1e8cb0bc42cdd188276fc56526b2f1cba1136edeba66384824e255ab4
6
+ metadata.gz: bd8c58de66c08eeceab67e4f0b83a8a95d25291401272e96b51725db2b7a9f2b2592c24f1d6f6cb7b4f6b4716fa93fc768c4fc81afcf1974b9339e14b1b61fa9
7
+ data.tar.gz: 8d335927771e40d35142a69ee9bf911fcfb6c660af6cde28269a16643e86a468c017cf2f54e362f09d90a78bfeffe6320071d0230f6240969fc495de0e91c8f2
data/README.md CHANGED
@@ -104,7 +104,7 @@ RSpec.describe Book do
104
104
  end
105
105
  ```
106
106
 
107
- `fixture` returns a `FixtureSet` and exposes records as methods (for example, `fixture.owner`).
107
+ `fixture` returns a `Repository` and exposes records as methods (for example, `fixture.owner`).
108
108
 
109
109
  ### 3. Configure RSpec
110
110
 
@@ -6,7 +6,7 @@ require "active_support/core_ext/array/wrap"
6
6
  require "active_support/inflector"
7
7
 
8
8
  module FixtureKit
9
- class FixtureCache
9
+ class Cache
10
10
  # In-memory cache to avoid re-reading/parsing JSON for every test
11
11
  @memory_cache = {}
12
12
 
@@ -15,7 +15,7 @@ module FixtureKit
15
15
 
16
16
  def clear_memory_cache(fixture_name = nil)
17
17
  if fixture_name
18
- @memory_cache.delete(fixture_name.to_s)
18
+ @memory_cache.delete(fixture_name)
19
19
  else
20
20
  @memory_cache.clear
21
21
  end
@@ -34,18 +34,18 @@ module FixtureKit
34
34
  end
35
35
  end
36
36
 
37
- # Pre-generate caches for all fixtures.
37
+ # Generate caches for all fixtures.
38
38
  # Each fixture is generated in a transaction that rolls back, so no data persists.
39
- def pregenerate_all
40
- fixture_path = FixtureKit.configuration.fixture_path
39
+ def generate_all
40
+ Registry.load_definitions
41
+ Registry.fixtures.each { |fixture| generate(fixture.name) }
42
+ end
41
43
 
42
- # First, load all fixture files to register them
43
- FixtureRegistry.load_definitions(fixture_path)
44
+ def generate(fixture_name)
45
+ clear(fixture_name)
44
46
 
45
- # Then iterate over the registry and generate caches
46
- FixtureRegistry.all_names.each do |name|
47
- runner = FixtureRunner.new(name)
48
- runner.generate_cache_only
47
+ FixtureKit.configuration.generator.run do
48
+ Runner.run(fixture_name, force: true)
49
49
  end
50
50
  end
51
51
  end
@@ -53,7 +53,7 @@ module FixtureKit
53
53
  attr_reader :records, :exposed
54
54
 
55
55
  def initialize(fixture_name)
56
- @fixture_name = fixture_name.to_s
56
+ @fixture_name = fixture_name
57
57
  @records = {}
58
58
  @exposed = {}
59
59
  end
@@ -107,15 +107,15 @@ module FixtureKit
107
107
  File.write(cache_file_path, JSON.pretty_generate(data))
108
108
  end
109
109
 
110
- # Query exposed records from the database and return a FixtureSet
111
- def build_fixture_set
110
+ # Query exposed records from the database and return a Repository.
111
+ def build_repository
112
112
  exposed_records = @exposed.each_with_object({}) do |(name, value), hash|
113
113
  was_array = value.is_a?(Array)
114
114
  records = Array.wrap(value).map { |record_info| find_exposed_record(record_info.fetch("model"), record_info.fetch("id"), name) }
115
115
  hash[name.to_sym] = was_array ? records : records.first
116
116
  end
117
117
 
118
- FixtureSet.new(exposed_records)
118
+ Repository.new(exposed_records)
119
119
  end
120
120
 
121
121
  private
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- class FixtureContext
4
+ class DefinitionContext
5
5
  attr_reader :exposed
6
6
 
7
7
  def initialize
@@ -5,12 +5,12 @@ module FixtureKit
5
5
  attr_reader :name, :block
6
6
 
7
7
  def initialize(name, &block)
8
- @name = name.to_sym
8
+ @name = name
9
9
  @block = block
10
10
  end
11
11
 
12
12
  def execute
13
- context = FixtureContext.new
13
+ context = DefinitionContext.new
14
14
  context.instance_eval(&block) if block
15
15
  context.exposed
16
16
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FixtureKit
4
+ module Registry
5
+ class << self
6
+ def fetch(name)
7
+ fixture = find(name)
8
+ return fixture if fixture
9
+
10
+ file_path = fixture_file_path(name)
11
+ unless File.file?(file_path)
12
+ raise FixtureKit::FixtureDefinitionNotFound,
13
+ "Could not find fixture definition file for '#{name}' at '#{file_path}'"
14
+ end
15
+
16
+ load file_path
17
+ find(name)
18
+ end
19
+
20
+ def find(name)
21
+ registry[name]
22
+ end
23
+
24
+ def fixtures
25
+ registry.values
26
+ end
27
+
28
+ def register(fixture)
29
+ registry[fixture.name] = fixture
30
+ end
31
+
32
+ # Load all fixture definition files.
33
+ # Uses `load` instead of `require` to ensure fixtures are registered
34
+ # even if the files were previously required (e.g., after a reset).
35
+ def load_definitions
36
+ fixture_path = FixtureKit.configuration.fixture_path
37
+ Dir.glob(File.join(fixture_path, "**/*.rb")).each do |file|
38
+ load file
39
+ end
40
+ end
41
+
42
+ def reset
43
+ @registry = nil
44
+ end
45
+
46
+ private
47
+
48
+ def fixture_file_path(name)
49
+ fixture_path = FixtureKit.configuration.fixture_path
50
+ File.expand_path(File.join(fixture_path, "#{name}.rb"))
51
+ end
52
+
53
+ def registry
54
+ @registry ||= {}
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- class FixtureSet
4
+ class Repository
5
5
  def initialize(exposed_records)
6
6
  @records = exposed_records
7
7
  @records.each_value { |value| value.freeze if value.is_a?(Array) }
@@ -6,11 +6,11 @@ module FixtureKit
6
6
  attr_reader :name
7
7
 
8
8
  def initialize(name)
9
- @name = name.to_s
9
+ @name = name
10
10
  end
11
11
 
12
12
  def fixture_set
13
- FixtureKit::FixtureRegistry.load_fixture(name)
13
+ FixtureKit::Runner.run(name)
14
14
  end
15
15
  end
16
16
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fixture_kit"
4
- require_relative "rspec/declaration"
5
- require_relative "rspec/generator"
6
4
 
7
5
  module FixtureKit
8
6
  module RSpec
7
+ autoload :Declaration, File.expand_path("rspec/declaration", __dir__)
8
+ autoload :Generator, File.expand_path("rspec/generator", __dir__)
9
+
9
10
  DECLARATION_METADATA_KEY = :fixture_kit_declaration
10
11
  PRESERVE_CACHE_ENV_KEY = "FIXTURE_KIT_PRESERVE_CACHE"
11
12
 
@@ -31,45 +32,57 @@ module FixtureKit
31
32
  # end
32
33
  # end
33
34
  def fixture(name)
34
- metadata[FixtureKit::RSpec::DECLARATION_METADATA_KEY] = FixtureKit::RSpec::Declaration.new(name)
35
+ metadata[DECLARATION_METADATA_KEY] = Declaration.new(name)
35
36
  end
36
37
  end
37
38
 
38
39
  # Instance methods (included via config.include)
39
40
  module InstanceMethods
40
- # Returns the FixtureSet for the current example's fixture.
41
+ # Returns the Repository for the current example's fixture.
41
42
  # Access exposed records as methods: fixture.alice, fixture.posts
42
43
  def fixture
43
44
  @_fixture_kit_fixture_set || raise("No fixture declared for this example group. Use `fixture \"name\"` in your describe/context block.")
44
45
  end
45
46
  end
46
- end
47
- end
48
47
 
49
- # Install the RSpec generator by default for this entrypoint.
50
- FixtureKit.configuration.generator = FixtureKit::RSpec::Generator
48
+ def self.configure!(config)
49
+ FixtureKit.configuration.generator = Generator
50
+ config.extend ClassMethods
51
+ config.include InstanceMethods
51
52
 
52
- # Configure RSpec integration
53
- RSpec.configure do |config|
54
- config.extend FixtureKit::RSpec::ClassMethods
55
- config.include FixtureKit::RSpec::InstanceMethods
53
+ # Load declared fixtures at the beginning of each example.
54
+ # Runs inside transactional fixtures and before user-defined before hooks.
55
+ config.prepend_before(:example, DECLARATION_METADATA_KEY) do |example|
56
+ declaration = example.metadata[DECLARATION_METADATA_KEY]
57
+ @_fixture_kit_fixture_set = declaration.fixture_set
58
+ end
56
59
 
57
- # Load declared fixtures at the beginning of each example.
58
- # Runs inside transactional fixtures and before user-defined before hooks.
59
- config.prepend_before(:example, FixtureKit::RSpec::DECLARATION_METADATA_KEY) do |example|
60
- declaration = example.metadata[FixtureKit::RSpec::DECLARATION_METADATA_KEY]
61
- @_fixture_kit_fixture_set = declaration.fixture_set
62
- end
60
+ # Setup caches at suite start only when at least one fixture-backed
61
+ # example exists in the loaded suite.
62
+ config.when_first_matching_example_defined(DECLARATION_METADATA_KEY) do
63
+ config.before(:suite) do
64
+ if FixtureKit.configuration.autogenerate
65
+ preserve_cache = ENV[PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
66
+ Cache.clear unless preserve_cache
67
+ else
68
+ fixture_names_for_loaded_examples.each do |fixture_name|
69
+ Cache.generate(fixture_name)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
63
75
 
64
- # Setup caches at suite start based on autogenerate setting
65
- # - autogenerate=true: Clear all caches (unless FIXTURE_KIT_PRESERVE_CACHE is set)
66
- # - autogenerate=false: Pre-generate all caches so tests don't fail
67
- config.before(:suite) do
68
- if FixtureKit.configuration.autogenerate
69
- preserve_cache = ENV[FixtureKit::RSpec::PRESERVE_CACHE_ENV_KEY].to_s.match?(/\A(1|true|yes)\z/i)
70
- FixtureKit::FixtureCache.clear unless preserve_cache
71
- else
72
- FixtureKit::FixtureCache.pregenerate_all
76
+ def self.fixture_names_for_loaded_examples
77
+ ::RSpec.world.filtered_examples.each_value.with_object(Set.new) do |examples, names|
78
+ examples.each do |example|
79
+ declaration = example.metadata[DECLARATION_METADATA_KEY]
80
+ names << declaration.name if declaration
81
+ end
82
+ end.to_a
73
83
  end
74
84
  end
75
85
  end
86
+
87
+ # Configure RSpec integration
88
+ FixtureKit::RSpec.configure!(RSpec.configuration)
@@ -3,14 +3,20 @@
3
3
  require "active_support/inflector"
4
4
 
5
5
  module FixtureKit
6
- class FixtureRunner
6
+ class Runner
7
+ def self.run(fixture_name, force: false)
8
+ new(fixture_name).run(force: force)
9
+ end
10
+
7
11
  def initialize(fixture_name)
8
- @fixture_name = fixture_name.to_sym
9
- @cache = FixtureCache.new(@fixture_name)
12
+ @fixture_name = fixture_name
13
+ @cache = Cache.new(@fixture_name)
10
14
  end
11
15
 
12
- def run
13
- if @cache.exists?
16
+ def run(force: false)
17
+ if force
18
+ execute_and_cache
19
+ elsif @cache.exists?
14
20
  execute_from_cache
15
21
  elsif FixtureKit.configuration.autogenerate
16
22
  execute_and_cache
@@ -26,27 +32,10 @@ module FixtureKit
26
32
  end
27
33
  end
28
34
 
29
- # Generate cache only (used for pregeneration in before(:suite))
30
- # Wraps execution in the configured generator lifecycle.
31
- # The default generator uses ActiveRecord::TestFixtures transactions.
32
- # Entry points (like `fixture_kit/rspec`) can install richer generators.
33
- # Always regenerates the cache, even if one exists
34
- def generate_cache_only
35
- # Clear any existing cache for this fixture
36
- FixtureCache.clear(@fixture_name.to_s)
37
-
38
- FixtureKit.configuration.generator.run do
39
- execute_and_cache
40
- end
41
-
42
- true
43
- end
44
-
45
35
  private
46
36
 
47
37
  def execute_and_cache
48
- fixture = FixtureRegistry.find(@fixture_name)
49
- raise ArgumentError, "Fixture '#{@fixture_name}' not found" unless fixture
38
+ fixture = Registry.fetch(@fixture_name)
50
39
 
51
40
  # Start capturing SQL
52
41
  capture = SqlCapture.new
@@ -64,8 +53,8 @@ module FixtureKit
64
53
  exposed_mapping: build_exposed_mapping(exposed)
65
54
  )
66
55
 
67
- # Return FixtureSet from the exposed records
68
- FixtureSet.new(exposed)
56
+ # Return Repository from the exposed records
57
+ Repository.new(exposed)
69
58
  end
70
59
 
71
60
  def execute_from_cache
@@ -80,8 +69,8 @@ module FixtureKit
80
69
  connection.execute(sql)
81
70
  end
82
71
 
83
- # Query exposed records and build FixtureSet
84
- @cache.build_fixture_set
72
+ # Query exposed records and build Repository.
73
+ @cache.build_repository
85
74
  end
86
75
 
87
76
  def build_exposed_mapping(exposed)
@@ -22,14 +22,14 @@ module FixtureKit
22
22
  name = relative_path.to_s.sub(/\.rb$/, "")
23
23
 
24
24
  fixture = Fixture.new(name, &block)
25
- FixtureRegistry.register(fixture)
25
+ Registry.register(fixture)
26
26
  fixture
27
27
  end
28
28
 
29
29
  def reset
30
30
  @configuration = nil
31
- FixtureRegistry.reset
32
- FixtureCache.clear_memory_cache
31
+ Registry.reset
32
+ Cache.clear_memory_cache
33
33
  end
34
34
  end
35
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FixtureKit
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/fixture_kit.rb CHANGED
@@ -6,18 +6,19 @@ module FixtureKit
6
6
  class DuplicateNameError < Error; end
7
7
  class CacheMissingError < Error; end
8
8
  class PregenerationError < Error; end
9
+ class FixtureDefinitionNotFound < Error; end
9
10
  class ExposedRecordNotFound < Error; end
10
11
 
11
12
  autoload :VERSION, File.expand_path("fixture_kit/version", __dir__)
12
13
  autoload :Configuration, File.expand_path("fixture_kit/configuration", __dir__)
13
14
  autoload :Singleton, File.expand_path("fixture_kit/singleton", __dir__)
14
15
  autoload :Fixture, File.expand_path("fixture_kit/fixture", __dir__)
15
- autoload :FixtureContext, File.expand_path("fixture_kit/fixture_context", __dir__)
16
- autoload :FixtureRegistry, File.expand_path("fixture_kit/fixture_registry", __dir__)
17
- autoload :FixtureSet, File.expand_path("fixture_kit/fixture_set", __dir__)
16
+ autoload :DefinitionContext, File.expand_path("fixture_kit/definition_context", __dir__)
17
+ autoload :Registry, File.expand_path("fixture_kit/registry", __dir__)
18
+ autoload :Repository, File.expand_path("fixture_kit/repository", __dir__)
18
19
  autoload :SqlCapture, File.expand_path("fixture_kit/sql_capture", __dir__)
19
- autoload :FixtureCache, File.expand_path("fixture_kit/fixture_cache", __dir__)
20
- autoload :FixtureRunner, File.expand_path("fixture_kit/fixture_runner", __dir__)
20
+ autoload :Cache, File.expand_path("fixture_kit/cache", __dir__)
21
+ autoload :Runner, File.expand_path("fixture_kit/runner", __dir__)
21
22
  autoload :Generator, File.expand_path("fixture_kit/generator", __dir__)
22
23
  autoload :TestCase, File.expand_path("fixture_kit/test_case", __dir__)
23
24
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixture_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ngan Pham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-20 00:00:00.000000000 Z
11
+ date: 2026-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -134,17 +134,17 @@ files:
134
134
  - LICENSE
135
135
  - README.md
136
136
  - lib/fixture_kit.rb
137
+ - lib/fixture_kit/cache.rb
137
138
  - lib/fixture_kit/configuration.rb
139
+ - lib/fixture_kit/definition_context.rb
138
140
  - lib/fixture_kit/fixture.rb
139
- - lib/fixture_kit/fixture_cache.rb
140
- - lib/fixture_kit/fixture_context.rb
141
- - lib/fixture_kit/fixture_registry.rb
142
- - lib/fixture_kit/fixture_runner.rb
143
- - lib/fixture_kit/fixture_set.rb
144
141
  - lib/fixture_kit/generator.rb
142
+ - lib/fixture_kit/registry.rb
143
+ - lib/fixture_kit/repository.rb
145
144
  - lib/fixture_kit/rspec.rb
146
145
  - lib/fixture_kit/rspec/declaration.rb
147
146
  - lib/fixture_kit/rspec/generator.rb
147
+ - lib/fixture_kit/runner.rb
148
148
  - lib/fixture_kit/singleton.rb
149
149
  - lib/fixture_kit/sql_capture.rb
150
150
  - lib/fixture_kit/test_case.rb
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module FixtureKit
4
- module FixtureRegistry
5
- class << self
6
- def register(fixture)
7
- fixtures[fixture.name.to_s] = fixture
8
- end
9
-
10
- def find(name)
11
- fixtures[name.to_s]
12
- end
13
-
14
- def all_names
15
- fixtures.keys
16
- end
17
-
18
- # Load all fixture definition files from the given path.
19
- # Uses `load` instead of `require` to ensure fixtures are registered
20
- # even if the files were previously required (e.g., after a reset).
21
- def load_definitions(fixture_path)
22
- Dir.glob(File.join(fixture_path, "**/*.rb")).each do |file|
23
- load file
24
- end
25
- end
26
-
27
- def reset
28
- @fixtures = nil
29
- end
30
-
31
- # Load a fixture's records into the database and return a FixtureSet.
32
- # Uses cached INSERT statements if available, otherwise executes fixture and caches.
33
- def load_fixture(name)
34
- name = name.to_s
35
-
36
- # Load the file on-demand if fixture not yet registered
37
- unless find(name)
38
- fixture_path = FixtureKit.configuration.fixture_path
39
- file_path = File.expand_path(File.join(fixture_path, "#{name}.rb"))
40
- load file_path
41
- end
42
-
43
- runner = FixtureRunner.new(name)
44
- runner.run
45
- end
46
-
47
- private
48
-
49
- def fixtures
50
- @fixtures ||= {}
51
- end
52
- end
53
- end
54
- end