fixtury 0.2.0 → 0.3.2

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: 43cad01d0dda49953fb41782235cb0786d60a0b84eb3d8e9f4145d6fa4268f04
4
- data.tar.gz: 5f4347bcf7a8ef5b7c72d42802e260f7ecc1b0e8d3772f4681bbef2aa7e9a963
3
+ metadata.gz: 11c65dc0db10367bf67f7d421ccc76e7b647a5bf73357431bc85dbd788667b00
4
+ data.tar.gz: 7042ed9b891e4fd0e2bdcade6cc0145160e5fb1dd6394d2bcd8de612ecceb1f8
5
5
  SHA512:
6
- metadata.gz: adc92b02ad90ae0afd4f4b10d146e26663d3dfd0d428f9739d9cfcf147dde7d2e09b05f84e956f116af62123477bc453f343ca3bda0a3bf17ae6d7edf61d7f35
7
- data.tar.gz: 4a5720793e764bc4f366512b8d172b31d64b68c89abd86862d2d199f68bd36b590e706bba56c8fa5471d071c5d84e370c30de0b7bf93109df3476d75caf22dca
6
+ metadata.gz: ed2b328ac82a6506a9c0d4fb26e049ee249cae994dd4406d2896c0b4be8e270dc54fef1a5f94a887077abc5a9a964a6412bea23bccce7ead21c600c70c2f4374
7
+ data.tar.gz: 18888bd37fdd508552353f5aa4d076f5bedd4ccf4420d7fc914450dcbaf5ea0c1cb29b428af3c94c3c617fd64359831f43594f65e3e9d300166329e0155e3b11
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fixtury (0.1.0)
4
+ fixtury (0.3.2)
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,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fixtury/definition_executor"
4
+
3
5
  module Fixtury
4
6
  class Definition
5
7
 
@@ -24,55 +26,23 @@ module Fixtury
24
26
  @enhancements.any?
25
27
  end
26
28
 
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
29
+ def info
30
+ {
31
+ name: name,
32
+ loc: location_from_callable(callable),
33
+ enhancements: enhancements.map { |e| location_from_callable(e) },
34
+ }
35
35
  end
36
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: 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
37
+ def call(store: nil, execution_context: nil)
38
+ executor = ::Fixtury::DefinitionExecutor.new(store: store, definition: self, execution_context: execution_context)
39
+ executor.__call
63
40
  end
64
41
 
65
- def provide_execution_context_hooks(execution_context)
66
- return yield self unless execution_context
42
+ def location_from_callable(callable)
43
+ return nil unless callable.respond_to?(:source_location)
67
44
 
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
45
+ callable.source_location.join(":")
76
46
  end
77
47
 
78
48
  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,16 +9,37 @@ 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
13
13
 
14
14
  def initialize(parent:, name:)
15
15
  @name = name
16
16
  @parent = parent
17
17
  @relative_name = @name.split("/").last
18
+ @around_fixture_definition = nil
18
19
  @frozen = false
19
20
  reset!
20
21
  end
21
22
 
23
+ def around_fixture(&block)
24
+ @around_fixture_definition = block
25
+ end
26
+
27
+ def around_fixture_hook(executor, &definition)
28
+ maybe_invoke_parent_around_fixture_hook(executor) do
29
+ if around_fixture_definition.nil?
30
+ yield
31
+ else
32
+ around_fixture_definition.call(executor, definition)
33
+ end
34
+ end
35
+ end
36
+
37
+ def maybe_invoke_parent_around_fixture_hook(executor, &block)
38
+ return yield unless parent
39
+
40
+ parent.around_fixture_hook(executor, &block)
41
+ end
42
+
22
43
  def reset!
23
44
  @children = {}
24
45
  @definitions = {}
@@ -99,6 +120,8 @@ module Fixtury
99
120
  end
100
121
  end
101
122
 
123
+ around_fixture(&other_ns.around_fixture_definition) if other_ns.around_fixture_definition
124
+
102
125
  self
103
126
  end
104
127
 
@@ -172,6 +195,7 @@ module Fixtury
172
195
  self.class.new(name: child_name, parent: self)
173
196
  end
174
197
  end
198
+ child
175
199
  end
176
200
 
177
201
  def find_child_definition(name:)
@@ -187,7 +211,7 @@ module Fixtury
187
211
  def build_child_name(name:)
188
212
  name = name&.to_s
189
213
  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]+$/)
214
+ 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
215
 
192
216
  arr = ["", self.name, name]
193
217
  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
@@ -80,7 +77,7 @@ module Fixtury
80
77
  def clear_cache!(pattern: nil)
81
78
  pattern ||= "*"
82
79
  pattern = "/" + pattern unless pattern.start_with?("/")
83
- glob = pattern.ends_with?("*")
80
+ glob = pattern.end_with?("*")
84
81
  pattern = pattern[0...-1] if glob
85
82
  references.delete_if do |key, _value|
86
83
  hit = glob ? key.start_with?(pattern) : key == pattern
@@ -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]
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fixtury/store"
4
+ require "active_support/core_ext/class/attribute"
4
5
 
5
6
  module Fixtury
6
7
  module TestHooks
@@ -10,54 +11,93 @@ module Fixtury
10
11
  included do
11
12
  class_attribute :fixtury_dependencies
12
13
  self.fixtury_dependencies = Set.new
