rom-factory 0.6.0 → 0.7.0

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