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.
@@ -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
- def initialize(dsl, block)
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
- def call
12
- if block.is_a?(Proc)
13
- dsl.instance_exec(&block)
14
- else
15
- block.call
16
- end
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
- def initialize(&block)
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
- @block.call(increment)
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
@@ -1,55 +1,44 @@
1
- require 'delegate'
2
- require 'rom/factory/struct'
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
- attr_reader :schema, :relation
9
+ include Dry::Core::Constants
7
10
 
8
- def initialize(schema, relation)
9
- @schema = schema
10
- @relation = relation
11
- @sequence = 0
12
- end
11
+ # @api private
12
+ attr_reader :attributes
13
+
14
+ # @api private
15
+ attr_reader :tuple_evaluator
13
16
 
14
- def tuple(attrs)
15
- schema.map {|k, v| [k, v.call] }.to_h.merge(attrs)
17
+ # @api private
18
+ def initialize(attributes, relation)
19
+ @attributes = attributes
20
+ @tuple_evaluator = TupleEvaluator.new(attributes, relation)
16
21
  end
17
22
 
18
- def create(attrs = {})
19
- struct(tuple(attrs.merge(primary_key => next_id)))
23
+ # @api private
24
+ def tuple(attrs = EMPTY_HASH)
25
+ tuple_evaluator.defaults(attrs)
20
26
  end
21
27
 
22
- def struct(attrs)
23
- Struct.define(relation.name.relation, relation.schema.project(*attrs.keys)).new(attrs)
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
- def primary_key
31
- relation.primary_key
32
- end
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
@@ -2,12 +2,12 @@ require 'faker'
2
2
  require 'dry/core/inflector'
3
3
 
4
4
  require 'rom/factory/builder'
5
- require 'rom/factory/attributes/regular'
6
- require 'rom/factory/attributes/callable'
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
- attr_reader :_name, :_relation, :_schema, :_factories, :_valid_names
26
+ define_method(:rand, ::Kernel.instance_method(:rand))
24
27
 
25
- def initialize(name, schema: {}, relation:, factories:, &block)
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
- @_schema = schema.dup
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(_schema, _relation)
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
- def association(name)
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
- other = _relation.__registry__[assoc.target]
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
- _schema[fk] = attributes::Callable.new(self, block)
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
- _schema[name] = attributes::Callable.new(self, attributes::Sequence.new(&block))
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
- _schema[name] = attributes::Callable.new(self, block)
147
+ _attributes << attributes::Callable.new(name, self, &block)
86
148
  else
87
- _schema[name] = attributes::Regular.new(*args)
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