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.
- checksums.yaml +4 -4
- data/.travis.yml +11 -10
- data/.yardopts +7 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +30 -4
- data/README.md +20 -190
- data/benchmarks/basic.rb +70 -0
- data/lib/rom/factory.rb +15 -0
- data/lib/rom/factory/attribute_registry.rb +64 -0
- data/lib/rom/factory/attributes.rb +12 -0
- data/lib/rom/factory/attributes/association.rb +98 -0
- data/lib/rom/factory/attributes/callable.rb +20 -8
- data/lib/rom/factory/attributes/sequence.rb +15 -3
- data/lib/rom/factory/attributes/value.rb +31 -0
- data/lib/rom/factory/builder.rb +26 -37
- data/lib/rom/factory/builder/persistable.rb +51 -0
- data/lib/rom/factory/dsl.rb +81 -18
- data/lib/rom/factory/factories.rb +121 -5
- data/lib/rom/factory/tuple_evaluator.rb +104 -0
- data/lib/rom/factory/version.rb +1 -1
- data/rom-factory.gemspec +4 -3
- metadata +37 -11
- data/lib/rom/factory/attributes/regular.rb +0 -13
- data/lib/rom/factory/struct.rb +0 -21
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'dry/core/constants'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Factory
|
5
|
+
include Dry::Core::Constants
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'rom/factory/attributes/value'
|
10
|
+
require 'rom/factory/attributes/callable'
|
11
|
+
require 'rom/factory/attributes/sequence'
|
12
|
+
require 'rom/factory/attributes/association'
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module ROM::Factory
|
2
|
+
module Attributes
|
3
|
+
# @api private
|
4
|
+
module Association
|
5
|
+
def self.new(assoc, builder, options = {})
|
6
|
+
const_get(assoc.definition.type).new(assoc, builder, options)
|
7
|
+
end
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
class Core
|
11
|
+
attr_reader :assoc, :options
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
def initialize(assoc, builder, options = {})
|
15
|
+
@assoc = assoc
|
16
|
+
@builder_proc = builder
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def builder
|
22
|
+
@__builder__ ||= @builder_proc.()
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def name
|
27
|
+
assoc.key
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def dependency?(*)
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def value?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @api private
|
42
|
+
class ManyToOne < Core
|
43
|
+
# @api private
|
44
|
+
def call(attrs = EMPTY_HASH)
|
45
|
+
if attrs.key?(name) && !attrs[foreign_key]
|
46
|
+
assoc.associate(attrs, attrs[name])
|
47
|
+
elsif !attrs[foreign_key]
|
48
|
+
struct = builder.persistable.create
|
49
|
+
tuple = { name => struct }
|
50
|
+
|
51
|
+
assoc.associate(tuple, struct)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def foreign_key
|
57
|
+
assoc.foreign_key
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
class OneToMany < Core
|
63
|
+
# @api private
|
64
|
+
def call(attrs = EMPTY_HASH, parent)
|
65
|
+
return if attrs.key?(name)
|
66
|
+
|
67
|
+
structs = count.times.map {
|
68
|
+
builder.persistable.create(assoc.associate(attrs, parent))
|
69
|
+
}
|
70
|
+
|
71
|
+
{ name => structs }
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
def dependency?(rel)
|
76
|
+
assoc.source == rel
|
77
|
+
end
|
78
|
+
|
79
|
+
# @api private
|
80
|
+
def count
|
81
|
+
options.fetch(:count)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
class OneToOne < OneToMany
|
87
|
+
# @api private
|
88
|
+
def call(attrs = EMPTY_HASH, parent)
|
89
|
+
return if attrs.key?(name)
|
90
|
+
|
91
|
+
struct = builder.persistable.create(assoc.associate(attrs, parent))
|
92
|
+
|
93
|
+
{ name => struct }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -1,19 +1,31 @@
|
|
1
1
|
module ROM::Factory
|
2
2
|
module Attributes
|
3
|
+
# @api private
|
3
4
|
class Callable
|
4
|
-
attr_reader :dsl, :block
|
5
|
+
attr_reader :name, :dsl, :block
|
5
6
|
|
6
|
-
|
7
|
+
# @api private
|
8
|
+
def initialize(name, dsl = nil, &block)
|
9
|
+
@name = name
|
7
10
|
@dsl = dsl
|
8
11
|
@block = block
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# @api private
|
15
|
+
def call(attrs, *args)
|
16
|
+
return if attrs.key?(name)
|
17
|
+
result = dsl.instance_exec(*args, &block)
|
18
|
+
{ name => result }
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def value?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def dependency_names
|
28
|
+
block.parameters.map(&:last)
|
17
29
|
end
|
18
30
|
end
|
19
31
|
end
|
@@ -1,18 +1,30 @@
|
|
1
1
|
module ROM::Factory
|
2
2
|
module Attributes
|
3
3
|
class Sequence
|
4
|
-
|
4
|
+
attr_reader :name, :count, :block
|
5
|
+
|
6
|
+
def initialize(name, &block)
|
7
|
+
@name = name
|
5
8
|
@count = 0
|
6
9
|
@block = block
|
7
10
|
end
|
8
11
|
|
9
|
-
def call
|
10
|
-
|
12
|
+
def call(attrs = EMPTY_HASH)
|
13
|
+
return if attrs.key?(name)
|
14
|
+
block.call(increment)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_proc
|
18
|
+
method(:call).to_proc
|
11
19
|
end
|
12
20
|
|
13
21
|
def increment
|
14
22
|
@count += 1
|
15
23
|
end
|
24
|
+
|
25
|
+
def dependency_names
|
26
|
+
EMPTY_ARRAY
|
27
|
+
end
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ROM::Factory
|
2
|
+
module Attributes
|
3
|
+
# @api private
|
4
|
+
class Value
|
5
|
+
attr_reader :name, :value
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
def initialize(name, value)
|
9
|
+
@name = name
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
# @api private
|
14
|
+
def call(attrs = EMPTY_HASH)
|
15
|
+
return if attrs.key?(name)
|
16
|
+
|
17
|
+
{ name => value }
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def value?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def dependency_names
|
27
|
+
EMPTY_ARRAY
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/rom/factory/builder.rb
CHANGED
@@ -1,55 +1,44 @@
|
|
1
|
-
require '
|
2
|
-
|
1
|
+
require 'dry/core/constants'
|
2
|
+
|
3
|
+
require 'rom/factory/tuple_evaluator'
|
4
|
+
require 'rom/factory/builder/persistable'
|
3
5
|
|
4
6
|
module ROM::Factory
|
7
|
+
# @api private
|
5
8
|
class Builder
|
6
|
-
|
9
|
+
include Dry::Core::Constants
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
# @api private
|
12
|
+
attr_reader :attributes
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
attr_reader :tuple_evaluator
|
13
16
|
|
14
|
-
|
15
|
-
|
17
|
+
# @api private
|
18
|
+
def initialize(attributes, relation)
|
19
|
+
@attributes = attributes
|
20
|
+
@tuple_evaluator = TupleEvaluator.new(attributes, relation)
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
|
-
|
23
|
+
# @api private
|
24
|
+
def tuple(attrs = EMPTY_HASH)
|
25
|
+
tuple_evaluator.defaults(attrs)
|
20
26
|
end
|
21
27
|
|
22
|
-
|
23
|
-
|
28
|
+
# @api private
|
29
|
+
def struct(attrs = EMPTY_HASH)
|
30
|
+
tuple_evaluator.struct(attrs)
|
24
31
|
end
|
32
|
+
alias_method :create, :struct
|
25
33
|
|
34
|
+
# @api private
|
26
35
|
def persistable
|
27
36
|
Persistable.new(self)
|
28
37
|
end
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def next_id
|
37
|
-
@sequence += 1
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class Persistable < SimpleDelegator
|
42
|
-
attr_reader :builder, :relation
|
43
|
-
|
44
|
-
def initialize(builder, relation = builder.relation)
|
45
|
-
super(builder)
|
46
|
-
@builder = builder
|
47
|
-
@relation = relation
|
48
|
-
end
|
49
|
-
|
50
|
-
def create(attrs = {})
|
51
|
-
tuple = builder.tuple(attrs)
|
52
|
-
struct(tuple.merge(primary_key => relation.insert(tuple)))
|
39
|
+
# @api private
|
40
|
+
def relation
|
41
|
+
tuple_evaluator.relation
|
53
42
|
end
|
54
43
|
end
|
55
44
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Factory
|
5
|
+
class Builder
|
6
|
+
# @api private
|
7
|
+
class Persistable < SimpleDelegator
|
8
|
+
# @api private
|
9
|
+
attr_reader :builder
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
attr_reader :relation
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
def initialize(builder, relation = builder.relation)
|
16
|
+
super(builder)
|
17
|
+
@builder = builder
|
18
|
+
@relation = relation
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def create(attrs = {})
|
23
|
+
tuple = tuple(attrs)
|
24
|
+
persisted = persist(tuple)
|
25
|
+
|
26
|
+
if tuple_evaluator.has_associations?
|
27
|
+
tuple_evaluator.persist_associations(tuple, persisted)
|
28
|
+
|
29
|
+
pk = primary_key_names.map { |key| persisted[key] }
|
30
|
+
|
31
|
+
relation.by_pk(*pk).combine(*tuple_evaluator.assoc_names).first
|
32
|
+
else
|
33
|
+
persisted
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
def persist(attrs)
|
41
|
+
relation.with(auto_struct: !tuple_evaluator.has_associations?).command(:create).call(attrs)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
def primary_key_names
|
46
|
+
relation.schema.primary_key.map(&:name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/rom/factory/dsl.rb
CHANGED
@@ -2,12 +2,12 @@ require 'faker'
|
|
2
2
|
require 'dry/core/inflector'
|
3
3
|
|
4
4
|
require 'rom/factory/builder'
|
5
|
-
require 'rom/factory/
|
6
|
-
require 'rom/factory/attributes
|
7
|
-
require 'rom/factory/attributes/sequence'
|
5
|
+
require 'rom/factory/attribute_registry'
|
6
|
+
require 'rom/factory/attributes'
|
8
7
|
|
9
8
|
module ROM
|
10
9
|
module Factory
|
10
|
+
# @api private
|
11
11
|
def self.fake(type, *args)
|
12
12
|
api = Faker.const_get(Dry::Core::Inflector.classify(type.to_s))
|
13
13
|
meth, *rest = args
|
@@ -19,55 +19,110 @@ module ROM
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
# Factory builder DSL
|
23
|
+
#
|
24
|
+
# @api public
|
22
25
|
class DSL < BasicObject
|
23
|
-
|
26
|
+
define_method(:rand, ::Kernel.instance_method(:rand))
|
24
27
|
|
25
|
-
|
28
|
+
attr_reader :_name, :_relation, :_attributes, :_factories, :_valid_names
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def initialize(name, attributes: AttributeRegistry.new, relation:, factories:)
|
26
32
|
@_name = name
|
27
33
|
@_relation = relation
|
28
34
|
@_factories = factories
|
29
|
-
@
|
35
|
+
@_attributes = attributes.dup
|
30
36
|
@_valid_names = _relation.schema.attributes.map(&:name)
|
31
37
|
yield(self)
|
32
38
|
end
|
33
39
|
|
40
|
+
# @api private
|
34
41
|
def call
|
35
|
-
::ROM::Factory::Builder.new(
|
42
|
+
::ROM::Factory::Builder.new(_attributes, _relation)
|
36
43
|
end
|
37
44
|
|
45
|
+
# Delegate to a builder and persist a struct
|
46
|
+
#
|
47
|
+
# @param [Symbol] The name of the registered builder
|
48
|
+
#
|
49
|
+
# @api public
|
38
50
|
def create(name, *args)
|
39
51
|
_factories[name, *args]
|
40
52
|
end
|
41
53
|
|
54
|
+
# Create a sequence attribute
|
55
|
+
#
|
56
|
+
# @param [Symbol] name The attribute name
|
57
|
+
#
|
58
|
+
# @api private
|
42
59
|
def sequence(meth, &block)
|
43
60
|
if _valid_names.include?(meth)
|
44
61
|
define_sequence(meth, block)
|
45
62
|
end
|
46
63
|
end
|
47
64
|
|
65
|
+
# Set timestamp attributes
|
66
|
+
#
|
67
|
+
# @api public
|
48
68
|
def timestamps
|
49
69
|
created_at { ::Time.now }
|
50
70
|
updated_at { ::Time.now }
|
51
71
|
end
|
52
72
|
|
73
|
+
# Create a fake value using Faker gem
|
74
|
+
#
|
75
|
+
# @overload fake(type)
|
76
|
+
# @example
|
77
|
+
# f.email { fake(:name) }
|
78
|
+
#
|
79
|
+
# @param [Symbol] type The value type to generate
|
80
|
+
#
|
81
|
+
# @overload fake(api, type)
|
82
|
+
# @example
|
83
|
+
# f.email { fake(:internet, :email) }
|
84
|
+
#
|
85
|
+
# @param [Symbol] api The faker API identifier ie. :internet, :product etc.
|
86
|
+
# @param [Symbol] type The value type to generate
|
87
|
+
#
|
88
|
+
# @overload fake(api, type, *args)
|
89
|
+
# @example
|
90
|
+
# f.email { fake(:number, :between, 10, 100) }
|
91
|
+
#
|
92
|
+
# @param [Symbol] api The faker API identifier ie. :internet, :product etc.
|
93
|
+
# @param [Symbol] type The value type to generate
|
94
|
+
# @param [Array] args Additional arguments
|
95
|
+
#
|
96
|
+
# @see https://github.com/stympy/faker/tree/master/doc
|
97
|
+
#
|
98
|
+
# @api public
|
53
99
|
def fake(*args)
|
54
100
|
::ROM::Factory.fake(*args)
|
55
101
|
end
|
56
102
|
|
57
|
-
|
103
|
+
# Create an association attribute
|
104
|
+
#
|
105
|
+
# @example belongs-to
|
106
|
+
# f.association(:group)
|
107
|
+
#
|
108
|
+
# @example has-many
|
109
|
+
# f.association(:posts, count: 2)
|
110
|
+
#
|
111
|
+
# @param [Symbol] name The name of the configured association
|
112
|
+
# @param [Hash] options Additional options
|
113
|
+
# @option options [Integer] count Number of objects to generate (has-many only)
|
114
|
+
#
|
115
|
+
# @api public
|
116
|
+
def association(name, options = {})
|
58
117
|
assoc = _relation.associations[name]
|
59
|
-
|
60
|
-
|
61
|
-
fk = _relation.foreign_key(other)
|
62
|
-
pk = other.primary_key
|
63
|
-
|
64
|
-
block = -> { create(name)[pk] }
|
118
|
+
builder = -> { _factories.for_relation(assoc.target) }
|
65
119
|
|
66
|
-
|
120
|
+
_attributes << attributes::Association.new(assoc, builder, options)
|
67
121
|
end
|
68
122
|
|
69
123
|
private
|
70
124
|
|
125
|
+
# @api private
|
71
126
|
def method_missing(meth, *args, &block)
|
72
127
|
if _valid_names.include?(meth)
|
73
128
|
define_attr(meth, *args, &block)
|
@@ -76,18 +131,26 @@ module ROM
|
|
76
131
|
end
|
77
132
|
end
|
78
133
|
|
134
|
+
# @api private
|
135
|
+
def respond_to_missing?(method_name, include_private = false)
|
136
|
+
_valid_names.include?(meth) || super
|
137
|
+
end
|
138
|
+
|
139
|
+
# @api private
|
79
140
|
def define_sequence(name, block)
|
80
|
-
|
141
|
+
_attributes << attributes::Callable.new(name, self, &attributes::Sequence.new(name, &block))
|
81
142
|
end
|
82
143
|
|
144
|
+
# @api private
|
83
145
|
def define_attr(name, *args, &block)
|
84
146
|
if block
|
85
|
-
|
147
|
+
_attributes << attributes::Callable.new(name, self, &block)
|
86
148
|
else
|
87
|
-
|
149
|
+
_attributes << attributes::Value.new(name, *args)
|
88
150
|
end
|
89
151
|
end
|
90
152
|
|
153
|
+
# @api private
|
91
154
|
def attributes
|
92
155
|
::ROM::Factory::Attributes
|
93
156
|
end
|