openapi3_parser 0.2.0 → 0.3.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -1
  3. data/CHANGELOG.md +5 -0
  4. data/README.md +13 -0
  5. data/TODO.md +11 -8
  6. data/lib/openapi3_parser.rb +16 -28
  7. data/lib/openapi3_parser/context.rb +92 -34
  8. data/lib/openapi3_parser/context/location.rb +37 -0
  9. data/lib/openapi3_parser/context/pointer.rb +34 -0
  10. data/lib/openapi3_parser/document.rb +115 -15
  11. data/lib/openapi3_parser/document/reference_register.rb +50 -0
  12. data/lib/openapi3_parser/error.rb +27 -1
  13. data/lib/openapi3_parser/node/object.rb +26 -1
  14. data/lib/openapi3_parser/node_factories/array.rb +15 -14
  15. data/lib/openapi3_parser/node_factories/callback.rb +2 -1
  16. data/lib/openapi3_parser/node_factories/link.rb +1 -1
  17. data/lib/openapi3_parser/node_factories/map.rb +13 -11
  18. data/lib/openapi3_parser/node_factories/openapi.rb +2 -0
  19. data/lib/openapi3_parser/node_factories/path_item.rb +13 -18
  20. data/lib/openapi3_parser/node_factories/paths.rb +2 -1
  21. data/lib/openapi3_parser/node_factories/reference.rb +7 -9
  22. data/lib/openapi3_parser/node_factories/responses.rb +4 -2
  23. data/lib/openapi3_parser/node_factories/security_requirement.rb +2 -1
  24. data/lib/openapi3_parser/node_factory.rb +39 -30
  25. data/lib/openapi3_parser/node_factory/fields/reference.rb +44 -0
  26. data/lib/openapi3_parser/node_factory/map.rb +5 -5
  27. data/lib/openapi3_parser/node_factory/object.rb +6 -5
  28. data/lib/openapi3_parser/node_factory/object/node_builder.rb +12 -11
  29. data/lib/openapi3_parser/node_factory/object/validator.rb +25 -14
  30. data/lib/openapi3_parser/nodes/components.rb +9 -11
  31. data/lib/openapi3_parser/nodes/discriminator.rb +1 -1
  32. data/lib/openapi3_parser/nodes/encoding.rb +1 -1
  33. data/lib/openapi3_parser/nodes/link.rb +1 -1
  34. data/lib/openapi3_parser/nodes/media_type.rb +2 -2
  35. data/lib/openapi3_parser/nodes/oauth_flow.rb +1 -1
  36. data/lib/openapi3_parser/nodes/openapi.rb +3 -4
  37. data/lib/openapi3_parser/nodes/operation.rb +5 -8
  38. data/lib/openapi3_parser/nodes/parameter/parameter_like.rb +2 -2
  39. data/lib/openapi3_parser/nodes/path_item.rb +2 -3
  40. data/lib/openapi3_parser/nodes/request_body.rb +1 -1
  41. data/lib/openapi3_parser/nodes/response.rb +3 -3
  42. data/lib/openapi3_parser/nodes/schema.rb +6 -7
  43. data/lib/openapi3_parser/nodes/server.rb +1 -2
  44. data/lib/openapi3_parser/nodes/server_variable.rb +1 -1
  45. data/lib/openapi3_parser/source.rb +136 -0
  46. data/lib/openapi3_parser/source/reference.rb +68 -0
  47. data/lib/openapi3_parser/source/reference_resolver.rb +81 -0
  48. data/lib/openapi3_parser/source_input.rb +71 -0
  49. data/lib/openapi3_parser/source_input/file.rb +102 -0
  50. data/lib/openapi3_parser/source_input/raw.rb +90 -0
  51. data/lib/openapi3_parser/source_input/resolve_next.rb +62 -0
  52. data/lib/openapi3_parser/source_input/string_parser.rb +43 -0
  53. data/lib/openapi3_parser/source_input/url.rb +123 -0
  54. data/lib/openapi3_parser/validation/error.rb +36 -3
  55. data/lib/openapi3_parser/validation/error_collection.rb +57 -15
  56. data/lib/openapi3_parser/validators/reference.rb +40 -0
  57. data/lib/openapi3_parser/version.rb +1 -1
  58. data/openapi3_parser.gemspec +10 -5
  59. metadata +34 -20
  60. data/lib/openapi3_parser/fields/map.rb +0 -83
  61. data/lib/openapi3_parser/node.rb +0 -115
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f60ae34f39675f7ce3616bd96dc3e383275d117d
4
- data.tar.gz: 4ecf6bffd69c875778c4d096f29cf7b5deadbf7e
3
+ metadata.gz: 651f457272264ca4fc7b45040203a9fa7deeabde
4
+ data.tar.gz: b00561ee20a2c121d9f1d030511b07870cd66ecc
5
5
  SHA512:
