fixtury 0.1.0 → 0.3.1

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: 7a4447982d6e785c9158850f3d158b07e1226f6a05d85917d41307c5455d41d9
4
- data.tar.gz: c7fac4726892a1851ca7b7f0d3ff8abfe2bbedf4d2706b2c3ae6443d6be863f1
3
+ metadata.gz: 28d8d17251e3c566a77365c589274b5f28f6cfac17c3b126a0047e6b696368ec
4
+ data.tar.gz: 8dab12b727161634b90ed49e56a025bfe72aade50c90509ee08ebfd4faac2d17
5
5
  SHA512:
6
- metadata.gz: 6c5858894f02eb54b555df7ee182e81e3741cbd278e37bbd0a3250b9cfea62a00341d3ff95a75adb0d5f56cfd3abcdb036877bee28601b87ff060c3e9bf6f43c
7
- data.tar.gz: 0e99678f39920cd68026905ddd202ea8b9f9648c3bef9e941e7fd084fb2daa1ed843b5d2839fc10cbf13f1cc27c224ac212d7cd01173b2f3f8692706cadde2cb
6
+ metadata.gz: fb5841b70b7ae8cac95e6f8cc6a1c32c47d71f356c3be0275dc463f1d6c1b5f88447e51807d48632bdb68b425e7bc9ead3293519085cc9416d45a027cf1c7abc
7
+ data.tar.gz: 662570f9d0adcea15b6198eed3deb5fb4aab0639d206941687ea9a230f121ab7d1c82da895ac7c39e6459262ad3c3562cbfcfcb6d3ba7839a90a6380549776ba
@@ -1,26 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fixtury (0.1.0)
4
+ fixtury (0.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (6.0.1)
9
+ activesupport (6.0.3.1)
10
10
  concurrent-ruby (~> 1.0, >= 1.0.2)
11
11
  i18n (>= 0.7, < 2)
12
12
  minitest (~> 5.1)
13
13
  tzinfo (~> 1.1)
14
- zeitwerk (~> 2.2)
14
+ zeitwerk (~> 2.2, >= 2.2.2)
15
15
  ansi (1.5.0)
16
16
  autotest (5.0.0)
17
17
  minitest-autotest (~> 1.0)
18
18
  builder (3.2.3)
19
19
  byebug (11.0.1)
20
- concurrent-ruby (1.1.5)
20
+ concurrent-ruby (1.1.6)
21
21
  globalid (0.4.2)
22
22
  activesupport (>= 4.2.0)
23
- i18n (1.7.0)
23
+ i18n (1.8.2)
24
24
  concurrent-ruby (~> 1.0)
25
25
  metaclass (0.0.4)
26
26
  minitest (5.13.0)
@@ -37,13 +37,13 @@ GEM
37
37
  mocha (1.8.0)
38
38
  metaclass (~> 0.0.1)
39
39
  path_expander (1.1.0)
40
- rake (10.5.0)
40
+ rake (13.0.1)
41
41
  ruby-progressbar (1.10.1)
42
42
  sqlite (1.0.2)
43
43
  thread_safe (0.3.6)
44
- tzinfo (1.2.5)
44
+ tzinfo (1.2.7)
45
45
  thread_safe (~> 0.1)
46
- zeitwerk (2.2.2)
46
+ zeitwerk (2.3.0)
47
47
 
48
48
  PLATFORMS
49
49
  ruby
@@ -57,7 +57,7 @@ DEPENDENCIES
57
57
  minitest (~> 5.0)
58
58
  minitest-reporters
59
59
  mocha
60
- rake (~> 10.0)
60
+ rake (~> 13.0)
61
61
  sqlite
62
62
 
63
63
  BUNDLED WITH
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.
@@ -34,6 +34,6 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "minitest", "~> 5.0"
35
35
  spec.add_development_dependency "minitest-reporters"
36
36
  spec.add_development_dependency "mocha"
37
- spec.add_development_dependency "rake", "~> 10.0"
37
+ spec.add_development_dependency "rake", "~> 13.0"
38
38
  spec.add_development_dependency "sqlite"
39
39
  end
@@ -7,15 +7,17 @@ require "fixtury/version"
7
7
  require "fixtury/schema"
8
8
  require "fixtury/locator"
9
9
  require "fixtury/store"
10
- require "fixtury/execution_context"
11
10
 
12
11
  module Fixtury
13
12
 
13
+ # Shortcut for opening the top level schema.
14
14
  def self.define(&block)
15
15
  schema.define(&block)
16
16
  schema
17
17
  end
18
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.
19
21
  def self.schema
20
22
  @top_level_schema ||= ::Fixtury::Schema.new(parent: nil, name: "")
21
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
- execution_context ||= ::Fixtury::ExecutionContext.new
29
-
30
- maybe_set_store_context(store: store) do
31
- value = run_callable(store: store, callable: callable, execution_context: execution_context, value: nil)
32
- enhancements.each do |e|
33
- value = run_callable(store: store, callable: e, execution_context: execution_context, value: value)
34
- end
35
- value
36
- end
29
+ def info
30
+ {
31
+ name: name,
32
+ loc: location_from_callable(callable),
33
+ enhancements: enhancements.map { |e| location_from_callable(e) },
34
+ }
37
35
  end
38
36
 
39
- protected
40
-
41
- def maybe_set_store_context(store:)
42
- return yield unless store
43
-
44
- store.with_relative_schema(schema) do
45
- yield
46
- 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
47
40
  end
48
41
 
49
- def run_callable(store:, callable:, execution_context:, value:)
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
- execution_context_hooks(execution_context) do
59
- if args.length.positive?
60
- execution_context.instance_exec(*args, &callable)
61
- else
62
- execution_context.instance_eval(&callable)
63
- end
64
- end
65
- end
42
+ def location_from_callable(callable)
43
+ return nil unless callable.respond_to?(:source_location)
66
44
 
67
- def execution_context_hooks(execution_context)
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 }
71
- else
72
- yield
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,}}, "/")
@@ -5,7 +5,6 @@ require "singleton"
5
5
  require "yaml"
