rom-factory 0.4.0 → 0.5.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.
@@ -4,28 +4,56 @@ require 'dry/core/inflector'
4
4
  require 'rom/factory/dsl'
5
5
 
6
6
  module ROM::Factory
7
+ # In-memory builder API
8
+ #
9
+ # @api public
7
10
  class Structs
11
+ # @!attribute [r] registry
12
+ # @return [Hash<Symbol=>Builder>]
8
13
  attr_reader :registry
9
14
 
15
+ # @api private
10
16
  def initialize(registry)
11
17
  @registry = registry
12
18
  end
13
19
 
20
+ # Build an in-memory struct
21
+ #
22
+ # @example create a struct with default attributes
23
+ # MyFactory[:user]
24
+ #
25
+ # @example create a struct with some attributes overridden
26
+ # MyFactory.structs[:user, name: "Jane"]
27
+ #
28
+ # @param [Symbol] name The name of the registered factory
29
+ # @param [Hash] attrs An optional hash with attributes
30
+ #
31
+ # @return [ROM::Struct]
32
+ #
33
+ # @api public
14
34
  def [](name, attrs = {})
15
35
  registry[name].create(attrs)
16
36
  end
17
37
  end
18
38
 
39
+ # A registry with all configured factories
40
+ #
41
+ # @api public
19
42
  class Factories
20
43
  extend Dry::Configurable
21
44
 
22
45
  setting :rom
23
46
 
24
47
  class << self
48
+ # @!attribute [r] registry
49
+ # @return [Hash<Symbol=>Builder>]
25
50
  attr_reader :registry
26
51
 
52
+ # @!attribute [r] structs
53
+ # @return [Structs] In-memory struct builder instance
27
54
  attr_reader :structs
28
55
 
56
+ # @api private
29
57
  def inherited(klass)
30
58
  registry = {}
31
59
  klass.instance_variable_set(:'@registry', registry)
@@ -33,6 +61,68 @@ module ROM::Factory
33
61
  super
34
62
  end
35
63
 
64
+ # Define a new builder
65
+ #
66
+ # @example a simple builder
67
+ # MyFactory.define(:user) do |f|
68
+ # f.name "Jane"
69
+ # f.email "jane@doe.org"
70
+ # end
71
+ #
72
+ # @example a builder using auto-generated fake values
73
+ # MyFactory.define(:user) do |f|
74
+ # f.name { fake(:name) }
75
+ # f.email { fake(:internet, :email) }
76
+ # end
77
+ #
78
+ # @example a builder using sequenced values
79
+ # MyFactory.define(:user) do |f|
80
+ # f.sequence(:name) { |n| "user-#{n}" }
81
+ # end
82
+ #
83
+ # @example a builder using values from other attribute(s)
84
+ # MyFactory.define(:user) do |f|
85
+ # f.name "Jane"
86
+ # f.email { |name| "#{name.downcase}@rom-rb.org" }
87
+ # end
88
+ #
89
+ # @example a builder with "belongs-to" association
90
+ # MyFactory.define(:group) do |f|
91
+ # f.name "Admins"
92
+ # end
93
+ #
94
+ # MyFactory.define(:user) do |f|
95
+ # f.name "Jane"
96
+ # f.association(:group)
97
+ # end
98
+ #
99
+ # @example a builder with "has-many" association
100
+ # MyFactory.define(:group) do |f|
101
+ # f.name "Admins"
102
+ # f.association(:users, count: 2)
103
+ # end
104
+ #
105
+ # MyFactory.define(:user) do |f|
106
+ # f.sequence(:name) { |n| "user-#{n}" }
107
+ # end
108
+ #
109
+ # @example a builder which extends another builder
110
+ # MyFactory.define(:user) do |f|
111
+ # f.name "Jane"
112
+ # f.admin false
113
+ # end
114
+ #
115
+ # MyFactory.define(admin: :user) do |f|
116
+ # f.admin true
117
+ # end
118
+ #
119
+ # @param [Symbol, Hash<Symbol=>Symbol>] spec Builder identifier, can point to a parent builder too
120
+ # @param [Hash] opts Additional options
121
+ # @option opts [Symbol] relation An optional relation name (defaults to pluralized builder name)
122
+ #
123
+ # @return [ROM::Factory::Builder]
124
+ #
125
+ # @api public
36
126
  def define(spec, **opts, &block)
37
127
  name, parent = spec.is_a?(Hash) ? spec.flatten(1) : spec
38
128
 
@@ -52,16 +142,42 @@ module ROM::Factory
52
142
  registry[name] = builder
53
143
  end
54
144
 