6
- metadata.gz: f5a96679f760117e345c9e992161444c06df7d667953fad6badf547dd83dee449b672cc9f265a26a7166e2cdec887d5205bf0dcda6abdbece2a2ce74aeb61153
7
- data.tar.gz: 087aea7ab5f4abceb69b9acce6854f70dac44fca1620bde695d7dd1a7813ca1a468728e3cc5a08ab5ae31d5320c75459f9537ce584c6ffbff082ca796d9a3602
6
+ metadata.gz: a38facc684d56b0194066ba88db7396107d882bc225fa07f14600bf21a5156c2f6032f8b284bac5146a6f3a9e792147e63fd35ca915b8b8d2f36fe57e738a662
7
+ data.tar.gz: f605fd5997afe6055b6e627eaca9971dd60dd6f2fbc19381a363490a596f46cf001317aed21130c4273115143c6353516c035747ab16867b62ece619434b6b1a
@@ -1,4 +1,13 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - 2.3.1
4
+ - 2.3.6
5
+ - 2.4.3
6
+ - jruby-9.1.15.0
7
+ - ruby-head
8
+ - jruby-head
9
+ matrix:
10
+ allow_failures:
11
+ - rvm: ruby-head
12
+ - rvm: jruby-head
13
+ fast_finish: true
@@ -1,3 +1,8 @@
1
+ # 0.3.0
2
+
3
+ - Allow opening files by URL
4
+ - Support references in different files
5
+
1
6
  # 0.2.0
2
7
 
3
8
  - Allow defaulting to empty arrays and maps
data/README.md CHANGED
@@ -25,6 +25,19 @@ Documentation for the API to navigate the OpenAPI nodes is available on
25
25
  [openapi-3]: https://github.com/OAI/OpenAPI-Specification
26
26
  [docs]: http://www.rubydoc.info/github/kevindew/openapi3_parser/Openapi3Parser/Nodes/Openapi
27
27
 
28
+ ## Installation
29
+
30
+ You can install this gem into your bundler application by adding this line to
31
+ your Gemfile:
32
+
33
+ ```
34
+ gem "openapi3_parser", "~> 0.2.0"
35
+ ```
36
+
37
+ and then running `$ bundle install`
38
+
39
+ Or install the gem onto your machine via ` $ gem install openapi3_parser`
40
+
28
41
  ## Status
29
42
 
30
43
  This is currently a work in progress and will remain so until it reaches 1.0.
data/TODO.md CHANGED
@@ -6,26 +6,29 @@ These are the steps defined to reach 1.0. Assistance is very welcome.
6
6
  - [ ] Refactor the various NodeFactory modules to be a less confusing
7
7
  hierachical structure. Consider having factories subclass instead of use
8
8
  mixin
9
- - [ ] Decouple Document class for the source file. Consider a source file class
9
+ - [x] Decouple Document class for the source file. Consider a source file class
10
10
  instead
11
- - [ ] Validate that a reference creates the type of node that is expected in
11
+ - [x] Validate that a reference creates the type of node that is expected in
12
12
  a context
13
- - [ ] Allow opening of references from additional files
14
- - [ ] Allow opening of openapi documents by URL
15
- - [ ] Support references by URL, consider option to limit behaviour
13
+ - [x] Allow opening of references from additional files
14
+ - [x] Allow opening of openapi documents by URL
15
+ - [x] Support references by URL
16
+ - [ ] Consider option to limit open by URL/path behaviour
16
17
  - [ ] Support converting CommonMark to HTML