6
6
  require "fixtury/locator"
7
7
  require "fixtury/errors/circular_dependency_error"
8
- require "fixtury/execution_context"
9
8
  require "fixtury/reference"
10
9
 
11
10
  module Fixtury
@@ -22,7 +21,6 @@ module Fixtury
22
21
  attr_reader :filepath, :references, :ttl, :auto_refresh_expired
23
22
  attr_reader :schema, :locator
24
23
  attr_reader :log_level
25
- attr_reader :execution_context
26
24
 
27
25
  def initialize(
28
26
  filepath: nil,
@@ -30,7 +28,6 @@ module Fixtury
30
28
  log_level: nil,
31
29
  ttl: nil,
32
30
  schema: nil,
33
- execution_context: nil,
34
31
  auto_refresh_expired: false
35
32
  )
36
33
  @schema = schema || ::Fixtury.schema
@@ -40,7 +37,6 @@ module Fixtury
40
37
  @locator = locator
41
38
  @filepath = filepath
42
39
  @references = @filepath && ::File.file?(@filepath) ? ::YAML.load_file(@filepath) : {}
43
- @execution_context = execution_context || ::Fixtury::ExecutionContext.new
44
40
  @ttl = ttl ? ttl.to_i : ttl
45
41
  @auto_refresh_expired = !!auto_refresh_expired
46
42
  self.class.instance ||= self
@@ -81,7 +77,7 @@ module Fixtury
81
77
  def clear_cache!(pattern: nil)
82
78
  pattern ||= "*"
83
79
  pattern = "/" + pattern unless pattern.start_with?("/")
84
- glob = pattern.ends_with?("*")
80
+ glob = pattern.end_with?("*")
85
81
  pattern = pattern[0...-1] if glob
86
82
  references.delete_if do |key, _value|
87
83
  hit = glob ? key.start_with?(pattern) : key == pattern
@@ -108,7 +104,7 @@ module Fixtury
108
104
  result
109
105
  end
110
106
 
111
- def get(name)
107
+ def get(name, execution_context: nil)
112
108
  dfn = schema.get_definition!(name)
113
109
  full_name = dfn.name
114
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
@@ -14,50 +15,86 @@ module Fixtury
14
15
 
15
16
  module ClassMethods
16
17
 
17
- def fixtury(*names)
18
- self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
19
- end
18
+ def fixtury(*names, &definition)
19
+ opts = names.extract_options!
20
+
21
+ # define fixtures if blocks are given
22
+ if block_given?
23
+ raise ArgumentError, "A fixture cannot be defined in an anonymous class" if name.nil?
24
+
25
+ namespace = fixtury_namespace
26
+
27
+ ns = ::Fixtury.schema
20
28
 
21
- def define_fixture(name, &block)
22
- fixture_name = name
23
- namespace_names = self.name.underscore.split("/")
29
+ namespace.split("/").each do |ns_name|
30
+ ns = ns.namespace(ns_name){}
31
+ end
24
32
 