145
+ # Create and persist a new struct
146
+ #
147
+ # @example create a struct with default attributes
148
+ # MyFactory[:user]
149
+ #
150
+ # @example create a struct with some attributes overridden
151
+ # MyFactory[:user, name: "Jane"]
152
+ #
153
+ # @param [Symbol] name The name of the registered factory
154
+ # @param [Hash] attrs An optional hash with attributes
155
+ #
156
+ # @return [ROM::Struct]
157
+ #
158
+ # @api public
159
+ def [](name, attrs = {})
160
+ registry[name].persistable.create(attrs)
161
+ end
162
+
163
+ # @api private
164
+ def for_relation(relation)
165
+ registry.fetch(infer_factory_name(relation.name.to_sym))
166
+ end
167
+
168
+ # @api private
169
+ def infer_factory_name(name)
170
+ ::Dry::Core::Inflector.singularize(name).to_sym
171
+ end
172
+
173
+ # @api private
55
174
  def infer_relation(name)
56
175
  ::Dry::Core::Inflector.pluralize(name).to_sym
57
176
  end
58
177
 
178
+ # @api private
59
179
  def extend_builder(name, parent, &block)
60
- DSL.new(name, schema: parent.schema, relation: parent.relation, factories: self, &block).call
61
- end
62
-
63
- def [](name, attrs = {})
64
- registry[name].persistable.create(attrs)
180
+ DSL.new(name, attributes: parent.attributes, relation: parent.relation, factories: self, &block).call
65
181
  end
66
182
  end
67
183
  end
@@ -0,0 +1,104 @@
1
+ module ROM
2
+ module Factory
3
+ # @api private
4
+ class TupleEvaluator
5
+ # @api private
6
+ attr_reader :attributes
7
+
8
+ # @api private
9
+ attr_reader :relation
10
+
11
+ # @api private
12
+ attr_reader :model
13
+
14
+ # @api private
15
+ attr_reader :sequence
16
+
17
+ # @api private
18
+ def initialize(attributes, relation)
19
+ @attributes = attributes
20
+ @relation = relation.with(auto_struct: true)
21
+ @model = @relation.combine(*assoc_names).mapper.model
22
+ @sequence = 0
23
+ end
24
+
25
+ # @api private
26
+ def defaults(attrs)
27
+ evaluate(attrs).merge(attrs)
28
+ end
29
+
30
+ # @api private
31
+ def struct(attrs)
32
+ model.new(struct_attrs.merge(defaults(attrs)))
33
+ end
34
+
35
+ # @api private
36
+ def persist_associations(tuple, parent)
37
+ assoc_names.each do |name|
38
+ assoc = tuple[name]
39
+ assoc.(parent) if assoc.is_a?(Proc)
40
+ end
41
+ end
42
+
43
+ # @api private
44
+ def assoc_names
45
+ attributes.associations.map(&:name)
46
+ end
47
+
48
+ # @api private
49
+ def has_associations?
50
+ assoc_names.size > 0
51
+ end
52
+
53
+ # @api private
54
+ def primary_key
55
+ relation.primary_key
56
+ end
57
+
58
+ private
59
+
60
+ # @api private
61
+ def evaluate(attrs)
62
+ evaluate_values(attrs).merge(evaluate_associations(attrs))
63
+ end
64
+
65
+ # @api private
66
+ def evaluate_values(attrs)
67
+ attributes.values.tsort.each_with_object({}) do |attr, h|
68
+ deps = attr.dependency_names.map { |k| h[k] }.compact
69
+ result = attr.(attrs, *deps)
70
+
71
+ if result
72
+ h.update(result)
73
+ end
74
+ end
75
+ end
76
+
77
+ # @api private
78
+ def evaluate_associations(attrs)
79
+ attributes.associations.each_with_object({}) do |assoc, h|
80
+ if assoc.dependency?(relation)
81
+ h[assoc.name] = -> parent { assoc.call(parent) }
82
+ else
83
+ result = assoc.(attrs)
84
+ h.update(result) if result
85
+ end
86
+ end
87
+ end
88
+
89
+ # @api private
90
+ def struct_attrs
91
+ relation.schema.
92
+ reject(&:primary_key?).
93
+ map { |attr| [attr.name, nil] }.
94
+ to_h.
95
+ merge(primary_key => next_id)
96
+ end
97
+
98
+ # @api private
99
+ def next_id
100
+ @sequence += 1
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module Factory
3
- VERSION = '0.4.0'.freeze
3
+ VERSION = '0.5.0'.freeze
4
4
  end
5
5
  end
