rom 2.0.2 → 3.0.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.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -7
  3. data/.yardopts +2 -0
  4. data/CHANGELOG.md +35 -1
  5. data/Gemfile +17 -2
  6. data/Rakefile +7 -2
  7. data/lib/rom/array_dataset.rb +44 -0
  8. data/lib/rom/association_set.rb +11 -5
  9. data/lib/rom/auto_curry.rb +55 -0
  10. data/lib/rom/command.rb +331 -47
  11. data/lib/rom/command_registry.rb +7 -18
  12. data/lib/rom/commands/class_interface.rb +120 -6
  13. data/lib/rom/commands/composite.rb +0 -1
  14. data/lib/rom/commands/graph.rb +7 -15
  15. data/lib/rom/commands/lazy/update.rb +1 -1
  16. data/lib/rom/configuration.rb +2 -0
  17. data/lib/rom/configuration_dsl/command.rb +6 -8
  18. data/lib/rom/configuration_dsl/mapper.rb +2 -3
  19. data/lib/rom/configuration_dsl/mapper_dsl.rb +0 -1
  20. data/lib/rom/configuration_dsl/relation.rb +4 -4
  21. data/lib/rom/configuration_dsl.rb +0 -4
  22. data/lib/rom/constants.rb +7 -1
  23. data/lib/rom/container.rb +11 -17
  24. data/lib/rom/create_container.rb +0 -2
  25. data/lib/rom/data_proxy.rb +94 -0
  26. data/lib/rom/enumerable_dataset.rb +68 -0
  27. data/lib/rom/gateway.rb +74 -32
  28. data/lib/rom/global/plugin_dsl.rb +0 -2
  29. data/lib/rom/global.rb +0 -2
  30. data/lib/rom/initializer.rb +26 -0
  31. data/lib/rom/lint/gateway.rb +17 -0
  32. data/lib/rom/mapper_registry.rb +1 -1
  33. data/lib/rom/memory/commands.rb +0 -2
  34. data/lib/rom/memory/dataset.rb +1 -2
  35. data/lib/rom/memory/relation.rb +14 -1
  36. data/lib/rom/memory/schema.rb +13 -0
  37. data/lib/rom/plugin_registry.rb +1 -1
  38. data/lib/rom/plugins/command/schema.rb +2 -2
  39. data/lib/rom/plugins/configuration/configuration_dsl.rb +6 -2
  40. data/lib/rom/plugins/relation/key_inference.rb +4 -2
  41. data/lib/rom/plugins/relation/registry_reader.rb +5 -1
  42. data/lib/rom/registry.rb +50 -0
  43. data/lib/rom/relation/class_interface.rb +142 -30
  44. data/lib/rom/relation/curried.rb +15 -15
  45. data/lib/rom/relation/view_dsl.rb +31 -0
  46. data/lib/rom/relation.rb +101 -41
  47. data/lib/rom/schema/attribute.rb +367 -0
  48. data/lib/rom/schema/dsl.rb +14 -10
  49. data/lib/rom/schema.rb +337 -19
  50. data/lib/rom/setup/auto_registration.rb +20 -17
  51. data/lib/rom/setup/auto_registration_strategies/base.rb +8 -3
  52. data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +4 -3
  53. data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +5 -4
  54. data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +3 -3
  55. data/lib/rom/setup/finalize/finalize_commands.rb +1 -1
  56. data/lib/rom/setup/finalize/finalize_mappers.rb +1 -1
  57. data/lib/rom/setup/finalize/finalize_relations.rb +4 -2
  58. data/lib/rom/setup/finalize.rb +1 -1
  59. data/lib/rom/transaction.rb +24 -0
  60. data/lib/rom/types.rb +9 -1
  61. data/lib/rom/version.rb +1 -1
  62. data/lib/rom.rb +4 -8
  63. data/rom.gemspec +5 -4
  64. data/spec/integration/command_registry_spec.rb +1 -14
  65. data/spec/integration/commands/create_spec.rb +5 -25
  66. data/spec/integration/commands/delete_spec.rb +1 -1
  67. data/spec/integration/commands/error_handling_spec.rb +1 -1
  68. data/spec/integration/commands/graph_spec.rb +20 -14
  69. data/spec/integration/commands/update_spec.rb +4 -27
  70. data/spec/integration/commands_spec.rb +1 -1
  71. data/spec/integration/{repositories → gateways}/extending_relations_spec.rb +1 -1
  72. data/spec/integration/{repositories → gateways}/setting_logger_spec.rb +2 -2
  73. data/spec/integration/mappers/combine_spec.rb +1 -1
  74. data/spec/integration/mappers/deep_embedded_spec.rb +1 -1
  75. data/spec/integration/mappers/definition_dsl_spec.rb +1 -1
  76. data/spec/integration/mappers/embedded_spec.rb +1 -1
  77. data/spec/integration/mappers/exclude_spec.rb +1 -1
  78. data/spec/integration/mappers/fold_spec.rb +1 -1
  79. data/spec/integration/mappers/group_spec.rb +1 -1
  80. data/spec/integration/mappers/overwrite_attributes_value_spec.rb +1 -1
  81. data/spec/integration/mappers/prefix_separator_spec.rb +1 -1
  82. data/spec/integration/mappers/prefix_spec.rb +1 -1
  83. data/spec/integration/mappers/prefixing_attributes_spec.rb +1 -1
  84. data/spec/integration/mappers/registering_custom_mappers_spec.rb +1 -1
  85. data/spec/integration/mappers/renaming_attributes_spec.rb +1 -1
  86. data/spec/integration/mappers/reusing_mappers_spec.rb +1 -1
  87. data/spec/integration/mappers/step_spec.rb +1 -1
  88. data/spec/integration/mappers/symbolizing_attributes_spec.rb +1 -1
  89. data/spec/integration/mappers/unfold_spec.rb +1 -1
  90. data/spec/integration/mappers/ungroup_spec.rb +2 -2
  91. data/spec/integration/mappers/unwrap_spec.rb +2 -2
  92. data/spec/integration/mappers/wrap_spec.rb +1 -1
  93. data/spec/integration/memory/commands/create_spec.rb +1 -1
  94. data/spec/integration/memory/commands/delete_spec.rb +1 -1
  95. data/spec/integration/memory/commands/update_spec.rb +1 -1
  96. data/spec/integration/multi_env_spec.rb +1 -1
  97. data/spec/integration/multi_repo_spec.rb +1 -1
  98. data/spec/integration/relations/default_dataset_spec.rb +1 -1
  99. data/spec/integration/relations/reading_spec.rb +1 -1
  100. data/spec/integration/relations/registry_dsl_spec.rb +1 -1
  101. data/spec/integration/setup_spec.rb +10 -4
  102. data/spec/shared/command_graph.rb +8 -4
  103. data/spec/shared/enumerable_dataset.rb +1 -1
  104. data/spec/spec_helper.rb +7 -9
  105. data/spec/support/schema.rb +14 -0
  106. data/spec/unit/rom/array_dataset_spec.rb +59 -0
  107. data/spec/unit/rom/association_set_spec.rb +4 -0
  108. data/spec/unit/rom/auto_curry_spec.rb +63 -0
  109. data/spec/unit/rom/commands/graph_spec.rb +12 -11
  110. data/spec/unit/rom/commands/lazy_spec.rb +8 -5
  111. data/spec/unit/rom/commands/pre_and_post_processors_spec.rb +336 -0
  112. data/spec/unit/rom/commands/result_spec.rb +1 -1
  113. data/spec/unit/rom/commands_spec.rb +26 -3
  114. data/spec/unit/rom/configuration_spec.rb +1 -1
  115. data/spec/unit/rom/container_spec.rb +15 -5
  116. data/spec/unit/rom/create_container_spec.rb +1 -1
  117. data/spec/unit/rom/enumerable_dataset_spec.rb +15 -0
  118. data/spec/unit/rom/gateway_spec.rb +1 -1
  119. data/spec/unit/rom/mapper_registry_spec.rb +1 -1
  120. data/spec/unit/rom/memory/commands_spec.rb +1 -1
  121. data/spec/unit/rom/memory/dataset_spec.rb +1 -1
  122. data/spec/unit/rom/memory/{repository_spec.rb → gateway_spec.rb} +1 -1
  123. data/spec/unit/rom/memory/inheritance_spec.rb +32 -0
  124. data/spec/unit/rom/memory/relation_spec.rb +15 -3
  125. data/spec/unit/rom/memory/storage_spec.rb +1 -1
  126. data/spec/unit/rom/plugin_spec.rb +1 -1
  127. data/spec/unit/rom/plugins/command/schema_spec.rb +5 -5
  128. data/spec/unit/rom/plugins/relation/key_inference_spec.rb +1 -1
  129. data/spec/unit/rom/registry_spec.rb +86 -0
  130. data/spec/unit/rom/relation/attribute_reader_spec.rb +17 -0
  131. data/spec/unit/rom/relation/call_spec.rb +51 -0
  132. data/spec/unit/rom/relation/composite_spec.rb +1 -1
  133. data/spec/unit/rom/relation/graph_spec.rb +1 -1
  134. data/spec/unit/rom/relation/lazy/combine_spec.rb +1 -1
  135. data/spec/unit/rom/relation/lazy_spec.rb +1 -1
  136. data/spec/unit/rom/relation/loaded_spec.rb +1 -1
  137. data/spec/unit/rom/relation/schema_spec.rb +50 -6
  138. data/spec/unit/rom/relation/view_spec.rb +122 -0
  139. data/spec/unit/rom/relation_spec.rb +20 -5
  140. data/spec/unit/rom/schema/accessing_attributes_spec.rb +52 -0
  141. data/spec/unit/rom/schema/append_spec.rb +17 -0
  142. data/spec/unit/rom/schema/exclude_spec.rb +15 -0
  143. data/spec/unit/rom/schema/finalize_spec.rb +59 -0
  144. data/spec/unit/rom/schema/key_predicate_spec.rb +15 -0
  145. data/spec/unit/rom/schema/merge_spec.rb +17 -0
  146. data/spec/unit/rom/schema/prefix_spec.rb +16 -0
  147. data/spec/unit/rom/schema/project_spec.rb +15 -0
  148. data/spec/unit/rom/schema/rename_spec.rb +22 -0
  149. data/spec/unit/rom/schema/type_spec.rb +49 -0
  150. data/spec/unit/rom/schema/uniq_spec.rb +21 -0
  151. data/spec/unit/rom/schema/wrap_spec.rb +17 -0
  152. data/spec/unit/rom/schema_spec.rb +2 -2
  153. metadata +79 -17
  154. data/lib/rom/plugins/relation/view/dsl.rb +0 -32
  155. data/lib/rom/plugins/relation/view.rb +0 -95
  156. data/spec/unit/rom/plugins/relation/view_spec.rb +0 -51
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'
@@ -17,82 +18,97 @@ module ROM
17
18
  # Base relation class