17
18
  - [ ] Reach parity with OpenAPI specification for validation
18
19
  - [ ] Consider a lenient mode for a document to only have to comply with type
19
20
  based validation
20
21
  - [ ] Improve test coverage
21
22
  - [ ] Publish documentation of the interface through the structure
22
- - [ ] Consider a resolved context class for representing context with a node
23
+ - [x] Consider a resolved context class for representing context with a node
23
24
  that can handle scenarios where a node is represented by both a reference
24
25
  and resolved context
25
26
  - [ ] Create error classes for various scenarios
26
27
  - [ ] Associate/resolve operation id / operation references
27
28
  - [ ] Do something to model expressions
28
- - [ ] Improve the modelling of namespace
29
+ - [x] Improve the modelling of namespace
29
30
  - [ ] Set up nicer string representations of key classes to help them be
30
31
  debugged
31
- - [ ] Ensure Array and Map nodes return empty ones by default rather than nil
32
+ - [x] Ensure Array and Map nodes return empty ones by default rather than nil
33
+ - [ ] Make JSON pointer public access to be consistent accepting string, array
34
+ or (potentially) a pointer class
@@ -1,52 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openapi3_parser/error"
4
3
  require "openapi3_parser/document"
5
-
6
- require "yaml"
7
- require "json"
4
+ require "openapi3_parser/source_input/raw"
5
+ require "openapi3_parser/source_input/file"
6
+ require "openapi3_parser/source_input/url"
8
7
 
9
8
  module Openapi3Parser
10
9
  # For a variety of inputs this will construct an OpenAPI document. For a
11
10
  # String/File input it will try to determine if the input is JSON or YAML.
12
11
  #
13
- # @param [String, Hash, File] input Source for the OpenAPI document
12
+ # @param [String, Hash, File] input Source for the OpenAPI document
14
13
  #
15
14
  # @return [Document]
16
15
  def self.load(input)
17
- # working_directory ||= if input.respond_to?(:read)
18
- # File.dirname(input)
19
- # else
20
- # Dir.pwd
21
- # end
22
-
23
- Document.new(parse_input(input))
16
+ Document.new(SourceInput::Raw.new(input))
24
17
  end
25
18
 
26
19
  # For a given string filename this will read the file and parse it as an
27
20
  # OpenAPI document. It will try detect automatically whether the contents
28
21
  # are JSON or YAML.
29
22
  #
30
- # @param [String] path Filename of the OpenAPI document
23
+ # @param [String] path Filename of the OpenAPI document
31
24
  #
32
25
  # @return [Document]
33
26
  def self.load_file(path)
34
- file = File.open(path)
35
- load(file)
27
+ Document.new(SourceInput::File.new(path))
36
28
  end
37
29
 
38
- def self.parse_input(input)
39
- return input if input.respond_to?(:keys)
40
-
41
- extension = input.respond_to?(:extname) ? input.extname : nil
42
- contents = input.respond_to?(:read) ? input.read : input
43
-
44
- if extension == ".json" || contents.strip[0] == "{"
45
- JSON.parse(contents)
46
- else
47
- YAML.safe_load(contents, [], [], true)
48
- end
30
+ # For a given string URL this will request the resource and parse it as an
31
+ # OpenAPI document. It will try detect automatically whether the contents
32
+ # are JSON or YAML.
33
+ #
34
+ # @param [String] url URL of the OpenAPI document
35
+ #
36
+ # @return [Document]
37
+ def self.load_url(url)
38
+ Document.new(SourceInput::Url.new(url.to_s))
49
39
  end
50
-
51
- private_class_method :parse_input
52
40
  end
@@ -1,54 +1,112 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openapi3_parser/context/location"
4
+
3
5
  module Openapi3Parser
6
+ # Context is a construct used in both the node factories and the nodes
7
+ # themselves. It is used to represent the data, and the source of it, that
8
+ # a node is associated with. It also acts as a bridge between a node/node
9
+ # factory and associated document.
10
+ #
11
+ # @attr_reader input
12
+ # @attr_reader [Context::Location] document_location
13
+ # @attr_reader [Context::Location, nil] source_location
14
+ # @attr_reader [Context, nil] referenced_by
4
15
  class Context