data/rom-factory.gemspec CHANGED
@@ -27,8 +27,9 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency "dry-configurable", "~> 0.1"
31
- spec.add_dependency "dry-core", "~> 0.2"
32
- spec.add_dependency "dry-struct", "~> 0.2"
30
+ spec.add_dependency "dry-configurable", "~> 0.7"
31
+ spec.add_dependency "dry-core", "~> 0.3", ">= 0.3.1"
32
+ spec.add_dependency "dry-struct", "~> 0.3"
33
+ spec.add_dependency "rom-core", "~> 4.0"
33
34
  spec.add_dependency "faker", "~> 1.7"
34
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-factory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janis Miezitis
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2017-03-03 00:00:00.000000000 Z
12
+ date: 2017-10-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-configurable
@@ -17,42 +17,62 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '0.1'
20
+ version: '0.7'
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '0.1'
27
+ version: '0.7'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: dry-core
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0.2'
34
+ version: '0.3'
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: 0.3.1
35
38
  type: :runtime
36
39
  prerelease: false
37
40
  version_requirements: !ruby/object:Gem::Requirement
38
41
  requirements:
39
42
  - - "~>"
40
43
  - !ruby/object:Gem::Version
41
- version: '0.2'
44
+ version: '0.3'
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.1
42
48
  - !ruby/object:Gem::Dependency
43
49
  name: dry-struct
44
50
  requirement: !ruby/object:Gem::Requirement
45
51
  requirements:
46
52
  - - "~>"
47
53
  - !ruby/object:Gem::Version
48
- version: '0.2'
54
+ version: '0.3'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.3'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rom-core
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
49
69
  type: :runtime
50
70
  prerelease: false
51
71
  version_requirements: !ruby/object:Gem::Requirement
52
72
  requirements:
53
73
  - - "~>"
54
74
  - !ruby/object:Gem::Version
55
- version: '0.2'
75
+ version: '4.0'
56
76
  - !ruby/object:Gem::Dependency
57
77
  name: faker
58
78
  requirement: !ruby/object:Gem::Requirement
@@ -78,21 +98,27 @@ files:
78
98
  - ".gitignore"
79
99
  - ".rspec"
80
100
  - ".travis.yml"
101
+ - ".yardopts"
81
102
  - CHANGELOG.md
82
103
  - CODE_OF_CONDUCT.md
83
104
  - Gemfile
84
105
  - LICENSE.txt
85
106
  - README.md
86
107
  - Rakefile
108
+ - benchmarks/basic.rb
87
109
  - lib/rom-factory.rb
88
110
  - lib/rom/factory.rb
111
+ - lib/rom/factory/attribute_registry.rb
112
+ - lib/rom/factory/attributes.rb
113
+ - lib/rom/factory/attributes/association.rb
89
114
  - lib/rom/factory/attributes/callable.rb
90
- - lib/rom/factory/attributes/regular.rb
91
115
  - lib/rom/factory/attributes/sequence.rb
116
+ - lib/rom/factory/attributes/value.rb
92
117
  - lib/rom/factory/builder.rb
118
+ - lib/rom/factory/builder/persistable.rb
93
119
  - lib/rom/factory/dsl.rb
94
120
  - lib/rom/factory/factories.rb
95
- - lib/rom/factory/struct.rb
121
+ - lib/rom/factory/tuple_evaluator.rb
96
122
  - lib/rom/factory/version.rb
97
123
  - rom-factory.gemspec
98
124
  homepage: https://github.com/rom-rb/rom-factory
@@ -116,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
142
  version: '0'
117
143
  requirements: []
118
144
  rubyforge_project:
119
- rubygems_version: 2.6.9
145
+ rubygems_version: 2.6.11
120
146
  signing_key:
121
147
  specification_version: 4
122
148
  summary: ROM based builder library to make your specs awesome. DSL partially inspired
@@ -1,13 +0,0 @@
1
- module ROM::Factory
2
- module Attributes
3
- class Regular
4
- def initialize(value)
5
- @value = value
6
- end
7
-
8
- def call
9
- @value
10
- end
11
- end
12
- end
13
- end
@@ -1,21 +0,0 @@
1
- require 'dry/struct'
2
- require 'dry/core/cache'
3
- require 'dry/core/class_builder'
4
-
5
- module ROM::Factory
6
- class Struct < Dry::Struct
7
- extend Dry::Core::Cache
8
-
9
- def self.define(name, schema)
10
- fetch_or_store(schema) do
11
- id = Dry::Core::Inflector.classify(Dry::Core::Inflector.singularize(name))
12
-
13
- Dry::Core::ClassBuilder.new(name: "ROM::Factory::Struct[#{id}]", parent: self).call do |klass|
14
- schema.each do |attr|
15
- klass.attribute attr.name, attr.type
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end