rom 2.0.2 → 3.0.0.beta1

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.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -4
  3. data/CHANGELOG.md +34 -1
  4. data/Gemfile +16 -2
  5. data/Rakefile +7 -2
  6. data/lib/rom/array_dataset.rb +44 -0
  7. data/lib/rom/association_set.rb +11 -5
  8. data/lib/rom/auto_curry.rb +55 -0
  9. data/lib/rom/command.rb +70 -41
  10. data/lib/rom/command_registry.rb +7 -18
  11. data/lib/rom/commands/class_interface.rb +7 -6
  12. data/lib/rom/commands/composite.rb +0 -1
  13. data/lib/rom/commands/graph.rb +7 -15
  14. data/lib/rom/commands/lazy/update.rb +1 -1
  15. data/lib/rom/configuration_dsl/command.rb +6 -8
  16. data/lib/rom/configuration_dsl/mapper.rb +2 -3
  17. data/lib/rom/configuration_dsl/mapper_dsl.rb +0 -1
  18. data/lib/rom/configuration_dsl/relation.rb +4 -4
  19. data/lib/rom/configuration_dsl.rb +0 -4
  20. data/lib/rom/constants.rb +1 -1
  21. data/lib/rom/container.rb +0 -2
  22. data/lib/rom/create_container.rb +0 -2
  23. data/lib/rom/data_proxy.rb +94 -0
  24. data/lib/rom/enumerable_dataset.rb +68 -0
  25. data/lib/rom/gateway.rb +23 -6
  26. data/lib/rom/global/plugin_dsl.rb +0 -2
  27. data/lib/rom/global.rb +0 -2
  28. data/lib/rom/initializer.rb +26 -0
  29. data/lib/rom/lint/gateway.rb +17 -0
  30. data/lib/rom/mapper_registry.rb +1 -1
  31. data/lib/rom/memory/commands.rb +0 -2
  32. data/lib/rom/memory/dataset.rb +1 -2
  33. data/lib/rom/memory/relation.rb +14 -1
  34. data/lib/rom/memory/schema.rb +13 -0
  35. data/lib/rom/plugin_registry.rb +1 -1
  36. data/lib/rom/plugins/configuration/configuration_dsl.rb +6 -2
  37. data/lib/rom/plugins/relation/key_inference.rb +4 -2
  38. data/lib/rom/plugins/relation/registry_reader.rb +5 -1
  39. data/lib/rom/registry.rb +50 -0
  40. data/lib/rom/relation/class_interface.rb +94 -26
  41. data/lib/rom/relation/curried.rb +15 -15
  42. data/lib/rom/relation/view_dsl.rb +31 -0
  43. data/lib/rom/relation.rb +49 -34
  44. data/lib/rom/schema/dsl.rb +7 -9
  45. data/lib/rom/schema/type.rb +115 -0
  46. data/lib/rom/schema.rb +218 -18
  47. data/lib/rom/setup/auto_registration.rb +20 -17
  48. data/lib/rom/setup/auto_registration_strategies/base.rb +8 -3
  49. data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +4 -3
  50. data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +5 -4
  51. data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +3 -3
  52. data/lib/rom/setup/finalize/finalize_commands.rb +1 -1
  53. data/lib/rom/setup/finalize/finalize_mappers.rb +1 -1
  54. data/lib/rom/setup/finalize/finalize_relations.rb +3 -1
  55. data/lib/rom/setup/finalize.rb +1 -1
  56. data/lib/rom/transaction.rb +24 -0
  57. data/lib/rom/types.rb +9 -1
  58. data/lib/rom/version.rb +1 -1
  59. data/lib/rom.rb +4 -8
  60. data/rom.gemspec +4 -3
  61. data/spec/integration/command_registry_spec.rb +1 -14
  62. data/spec/integration/commands/create_spec.rb +5 -25
  63. data/spec/integration/commands/delete_spec.rb +1 -1
  64. data/spec/integration/commands/error_handling_spec.rb +1 -1
  65. data/spec/integration/commands/graph_spec.rb +20 -14
  66. data/spec/integration/commands/update_spec.rb +4 -27
  67. data/spec/integration/commands_spec.rb +1 -1
  68. data/spec/integration/{repositories → gateways}/extending_relations_spec.rb +1 -1
  69. data/spec/integration/{repositories → gateways}/setting_logger_spec.rb +2 -2
  70. data/spec/integration/mappers/combine_spec.rb +1 -1
  71. data/spec/integration/mappers/deep_embedded_spec.rb +1 -1
  72. data/spec/integration/mappers/definition_dsl_spec.rb +1 -1
  73. data/spec/integration/mappers/embedded_spec.rb +1 -1
  74. data/spec/integration/mappers/exclude_spec.rb +1 -1
  75. data/spec/integration/mappers/fold_spec.rb +1 -1
  76. data/spec/integration/mappers/group_spec.rb +1 -1
  77. data/spec/integration/mappers/overwrite_attributes_value_spec.rb +1 -1
  78. data/spec/integration/mappers/prefix_separator_spec.rb +1 -1
  79. data/spec/integration/mappers/prefix_spec.rb +1 -1
  80. data/spec/integration/mappers/prefixing_attributes_spec.rb +1 -1
  81. data/spec/integration/mappers/registering_custom_mappers_spec.rb +1 -1
  82. data/spec/integration/mappers/renaming_attributes_spec.rb +1 -1
  83. data/spec/integration/mappers/reusing_mappers_spec.rb +1 -1
  84. data/spec/integration/mappers/step_spec.rb +1 -1
  85. data/spec/integration/mappers/symbolizing_attributes_spec.rb +1 -1
  86. data/spec/integration/mappers/unfold_spec.rb +1 -1
  87. data/spec/integration/mappers/ungroup_spec.rb +1 -1
  88. data/spec/integration/mappers/unwrap_spec.rb +2 -2
  89. data/spec/integration/mappers/wrap_spec.rb +1 -1
  90. data/spec/integration/memory/commands/create_spec.rb +1 -1
  91. data/spec/integration/memory/commands/delete_spec.rb +1 -1
  92. data/spec/integration/memory/commands/update_spec.rb +1 -1
  93. data/spec/integration/multi_env_spec.rb +1 -1
  94. data/spec/integration/multi_repo_spec.rb +1 -1
  95. data/spec/integration/relations/default_dataset_spec.rb +1 -1
  96. data/spec/integration/relations/reading_spec.rb +1 -1
  97. data/spec/integration/relations/registry_dsl_spec.rb +1 -1
  98. data/spec/integration/setup_spec.rb +10 -4
  99. data/spec/shared/command_graph.rb +8 -4
  100. data/spec/shared/enumerable_dataset.rb +1 -1
  101. data/spec/spec_helper.rb +7 -9
  102. data/spec/support/schema.rb +14 -0
  103. data/spec/unit/rom/array_dataset_spec.rb +59 -0
  104. data/spec/unit/rom/association_set_spec.rb +4 -0
  105. data/spec/unit/rom/auto_curry_spec.rb +63 -0
  106. data/spec/unit/rom/commands/graph_spec.rb +12 -11
  107. data/spec/unit/rom/commands/lazy_spec.rb +8 -5
  108. data/spec/unit/rom/commands/pre_and_post_processors_spec.rb +269 -0
  109. data/spec/unit/rom/commands/result_spec.rb +1 -1
  110. data/spec/unit/rom/commands_spec.rb +9 -3
  111. data/spec/unit/rom/configuration_spec.rb +1 -1
  112. data/spec/unit/rom/container_spec.rb +11 -5
  113. data/spec/unit/rom/create_container_spec.rb +1 -1
  114. data/spec/unit/rom/enumerable_dataset_spec.rb +15 -0
  115. data/spec/unit/rom/gateway_spec.rb +1 -1
  116. data/spec/unit/rom/mapper_registry_spec.rb +1 -1
  117. data/spec/unit/rom/memory/commands_spec.rb +1 -1
  118. data/spec/unit/rom/memory/dataset_spec.rb +1 -1
  119. data/spec/unit/rom/memory/{repository_spec.rb → gateway_spec.rb} +1 -1
  120. data/spec/unit/rom/memory/inheritance_spec.rb +32 -0
  121. data/spec/unit/rom/memory/relation_spec.rb +15 -3
  122. data/spec/unit/rom/memory/storage_spec.rb +1 -1
  123. data/spec/unit/rom/plugin_spec.rb +1 -1
  124. data/spec/unit/rom/plugins/relation/key_inference_spec.rb +1 -1
  125. data/spec/unit/rom/registry_spec.rb +86 -0
  126. data/spec/unit/rom/relation/attribute_reader_spec.rb +17 -0
  127. data/spec/unit/rom/relation/composite_spec.rb +1 -1
  128. data/spec/unit/rom/relation/graph_spec.rb +1 -1
  129. data/spec/unit/rom/relation/lazy/combine_spec.rb +1 -1
  130. data/spec/unit/rom/relation/lazy_spec.rb +1 -1
  131. data/spec/unit/rom/relation/loaded_spec.rb +1 -1
  132. data/spec/unit/rom/relation/schema_spec.rb +10 -6
  133. data/spec/unit/rom/relation/view_spec.rb +112 -0
  134. data/spec/unit/rom/relation_spec.rb +16 -2
  135. data/spec/unit/rom/schema/accessing_attributes_spec.rb +52 -0
  136. data/spec/unit/rom/schema/exclude_spec.rb +15 -0
  137. data/spec/unit/rom/schema/finalize_spec.rb +59 -0
  138. data/spec/unit/rom/schema/key_predicate_spec.rb +15 -0
  139. data/spec/unit/rom/schema/merge_spec.rb +17 -0
  140. data/spec/unit/rom/schema/prefix_spec.rb +16 -0
  141. data/spec/unit/rom/schema/project_spec.rb +15 -0
  142. data/spec/unit/rom/schema/rename_spec.rb +22 -0
  143. data/spec/unit/rom/schema/type_spec.rb +49 -0
  144. data/spec/unit/rom/schema/wrap_spec.rb +17 -0
  145. data/spec/unit/rom/schema_spec.rb +2 -2
  146. metadata +69 -17
  147. data/lib/rom/plugins/relation/view/dsl.rb +0 -32
  148. data/lib/rom/plugins/relation/view.rb +0 -95
  149. data/spec/unit/rom/plugins/relation/view_spec.rb +0 -51
