fixtury 0.1.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +64 -0
- data/README.md +23 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/fixtury.gemspec +39 -0
- data/lib/fixtury.rb +25 -0
- data/lib/fixtury/definition.rb +66 -0
- data/lib/fixtury/errors/already_defined_error.rb +13 -0
- data/lib/fixtury/errors/circular_dependency_error.rb +13 -0
- data/lib/fixtury/errors/fixture_not_defined_error.rb +13 -0
- data/lib/fixtury/errors/schema_frozen_error.rb +11 -0
- data/lib/fixtury/errors/unrecognizable_locator_error.rb +11 -0
- data/lib/fixtury/execution_context.rb +8 -0
- data/lib/fixtury/hooks.rb +83 -0
- data/lib/fixtury/locator.rb +43 -0
- data/lib/fixtury/locator_backend/common.rb +57 -0
- data/lib/fixtury/locator_backend/globalid.rb +32 -0
- data/lib/fixtury/locator_backend/memory.rb +36 -0
- data/lib/fixtury/path.rb +36 -0
- data/lib/fixtury/railtie.rb +11 -0
- data/lib/fixtury/reference.rb +13 -0
- data/lib/fixtury/schema.rb +215 -0
- data/lib/fixtury/store.rb +165 -0
- data/lib/fixtury/tasks.rake +10 -0
- data/lib/fixtury/version.rb +7 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 73b1b15d6ac9941d265b535f8510cc2896eb4c3fc711af20339b045228e8a888
|
4
|
+
data.tar.gz: ca6171cdfc7c721a88dc6e4055db600e10897894295b40022e45e7a55c5c5497
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d892685cc0b8a4f54c09c188d1afc0dcc8928008f072c76bf18f0fed7ace0c49d194070d61b65dc4653c528b321f08e4ba3adb59cd2d33a192b4e3f984c09776
|
7
|
+
data.tar.gz: cceeab65fe5c416067a7be5ae8fd3c9dc65d73fcf5c812ffc89ba695d60fcae5790414baeea1e43d9c098279e8dddc8a87eede66f275eeb02bfaf11fb1c447f3
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fixtury (0.1.0.alpha)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
activesupport (6.0.1)
|
10
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
11
|
+
i18n (>= 0.7, < 2)
|
12
|
+
minitest (~> 5.1)
|
13
|
+
tzinfo (~> 1.1)
|
14
|
+
zeitwerk (~> 2.2)
|
15
|
+
ansi (1.5.0)
|
16
|
+
autotest (5.0.0)
|
17
|
+
minitest-autotest (~> 1.0)
|
18
|
+
builder (3.2.3)
|
19
|
+
byebug (11.0.1)
|
20
|
+
concurrent-ruby (1.1.5)
|
21
|
+
globalid (0.4.2)
|
22
|
+
activesupport (>= 4.2.0)
|
23
|
+
i18n (1.7.0)
|
24
|
+
concurrent-ruby (~> 1.0)
|
25
|
+
metaclass (0.0.4)
|
26
|
+
minitest (5.13.0)
|
27
|
+
minitest-autotest (1.1.1)
|
28
|
+
minitest-server (~> 1.0)
|
29
|
+
path_expander (~> 1.0)
|
30
|
+
minitest-reporters (1.4.2)
|
31
|
+
ansi
|
32
|
+
builder
|
33
|
+
minitest (>= 5.0)
|
34
|
+
ruby-progressbar
|
35
|
+
minitest-server (1.0.5)
|
36
|
+
minitest (~> 5.0)
|
37
|
+
mocha (1.8.0)
|
38
|
+
metaclass (~> 0.0.1)
|
39
|
+
path_expander (1.1.0)
|
40
|
+
rake (10.5.0)
|
41
|
+
ruby-progressbar (1.10.1)
|
42
|
+
sqlite (1.0.2)
|
43
|
+
thread_safe (0.3.6)
|
44
|
+
tzinfo (1.2.5)
|
45
|
+
thread_safe (~> 0.1)
|
46
|
+
zeitwerk (2.2.2)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
autotest
|
53
|
+
bundler (~> 2.0)
|
54
|
+
byebug
|
55
|
+
fixtury!
|
56
|
+
globalid
|
57
|
+
minitest (~> 5.0)
|
58
|
+
minitest-reporters
|
59
|
+
mocha
|
60
|
+
rake (~> 10.0)
|
61
|
+
sqlite
|
62
|
+
|
63
|
+
BUNDLED WITH
|
64
|
+
2.0.2
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Fixtury
|
2
|
+
|
3
|
+
The goal of this library is to provide an interface for accessing fixture data on-demand rather than having to manage all resources up front. By centralizing and wrapping the definitions of data generation, we can preload and optimize how we load data, yet allow deferred behaviors when desired.
|
4
|
+
|
5
|
+
For example, if a developer is running a test locally, there's no reason to build all fixtures for your suite.
|
6
|
+
|
7
|
+
```
|
8
|
+
class MyTest < ::Test
|
9
|
+
|
10
|
+
fixtury "users.fresh"
|
11
|
+
let(:user) { fixtury("users.fresh") }
|
12
|
+
|
13
|
+
def test_whatever
|
14
|
+
assert_eq "Doug", user.first_name
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
```
|
20
|
+
|
21
|
+
Loading this file would ensure `users.fresh` is loaded into the fixture set before the suite is run. In the context of ActiveSupport::TestCase, the Fixtury::Hooks file will ensure the database records are present prior to your suite running. Setting `use_transactional_fixtures` ensures all records are rolled back prior to running another test.
|
22
|
+
|
23
|
+
In a CI environment, we'd likely want to preload all fixtures. This can be done by requiring all the test files, then telling the fixtury store to load all definitions.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "fixtury"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/fixtury.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path("lib", __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "fixtury/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "fixtury"
|
9
|
+
spec.version = Fixtury::VERSION
|
10
|
+
spec.authors = ["Mike Nelson"]
|
11
|
+
spec.email = ["mike@guideline.com"]
|
12
|
+
|
13
|
+
spec.summary = "Treat fixtures like factories and factories like fixtures"
|
14
|
+
spec.homepage = "https://github.com/guideline-tech/fixtury"
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = spec.homepage
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "autotest"
|
31
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
32
|
+
spec.add_development_dependency "byebug"
|
33
|
+
spec.add_development_dependency "globalid"
|
34
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
35
|
+
spec.add_development_dependency "minitest-reporters"
|
36
|
+
spec.add_development_dependency "mocha"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "sqlite"
|
39
|
+
end
|
data/lib/fixtury.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/core_ext/module/attribute_accessors"
|
5
|
+
require "active_support/core_ext/module/delegation"
|
6
|
+
require "fixtury/version"
|
7
|
+
require "fixtury/schema"
|
8
|
+
require "fixtury/locator"
|
9
|
+
require "fixtury/store"
|
10
|
+
require "fixtury/execution_context"
|
11
|
+
|
12
|
+
module Fixtury
|
13
|
+
|
14
|
+
def self.define(&block)
|
15
|
+
schema.define(&block)
|
16
|
+
schema
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.schema
|
20
|
+
@top_level_schema ||= ::Fixtury::Schema.new(parent: nil, name: "")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
require "fixtury/railtie" if defined?(Rails)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fixtury
|
4
|
+
class Definition
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :schema
|
8
|
+
|
9
|
+
attr_reader :callable
|
10
|
+
attr_reader :enhancements
|
11
|
+
|
12
|
+
def initialize(schema: nil, name:, &block)
|
13
|
+
@name = name
|
14
|
+
@schema = schema
|
15
|
+
@callable = block
|
16
|
+
@enhancements = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def enhance(&block)
|
20
|
+
@enhancements << block
|
21
|
+
end
|
22
|
+
|
23
|
+
def enhanced?
|
24
|
+
@enhancements.any?
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(store: nil, execution_context: nil)
|
28
|
+
maybe_set_store_context(store: store) do
|
29
|
+
value = run_callable(store: store, callable: callable, execution_context: execution_context, value: nil)
|
30
|
+
enhancements.each do |e|
|
31
|
+
value = run_callable(store: store, callable: e, execution_context: execution_context, value: value)
|
32
|
+
end
|
33
|
+
value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def maybe_set_store_context(store:)
|
40
|
+
return yield unless store
|
41
|
+
|
42
|
+
store.with_relative_schema(schema) do
|
43
|
+
yield
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_callable(store:, callable:, execution_context:, value:)
|
48
|
+
execution_context ||= self
|
49
|
+
|
50
|
+
args = []
|
51
|
+
args << value unless value.nil?
|
52
|
+
if callable.arity > args.length
|
53
|
+
raise ArgumentError, "A store store must be provided if the definition expects it." unless store
|
54
|
+
|
55
|
+
args << store
|
56
|
+
end
|
57
|
+
|
58
|
+
if args.length.positive?
|
59
|
+
execution_context.instance_exec(*args, &callable)
|
60
|
+
else
|
61
|
+
execution_context.instance_eval(&callable)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fixtury/store"
|
4
|
+
|
5
|
+
module Fixtury
|
6
|
+
module Hooks
|
7
|
+
|
8
|
+
extend ::ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_attribute :fixtury_dependencies
|
12
|
+
self.fixtury_dependencies = Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
def fixtury(*names)
|
18
|
+
self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def fixtury(name)
|
24
|
+
raise ArgumentError unless self.fixtury_dependencies.include?(name.to_s)
|
25
|
+
|
26
|
+
::Fixtury::Store.instance.get(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def fixtury_loaded?(name)
|
30
|
+
::Fixtury::Store.instance.loaded?(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def fixtury_database_connections
|
34
|
+
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
|
35
|
+
end
|
36
|
+
|
37
|
+
# piggybacking activerecord fixture setup for now.
|
38
|
+
def setup_fixtures(*args)
|
39
|
+
if fixtury_dependencies.any?
|
40
|
+
setup_fixtury_fixtures
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# piggybacking activerecord fixture setup for now.
|
47
|
+
def teardown_fixtures(*args)
|
48
|
+
if fixtury_dependencies.any?
|
49
|
+
teardown_fixtury_fixtures
|
50
|
+
else
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup_fixtury_fixtures
|
56
|
+
return unless use_transactional_fixtures
|
57
|
+
|
58
|
+
clear_expired_fixtury_fixtures!
|
59
|
+
load_all_fixtury_fixtures!
|
60
|
+
|
61
|
+
fixtury_database_connections.each do |conn|
|
62
|
+
conn.begin_transaction joinable: false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def teardown_fixtury_fixtures
|
67
|
+
return unless use_transactional_fixtures
|
68
|
+
|
69
|
+
fixtury_database_connections.each(&:rollback_transaction)
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear_expired_fixtury_fixtures!
|
73
|
+
::Fixtury::Store.instance.clear_expired_references!
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_all_fixtury_fixtures!
|
77
|
+
fixtury_dependencies.each do |name|
|
78
|
+
fixtury(name) unless fixtury_loaded?(name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fixtury
|
4
|
+
class Locator
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
attr_accessor :instance
|
9
|
+
|
10
|
+
def instance
|
11
|
+
@instance ||= begin
|
12
|
+
require "fixtury/locator_backend/memory"
|
13
|
+
::Fixtury::Locator.new(
|
14
|
+
backend: ::Fixtury::LocatorBackend::Memory.new
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :backend
|
22
|
+
|
23
|
+
def initialize(backend:)
|
24
|
+
@backend = backend
|
25
|
+
end
|
26
|
+
|
27
|
+
def load(ref)
|
28
|
+
raise ArgumentError, "Unable to load a nil ref" if ref.nil?
|
29
|
+
|
30
|
+
backend.load(ref)
|
31
|
+
end
|
32
|
+
|
33
|
+
def dump(value)
|
34
|
+
raise ArgumentError, "Unable to dump a nil value" if value.nil?
|
35
|
+
|
36
|
+
ref = backend.dump(value)
|
37
|
+
raise ArgumentError, "The value resulted in a nil ref" if ref.nil?
|
38
|
+
|
39
|
+
ref
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fixtury/errors/unrecognizable_locator_error"
|
4
|
+
|
5
|
+
module Fixtury
|
6
|
+
module LocatorBackend
|
7
|
+
module Common
|
8
|
+
|
9
|
+
def recognized_reference?(_ref)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def recognized_value?(_value)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_recognized_reference(_ref)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
def dump_recognized_value(_value)
|
22
|
+
raise NotImplementedError
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(ref)
|
26
|
+
return load_recognized_reference(ref) if recognized_reference?(ref)
|
27
|
+
|
28
|
+
case ref
|
29
|
+
when Array
|
30
|
+
ref.map { |subref| self.load(subref) }
|
31
|
+
when Hash
|
32
|
+
ref.each_with_object({}) do |(k, subref), h|
|
33
|
+
h[k] = self.load(subref)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise ::Fixtury::Errors::UnrecognizableLocatorError.new(:load, ref)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def dump(value)
|
41
|
+
return dump_recognized_value(value) if recognized_value?(value)
|
42
|
+
|
43
|
+
case value
|
44
|
+
when Array
|
45
|
+
value.map { |subvalue| dump(subvalue) }
|
46
|
+
when Hash
|
47
|
+
ref.each_with_object({}) do |(k, subvalue), h|
|
48
|
+
h[k] = dump(subvalue)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
raise ::Fixtury::Errors::UnrecognizableLocatorError.new(:dump, value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./common"
|
4
|
+
require "globalid"
|
5
|
+
|
6
|
+
module Fixtury
|
7
|
+
module LocatorBackend
|
8
|
+
class GlobalID
|
9
|
+
|
10
|
+
include ::Fixtury::LocatorBackend::Common
|
11
|
+
|
12
|
+
MATCHER = %r{^gid://}.freeze
|
13
|
+
|
14
|
+
def recognized_reference?(ref)
|
15
|
+
ref.is_a?(String) && MATCHER.match?(ref)
|
16
|
+
end
|
17
|
+
|
18
|
+
def recognized_value?(val)
|
19
|
+
val.respond_to?(:to_global_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_recognized_reference(ref)
|
23
|
+
::GlobalID::Locator.locate ref
|
24
|
+
end
|
25
|
+
|
26
|
+
def dump_recognized_value(value)
|
27
|
+
value.to_global_id.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./common"
|
4
|
+
|
5
|
+
module Fixtury
|
6
|
+
module LocatorBackend
|
7
|
+
class Memory
|
8
|
+
|
9
|
+
include ::Fixtury::LocatorBackend::Common
|
10
|
+
|
11
|
+
MATCHER = /^fixtury-oid-(?<object_id>[\d]+)$/.freeze
|
12
|
+
|
13
|
+
def recognized_reference?(ref)
|
14
|
+
ref.is_a?(String) && MATCHER.match?(ref)
|
15
|
+
end
|
16
|
+
|
17
|
+
def recognized_value?(_val)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_recognized_reference(ref)
|
22
|
+
match = MATCHER.match(ref)
|
23
|
+
return nil unless match
|
24
|
+
|
25
|
+
::ObjectSpace._id2ref(match[:object_id].to_i)
|
26
|
+
rescue RangeError
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def dump_recognized_value(value)
|
31
|
+
"fixtury-oid-#{value.object_id}"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/fixtury/path.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fixtury
|
4
|
+
class Path
|
5
|
+
|
6
|
+
def initialize(namespace:, path:)
|
7
|
+
@namespace = namespace.to_s
|
8
|
+
@path = path.to_s
|
9
|
+
@full_path = (
|
10
|
+
@path.start_with?("/") ?
|
11
|
+
@path :
|
12
|
+
File.expand_path(::File.join(@namespace, @path), "/")
|
13
|
+
)
|
14
|
+
@segments = @full_path.split("/")
|
15
|
+
end
|
16
|
+
|
17
|
+
def top_level_namespace
|
18
|
+
return "" if @segments.size == 1
|
19
|
+
|
20
|
+
@segments.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def relative?
|
24
|
+
@path.start_with?(".")
|
25
|
+
end
|
26
|
+
|
27
|
+
def possible_absolute_paths
|
28
|
+
@possible_absolute_paths ||= begin
|
29
|
+
out = [@full_path]
|
30
|
+
out << @path unless relative?
|
31
|
+
out
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fixtury/definition"
|
4
|
+
require "fixtury/path"
|
5
|
+
require "fixtury/errors/already_defined_error"
|
6
|
+
require "fixtury/errors/fixture_not_defined_error"
|
7
|
+
require "fixtury/errors/schema_frozen_error"
|
8
|
+
|
9
|
+
module Fixtury
|
10
|
+
class Schema
|
11
|
+
|
12
|
+
attr_reader :definitions, :children, :name, :parent, :relative_name
|
13
|
+
|
14
|
+
def initialize(parent:, name:)
|
15
|
+
@name = name
|
16
|
+
@parent = parent
|
17
|
+
@relative_name = @name.split("/").last
|
18
|
+
@frozen = false
|
19
|
+
reset!
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset!
|
23
|
+
@children = {}
|
24
|
+
@definitions = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def freeze!
|
28
|
+
@frozen = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def frozen?
|
32
|
+
!!@frozen
|
33
|
+
end
|
34
|
+
|
35
|
+
def top_level_schema
|
36
|
+
top_level_schema? ? self : parent.top_level_schema
|
37
|
+
end
|
38
|
+
|
39
|
+
def top_level_schema?
|
40
|
+
parent.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
def define(&block)
|
44
|
+
ensure_not_frozen!
|
45
|
+
instance_eval(&block)
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# helpful for inspection
|
50
|
+
def structure(indent = "")
|
51
|
+
out = []
|
52
|
+
out << "#{indent}ns:#{relative_name}"
|
53
|
+
definitions.keys.sort.each do |key|
|
54
|
+
out << "#{indent} defn:#{key}"
|
55
|
+
end
|
56
|
+
|
57
|
+
children.keys.sort.each do |key|
|
58
|
+
child = children[key]
|
59
|
+
out << child.structure("#{indent} ")
|
60
|
+
end
|
61
|
+
|
62
|
+
out.join("\n")
|
63
|
+
end
|
64
|
+
|
65
|
+
def namespace(name, &block)
|
66
|
+
ensure_not_frozen!
|
67
|
+
ensure_no_conflict!(name: name, definitions: true, namespaces: false)
|
68
|
+
|
69
|
+
child = find_or_create_child_schema(name: name)
|
70
|
+
child.instance_eval(&block)
|
71
|
+
child
|
72
|
+
end
|
73
|
+
|
74
|
+
def fixture(name, &block)
|
75
|
+
ensure_not_frozen!
|
76
|
+
ensure_no_conflict!(name: name, definitions: true, namespaces: true)
|
77
|
+
create_child_definition(name: name, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def enhance(name, &block)
|
81
|
+
ensure_not_frozen!
|
82
|
+
definition = get_definition!(name)
|
83
|
+
definition.enhance(&block)
|
84
|
+
definition
|
85
|
+
end
|
86
|
+
|
87
|
+
def merge(other_ns)
|
88
|
+
ensure_not_frozen!
|
89
|
+
other_ns.definitions.each_pair do |name, dfn|
|
90
|
+
fixture(name, &dfn.callable)
|
91
|
+
dfn.enhancements.each do |e|
|
92
|
+
enhance(name, &e)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
other_ns.children.each_pair do |name, other_ns_child|
|
97
|
+
namespace(name) do
|
98
|
+
merge(other_ns_child)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_definition!(name)
|
106
|
+
dfn = get_definition(name)
|
107
|
+
raise ::Fixtury::Errors::FixtureNotDefinedError, name unless dfn
|
108
|
+
|
109
|
+
dfn
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_definition(name)
|
113
|
+
path = ::Fixtury::Path.new(namespace: self.name, path: name)
|
114
|
+
top_level = top_level_schema
|
115
|
+
|
116
|
+
dfn = nil
|
117
|
+
path.possible_absolute_paths.each do |abs_path|
|
118
|
+
*namespaces, definition_name = abs_path.split("/")
|
119
|
+
|
120
|
+
namespaces.shift if namespaces.first == top_level.name
|
121
|
+
target = top_level
|
122
|
+
|
123
|
+
namespaces.each do |ns|
|
124
|
+
next if ns.empty?
|
125
|
+
|
126
|
+
target = target.children[ns]
|
127
|
+
break unless target
|
128
|
+
end
|
129
|
+
|
130
|
+
dfn = target.definitions[definition_name] if target
|
131
|
+
return dfn if dfn
|
132
|
+
end
|
133
|
+
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_namespace(name)
|
138
|
+
path = ::Fixtury::Path.new(namespace: self.name, path: name)
|
139
|
+
top_level = top_level_schema
|
140
|
+
|
141
|
+
path.possible_absolute_paths.each do |abs_path|
|
142
|
+
*namespaces, _definition_name = abs_path.split("/")
|
143
|
+
|
144
|
+
namespaces.shift if namespaces.first == top_level.name
|
145
|
+
target = top_level
|
146
|
+
|
147
|
+
namespaces.each do |ns|
|
148
|
+
next if ns.empty?
|
149
|
+
|
150
|
+
target = target.children[ns]
|
151
|
+
break unless target
|
152
|
+
end
|
153
|
+
|
154
|
+
return target if target
|
155
|
+
end
|
156
|
+
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
protected
|
161
|
+
|
162
|
+
def find_child_schema(name:)
|
163
|
+
children[name.to_s]
|
164
|
+
end
|
165
|
+
|
166
|
+
def find_or_create_child_schema(name:)
|
167
|
+
name = name.to_s
|
168
|
+
child = find_child_schema(name: name)
|
169
|
+
child ||= begin
|
170
|
+
children[name] = begin
|
171
|
+
child_name = build_child_name(name: name)
|
172
|
+
self.class.new(name: child_name, parent: self)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def find_child_definition(name:)
|
178
|
+
definitions[name.to_s]
|
179
|
+
end
|
180
|
+
|
181
|
+
def create_child_definition(name:, &block)
|
182
|
+
child_name = build_child_name(name: name)
|
183
|
+
definition = ::Fixtury::Definition.new(name: child_name, schema: self, &block)
|
184
|
+
definitions[name.to_s] = definition
|
185
|
+
end
|
186
|
+
|
187
|
+
def build_child_name(name:)
|
188
|
+
name = name&.to_s
|
189
|
+
raise ArgumentError, "`name` must be provided" if name.nil?
|
190
|
+
raise ArgumentError, "#{name} is invalid. `name` must contain only a-z, A-Z, 0-9, and _." unless name.match(/^[a-zA-Z_0-9]+$/)
|
191
|
+
|
192
|
+
arr = ["", self.name, name]
|
193
|
+
arr.join("/").gsub(%r{/{2,}}, "/")
|
194
|
+
end
|
195
|
+
|
196
|
+
def ensure_no_conflict!(name:, namespaces:, definitions:)
|
197
|
+
if definitions
|
198
|
+
definition = find_child_definition(name: name)
|
199
|
+
raise ::Fixtury::Errors::AlreadyDefinedError, definition.name if definition
|
200
|
+
end
|
201
|
+
|
202
|
+
if namespaces
|
203
|
+
ns = find_child_schema(name: name)
|
204
|
+
raise ::Fixtury::Errors::AlreadyDefinedError, ns.name if ns
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def ensure_not_frozen!
|
209
|
+
return unless frozen?
|
210
|
+
|
211
|
+
raise ::Fixtury::Errors::SchemaFrozenError
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "singleton"
|
4
|
+
require "yaml"
|
5
|
+
require "fixtury/locator"
|
6
|
+
require "fixtury/errors/circular_dependency_error"
|
7
|
+
require "fixtury/execution_context"
|
8
|
+
require "fixtury/reference"
|
9
|
+
|
10
|
+
module Fixtury
|
11
|
+
class Store
|
12
|
+
|
13
|
+
cattr_accessor :instance
|
14
|
+
|
15
|
+
HOLDER = "__BUILDING_FIXTURE__"
|
16
|
+
|
17
|
+
attr_reader :filepath, :references, :ttl, :auto_refresh_expired
|
18
|
+
attr_reader :schema, :locator
|
19
|
+
attr_reader :verbose
|
20
|
+
attr_reader :execution_context
|
21
|
+
|
22
|
+
def initialize(
|
23
|
+
filepath: nil,
|
24
|
+
locator: ::Fixtury::Locator.instance,
|
25
|
+
verbose: false,
|
26
|
+
ttl: nil,
|
27
|
+
schema: nil,
|
28
|
+
auto_refresh_expired: false
|
29
|
+
)
|
30
|
+
@schema = schema || ::Fixtury.schema
|
31
|
+
@verbose = verbose
|
32
|
+
@locator = locator
|
33
|
+
@filepath = filepath
|
34
|
+
@references = @filepath && ::File.file?(@filepath) ? ::YAML.load_file(@filepath) : {}
|
35
|
+
@execution_context = ::Fixtury::ExecutionContext.new
|
36
|
+
@ttl = ttl ? ttl.to_i : ttl
|
37
|
+
@auto_refresh_expired = !!auto_refresh_expired
|
38
|
+
self.class.instance ||= self
|
39
|
+
end
|
40
|
+
|
41
|
+
def dump_to_file
|
42
|
+
return unless filepath
|
43
|
+
|
44
|
+
::File.open(filepath, "wb") { |io| io.write(references.to_yaml) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def clear_expired_references!
|
48
|
+
return unless ttl
|
49
|
+
|
50
|
+
references.delete_if do |name, ref|
|
51
|
+
is_expired = ref_expired?(ref)
|
52
|
+
log { "expiring #{name}" } if is_expired
|
53
|
+
is_expired
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def load_all(schema = self.schema)
|
58
|
+
schema.definitions.each_pair do |_key, dfn|
|
59
|
+
get(dfn.name)
|
60
|
+
end
|
61
|
+
|
62
|
+
schema.schemas.each_pair do |_key, ns|
|
63
|
+
load_all(ns)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def clear_cache!(pattern: nil)
|
68
|
+
pattern ||= "*"
|
69
|
+
pattern = "/" + pattern unless pattern.start_with?("/")
|
70
|
+
glob = pattern.ends_with?("*")
|
71
|
+
pattern = pattern[0...-1] if glob
|
72
|
+
references.delete_if do |key, _value|
|
73
|
+
hit = glob ? key.start_with?(pattern) : key == pattern
|
74
|
+
log(true) { "clearing #{key}" } if hit
|
75
|
+
hit
|
76
|
+
end
|
77
|
+
dump_to_file
|
78
|
+
end
|
79
|
+
|
80
|
+
def with_relative_schema(schema)
|
81
|
+
prior = @schema
|
82
|
+
@schema = schema
|
83
|
+
yield
|
84
|
+
ensure
|
85
|
+
@schema = prior
|
86
|
+
end
|
87
|
+
|
88
|
+
def loaded?(name)
|
89
|
+
dfn = schema.get_definition!(name)
|
90
|
+
full_name = dfn.name
|
91
|
+
ref = references[full_name]
|
92
|
+
result = ref && ref != HOLDER
|
93
|
+
log { result ? "hit #{full_name}" : "miss #{full_name}" }
|
94
|
+
result
|
95
|
+
end
|
96
|
+
|
97
|
+
def get(name)
|
98
|
+
dfn = schema.get_definition!(name)
|
99
|
+
full_name = dfn.name
|
100
|
+
ref = references[full_name]
|
101
|
+
|
102
|
+
if ref == HOLDER
|
103
|
+
raise ::Fixtury::Errors::CircularDependencyError, full_name
|
104
|
+
end
|
105
|
+
|
106
|
+
if ref && auto_refresh_expired && ref_expired?(ref)
|
107
|
+
log { "refreshing #{full_name}" }
|
108
|
+
clear_ref(ref)
|
109
|
+
ref = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
value = nil
|
113
|
+
|
114
|
+
if ref
|
115
|
+
log { "hit #{full_name}" }
|
116
|
+
value = load_ref(ref.value)
|
117
|
+
unless value
|
118
|
+
clear_ref(full_name)
|
119
|
+
log { "missing #{full_name}" }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if value.nil?
|
124
|
+
# set the references to HOLDER so any recursive behavior ends up hitting a circular dependency error if the same fixture load is attempted
|
125
|
+
references[full_name] = HOLDER
|
126
|
+
|
127
|
+
value = dfn.call(store: self, execution_context: execution_context)
|
128
|
+
|
129
|
+
log { "store #{full_name}" }
|
130
|
+
|
131
|
+
ref = dump_ref(full_name, value)
|
132
|
+
ref = ::Fixtury::Reference.new(full_name, ref)
|
133
|
+
references[full_name] = ref
|
134
|
+
end
|
135
|
+
|
136
|
+
value
|
137
|
+
end
|
138
|
+
alias [] get
|
139
|
+
|
140
|
+
def load_ref(ref)
|
141
|
+
locator.load(ref)
|
142
|
+
end
|
143
|
+
|
144
|
+
def dump_ref(_name, value)
|
145
|
+
locator.dump(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
def clear_ref(name)
|
149
|
+
references.delete(name)
|
150
|
+
end
|
151
|
+
|
152
|
+
def ref_expired?(ref)
|
153
|
+
return false unless ttl
|
154
|
+
|
155
|
+
ref.created_at < (Time.now.to_i - ttl)
|
156
|
+
end
|
157
|
+
|
158
|
+
def log(local_verbose = false, &block)
|
159
|
+
return unless verbose || local_verbose
|
160
|
+
|
161
|
+
puts "[fixtury|store] #{block.call}"
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :fixtury do
|
4
|
+
task :setup
|
5
|
+
|
6
|
+
desc "Clear fixtures from your cache. Accepts a pattern or fixture name such as foo/bar or /foo/*. Default pattern is /*"
|
7
|
+
task :clear_cache, [:pattern] => :setup do |_t, args|
|
8
|
+
::Fixtury::Store.instance.clear_cache!(pattern: args[:pattern])
|
9
|
+
end
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fixtury
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.alpha
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Nelson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: autotest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: globalid
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-reporters
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: mocha
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '10.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '10.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: sqlite
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- mike@guideline.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".gitignore"
|
147
|
+
- ".ruby-version"
|
148
|
+
- ".travis.yml"
|
149
|
+
- Gemfile
|
150
|
+
- Gemfile.lock
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- bin/console
|
154
|
+
- bin/setup
|
155
|
+
- fixtury.gemspec
|
156
|
+
- lib/fixtury.rb
|
157
|
+
- lib/fixtury/definition.rb
|
158
|
+
- lib/fixtury/errors/already_defined_error.rb
|
159
|
+
- lib/fixtury/errors/circular_dependency_error.rb
|
160
|
+
- lib/fixtury/errors/fixture_not_defined_error.rb
|
161
|
+
- lib/fixtury/errors/schema_frozen_error.rb
|
162
|
+
- lib/fixtury/errors/unrecognizable_locator_error.rb
|
163
|
+
- lib/fixtury/execution_context.rb
|
164
|
+
- lib/fixtury/hooks.rb
|
165
|
+
- lib/fixtury/locator.rb
|
166
|
+
- lib/fixtury/locator_backend/common.rb
|
167
|
+
- lib/fixtury/locator_backend/globalid.rb
|
168
|
+
- lib/fixtury/locator_backend/memory.rb
|
169
|
+
- lib/fixtury/path.rb
|
170
|
+
- lib/fixtury/railtie.rb
|
171
|
+
- lib/fixtury/reference.rb
|
172
|
+
- lib/fixtury/schema.rb
|
173
|
+
- lib/fixtury/store.rb
|
174
|
+
- lib/fixtury/tasks.rake
|
175
|
+
- lib/fixtury/version.rb
|
176
|
+
homepage: https://github.com/guideline-tech/fixtury
|
177
|
+
licenses: []
|
178
|
+
metadata:
|
179
|
+
allowed_push_host: https://rubygems.org
|
180
|
+
homepage_uri: https://github.com/guideline-tech/fixtury
|
181
|
+
source_code_uri: https://github.com/guideline-tech/fixtury
|
182
|
+
changelog_uri: https://github.com/guideline-tech/fixtury
|
183
|
+
post_install_message:
|
184
|
+
rdoc_options: []
|
185
|
+
require_paths:
|
186
|
+
- lib
|
187
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
188
|
+
requirements:
|
189
|
+
- - ">="
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0'
|
192
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - ">"
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: 1.3.1
|
197
|
+
requirements: []
|
198
|
+
rubygems_version: 3.0.6
|
199
|
+
signing_key:
|
200
|
+
specification_version: 4
|
201
|
+
summary: Treat fixtures like factories and factories like fixtures
|
202
|
+
test_files: []
|