rom 2.0.2 → 3.0.0.beta1

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