rom-factory 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5b3b45c040487ccf89d27457241ba3b117e51ed1a346e089d0da8f6bb223fb1
4
- data.tar.gz: c56f6cac0fd002de3cb4b34e272704086cf4fd196e66e3931c82c9a17fbccfe1
3
+ metadata.gz: 2f2cad99dc93108f4bb7e9ac571ed198eaedee1b541b886fc018bb4501360817
4
+ data.tar.gz: 7fde94283a3d31672b6fa3ea8c082320524dc106025d4f50ac64ab3005bda6c7
5
5
  SHA512:
6
- metadata.gz: decd39e777394bdc13022f9ae17c2a340bf325d66950c7487b6ffc60505e4b1097d437fc9e0a8cda287ac2e47aef71145ea5191d26b8d8d6fa6ecb9581e0fdd9
7
- data.tar.gz: 2c2d34cfe48bcc3909b3599ea8fa241ae974c4993e021bcfdededbdba5a5c0f62e4a91a393033173ba649fcb74e3ded0458562d426c2b3b948275b1cc5639da6
6
+ metadata.gz: 8d14003a058a9eda9b2a407be0d4571de6213ded73532a01a0dbee8bdb3de47836a517425a742cf117b8674b67b64bf00d7e2ac9f47887f110a05d8143d7e0be
7
+ data.tar.gz: 389317084e6d7331c8e1fafcd304f34c458e393a63aee70debf0e04bd78d026b005a5f5ff5678eb3be29e499fdb2f1052b27a3602c31232d8ca14588cd506bfa
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## 0.7.0 2018-11-17
2
+
3
+ ### Added
4
+
5
+ * Support for traits (v-kolesnikov)
6
+ * Support building structs with associations (@ianks)
7
+
8
+ ### Fixed
9
+
10
+ * Overwritten attributes with dependencies (JanaVPetrova)
11
+
12
+ [Compare v0.6.0...v0.7.0](https://github.com/rom-rb/rom-factory/compare/v0.6.0...v0.7.0)
13
+
1
14
  ## 0.6.0 2018-01-31
2
15
 
3
16
  ### Added
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ gem 'rspec', '~> 3.0'
7
7
 
8
8
  group :test do
9
9
  gem 'rom-sql', '~> 2.1'
10
+ gem 'rom-core', '~> 4.2', '>= 4.2.1'
10
11
  gem 'inflecto'
11
12
  gem 'pry-byebug', platforms: :mri
12
13
  gem 'pry', platforms: :jruby
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/rom-factory
2
2
  [travis]: https://travis-ci.org/rom-rb/rom-factory
3
- [gemnasium]: https://gemnasium.com/rom-rb/rom-factory
4
3
  [codeclimate]: https://codeclimate.com/github/rom-rb/rom-factory
5
4
  [inchpages]: http://inch-ci.org/github/rom-rb/rom-factory
6
5
 
@@ -8,7 +7,6 @@
8
7
 
9
8
  [![Gem Version](https://badge.fury.io/rb/rom-factory.svg)][gem]
10
9
  [![Build Status](https://travis-ci.org/rom-rb/rom-factory.svg?branch=master)][travis]
11
- [![Dependency Status](https://gemnasium.com/rom-rb/rom-factory.svg)][gemnasium]
12
10
  [![Code Climate](https://codeclimate.com/github/rom-rb/rom-factory/badges/gpa.svg)][codeclimate]
13
11
  [![Test Coverage](https://codeclimate.com/github/rom-rb/rom-factory/badges/coverage.svg)][codeclimate]
14
12
  [![Inline docs](http://inch-ci.org/github/rom-rb/rom-factory.svg?branch=master)][inchpages]
@@ -2,24 +2,25 @@ module ROM::Factory
2
2
  module Attributes
3
3
  # @api private
4
4
  module Association
5
- def self.new(assoc, builder, options = {})
6
- const_get(assoc.definition.type).new(assoc, builder, options)
5
+ def self.new(assoc, builder, *traits, **options)
6
+ const_get(assoc.definition.type).new(assoc, builder, *traits, **options)
7
7
  end
8
8
 
9
9
  # @api private
10
10
  class Core
11
- attr_reader :assoc, :options
11
+ attr_reader :assoc, :options, :traits
12
12
 
13
13
  # @api private
14
- def initialize(assoc, builder, options = {})
14
+ def initialize(assoc, builder, *traits, **options)
15
15
  @assoc = assoc
16
16
  @builder_proc = builder
17
+ @traits = traits
17
18
  @options = options
18
19
  end
19
20
 
20
21
  # @api private
21
22
  def builder
22
- @__builder__ ||= @builder_proc.()
23
+ @__builder__ ||= @builder_proc.call
23
24
  end
24
25
 
25
26
  # @api private
@@ -41,13 +42,16 @@ module ROM::Factory
41
42
  # @api private
42
43
  class ManyToOne < Core
43
44
  # @api private
44
- def call(attrs = EMPTY_HASH)
45
+ def call(attrs, opts)
45
46
  if attrs.key?(name) && !attrs[foreign_key]
46
47
  assoc.associate(attrs, attrs[name])
47
48
  elsif !attrs[foreign_key]
48
- struct = builder.persistable.create
49
+ struct = if opts.fetch(:persist, true)
50
+ builder.persistable.create(*traits)
51
+ else
52
+ builder.struct(*traits, attrs)
53
+ end
49
54
  tuple = { name => struct }
50
-
51
55
  assoc.associate(tuple, struct)
52
56
  end
53
57
  end
@@ -61,12 +65,19 @@ module ROM::Factory
61
65
  # @api private
62
66
  class OneToMany < Core
63
67
  # @api private
64
- def call(attrs = EMPTY_HASH, parent)
68
+ def call(attrs = EMPTY_HASH, parent, persist: true)
65
69
  return if attrs.key?(name)
66
70
 
67
- structs = count.times.map {
68
- builder.persistable.create(assoc.associate(attrs, parent))
69
- }
71
+ structs = Array.new(count).map do
72
+ # hash which contains the foreign key info, i.e: { user_id: 1 }
73
+ association_hash = assoc.associate(attrs, parent)
74
+
75
+ if persist
76
+ builder.persistable.create(*traits, association_hash)
77
+ else
78
+ builder.struct(*traits, attrs.merge(association_hash))
79
+ end
80
+ end
70
81
 
71
82
  { name => structs }
72
83
  end
@@ -85,10 +96,19 @@ module ROM::Factory
85
96
  # @api private
86
97
  class OneToOne < OneToMany
87
98
  # @api private
88
- def call(attrs = EMPTY_HASH, parent)
99
+ def call(attrs = EMPTY_HASH, parent, opts)
89
100
  return if attrs.key?(name)
90
101
 
91
- struct = builder.persistable.create(assoc.associate(attrs, parent))
102
+ association_hash = assoc.associate(attrs, parent)
103
+
104
+ struct = if opts.fetch(:persist, true)
105
+ builder.persistable.create(*traits, association_hash)
106
+ else
107
+ belongs_to_name = Dry::Core::Inflector.singularize(assoc.source_alias)
108
+ belongs_to_associations = { belongs_to_name.to_sym => parent }
109
+ final_attrs = attrs.merge(association_hash).merge(belongs_to_associations)
110
+ builder.struct(*traits, final_attrs)
111
+ end
92
112
 
93
113
  { name => struct }
94
114
  end
@@ -13,8 +13,7 @@ module ROM::Factory
13
13
 
14
14
  # @api private
15
15
  def call(attrs, *args)
16
- return if attrs.key?(name)
17
- result = dsl.instance_exec(*args, &block)
16
+ result = attrs[name] || dsl.instance_exec(*args, &block)
18
17
  { name => result }
19
18
  end
20
19
 
@@ -19,16 +19,16 @@ module ROM
19
19
  end
20
20
 
21
21
  # @api private
22
- def create(attrs = {})
23
- tuple = tuple(attrs)
22
+ def create(*traits, **attrs)
23
+ tuple = tuple(*traits, attrs)
24
24
  persisted = persist(tuple)
25
25
 
26
- if tuple_evaluator.has_associations?
27
- tuple_evaluator.persist_associations(tuple, persisted)
26
+ if tuple_evaluator.has_associations?(traits)
27
+ tuple_evaluator.persist_associations(tuple, persisted, traits)
28
28
 
29
29
  pk = primary_key_names.map { |key| persisted[key] }
30
30
 
31
- relation.by_pk(*pk).combine(*tuple_evaluator.assoc_names).first
31
+ relation.by_pk(*pk).combine(*tuple_evaluator.assoc_names(traits)).first
32
32
  else
33
33
  persisted
34
34
  end
@@ -16,18 +16,22 @@ module ROM::Factory
16
16
  # @return [ROM::Factory::Attributes]
17
17
  param :attributes
18
18
 
19
+ # @!attribute [r] traits
20
+ # @return [Hash]
21
+ param :traits, default: -> { EMPTY_HASH }
22
+
19
23
  # @!attribute [r] relation
20
24
  # @return [ROM::Relation]
21
25
  option :relation, reader: false
22
26
 
23
27
  # @api private
24
- def tuple(attrs = EMPTY_HASH)
25
- tuple_evaluator.defaults(attrs)
28
+ def tuple(*traits, **attrs)
29
+ tuple_evaluator.defaults(traits, attrs)
26
30
  end
27
31
 
28
32
  # @api private
29
- def struct(attrs = EMPTY_HASH)
30
- tuple_evaluator.struct(attrs)
33
+ def struct(*traits, **attrs)
34
+ tuple_evaluator.struct(*traits, attrs)
31
35
  end
32
36
  alias_method :create, :struct
33
37
 
@@ -43,7 +47,7 @@ module ROM::Factory
43
47
 
44
48
  # @api private
45
49
  def tuple_evaluator
46
- @__tuple_evaluator__ ||= TupleEvaluator.new(attributes, options[:relation])
50
+ @__tuple_evaluator__ ||= TupleEvaluator.new(attributes, options[:relation], traits)
47
51
  end
48
52
 
49
53
  # @api private
@@ -26,6 +26,7 @@ module ROM
26
26
  define_method(:rand, ::Kernel.instance_method(:rand))
27
27
 
28
28
  attr_reader :_name, :_relation, :_attributes, :_factories, :_valid_names
29
+ attr_reader :_traits
29
30
 
30
31
  # @api private
31
32
  def initialize(name, attributes: AttributeRegistry.new, relation:, factories:)
@@ -33,13 +34,14 @@ module ROM
33
34
  @_relation = relation
34
35
  @_factories = factories
35
36
  @_attributes = attributes.dup
37
+ @_traits = {}
36
38
  @_valid_names = _relation.schema.attributes.map(&:name)
37
39
  yield(self)
38
40
  end
39
41
 
40
42
  # @api private
41
43
  def call
42
- ::ROM::Factory::Builder.new(_attributes, relation: _relation)
44
+ ::ROM::Factory::Builder.new(_attributes, _traits, relation: _relation)
43
45
  end
44
46
 
45
47
  # Delegate to a builder and persist a struct
@@ -57,9 +59,7 @@ module ROM
57
59
  #
58
60
  # @api private
59
61
  def sequence(meth, &block)
60
- if _valid_names.include?(meth)
61
- define_sequence(meth, block)
62
- end
62
+ define_sequence(meth, block) if _valid_names.include?(meth)
63
63
  end
64
64
 
65
65
  # Set timestamp attributes
@@ -100,6 +100,18 @@ module ROM
100
100
  ::ROM::Factory.fake(*args)
101
101
  end
102
102
 
103
+ def trait(name, parents = [], &block)
104
+ _traits[name] = DSL.new(
105
+ "#{_name}_#{name}",
106
+ attributes: _traits.values_at(*parents).flat_map(&:elements).inject(
107
+ AttributeRegistry.new, :<<
108
+ ),
109
+ relation: _relation,
110
+ factories: _factories,
111
+ &block
112
+ )._attributes
113
+ end
114
+
103
115
  # Create an association attribute
104
116
  #
105
117
  # @example belongs-to
@@ -113,11 +125,11 @@ module ROM
113
125
  # @option options [Integer] count Number of objects to generate (has-many only)
114
126
  #
115
127
  # @api public
116
- def association(name, options = {})
128
+ def association(name, *traits, **options)
117
129
  assoc = _relation.associations[name]
118
130
  builder = -> { _factories.for_relation(assoc.target) }
119
131
 
120
- _attributes << attributes::Association.new(assoc, builder, options)
132
+ _attributes << attributes::Association.new(assoc, builder, *traits, **options)
121
133
  end
122
134
 
123
135
  private
@@ -143,11 +155,11 @@ module ROM
143
155
 
144
156
  # @api private
145
157
  def define_attr(name, *args, &block)
146
- if block
147
- _attributes << attributes::Callable.new(name, self, block)
148
- else
149
- _attributes << attributes::Value.new(name, *args)
150
- end
158
+ _attributes << if block
159
+ attributes::Callable.new(name, self, block)
160
+ else
161
+ attributes::Value.new(name, *args)
162
+ end
151
163
  end
152
164
 
153
165
  # @api private
@@ -39,8 +39,8 @@ module ROM::Factory
39
39
  # @return [ROM::Struct]
40
40
  #
41
41
  # @api public
42
- def [](name, attrs = {})
43
- registry[name].struct_namespace(struct_namespace).create(attrs)
42
+ def [](name, *traits, **attrs)
43
+ registry[name].struct_namespace(struct_namespace).create(*traits, attrs)
44
44
  end
45
45
  end
46
46
 
@@ -160,8 +160,8 @@ module ROM::Factory
160
160
  # @return [ROM::Struct]
161
161
  #
162
162
  # @api public
163
- def [](name, attrs = {})
164
- registry[name].persistable(struct_namespace).create(attrs)
163
+ def [](name, *traits, **attrs)
164
+ registry[name].persistable(struct_namespace).create(*traits, attrs)
165
165
  end
166
166
 
167
167
  # Return in-memory struct builder
@@ -10,6 +10,9 @@ module ROM
10
10
  # @api private
11
11
  attr_reader :relation
12
12
 
13
+ # @api private
14
+ attr_reader :traits
15
+
13
16
  # @api private
14
17
  attr_reader :model
15
18
 
@@ -17,39 +20,61 @@ module ROM
17
20
  attr_reader :sequence
18
21
 
19
22
  # @api private
20
- def initialize(attributes, relation)
23
+ def initialize(attributes, relation, traits = {})
21
24
  @attributes = attributes
22
25
  @relation = relation.with(auto_struct: true)
26
+ @traits = traits
23
27
  @model = @relation.combine(*assoc_names).mapper.model
24
28
  @sequence = Sequences[relation]
25
29
  end
26
30
 
27
31
  # @api private
28
- def defaults(attrs)
29
- evaluate(attrs).merge(attrs)
32
+ def defaults(traits, attrs, opts = EMPTY_HASH)
33
+ evaluate(traits, attrs, opts).merge(attrs)
30
34
  end
31
35
 
32
36
  # @api private
33
- def struct(attrs)
34
- model.new(struct_attrs.merge(defaults(attrs)))
37
+ def struct(*traits, attrs)
38
+ merged_attrs = struct_attrs.merge(defaults(traits, attrs, persist: false))
39
+ is_callable = proc { |_name, value| value.respond_to?(:call) }
40
+
41
+ callables = merged_attrs.select(&is_callable)
42
+ attributes = merged_attrs.reject(&is_callable)
43
+
44
+ materialized_callables = {}
45
+ callables.each do |_name, callable|
46
+ materialized_callables.merge!(callable.call(attributes, persist: false))
47
+ end
48
+
49
+ attributes.merge!(materialized_callables)
50
+ attributes = relation.output_schema.call(attributes)
51
+
52
+ model.new(attributes)
35
53
  end
36
54
 
37
55
  # @api private
38
- def persist_associations(tuple, parent)
39
- assoc_names.each do |name|
56
+ def persist_associations(tuple, parent, traits = [])
57
+ assoc_names(traits).each do |name|
40
58
  assoc = tuple[name]
41
- assoc.(parent) if assoc.is_a?(Proc)
59
+ assoc.call(parent, persist: true) if assoc.is_a?(Proc)
42
60
  end
43
61
  end
44
62
 
45
63
  # @api private
46
- def assoc_names
47
- attributes.associations.map(&:name)
64
+ def assoc_names(traits = [])
65
+ assocs(traits).map(&:name)
66
+ end
67
+
68
+ def assocs(traits_names = [])
69
+ traits
70
+ .values_at(*traits_names)
71
+ .map(&:associations).flat_map(&:elements)
72
+ .inject(AttributeRegistry.new(attributes.associations.elements), :<<)
48
73
  end
49
74
 
50
75
  # @api private
51
- def has_associations?
52
- assoc_names.size > 0
76
+ def has_associations?(traits = [])
77
+ !assoc_names(traits).empty?
53
78
  end
54
79
 
55
80
  # @api private
@@ -60,12 +85,14 @@ module ROM
60
85
  private
61
86
 
62
87
  # @api private
63
- def evaluate(attrs)
64
- evaluate_values(attrs).merge(evaluate_associations(attrs))
88
+ def evaluate(traits, attrs, opts)
89
+ evaluate_values(attrs, opts)
90
+ .merge(evaluate_associations(attrs, opts))
91
+ .merge(evaluate_traits(traits, attrs, opts))
65
92
  end
66
93
 
67
94
  # @api private
68
- def evaluate_values(attrs)
95
+ def evaluate_values(attrs, opts)
69
96
  attributes.values.tsort.each_with_object({}) do |attr, h|
70
97
  deps = attr.dependency_names.map { |k| h[k] }.compact
71
98
  result = attr.(attrs, *deps)
@@ -76,13 +103,23 @@ module ROM
76
103
  end
77
104
  end
78
105
 
106
+ def evaluate_traits(traits, attrs, opts)
107
+ return {} if traits.empty?
108
+
109
+ traits_attrs = self.traits.values_at(*traits).flat_map(&:elements)
110
+ registry = AttributeRegistry.new(traits_attrs)
111
+ self.class.new(registry, relation).defaults([], attrs, opts)
112
+ end
113
+
79
114
  # @api private
80
- def evaluate_associations(attrs)
115
+ def evaluate_associations(attrs, opts)
81
116
  attributes.associations.each_with_object({}) do |assoc, h|
82
117
  if assoc.dependency?(relation)
83
- h[assoc.name] = -> parent { assoc.call(parent) }
118
+ h[assoc.name] = ->(parent, call_opts) do
119
+ assoc.call(parent, opts.merge(call_opts))
120
+ end
84
121
  else
85
- result = assoc.(attrs)
122
+ result = assoc.(attrs, opts)
86
123
  h.update(result) if result
87
124
  end
88
125
  end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module Factory
3
- VERSION = '0.6.0'.freeze
3
+ VERSION = '0.7.0'.freeze
4
4
  end
5
5
  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.6.0
4
+ version: 0.7.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: 2018-01-31 00:00:00.000000000 Z
12
+ date: 2018-11-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dry-configurable
@@ -132,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
132
  version: '0'
133
133
  requirements: []
134
134
  rubyforge_project:
135
- rubygems_version: 2.7.4
135
+ rubygems_version: 2.7.6
136
136
  signing_key:
137
137
  specification_version: 4
138
138
  summary: ROM based builder library to make your specs awesome. DSL partially inspired