5
- attr_reader :input, :namespace, :document, :parent
16
+ # Create a context for the root of a document
17
+ # @return [Context]
18
+ def self.root(input, source)
19
+ location = Location.new(source, [])
20
+ new(input, document_location: location)
21
+ end
22
+
23
+ # Create a context for a field within the current contexts data
24
+ # eg for a context of:
25
+ # root = Context.root({ "test" => {} }, source)
26
+ # we can get the context of "test" with:
27
+ # test = Context.next_field(root, "test")
28
+ #
29
+ # @param [Context] parent_context
30
+ # @param [String] field
31
+ # @return [Context]
32
+ def self.next_field(parent_context, field)
33
+ pc = parent_context
34
+ input = pc.input.respond_to?(:[]) ? pc.input[field] : nil
35
+ new(input,
36
+ document_location: Location.next_field(pc.document_location, field),
37
+ source_location: Location.next_field(pc.source_location, field),
38
+ referenced_by: pc.referenced_by)
39
+ end
40
+
41
+ # Creates the context for a field that is referenced by a context.
42
+ # In this scenario the context of the document is the same but we are in
43
+ # a different part of the source file, or even a different source file
44
+ #
45
+ # @param [Context] referencer_context
46
+ # @param input
47
+ # @param [Source] source
48
+ # @param [::Array] pointer_segments
49
+ # @return [Context]
50
+ def self.reference_field(referencer_context,
51
+ input:,
52
+ source:,
53
+ pointer_segments:)
54
+ new(input,
55
+ document_location: referencer_context.document_location,
56
+ source_location: Location.new(source, pointer_segments),
57
+ referenced_by: referencer_context)
58
+ end
59
+
60
+ attr_reader :input, :document_location, :source_location, :referenced_by
6
61
 
7
- def initialize(input:, namespace: [], document:, parent: nil)
62
+ # @param input
63
+ # @param [Context::Location] document_location
64
+ # @param [Context::Location, nil] source_location
65
+ # @param [Context, nil] referenced_by
66
+ def initialize(input,
67
+ document_location:,
68
+ source_location: nil,
69
+ referenced_by: nil)
8
70
  @input = input
9
- @namespace = namespace.freeze
10
- @document = document
11
- @parent = parent
71
+ @document_location = document_location
72
+ @source_location = source_location || document_location
73
+ @referenced_by = referenced_by
12
74
  end
13
75
 
14
- def self.root(input, document)
15
- new(input: input, document: document)
76
+ # @return [Document]
77
+ def document
78
+ document_location.source.document
16
79
  end
17
80
 
18
- def stringify_namespace
19
- return "root" if namespace.empty?
20
- namespace
21
- .map { |i| i.to_s.include?("/") ? %("#{i}") : i }
22
- .join("/")
81
+ # @return [Source]
82
+ def source
83
+ source_location.source
23
84
  end
24
85
 
25
- def next_namespace(segment, next_input = nil)
26
- next_input ||= input.nil? ? nil : input[segment]
27
- self.class.new(
28
- input: next_input,
29
- namespace: namespace + [segment],
30
- document: document,
31
- parent: self
32
- )
86
+ # @return [Source::ReferenceResolver]
87
+ def register_reference(reference, factory)
88
+ source.register_reference(reference, factory, self)
33
89
  end
34
90
 
35
- def resolve_reference
36
- document.resolve_reference(input["$ref"]) do |resolved_input, namespace|
37
- # @TODO track reference for cyclic depenendies
38
- next_context = resolved_reference(resolved_input, namespace)
39
- yield(next_context)
40
- end
91
+ # @deprecated
92
+ def namespace
93
+ document_location.pointer.segments
41
94
  end
42
95
 