@@ -1,13 +1,20 @@
1
1
  require 'set'
2
2
 
3
- require 'rom/support/auto_curry'
3
+ require 'dry/core/inflector'
4
+ require 'dry/core/constants'
5
+ require 'dry/core/class_attributes'
6
+
7
+ require 'rom/auto_curry'
4
8
  require 'rom/relation/curried'
5
9
  require 'rom/relation/name'
10
+ require 'rom/relation/view_dsl'
6
11
  require 'rom/schema'
7
12
 
8
13
  module ROM
9
14
  class Relation
10
15
  module ClassInterface
16
+ include Dry::Core::Constants
17
+
11
18
  # Register adapter relation subclasses during setup phase
12
19
  #
13
20
  # In adition those subclasses are extended with an interface for accessing
@@ -17,14 +24,14 @@ module ROM
17
24
  def inherited(klass)
18
25
  super
19
26
 
20
- klass.extend ClassMacros
21
- klass.defines :adapter
22
-
23
27
  if respond_to?(:adapter) && adapter.nil?
24
28
  raise MissingAdapterIdentifierError,
25
- "relation class +#{self}+ is missing the adapter identifier"
29
+ "relation class +#{self}+ is missing the adapter identifier"
26
30
  end
27
31
 
32
+ klass.extend Dry::Core::ClassAttributes
33
+ klass.defines :adapter, :schema_class
34
+
28
35
  # Extend with functionality required by adapters *only* if this is a direct