18
19
  #
19
20
  # Relation is a proxy for the dataset object provided by the gateway. It
20
- # forwards every method to the dataset, which is why the "native" interface of
21
+ # can forward methods to the dataset, which is why the "native" interface of
21
22
  # the underlying gateway is available in the relation. This interface,
22
23
  # however, is considered private and should not be used outside of the
23
24
  # relation instance.
24
25
  #
25
- # ROM builds sub-classes of this class for every relation defined in the
26
- # environment for easy inspection and extensibility - every gateway can
27
- # provide extensions for those sub-classes but there is always a vanilla
28
- # relation instance stored in the schema registry.
26
+ # Individual adapters sets up their relation classes and provide different APIs
27
+ # depending on their persistence backend.
28
+ #
29
+ # Vanilla Relation class doesn't have APIs that are specific to ROM container setup.
30
+ # When adapter Relation class inherits from this class, these APIs are added automatically,
31
+ # so that they can be registered within a container.
32
+ #
33
+ # @see ROM::Relation::ClassInterface
29
34
  #
30
35
  # @api public
31
36
  class Relation
37
+ # Default no-op output schema which is called in `Relation#each`
38
+ NOOP_OUTPUT_SCHEMA = -> tuple { tuple }.freeze
39
+
40
+ extend Initializer
32
41
  extend ClassInterface
