rom-factory 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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