29
36
  # descendant of an adapter-specific relation subclass
30
37
  return unless respond_to?(:adapter) && klass.superclass == ROM::Relation[adapter]
@@ -32,22 +39,30 @@ module ROM
32
39
  klass.class_eval do
33
40
  use :registry_reader
34
41
 
35
- defines :gateway, :dataset, :dataset_proc, :register_as, :schema_dsl, :schema_inferrer
42
+ defines :gateway, :dataset, :dataset_proc, :register_as,
43
+ :schema_dsl, :schema_inferrer
36
44
 
37
45
  gateway :default
38
46
  schema_dsl Schema::DSL
47
+ schema_class Schema
39
48
  schema_inferrer nil
40
49
 
41
50
  dataset default_name
42
51
 
43
- # Relation's dataset name
44
- #
45
- # In example a table name in an SQL database
46
- #
47
- # @return [Symbol]
48
- #
49
- # @api public
50
- attr_reader :name
52
+ # skip defining :name option if it's a class that already has this method
53
+ # which can happen when an adapter's relation class inherits from another
54
+ # adapter's relation class (ie YAML::Relation < Memory::Relation)
55
+ unless instance_methods.include?(:name)
56
+ # Relation's dataset name
57
+ #
58
+ # In example a table name in an SQL database
59
+ #
60
+ # @return [Symbol]
61
+ #
62
+ # @api public
63
+ option :name, reader: true, optional: true,
64
+ default: -> r { Name.new(r.class.register_as, r.class.dataset) }
65
+ end
51
66
 