33
42
 
34
- include Options
35
43
  include Dry::Equalizer(:dataset)
36
44
  include Materializable
37
45
  include Pipeline
38
46
 
47
+ # @!attribute [r] dataset
48
+ # @return [Object] dataset used by the relation provided by relation's gateway
49
+ # @api public
50
+ param :dataset
51
+
39
52
  # @!attribute [r] mappers
40
53
  # @return [MapperRegistry] an optional mapper registry (empty by default)
41
54
  option :mappers, reader: true, default: proc { MapperRegistry.new }
42
55
 
43
- # @!attribute [r] schema_hash
56
+ # @!attribute [r] schema
57
+ # @return [Schema] relation schema, defaults to class-level canonical
58
+ # schema (if it was defined) and sets an empty one as
59
+ # the fallback
60
+ # @api public
61
+ option :schema, reader: true, optional: true, default: method(:default_schema).to_proc
62
+
63
+ # @!attribute [r] input_schema
44
64
  # @return [Object#[]] tuple processing function, uses schema or defaults to Hash[]
45
65
  # @api private
46
- option :schema_hash, reader: true, default: -> relation {
47
- relation.schema? ? Types::Coercible::Hash.schema(relation.schema.to_h) : Hash
66
+ option :input_schema, reader: true, default: -> relation {
67
+ relation.schema? ? schema.to_input_hash : Hash
48
68
  }
49
69
 
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
70
+ # @!attribute [r] output_schema
71
+ # @return [Object#[]] tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA
72
+ # @api private
73
+ option :output_schema, reader: true, optional: true, default: -> relation {
74
+ relation.schema.any?(&:read?) ? schema.to_output_hash : NOOP_OUTPUT_SCHEMA
54
75
  }
55
76
 
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
77
+ # Return schema attribute
78
+ #
79
+ # @example accessing canonical attribute
80
+ # users[:id]
81
+ # # => #<ROM::SQL::Attribute[Integer] primary_key=true name=:id source=ROM::Relation::Name(users)>
67
82
  #
68
- # @param dataset [Object]
83
+ # @example accessing joined attribute
84
+ # tasks_with_users = tasks.join(users).select_append(tasks[:title])
85
+ # tasks_with_users[:title, :tasks]
86
+ # # => #<ROM::SQL::Attribute[String] primary_key=false name=:title source=ROM::Relation::Name(tasks)>
69
87
  #
70
- # @param options [Hash]
71
- # @option :mappers [MapperRegistry]
72
- # @option :schema_hash [#[]]
73
- # @option :associations [AssociationSet]
88
+ # @return [Schema::Attribute]
74
89
  #
75
90
  # @api public
76
- def initialize(dataset, options = EMPTY_HASH)
77
- @dataset = dataset
78
- @schema = self.class.schema
79
- super
91
+ def [](name)
92
+ schema[name]
80
93
  end
81
94
 
82
95
  # Yields relation tuples
83
96
  #
97
+ # Every tuple is processed through Relation#output_schema, it's a no-op by default
98
+ #
84
99
  # @yield [Hash]
100
+ #
85
101
  # @return [Enumerator] if block is not provided
86
102
  #
87
103
  # @api public
88
104
  def each(&block)
89
105
  return to_enum unless block
90
- dataset.each { |tuple| yield(tuple) }
106
+ dataset.each { |tuple| yield(output_schema[tuple]) }
91
107
  end
92
108
 
93
109
  # Composes with other relations
94
110
  #
95
- # @param *others [Array<Relation>] The other relation(s) to compose with
111
+ # @param [Array<Relation>] others The other relation(s) to compose with
96
112
  #
97
113
  # @return [Relation::Graph]
98
114
  #
@@ -143,27 +159,71 @@ module ROM
143
159
  #
144
160
  # @api private
145
161
  def schema?
146
- ! schema.nil?
162
+ ! schema.empty?
163
+ end
164
+
165
+ # Return a new relation with provided dataset and additional options
166
+ #
167
+ # Use this method whenever you need to use dataset API to get a new dataset
168
+ # and you want to return a relation back. Typically relation API should be
169
+ # enough though. If you find yourself using this method, it might be worth
170
+ # to consider reporting an issue that some dataset functionality is not available
171
+ # through relation API.
172
+ #
173
+ # @example with a new dataset
174
+ # users.new(users.dataset.some_method)
175
+ #
176
+ # @example with a new dataset and options
177
+ # users.new(users.dataset.some_method, other: 'options')
178
+ #
179
+ # @param [Object] dataset
180
+ # @param [Hash] new_opts Additional options
181
+ #
182
+ # @api public
183
+ def new(dataset, new_opts = EMPTY_HASH)
184
+ self.class.new(dataset, new_opts.empty? ? options : options.merge(new_opts))
147
185
  end
148
186
 
149
187
  # Returns a new instance with the same dataset but new options
150
188
  #
189
+ # @example
190
+ # users.with(output_schema: -> tuple { .. })
191
+ #
151
192
  # @param new_options [Hash]
152
193
  #
153
194
  # @return [Relation]
154
195
  #
155
196
  # @api private
156
197
  def with(new_options)
157
- __new__(dataset, options.merge(new_options))
198
+ new(dataset, options.merge(new_options))
158
199
  end
159
200
 
160
- private
201
+ # Return all registered relation schemas
202
+ #
203
+ # This holds all schemas defined via `view` DSL
204
+ #
205
+ # @return [Hash<Symbol=>Schema>]
206
+ #
207
+ # @api public
208
+ def schemas
209
+ @schemas ||= self.class.schemas
210
+ end
161
211
 
162
- # @api private
163
- def __new__(dataset, new_opts = EMPTY_HASH)
164
- self.class.new(dataset, options.merge(new_opts))
212
+ # Return schema's association set (empty by default)
213
+ #
214
+ # @return [AssociationSet] Schema's association set (empty by default)
215
+ #
216
+ # @api public
217
+ def associations
218
+ @associations ||= schema.associations
165
219
  end
166
220
 
221
+ private
222
+
223
+ # Hook used by `Pipeline` to get the class that should be used for composition
224
+ #
225
+ # @return [Class]
226
+ #
167
227
  # @api private
168
228
  def composite_class
169
229
  Relation::Composite
@@ -0,0 +1,367 @@
1
+ require 'delegate'
2
+ require 'dry/equalizer'
3
+ require 'dry/types/decorator'
4
+
5
+ module ROM
6
+ class Schema
7
+ # Schema attributes provide meta information about types and an API
8
+ # for additional operations. This class can be extended by adapters to provide
9
+ # database-specific features. In example rom-sql provides SQL::Attribute
10
+ # with more features like creating SQL expressions for queries.
11
+ #
12
+ # Schema attributes are accessible through canonical relation schemas and
13
+ # instance-level schemas.
14
+ #
15
+ # @api public
16
+ class Attribute
17
+ include Dry::Equalizer(:type)
18
+
19
+ # !@attribute [r] type
20
+ # @return [Dry::Types::Definition, Dry::Types::Sum, Dry::Types::Constrained]
21
+ attr_reader :type
22
+
23
+ # @api private
24
+ def initialize(type)
25
+ @type = type
26
+ end
27
+
28
+ # @api private
29
+ def [](input)
30
+ type[input]
31
+ end
32
+
33
+ # Return true if this attribute type is a primary key
34
+ #
35
+ # @example
36
+ # class Users < ROM::Relation[:memory]
37
+ # schema do
38
+ # attribute :id, Types::Int
39
+ # attribute :name, Types::String
40
+ #
41
+ # primary_key :id
42
+ # end
43
+ # end
44
+ #
45
+ # Users.schema[:id].primary_key?
46
+ # # => true
47
+ #
48
+ # Users.schema[:name].primary_key?
49
+ # # => false
50
+ #
51
+ # @return [TrueClass,FalseClass]
52
+ #
53
+ # @api public
54
+ def primary_key?
55
+ meta[:primary_key].equal?(true)
56
+ end
57
+
58
+ # Return true if this attribute type is a foreign key
59
+ #
60
+ # @example
61
+ # class Tasks < ROM::Relation[:memory]
62
+ # schema do
63
+ # attribute :id, Types::Int
64
+ # attribute :user_id, Types.ForeignKey(:users)
65
+ # end
66
+ # end
67
+ #
68
+ # Users.schema[:user_id].foreign_key?
69
+ # # => true
70
+ #
71
+ # Users.schema[:id].foreign_key?
72
+ # # => false
73
+ #
74
+ # @return [TrueClass,FalseClass]
75
+ #
76
+ # @api public
77
+ def foreign_key?
78
+ meta[:foreign_key].equal?(true)
79
+ end
80
+
81
+ # Return true if this attribute type is a foreign key
82
+ #
83
+ # @example
84
+ # class Tasks < ROM::Relation[:memory]
85
+ # schema do
86
+ # attribute :user_id, Types::Int.meta(alias: :id)
87
+ # attribute :name, Types::String
88
+ # end
89
+ # end
90
+ #
91
+ # Users.schema[:user_id].aliased?
92
+ # # => true
93
+ #
94
+ # Users.schema[:name].aliased?
95
+ # # => false
96
+ #
97
+ # @return [TrueClass,FalseClass]
98
+ #
99
+ # @api public
100
+ def aliased?
101
+ !meta[:alias].nil?
102
+ end
103
+
104
+ # Return source relation of this attribute type
105
+ #
106
+ # @example
107
+ # class Tasks < ROM::Relation[:memory]
108
+ # schema do
109
+ # attribute :id, Types::Int
110
+ # attribute :user_id, Types.ForeignKey(:users)
111
+ # end
112
+ # end
113
+ #
114
+ # Users.schema[:id].source
115
+ # # => :tasks
116
+ #
117
+ # Users.schema[:user_id].source
118
+ # # => :tasks
119
+ #
120
+ # @return [Symbol, Relation::Name]
121
+ #
122
+ # @api public
123
+ def source
124
+ meta[:source]
125
+ end
126
+
127
+ # Return target relation of this attribute type
128
+ #
129
+ # @example
130
+ # class Tasks < ROM::Relation[:memory]
131
+ # schema do
132
+ # attribute :id, Types::Int
133
+ # attribute :user_id, Types.ForeignKey(:users)
134
+ # end
135
+ # end
136
+ #
137
+ # Users.schema[:id].target
138
+ # # => nil
139
+ #
140
+ # Users.schema[:user_id].target
141
+ # # => :users
142
+ #
143
+ # @return [NilClass, Symbol, Relation::Name]
144
+ #
145
+ # @api public
146
+ def target
147
+ meta[:target]
148
+ end
149
+
150
+ # Return the canonical name of this attribute name
151
+ #
152
+ # This *always* returns the name that is used in the datastore, even when
153
+ # an attribute is aliased
154
+ #
155
+ # @example
156
+ # class Tasks < ROM::Relation[:memory]
157
+ # schema do
158
+ # attribute :user_id, Types::Int.meta(alias: :id)
159
+ # attribute :name, Types::String
160
+ # end
161
+ # end
162
+ #
163
+ # Users.schema[:id].name
164
+ # # => :id
165
+ #
166
+ # Users.schema[:user_id].name
167
+ # # => :user_id
168
+ #
169
+ # @return [Symbol]
170
+ #
171
+ # @api public
172
+ def name
173
+ meta[:name]
174
+ end
175
+
176
+ # Return attribute's alias
177
+ #
178
+ # @example
179
+ # class Tasks < ROM::Relation[:memory]
180
+ # schema do
181
+ # attribute :user_id, Types::Int.meta(alias: :id)
182
+ # attribute :name, Types::String
183
+ # end
184
+ # end
185
+ #
186
+ # Users.schema[:user_id].alias
187
+ # # => :user_id
188
+ #
189
+ # Users.schema[:name].alias
190
+ # # => nil
191
+ #
192
+ # @return [NilClass,Symbol]
193
+ #
194
+ # @api public
195
+ def alias
196
+ meta[:alias]
197
+ end
198
+
199
+ # Return new attribute type with provided alias
200
+ #
201
+ # @example
202
+ # class Tasks < ROM::Relation[:memory]
203
+ # schema do
204
+ # attribute :user_id, Types::Int
205
+ # attribute :name, Types::String
206
+ # end
207
+ # end
208
+ #
209
+ # aliased_user_id = Users.schema[:user_id].aliased(:id)
210
+ #
211
+ # aliased_user_id.aliased?
212
+ # # => true
213
+ #
214
+ # aliased_user_id.name
215
+ # # => :user_id
216
+ #
217
+ # aliased_user_id.alias
218
+ # # => :id
219
+ #
220
+ # @param [Symbol] name The alias
221
+ #
222
+ # @return [Schema::Attribute]
223
+ #
224
+ # @api public
225
+ def aliased(name)
226
+ meta(alias: name)
227
+ end
228
+ alias_method :as, :aliased
229
+
230
+ # Return new attribute type with an alias using provided prefix
231
+ #
232
+ # @example
233
+ # class Users < ROM::Relation[:memory]
234
+ # schema do
235
+ # attribute :id, Types::Int
236
+ # attribute :name, Types::String
237
+ # end
238
+ # end
239
+ #
240
+ # prefixed_id = Users.schema[:id].prefixed
241
+ #
242
+ # prefixed_id.aliased?
243
+ # # => true
244
+ #
245
+ # prefixed_id.name
246
+ # # => :id
247
+ #
248
+ # prefixed_id.alias
249
+ # # => :users_id
250
+ #
251
+ # prefixed_id = Users.schema[:id].prefixed(:user)
252
+ #
253
+ # prefixed_id.alias
254
+ # # => :user_id
255
+ #
256
+ # @param [Symbol] prefix The prefix (defaults to source.dataset)
257
+ #
258
+ # @return [Schema::Attribute]
259
+ #
260
+ # @api public
261
+ def prefixed(prefix = source.dataset)
262
+ aliased(:"#{prefix}_#{name}")
263
+ end
264
+
265
+ # Return if the attribute type is from a wrapped relation
266
+ #
267
+ # Wrapped attributes are used when two schemas from different relations
268
+ # are merged together. This way we can identify them easily and handle
269
+ # correctly in places like auto-mapping.
270
+ #
271
+ # @api public
272
+ def wrapped?
273
+ meta[:wrapped].equal?(true)
274
+ end
275
+
276
+ # Return attribute type wrapped for the specified relation name
277
+ #
278
+ # @param [Symbol] name The name of the source relation (defaults to source.dataset)
279
+ #
280
+ # @return [Schema::Attribute]
281
+ #
282
+ # @api public
283
+ def wrapped(name = source.dataset)
284
+ self.class.new(prefixed(name).meta(wrapped: true))
285
+ end
286
+
287
+ # Return attribute type with additional meta information
288
+ #
289
+ # Return meta information hash if no opts are provided
290
+ #
291
+ # @param [Hash] opts The meta options
292
+ #
293
+ # @return [Schema::Attribute]
294
+ #
295
+ # @api public
296
+ def meta(opts = nil)
297
+ if opts
298
+ self.class.new(type.meta(opts))
299
+ else
300
+ type.meta
301
+ end
302
+ end
303
+
304
+ # Return string representation of the attribute type
305
+ #
306
+ # @return [String]
307
+ #
308
+ # @api public
309
+ def inspect
310
+ %(#<#{self.class}[#{type.name}] #{meta.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}>)
311
+ end
312
+ alias_method :pretty_inspect, :inspect
313
+
314
+ # Check if the attribute type is equal to another
315
+ #
316
+ # @param [Dry::Type, Schema::Attribute]
317
+ #
318
+ # @return [TrueClass,FalseClass]
319
+ #
320
+ # @api public
321
+ def eql?(other)
322
+ other.is_a?(self.class) ? super : type.eql?(other)
323
+ end
324
+
325
+ # Return if this attribute type has additional attribute type for reading
326
+ # tuple values
327
+ #
328
+ # @return [TrueClass, FalseClass]
329
+ #
330
+ # @api private
331
+ def read?
332
+ ! meta[:read].nil?
333
+ end
334
+
335
+ # Return read type or self
336
+ #
337
+ # @return [Schema::Attribute]
338
+ #
339
+ # @api private
340
+ def to_read_type
341
+ read? ? meta[:read] : type
342
+ end
343
+
344
+ # @api private
345
+ def respond_to_missing?(name, include_private = false)
346
+ type.respond_to?(name) || super
347
+ end
348
+
349
+ private
350
+
351
+ # @api private
352
+ def method_missing(meth, *args, &block)
353
+ if type.respond_to?(meth)
354
+ response = type.__send__(meth, *args, &block)
355
+
356
+ if response.is_a?(type.class)
357
+ self.class.new(type)
358
+ else
359
+ response
360
+ end
361
+ else
362
+ super
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
@@ -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
 
@@ -34,9 +32,15 @@ module ROM
34
32
  # @see Relation.schema
35
33
  #
36
34
  # @api public
37
- def attribute(name, type)
35
+ def attribute(name, type, options = EMPTY_HASH)
38
36
  @attributes ||= {}
39
- @attributes[name] = type.meta(name: name)
37
+
38
+ @attributes[name] =
39
+ if options[:read]
40
+ type.meta(name: name, source: relation, read: options[:read])
41
+ else
42
+ type.meta(name: name, source: relation)
43
+ end
40
44
  end
41
45
 
42
46
  # Specify which key(s) should be the primary key
@@ -51,7 +55,7 @@ module ROM
51
55
 
52
56
  # @api private
53
57
  def call
54
- Schema.new(name, attributes, inferrer: inferrer)
58
+ schema_class.define(relation, attributes: attributes.values, inferrer: inferrer)
55
59
  end
56
60
  end
57
61
  end