fixtury 0.2.1 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9090092db55914068c6478939047d638ee8bb678ee53a41f0d36380bf2df7dea
4
- data.tar.gz: 4e58164635a72d63e059a5622b1c867556235ba09537e127b5498f94a3bf8e60
3
+ metadata.gz: 5419cf56fd821e06b8b0fc7d55f564d87e85aaab5e7e21933c5ef923f3f32294
4
+ data.tar.gz: e53da80eda66ee2abd07baec7ccd847ba18a5e8631b01014f7e62a43616ac509
5
5
  SHA512:
6
- metadata.gz: 05e1ddd158beafe83cbe9e9d32abf10a49dcdb59709cc372f31a29a1a52c3eafe0b802eba8015ccdb429444678d94941db42dedb6202396b958c8d53d2e6f9d2
7
- data.tar.gz: 70e2abc1abc6324e3523631a6f2caf936d0b0859eeb14b41fec843b766bf648d047b1e09e9d9bb6da60131f06be23e7e02c1adcf86be30910387581b7e191d3e
6
+ metadata.gz: a69c33f32f35346abae60a3f3c27f232dc813fbe551f99055d7e2fcc8eacd5c6873fdf5a925be4d3ef3635c7558577918018dc8c399674e26232fc0fd0a0d10e
7
+ data.tar.gz: 4b9e9f3f0c4e538248eda219cb57d6f618a870407ae8f9bfeabf17aab49850e8e253bfd77a7279a9b70b164b572fb41021211b22b46d6e656c7fc57e21af6959
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fixtury (0.2.1)
4
+ fixtury (0.3.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,11 +1,14 @@
1
1
  # Fixtury
2
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.
3
+ Fixtury aims to provide an interface for creating, managing, and accessing test data in a simple and efficient way. It has no opinion on how you generate the data, it simply provides efficient ways to access it.
4
4
 
5
- For example, if a developer is running a test locally, there's no reason to build all fixtures for your suite.
5
+ Often, fixture frameworks require you to either heavily maintain static fixtures or generate all your fixtures at runtime. Fixtury attempts to find a middle ground that enables a faster and more effecient development process.
6
6
 
7
- ```
8
- class MyTest < ::Test
7
+ For example, if a developer is running a test locally in their development environment there's no reason to build all fixtures for your suite of 30k tests. Instead, if we're able to track the fixture dependencies of the tests that are running we can build (and cache) the data relevant for the specific tests that are run.
8
+
9
+ ```ruby
10
+ class MyTest < ::ActiveSupport::TestCase
11
+ include ::Fixtury::TestHooks
9
12
 
10
13
  fixtury "users.fresh"
11
14
  let(:user) { fixtury("users.fresh") }
@@ -15,7 +18,6 @@ class MyTest < ::Test
15
18
  end
16
19
 
17
20
  end
18
-
19
21
  ```
20
22
 
21
23
  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.
@@ -10,11 +10,14 @@ require "fixtury/store"
10
10
 
11
11
  module Fixtury
12
12
 
13
+ # Shortcut for opening the top level schema.
13
14
  def self.define(&block)
14
15
  schema.define(&block)
15
16
  schema
16
17
  end
17
18
 
19
+ # The default top level schema. Fixtury::Schema instances can be completely self-contained but most
20
+ # usage would be through this shared definition.
18
21
  def self.schema
19
22
  @top_level_schema ||= ::Fixtury::Schema.new(parent: nil, name: "")
20
23
  end
@@ -1,18 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fixtury/definition_executor"
4
+
3
5
  module Fixtury
4
6
  class Definition
5
7
 
6
8
  attr_reader :name
7
9
  attr_reader :schema
10
+ alias parent schema
11
+ attr_reader :options
8
12
 
9
13
  attr_reader :callable
10
14
  attr_reader :enhancements
11
15
 
12
- def initialize(schema: nil, name:, &block)
16
+ def initialize(schema: nil, name:, options: {}, &block)
13
17
  @name = name
14
18
  @schema = schema
15
19
  @callable = block
20
+ @options = options
16
21
  @enhancements = []
17
22
  end
18
23
 
@@ -24,55 +29,23 @@ module Fixtury
24
29
  @enhancements.any?
25
30
  end
26
31
 
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
32
+ def info
33
+ {
34
+ name: name,
35
+ loc: location_from_callable(callable),
36
+ enhancements: enhancements.map { |e| location_from_callable(e) },
37
+ }
35
38
  end
36
39
 
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: nil, value:)
48
- args = []
49
- args << value unless value.nil?
50
- if callable.arity > args.length
51
- raise ArgumentError, "A store store must be provided if the definition expects it." unless store
52
-
53
- args << store
54
- end
55
-
56
- provide_execution_context_hooks(execution_context) do |ctxt|
57
- if args.length.positive?
58
- ctxt.instance_exec(*args, &callable)
59
- else
60
- ctxt.instance_eval(&callable)
61
- end
62
- end
40
+ def call(store: nil, execution_context: nil)
41
+ executor = ::Fixtury::DefinitionExecutor.new(store: store, definition: self, execution_context: execution_context)
42
+ executor.__call
63
43
  end
64
44
 
65
- def provide_execution_context_hooks(execution_context)
66
- return yield self unless execution_context
45
+ def location_from_callable(callable)
46
+ return nil unless callable.respond_to?(:source_location)
67
47
 
68
- execution_context.before_fixture(self) if execution_context.respond_to?(:before_fixture)
69
- value = if execution_context.respond_to?(:around_fixture)
70
- execution_context.around_fixture(self) { yield execution_context }
71
- else
72
- yield execution_context
73
- end
74
- execution_context.after_fixture(self, value) if execution_context.respond_to?(:after_fixture)
75
- value
48
+ callable.source_location.join(":")
76
49
  end
77
50
 
78
51
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fixtury
4
+ class DefinitionExecutor
5
+
6
+ attr_reader :value, :execution_type, :definition, :store, :execution_context
7
+
8
+ def initialize(store: nil, execution_context: nil, definition:)
9
+ @store = store
10
+ @definition = definition
11
+ @execution_context = execution_context
12
+ @execution_type = nil
13
+ @value = nil
14
+ end
15
+
16
+ def __call
17
+ maybe_set_store_context do
18
+ provide_schema_hooks do
19
+ run_callable(callable: definition.callable, type: :definition)
20
+ definition.enhancements.each do |e|
21
+ run_callable(callable: e, type: :enhancement)
22
+ end
23
+ end
24
+ end
25
+
26
+ value
27
+ end
28
+
29
+ def get(name)
30
+ raise ArgumentError, "A store is required for #{definition.name}" unless store
31
+
32
+ store.get(name, execution_context: execution_context)
33
+ end
34
+ alias [] get
35
+
36
+ def method_missing(method_name, *args, &block)
37
+ return super unless execution_context
38
+
39
+ execution_context.send(method_name, *args, &block)
40
+ end
41
+
42
+ def respond_to_missing?(method_name)
43
+ return super unless execution_context
44
+
45
+ execution_context.respond_to?(method_name, true)
46
+ end
47
+
48
+ private
49
+
50
+ def run_callable(callable:, type:)
51
+ @execution_type = type
52
+
53
+ @value = if callable.arity.positive?
54
+ instance_exec(self, &callable)
55
+ else
56
+ instance_eval(&callable)
57
+ end
58
+ end
59
+
60
+ def maybe_set_store_context
61
+ return yield unless store
62
+
63
+ store.with_relative_schema(definition.schema) do
64
+ yield
65
+ end
66
+ end
67
+
68
+ def provide_schema_hooks
69
+ return yield unless definition.schema
70
+
71
+ @value = definition.schema.around_fixture_hook(self) do
72
+ yield
73
+ value
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -9,12 +9,17 @@ module Fixtury
9
9
  new(name, HOLDER_VALUE)
10
10
  end
11
11
 
12
- attr_reader :name, :value, :created_at
12
+ def self.create(name, value)
13
+ new(name, value)
14
+ end
15
+
16
+ attr_reader :name, :value, :created_at, :options
13
17
 
14
- def initialize(name, value)
18
+ def initialize(name, value, options = {})
15
19
  @name = name
16
20
  @value = value
17
21
  @created_at = Time.now.to_i
22
+ @options = options
18
23
  end
19
24
 
20
25
  def holder?
@@ -9,16 +9,38 @@ require "fixtury/errors/schema_frozen_error"
9
9
  module Fixtury
10
10
  class Schema
11
11
 
12
- attr_reader :definitions, :children, :name, :parent, :relative_name
12
+ attr_reader :definitions, :children, :name, :parent, :relative_name, :around_fixture_definition, :options
13
13
 
14
- def initialize(parent:, name:)
14
+ def initialize(parent:, name:, options: {})
15
15
  @name = name
16
16
  @parent = parent
17
17
  @relative_name = @name.split("/").last
18
+ @around_fixture_definition = nil
19
+ @options = options
18
20
  @frozen = false
19
21
  reset!
20
22
  end
21
23
 
24
+ def around_fixture(&block)
25
+ @around_fixture_definition = block
26
+ end
27
+
28
+ def around_fixture_hook(executor, &definition)
29
+ maybe_invoke_parent_around_fixture_hook(executor) do
30
+ if around_fixture_definition.nil?
31
+ yield
32
+ else
33
+ around_fixture_definition.call(executor, definition)
34
+ end
35
+ end
36
+ end
37
+
38
+ def maybe_invoke_parent_around_fixture_hook(executor, &block)
39
+ return yield unless parent
40
+
41
+ parent.around_fixture_hook(executor, &block)
42
+ end
43
+
22
44
  def reset!
23
45
  @children = {}
24
46
  @definitions = {}
@@ -62,19 +84,19 @@ module Fixtury
62
84
  out.join("\n")
63
85
  end
64
86
 
65
- def namespace(name, &block)
87
+ def namespace(name, options = {}, &block)
66
88
  ensure_not_frozen!
67
89
  ensure_no_conflict!(name: name, definitions: true, namespaces: false)
68
90
 
69
- child = find_or_create_child_schema(name: name)
91
+ child = find_or_create_child_schema(name: name, options: options)
70
92
  child.instance_eval(&block) if block_given?
71
93
  child
72
94
  end
73
95
 
74
- def fixture(name, &block)
96
+ def fixture(name, options = {}, &block)
75
97
  ensure_not_frozen!
76
98
  ensure_no_conflict!(name: name, definitions: true, namespaces: true)
77
- create_child_definition(name: name, &block)
99
+ create_child_definition(name: name, options: options, &block)
78
100
  end
79
101
 
80
102
  def enhance(name, &block)
@@ -99,6 +121,8 @@ module Fixtury
99
121
  end
100
122
  end
101
123
 
124
+ around_fixture(&other_ns.around_fixture_definition) if other_ns.around_fixture_definition
125
+
102
126
  self
103
127
  end
104
128
 
@@ -163,31 +187,32 @@ module Fixtury
163
187
  children[name.to_s]
164
188
  end
165
189
 
166
- def find_or_create_child_schema(name:)
190
+ def find_or_create_child_schema(name:, options:)
167
191
  name = name.to_s
168
192
  child = find_child_schema(name: name)
169
193
  child ||= begin
170
194
  children[name] = begin
171
195
  child_name = build_child_name(name: name)
172
- self.class.new(name: child_name, parent: self)
196
+ self.class.new(name: child_name, parent: self, options: options)
173
197
  end
174
198
  end
199
+ child
175
200
  end
176
201
 
177
202
  def find_child_definition(name:)
178
203
  definitions[name.to_s]
179
204
  end
180
205
 
181
- def create_child_definition(name:, &block)
206
+ def create_child_definition(name:, options:, &block)
182
207
  child_name = build_child_name(name: name)
183
- definition = ::Fixtury::Definition.new(name: child_name, schema: self, &block)
208
+ definition = ::Fixtury::Definition.new(name: child_name, schema: self, options: options, &block)
184
209
  definitions[name.to_s] = definition
185
210
  end
186
211
 
187
212
  def build_child_name(name:)
188
213
  name = name&.to_s
189
214
  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]+$/)
215
+ raise ArgumentError, "#{name} is invalid. `name` must contain only a-z, A-Z, 0-9, and _." unless /^[a-zA-Z_0-9]+$/.match?(name)
191
216
 
192
217
  arr = ["", self.name, name]
193
218
  arr.join("/").gsub(%r{/{2,}}, "/")
@@ -21,7 +21,6 @@ module Fixtury
21
21
  attr_reader :filepath, :references, :ttl, :auto_refresh_expired
22
22
  attr_reader :schema, :locator
23
23
  attr_reader :log_level
24
- attr_reader :execution_context
25
24
 
26
25
  def initialize(
27
26
  filepath: nil,
@@ -29,7 +28,6 @@ module Fixtury
29
28
  log_level: nil,
30
29
  ttl: nil,
31
30
  schema: nil,
32
- execution_context: nil,
33
31
  auto_refresh_expired: false
34
32
  )
35
33
  @schema = schema || ::Fixtury.schema
@@ -39,7 +37,6 @@ module Fixtury
39
37
  @locator = locator
40
38
  @filepath = filepath
41
39
  @references = @filepath && ::File.file?(@filepath) ? ::YAML.load_file(@filepath) : {}
42
- @execution_context = execution_context
43
40
  @ttl = ttl ? ttl.to_i : ttl
44
41
  @auto_refresh_expired = !!auto_refresh_expired
45
42
  self.class.instance ||= self
@@ -107,7 +104,7 @@ module Fixtury
107
104
  result
108
105
  end
109
106
 
110
- def get(name)
107
+ def get(name, execution_context: nil)
111
108
  dfn = schema.get_definition!(name)
112
109
  full_name = dfn.name
113
110
  ref = references[full_name]
@@ -11,6 +11,9 @@ module Fixtury
11
11
  included do
12
12
  class_attribute :fixtury_dependencies
13
13
  self.fixtury_dependencies = Set.new
14
+
15
+ class_attribute :local_fixtury_dependencies
16
+ self.local_fixtury_dependencies = Set.new
14
17
  end
15
18
 
16
19
  module ClassMethods
@@ -22,7 +25,7 @@ module Fixtury
22
25
  if block_given?
23
26
  raise ArgumentError, "A fixture cannot be defined in an anonymous class" if name.nil?
24
27
 
25
- namespace = name.underscore
28
+ namespace = fixtury_namespace
26
29
 
27
30
  ns = ::Fixtury.schema
28
31
 
@@ -32,7 +35,7 @@ module Fixtury
32
35
 
33
36
  names.each do |fixture_name|
34
37
  ns.fixture(fixture_name, &definition)
35
- self.fixtury_dependencies += ["#{namespace}/#{fixture_name}"]
38
+ self.local_fixtury_dependencies += ["/#{namespace}/#{fixture_name}"]
36
39
  end
37
40
 
38
41
  # otherwise, just record the dependency
@@ -40,14 +43,16 @@ module Fixtury
40
43
  self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
41
44
  end
42
45
 
43
- if opts[:accessor]
46
+ accessor_option = opts.key?(:accessor) ? opts[:accessor] : true
47
+
48
+ if accessor_option
44
49
 
45
- if opts[:accessor] != true && names.length > 1
50
+ if accessor_option != true && names.length > 1
46
51
  raise ArgumentError, "A named :accessor option is only available when providing one fixture"
47
52
  end
48
53
 
49
54
  names.each do |fixture_name|
50
- method_name = opts[:accessor] == true ? fixture_name.split("/").last : opts[:accessor]
55
+ method_name = accessor_option == true ? fixture_name.split("/").last : accessor_option
51
56
  ivar = :"@#{method_name}"
52
57
 
53
58
  class_eval <<-EV, __FILE__, __LINE__ + 1
@@ -62,6 +67,10 @@ module Fixtury
62
67
  end
63
68
  end
64
69
 
70
+ def fixtury_namespace
71
+ name.underscore
72
+ end
73
+
65
74
  end
66
75
 
67
76
  def fixtury(name)
@@ -69,18 +78,16 @@ module Fixtury
69
78
 
70
79
  name = name.to_s
71
80
 
72
- unless name.include?("/")
73
- local_name = "#{self.class.name.underscore}/#{name}"
74
- if self.fixtury_dependencies.include?(local_name)
75
- return fixtury_store.get(local_name)
76
- end
81
+ local_alias = "/#{self.class.fixtury_namespace}/#{name}"
82
+ if self.local_fixtury_dependencies.include?(local_alias)
83
+ return fixtury_store.get(local_alias, execution_context: self)
77
84
  end
78
85
 
79
86
  unless self.fixtury_dependencies.include?(name)
80
87
  raise ArgumentError, "Unrecognized fixtury dependency `#{name}` for #{self.class}"
81
88
  end
82
89
 
83
- fixtury_store.get(name)
90
+ fixtury_store.get(name, execution_context: self)
84
91
  end
85
92
 
86
93
  def fixtury_store
@@ -99,7 +106,7 @@ module Fixtury
99
106
 
100
107
  # piggybacking activerecord fixture setup for now.
101
108
  def setup_fixtures(*args)
102
- if fixtury_dependencies.any?
109
+ if fixtury_dependencies.any? || local_fixtury_dependencies.any?
103
110
  setup_fixtury_fixtures
104
111
  else
105
112
  super
@@ -108,7 +115,7 @@ module Fixtury
108
115
 
109
116
  # piggybacking activerecord fixture setup for now.
110
117
  def teardown_fixtures(*args)
111
- if fixtury_dependencies.any?
118
+ if fixtury_dependencies.any? || local_fixtury_dependencies.any?
112
119
  teardown_fixtury_fixtures
113
120
  else
114
121
  super
@@ -139,7 +146,7 @@ module Fixtury
139
146
  end
140
147
 
141
148
  def load_all_fixtury_fixtures!
142
- fixtury_dependencies.each do |name|
149
+ (fixtury_dependencies | local_fixtury_dependencies).each do |name|
143
150
  fixtury(name) unless fixtury_loaded?(name)
144
151
  end
145
152
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fixtury
4
4
 
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.3"
6
6
 
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixtury
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Nelson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-13 00:00:00.000000000 Z
11
+ date: 2020-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: autotest
@@ -155,6 +155,7 @@ files:
155
155
  - fixtury.gemspec
156
156
  - lib/fixtury.rb
157
157
  - lib/fixtury/definition.rb
158
+ - lib/fixtury/definition_executor.rb
158
159
  - lib/fixtury/errors/already_defined_error.rb
159
160
  - lib/fixtury/errors/circular_dependency_error.rb
160
161
  - lib/fixtury/errors/fixture_not_defined_error.rb