52
67
  # Set dataset name
53
68
  #
@@ -95,12 +110,6 @@ module ROM
95
110
  end
96
111
  end
97
112
 
98
- # @api private
99
- def initialize(dataset, options = EMPTY_HASH)
100
- @name = Name.new(self.class.register_as, self.class.dataset)
101
- super
102
- end
103
-
104
113
  # Return name of the source gateway of this relation
105
114
  #
106
115
  # @return [Symbol]
@@ -160,12 +169,62 @@ module ROM
160
169
 
161
170
  name = Name[register_as, self.dataset]
162
171
  inferrer = infer ? schema_inferrer : nil
163
- dsl = schema_dsl.new(name, inferrer, &block)
172
+ dsl = schema_dsl.new(name, schema_class: schema_class, inferrer: inferrer, &block)
164
173
 
165
174
  @schema = dsl.call
166
175
  end
167
176
  end
168
177
 
178
+ # Define a relation view with a specific header
179
+ #
180
+ # With headers defined all the mappers will be inferred automatically
181
+ #
182
+ # @example
183
+ # class Users < ROM::Relation[:sql]
184
+ # view(:by_name, [:id, :name]) do |name|
185
+ # where(name: name)
186
+ # end
187
+ #
188
+ # view(:listing, [:id, :name, :email]) do
189
+ # select(:id, :name, :email).order(:name)
190
+ # end
191
+ # end
192
+ #
193
+ # @api public
194
+ def view(*args, &block)
195
+ if args.size == 1 && block.arity > 0
196
+ raise ArgumentError, "header must be set as second argument"
197
+ end
198
+
199
+ name, new_schema_fn, relation_block =
200
+ if args.size == 1
201
+ ViewDSL.new(*args, schema, &block).call
202
+ else
203
+ [*args, block]
204
+ end
205
+
206
+ schemas[name] =
207
+ if args.size == 2
208
+ schema.project(*args[1])
209
+ else
210
+ new_schema_fn
211
+ end
212
+
213
+ if relation_block.arity > 0
214
+ auto_curry_guard do
215
+ define_method(name, &relation_block)
216
+
217
+ auto_curry(name) do
218
+ schemas[name].(self)
219
+ end
220
+ end
221
+ else
222
+ define_method(name) do
223
+ schemas[name].(instance_exec(&relation_block))
224
+ end
225
+ end
226
+ end
227
+
169
228
  # Dynamically define a method that will forward to the dataset and wrap
170
229
  # response in the relation itself
171
230
  #
@@ -179,7 +238,7 @@ module ROM
179
238
  methods.each do |method|
180
239
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
181
240
  def #{method}(*args, &block)