14
+
15
+ class_attribute :local_fixtury_dependencies
16
+ self.local_fixtury_dependencies = Set.new
13
17
  end
14
18
 
15
19
  module ClassMethods
16
20
 
17
- def fixtury(*names)
18
- self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
19
- end
21
+ def fixtury(*names, &definition)
22
+ opts = names.extract_options!
23
+
24
+ # define fixtures if blocks are given
25
+ if block_given?
26
+ raise ArgumentError, "A fixture cannot be defined in an anonymous class" if name.nil?
27
+
28
+ namespace = fixtury_namespace
20
29
 
21
- def define_fixture(name, &block)
22
- fixture_name = name
23
- namespace_names = self.name.underscore.split("/")
30
+ ns = ::Fixtury.schema
24
31
 
25
- ns = ::Fixtury.schema
32
+ namespace.split("/").each do |ns_name|
33
+ ns = ns.namespace(ns_name){}
34
+ end
26
35
 
27
- namespace_names.each do |ns_name|
28
- ns = ns.namespace(ns_name){}
36
+ names.each do |fixture_name|
37
+ ns.fixture(fixture_name, &definition)
38
+ self.local_fixtury_dependencies += ["/#{namespace}/#{fixture_name}"]
39
+ end
40
+
41
+ # otherwise, just record the dependency
42
+ else
43
+ self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
29
44
  end
30
45
 
31
- ns.fixture(fixture_name, &block)
46
+ accessor_option = opts.key?(:accessor) ? opts[:accessor] : true
47
+
48
+ if accessor_option
49
+
50
+ if accessor_option != true && names.length > 1
51
+ raise ArgumentError, "A named :accessor option is only available when providing one fixture"
52
+ end
53
+
54
+ names.each do |fixture_name|
55
+ method_name = accessor_option == true ? fixture_name.split("/").last : accessor_option
56
+ ivar = :"@#{method_name}"
32
57
 
33
- fixtury("#{namespace_names.join("/")}/#{fixture_name}")
58
+ class_eval <<-EV, __FILE__, __LINE__ + 1
59
+ def #{method_name}
60
+ return #{ivar} if defined?(#{ivar})
61
+
62
+ value = fixtury("#{fixture_name}")
63
+ #{ivar} = value
64
+ end
65
+ EV
66
+ end
67
+ end
68
+ end
69
+
70
+ def fixtury_namespace
71
+ name.underscore
34
72
  end
35
73
 
36
74
  end
37
75
 
38
76
  def fixtury(name)
39
- return nil unless ::Fixtury::Store.instance
77
+ return nil unless fixtury_store
40
78
 
41
79
  name = name.to_s
42
80
 
43
- unless name.include?("/")
44
- local_name = "#{self.class.name.underscore}/#{name}"
45
- if self.fixtury_dependencies.include?(local_name)
46
- return ::Fixtury::Store.instance.get(local_name)
47
- 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)
48
84
  end
49
85
 
50
86
  unless self.fixtury_dependencies.include?(name)
51
87
  raise ArgumentError, "Unrecognized fixtury dependency `#{name}` for #{self.class}"
52
88
  end
53
89
 
54
- ::Fixtury::Store.instance.get(name)
90
+ fixtury_store.get(name, execution_context: self)
91
+ end
92
+
93
+ def fixtury_store
94
+ ::Fixtury::Store.instance
55
95
  end
56
96
 
57
97
  def fixtury_loaded?(name)
58
- return false unless ::Fixtury::Store.instance
98
+ return false unless fixtury_store
59
99
 
60
- ::Fixtury::Store.instance.loaded?(name)
100
+ fixtury_store.loaded?(name)
61
101
  end
62
102
 
63
103
  def fixtury_database_connections
@@ -66,7 +106,7 @@ module Fixtury
66
106
 
67
107
  # piggybacking activerecord fixture setup for now.
68
108
  def setup_fixtures(*args)
69
- if fixtury_dependencies.any?
109
+ if fixtury_dependencies.any? || local_fixtury_dependencies.any?
70
110
  setup_fixtury_fixtures
71
111
  else
72
112
  super
@@ -75,7 +115,7 @@ module Fixtury
75
115
 
76
116
  # piggybacking activerecord fixture setup for now.
77
117
  def teardown_fixtures(*args)
78
- if fixtury_dependencies.any?
118
+ if fixtury_dependencies.any? || local_fixtury_dependencies.any?
79
119
  teardown_fixtury_fixtures
80
120
  else
81
121
  super
@@ -100,13 +140,13 @@ module Fixtury
100
140
  end
101
141
 
102
142
  def clear_expired_fixtury_fixtures!
103
- return unless ::Fixtury::Store.instance
143
+ return unless fixtury_store
104
144
 
105
- ::Fixtury::Store.instance.clear_expired_references!
145
+ fixtury_store.clear_expired_references!
106
146
  end
107
147
 
108
148
  def load_all_fixtury_fixtures!
109
- fixtury_dependencies.each do |name|
149
+ (fixtury_dependencies | local_fixtury_dependencies).each do |name|
110
150
  fixtury(name) unless fixtury_loaded?(name)
111
151
  end
112
152
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fixtury
4
4
 
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.2"
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.0
4
+ version: 0.3.2
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-08 00:00:00.000000000 Z
11
+ date: 2020-10-21 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