25
- ns = ::Fixtury.schema
33
+ names.each do |fixture_name|
34
+ ns.fixture(fixture_name, &definition)
35
+ self.fixtury_dependencies += ["/#{namespace}/#{fixture_name}"]
36
+ end
26
37
 
27
- namespace_names.each do |ns_name|
28
- ns = ns.namespace(ns_name){}
38
+ # otherwise, just record the dependency
39
+ else
40
+ self.fixtury_dependencies += names.flatten.compact.map(&:to_s)
29
41
  end
30
42
 
31
- ns.fixture(fixture_name, &block)
43
+ accessor_option = opts.key?(:accessor) ? opts[:accessor] : true
44
+
45
+ if accessor_option
46
+
47
+ if accessor_option != true && names.length > 1
48
+ raise ArgumentError, "A named :accessor option is only available when providing one fixture"
49
+ end
50
+
51
+ names.each do |fixture_name|
52
+ method_name = accessor_option == true ? fixture_name.split("/").last : accessor_option
53
+ ivar = :"@#{method_name}"
32
54
 
33
- fixtury("#{namespace_names.join("/")}/#{fixture_name}")
55
+ class_eval <<-EV, __FILE__, __LINE__ + 1
56
+ def #{method_name}
57
+ return #{ivar} if defined?(#{ivar})
58
+
59
+ value = fixtury("#{fixture_name}")
60
+ #{ivar} = value
61
+ end
62
+ EV
63
+ end
64
+ end
65
+ end
66
+
67
+ def fixtury_namespace
68
+ name.underscore
34
69
  end
35
70
 
36
71
  end
37
72
 
38
73
  def fixtury(name)
39
- return nil unless ::Fixtury::Store.instance
74
+ return nil unless fixtury_store
40
75
 
41
76
  name = name.to_s
42
77
 
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
78
+ local_alias = "/#{self.class.fixtury_namespace}/#{name}"
79
+ if self.fixtury_dependencies.include?(local_alias)
80
+ return fixtury_store.get(local_alias, execution_context: self)
48
81
  end
49
82
 
50
83
  unless self.fixtury_dependencies.include?(name)
51
84
  raise ArgumentError, "Unrecognized fixtury dependency `#{name}` for #{self.class}"
52
85
  end
53
86
 
54
- ::Fixtury::Store.instance.get(name)
87
+ fixtury_store.get(name, execution_context: self)
88
+ end
89
+
90
+ def fixtury_store
91
+ ::Fixtury::Store.instance
55
92
  end
56
93
 
57
94
  def fixtury_loaded?(name)
58
- return false unless ::Fixtury::Store.instance
95
+ return false unless fixtury_store
59
96
 
60
- ::Fixtury::Store.instance.loaded?(name)
97
+ fixtury_store.loaded?(name)
61
98
  end
62
99
 
63
100
  def fixtury_database_connections
@@ -100,9 +137,9 @@ module Fixtury
100
137
  end
101
138
 
102
139
  def clear_expired_fixtury_fixtures!
103
- return unless ::Fixtury::Store.instance
140
+ return unless fixtury_store
104
141
 
105
- ::Fixtury::Store.instance.clear_expired_references!
142
+ fixtury_store.clear_expired_references!
106
143
  end
107
144
 
108
145
  def load_all_fixtury_fixtures!
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fixtury
4
4
 
5
- VERSION = "0.1.0"
5
+ VERSION = "0.3.1"
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.1.0
4
+ version: 0.3.1
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-07 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
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '10.0'
117
+ version: '13.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '10.0'
124
+ version: '13.0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: sqlite
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -155,12 +155,12 @@ 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
161
162
  - lib/fixtury/errors/schema_frozen_error.rb
162
163
  - lib/fixtury/errors/unrecognizable_locator_error.rb
163
- - lib/fixtury/execution_context.rb
164
164
  - lib/fixtury/locator.rb
165
165
  - lib/fixtury/locator_backend/common.rb
166
166
  - lib/fixtury/locator_backend/globalid.rb
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # a class made available so helper methods can be provided within the fixture dsl
4
- module Fixtury
5
- class ExecutionContext
6
-
7
- def before_fixture(_dfn); end
8
-
9
- def around_fixture(_dfn)
10
- yield
11
- end
12
-
13
- def after_fixture(_dfn, _value); end
14
-
15
- end
16
- end