43
- private
96
+ def inspect
97
+ %{#{self.class.name}(document_location: #{document_location}, } +
98
+ %{source_location: #{source_location}), referenced_by: } +
99
+ %{#{referenced_by})}
100
+ end
101
+
102
+ def location_summary
103
+ summary = document_location.to_s
104
+ summary += " (#{source_location})" if document_location != source_location
105
+ summary
106
+ end
44
107
 
45
- def resolved_reference(input, namespace)
46
- self.class.new(
47
- input: input,
48
- namespace: namespace,
49
- document: document,
50
- parent: parent
51
- )
108
+ def to_s
109
+ location_summary
52
110
  end
53
111
  end
54
112
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openapi3_parser/context/pointer"
4
+
5
+ module Openapi3Parser
6
+ class Context
7
+ # Class used to represent a location within an OpenAPI document.
8
+ # It contains a source, which is the source file/data used for the contents
9
+ # and the pointer which indicates where in the object like file the data is
10
+ class Location
11
+ def self.next_field(location, field)
12
+ new(location.source, location.pointer.segments + [field])
13
+ end
14
+
15
+ attr_reader :source, :pointer
16
+
17
+ # @param [Openapi3Parser::Source] source
18
+ # @param [::Array] pointer_segments
19
+ def initialize(source, pointer_segments)
20
+ @source = source
21
+ @pointer = Pointer.new(pointer_segments.freeze)
22
+ end
23
+
24
+ def ==(other)
25
+ source == other.source && pointer == other.pointer
26
+ end
27
+
28
+ def to_s
29
+ source.relative_to_root + pointer.fragment
30
+ end
31
+
32
+ def inspect
33
+ %{#{self.class.name}(source: #{source.inspect}, pointer: #{pointer})}
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openapi3Parser
4
+ class Context
5
+ # A class to decorate the array of fields that make up a pointer and
6
+ # provide common means to convert it into different representations.
7
+ class Pointer
8
+ attr_reader :segments
9
+
10
+ # @param [::Array] segments
11
+ def initialize(segments)
12
+ @segments = segments.freeze
13
+ end
14
+
15
+ def ==(other)
16
+ segments == other.segments
17
+ end
18
+
19
+ def fragment
20
+ segments.map { |s| CGI.escape(s.to_s).gsub("+", "%20") }
21
+ .join("/")
22
+ .prepend("#/")
23
+ end
24
+
25
+ def to_s
26
+ fragment
27
+ end
28
+
29
+ def inspect
30
+ %{#{self.class.name}(segments: #{segments}, fragment: "#{fragment}")}
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,48 +1,148 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "openapi3_parser/context"
4
+ require "openapi3_parser/document/reference_register"
4
5
  require "openapi3_parser/error"
5
6
  require "openapi3_parser/node_factories/openapi"
7
+ require "openapi3_parser/source"
8
+ require "openapi3_parser/validation/error_collection"
6
9
 
7
10
  require "forwardable"
8
11
 
9
12
  module Openapi3Parser
13
+ # Document is the root construct of a created OpenAPI Document and can be
14
+ # used to navigate the contents of a document or to check it's validity.
15
+ #
16
+ # @attr_reader [Source] root_source
10
17
  class Document
11
18
  extend Forwardable
19
+ include Enumerable
12
20
 
13
- attr_reader :input
21
+ attr_reader :root_source
14
22
 
15
- def_delegators :factory, :valid?, :errors
23
+ # @!method valid?
24
+ # Whether this OpenAPI document has any validation issues or not. See
25
+ # #errors to access the errors
26
+ #
27
+ # @return [Boolean]
28
+ def_delegator :factory, :valid?
29
+
30
+ # @!method openapi
31
+ # The value of the openapi version field for this document
32
+ # @see Nodes::Openapi#openapi
33
+ # @return [String]
34
+ # @!method info
35
+ # The value of the info field on the OpenAPI document
36
+ # @see Nodes::Openapi#info
37
+ # @return [Nodes::Info]
38
+ # @!method servers
39
+ # The value of the servers field on the OpenAPI document
40
+ # @see Nodes::Openapi#servers
41
+ # @return [Nodes::Array<Nodes::Server>]
42
+ # @!method paths
43
+ # The value of the paths field on the OpenAPI document
44
+ # @see Nodes::Openapi#paths
45
+ # @return [Nodes::Paths]
46
+ # @!method components
47
+ # The value of the components field on the OpenAPI document
48
+ # @see Nodes::Openapi#components
49
+ # @return [Nodes::Components]
50
+ # @!method security
51
+ # The value of the security field on the OpenAPI document
52
+ # @see Nodes::Openapi#security
53
+ # @return [Nodes::Array<Nodes::SecurityRequirement>]
54
+ # @!method tags
55
+ # The value of the tags field on the OpenAPI document
56
+ # @see Nodes::Openapi#tags
57
+ # @return [Nodes::Array<Nodes::Tag>]
58
+ # @!method external_docs
59
+ # The value of the external_docs field on the OpenAPI document
60
+ # @see Nodes::Openapi#external_docs
61
+ # @return [Nodes::ExternalDocumentation]
62
+ # @!method extension
63
+ # Look up an extension field provided for the root object of the document
64
+ # @see Node::Object#extension
65
+ # @return [Hash, Array, Numeric, String, true, false, nil]
66
+ # @!method []
67
+ # Look up an attribute on the root of the OpenAPI document by String
68
+ # or Symbol
69
+ # @see Node::Object#[]
70
+ # @return Object
71
+ # @!method each
72
+ # Iterate through the attributes of the root object
73
+ # @see Node::Object#each
16
74
  def_delegators :root, :openapi, :info, :servers, :paths, :components,
17
75
  :security, :tags, :external_docs, :extension, :[], :each
18
76
 
19
- def initialize(input)
20
- @input = input
77
+ # @param [SourceInput] source_input
78
+ def initialize(source_input)
79
+ @reference_register = ReferenceRegister.new
80
+ @root_source = Source.new(source_input, self, reference_register)
81
+ @build_in_progress = false
82
+ @built = false
21
83
  end
22
84
 
85
+ # @return [Nodes::Openapi]
23
86
  def root
24
87
  factory.node
25
88
  end
26
89
 
27
- def resolve_reference(reference)
28
- if reference[0..1] != "#/"
29
- raise Error, "Only anchor references are currently supported"
30
- end
90
+ # All the additional sources that have been referenced as part of loading
91
+ # the OpenAPI document
92
+ #
93
+ # @return [Array<Source>]
94
+ def reference_sources
95
+ build unless built
96
+ reference_register.sources
97
+ end
31
98
 
32
- parts = reference.split("/").drop(1).map do |field|
33
- CGI.unescape(field.gsub("+", "%20"))
34
- end
99
+ # All of the sources involved in this OpenAPI document
100
+ #
101
+ # @return [Array<Source>]
102
+ def sources
103
+ [root_source] + reference_sources
104
+ end
35
105
 
36
- result = input.dig(*parts)
37
- raise Error, "Could not resolve reference #{reference}" unless result
106
+ # Any validation errors that are present on the OpenAPI document
107
+ #
108
+ # @return [Validation::ErrorCollection]
109
+ def errors
110
+ reference_factories.inject(factory.errors) do |memo, f|
111
+ Validation::ErrorCollection.combine(memo, f.errors)
112
+ end
113
+ end
38
114
 
39
- yield(result, parts)
115
+ # Look up whether an instance of SourceInput is already a known source
116
+ # for this document.
117
+ #
118
+ # @param [SourceInput] source_input
119
+ # @return [Source, nil]
120
+ def source_for_source_input(source_input)
121
+ sources.find { |source| source.source_input == source_input }
40
122
  end
41
123
 
42
124
  private
43
125
 
126
+ attr_reader :reference_register, :built, :build_in_progress
127
+
128
+ def build
129
+ return if build_in_progress || built
130
+ @build_in_progress = true
131
+ context = Context.root(root_source.data, root_source)
132
+ @factory = NodeFactories::Openapi.new(context)
133
+ reference_register.freeze
134
+ @build_in_progress = false
135
+ @built = true
136
+ end
137
+
44
138
  def factory
45
- @factory ||= NodeFactories::Openapi.new(Context.root(input, self))
139
+ build unless built
140
+ @factory
141
+ end
142
+
143
+ def reference_factories
144
+ build unless built
145
+ reference_register.factories
46
146
  end
47
147
  end
48
148
  end