182
- __new__(dataset.__send__(:#{method}, *args, &block))
241
+ new(dataset.__send__(:#{method}, *args, &block))
183
242
  end
184
243
  RUBY
185
244
  end
@@ -203,7 +262,7 @@ module ROM
203
262
  # @api private
204
263
  def default_name
205
264
  return unless name
206
- Inflector.underscore(name).tr('/', '_').to_sym
265
+ Dry::Core::Inflector.underscore(name).tr('/', '_').to_sym
207
266
  end
208
267
 
209
268
  # @api private
@@ -214,16 +273,25 @@ module ROM
214
273
  # @api private
215
274
  def view_methods
216
275
  ancestor_methods = ancestors.reject { |klass| klass == self }
217
- .map(&:instance_methods).flatten
276
+ .map(&:instance_methods).flatten(1)
218
277
 
219
278
  instance_methods - ancestor_methods + auto_curried_methods
220
279
  end
221
280
 
281
+ # @api private
282
+ def schemas
283
+ @schemas ||= {}
284
+ end
285
+
222
286
  # Hook to finalize a relation after its instance was created
223
287
  #
224
288
  # @api private
225
- def finalize(_container, _relation)
226
- # noop
289
+ def finalize(_container, relation)
290
+ schemas = relation.schemas.reduce({}) do |h, (a, e)|
291
+ h.update(a => e.is_a?(Proc) ? instance_exec(&e) : e)
292
+ end
293
+ relation.schemas.update(schemas)
294
+ relation
227
295
  end
228
296
  end
229
297
  end
@@ -1,5 +1,5 @@
1
- require 'rom/support/options'
2
-
1
+ require 'rom/types'
2
+ require 'rom/initializer'
3
3
  require 'rom/pipeline'
4
4
  require 'rom/relation/name'
5
5
  require 'rom/relation/materializable'
@@ -7,23 +7,23 @@ require 'rom/relation/materializable'
7
7
  module ROM
8
8
  class Relation
9
9
  class Curried
10
- include Options
10
+ extend Initializer
11
11
  include Materializable
12
12
  include Pipeline
13
13
 
14
- option :name, type: Symbol
15
- option :arity, type: Integer, reader: true, default: -1
16
- option :curry_args, type: Array, reader: true, default: EMPTY_ARRAY
17
-
18
- attr_reader :relation
14
+ param :relation
19
15
 
20
- attr_reader :name
16
+ option :name, optional: true, type: Types::Strict::Symbol
17
+ option :arity, type: Types::Strict::Int, reader: true, default: proc { -1 }
18
+ option :curry_args, reader: true, default: proc { EMPTY_ARRAY }
21
19
 
22
- # @api private
23
- def initialize(relation, options = EMPTY_HASH)
24
- @relation = relation
25
- @name = relation.name.with(options[:name])
26
- super
20
+ # Relation name
21
+ #
22
+ # @return [ROM::Relation::Name]
23
+ #
24
+ # @api public
25
+ def name
26
+ @name == Dry::Initializer::UNDEFINED ? relation.name : relation.name.with(@name)
27
27
  end
28
28
 
29
29
  # Load relation if args match the arity
@@ -75,7 +75,7 @@ module ROM
75
75
 
76
76
  # @api private
77
77
  def __new__(relation, new_opts = EMPTY_HASH)
78
- Curried.new(relation, options.merge(new_opts))
78
+ self.class.new(relation, new_opts.empty? ? options : options.merge(new_opts))
79
79
  end
80
80
 
81
81
  # @api private
@@ -0,0 +1,31 @@
1
+ module ROM
2
+ class Relation
3
+ class ViewDSL
4
+ attr_reader :name
5
+
6
+ attr_reader :relation_block
7
+
8
+ attr_reader :new_schema
9
+
10
+ def initialize(name, schema, &block)
11
+ @name = name
12
+ @schema = schema
13
+ @new_schema = nil
14
+ @relation_block = nil
15
+ instance_eval(&block)
16
+ end
17
+
18
+ def schema(&block)
19
+ @new_schema = -> { @schema.instance_exec(&block) }
20
+ end
21
+
22
+ def relation(&block)
23
+ @relation_block = lambda(&block)
24
+ end
25
+
26
+ def call
27
+ [name, new_schema, relation_block]
28
+ end
29
+ end
30
+ end
31
+ end
data/lib/rom/relation.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'rom/initializer'
1
2
  require 'rom/relation/class_interface'
2
3
 
3
4
  require 'rom/pipeline'
@@ -29,17 +30,31 @@ module ROM
29
30
  #
30
31
  # @api public
31
32
  class Relation
33
+ extend Initializer
32
34
  extend ClassInterface
33
35
 
34
- include Options
35
36
  include Dry::Equalizer(:dataset)
36
37
  include Materializable
37
38
  include Pipeline
38
39
 
40
+ # @!attribute [r] dataset
41
+ # @return [Object] dataset used by the relation provided by relation's gateway
42
+ # @api public
43
+ param :dataset
44
+
39
45
  # @!attribute [r] mappers
40
46
  # @return [MapperRegistry] an optional mapper registry (empty by default)
41
47
  option :mappers, reader: true, default: proc { MapperRegistry.new }
42
48
 
49
+ # @!attribute [r] schema
50
+ # @return [Schema] relation schema, defaults to class-level canonical
51
+ # schema (if it was defined) and sets an empty one as
52
+ # the fallback
53
+ # @api public
54
+ option :schema, reader: true, optional: true, default: -> relation {
55
+ relation.class.schema || Schema.define(Dry::Core::Inflector.underscore(relation.class.name || EMPTY_STRING).to_sym)
56
+ }
57
+
43
58
  # @!attribute [r] schema_hash
44
59
  # @return [Object#[]] tuple processing function, uses schema or defaults to Hash[]
45
60
  # @api private
@@ -47,36 +62,13 @@ module ROM
47
62
  relation.schema? ? Types::Coercible::Hash.schema(relation.schema.to_h) : Hash
48
63
  }
49
64
 
50
- # @!attribute [r] associations
51
- # @return [AssociationSet] Schema's association set (empty by default)
52
- option :associations, reader: true, default: -> rel {
53
- rel.schema? ? rel.schema.associations : Schema::EMPTY_ASSOCIATION_SET
54
- }
55
-
56
- # @!attribute [r] dataset
57
- # @return [Object] dataset used by the relation provided by relation's gateway
58
- # @api public
59
- attr_reader :dataset
60
-
61
- # @!attribute [r] schema
62
- # @return [Schema] returns relation schema object (if defined)
63
- # @api public
64
- attr_reader :schema
65
-
66
- # Initializes a relation object
65
+ # Return schema attribute
67
66
  #
68
- # @param dataset [Object]
69
- #
70
- # @param options [Hash]
71
- # @option :mappers [MapperRegistry]
72
- # @option :schema_hash [#[]]
73
- # @option :associations [AssociationSet]
67
+ # @return [Schema::Type]
74
68
  #
75
69
  # @api public
76
- def initialize(dataset, options = EMPTY_HASH)
77
- @dataset = dataset
78
- @schema = self.class.schema
79
- super
70
+ def [](name)
71
+ schema[name]
80
72
  end
81
73
 
82
74
  # Yields relation tuples
@@ -143,7 +135,17 @@ module ROM
143
135
  #
144
136
  # @api private
145
137
  def schema?
146
- ! schema.nil?
138
+ ! schema.empty?
139
+ end
140
+
141
+ # Return a new relation with provided dataset and additional options
142
+ #
143
+ # @param [Object] dataset
144
+ # @param [Hash] new_opts Additional options
145
+ #
146
+ # @api public
147
+ def new(dataset, new_opts = EMPTY_HASH)
148
+ self.class.new(dataset, new_opts.empty? ? options : options.merge(new_opts))
147
149
  end
148
150
 
149
151
  # Returns a new instance with the same dataset but new options
@@ -154,16 +156,29 @@ module ROM
154
156
  #
155
157
  # @api private
156
158
  def with(new_options)
157
- __new__(dataset, options.merge(new_options))
159
+ new(dataset, options.merge(new_options))
158
160
  end
159
161
 
160
- private
162
+ # Return all registered relation schemas
163
+ #
164
+ # @return [Hash<Symbol=>Schema>]
165
+ #
166
+ # @api public
167
+ def schemas
168
+ @schemas ||= self.class.schemas
169
+ end
161
170
 
162
- # @api private
163
- def __new__(dataset, new_opts = EMPTY_HASH)
164
- self.class.new(dataset, options.merge(new_opts))
171
+ # Return schema's association set (empty by default)
172
+ #
173
+ # @return [AssociationSet] Schema's association set (empty by default)
174
+ #
175
+ # @api public
176
+ def associations
177
+ @associations ||= schema.associations
165
178
  end
166
179
 
180
+ private
181
+
167
182
  # @api private
168
183
  def composite_class
169
184
  Relation::Composite
@@ -13,19 +13,17 @@ module ROM
13
13
 
14
14
  # @api public
15
15
  class DSL < BasicObject
16
- attr_reader :name, :attributes, :inferrer
16
+ attr_reader :relation, :attributes, :inferrer, :schema_class
17
17
 
18
18
  # @api private
19
- def initialize(name, inferrer, &block)
20
- @name = name
19
+ def initialize(relation, schema_class: Schema, inferrer: Schema::DEFAULT_INFERRER, &block)
20
+ @relation = relation
21
21
  @inferrer = inferrer
22
- @attributes = nil
22
+ @schema_class = schema_class
23
+ @attributes = {}
23
24
 
24
25
  if block
25
26
  instance_exec(&block)
26
- elsif inferrer.nil?
27
- raise ArgumentError,
28
- 'You must pass a block to define a schema or set an inferrer for automatic inferring'
29
27
  end
30
28
  end
31
29
 
@@ -36,7 +34,7 @@ module ROM
36
34
  # @api public
37
35
  def attribute(name, type)
38
36
  @attributes ||= {}
39
- @attributes[name] = type.meta(name: name)
37
+ @attributes[name] = type.meta(name: name, source: relation)
40
38
  end
41
39
 
42
40
  # Specify which key(s) should be the primary key
@@ -51,7 +49,7 @@ module ROM
51
49
 
52
50
  # @api private
53
51
  def call
54
- Schema.new(name, attributes, inferrer: inferrer)
52
+ schema_class.define(relation, attributes: attributes.values, inferrer: inferrer)
55
53
  end
56
54
  end
57
55
  end
@@ -0,0 +1,115 @@
1
+ require 'delegate'
2
+ require 'dry/equalizer'
3
+ require 'dry/types/decorator'
4
+
5
+ module ROM
6
+ class Schema
7
+ class Type
8
+ include Dry::Equalizer(:type)
9
+
10
+ attr_reader :type
11
+
12
+ def initialize(type)
13
+ @type = type
14
+ end
15
+
16
+ # @api public
17
+ def primary_key?
18
+ meta[:primary_key].equal?(true)
19
+ end
20
+
21
+ # @api public
22
+ def foreign_key?
23
+ meta[:foreign_key].equal?(true)
24
+ end
25
+
26
+ # @api public
27
+ def aliased?
28
+ !meta[:alias].nil?
29
+ end
30
+
31
+ # @api public
32
+ def source
33
+ meta[:source]
34
+ end
35
+
36
+ # @api public
37
+ def target
38
+ meta[:target]
39
+ end
40
+
41
+ # @api public
42
+ def name
43
+ meta[:name]
44
+ end
45
+
46
+ # @api public
47
+ def alias
48
+ meta[:alias]
49
+ end
50
+
51
+ # @api public
52
+ def aliased(name)
53
+ meta(alias: name)
54
+ end
55
+ alias_method :as, :aliased
56
+
57
+ # @api public
58
+ def prefixed(prefix = source.dataset)
59
+ aliased(:"#{prefix}_#{name}")
60
+ end
61
+
62
+ # @api public
63
+ def wrapped?
64
+ meta[:wrapped].equal?(true)
65
+ end
66
+
67
+ # @api public
68
+ def wrapped(name = source.dataset)
69
+ self.class.new(prefixed(name).meta(wrapped: true))
70
+ end
71
+
72
+ # @api public
73
+ def meta(opts = nil)
74
+ if opts
75
+ self.class.new(type.meta(opts))
76
+ else
77
+ type.meta
78
+ end
79
+ end
80
+
81
+ # @api public
82
+ def inspect
83
+ %(#<#{self.class}[#{type.name}] #{meta.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}>)
84
+ end
85
+ alias_method :pretty_inspect, :inspect
86
+
87
+ # @api public
88
+ def eql?(other)
89
+ other.is_a?(self.class) ? super : type.eql?(other)
90
+ end
91
+
92
+ # @api private
93
+ def respond_to_missing?(name, include_private = false)
94
+ type.respond_to?(name) || super
95
+ end
96
+
97
+ private
98
+
99
+ # @api private
100
+ def method_missing(meth, *args, &block)
101
+ if type.respond_to?(meth)
102
+ response = type.__send__(meth, *args, &block)
103
+
104
+ if response.is_a?(type.class)
105
+ self.class.new(type)
106
+ else
107
+ response
108
+ end
109
+ else
110
+ super
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end