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.
@@ -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