rom 3.0.0.beta3 → 3.0.0.rc1

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
  SHA1:
3
- metadata.gz: 98cf29125e7e877f9eadf9603755d7bacf9603af
4
- data.tar.gz: 596ae65060eb04d7f52be2406908a4541b758c6e
3
+ metadata.gz: 3b8faaf70d24fe2d5212f777e8023dfc4b6a2e34
4
+ data.tar.gz: 4fe312831401a4a99c9837ef1948af386b558762
5
5
  SHA512:
6
- metadata.gz: 8cf082f7aa10bc96e0b186f8bf42b6d64fa3a703780cf763292f2393374009a5a26f5387b4b0df2381371d82cf49bdb210438c07fb0b9303a72d5065e3952fd8
7
- data.tar.gz: 864cd037917d3df2ded0efca14a7a27aa53023ce296532df70aa28f7cb5ad1b917b41b6ada18e6be6ec566f321cd6d4920d9f954096330e07e11ab89539f40b4
6
+ metadata.gz: e46223303dd3c701780526df9184c5c2c03cec5aee5a5cc3d3ba64ebd6e962619c2b04af8c309db4410d19bbf999e41c2ef813a667bccd5d5c8c468d69ff9fef
7
+ data.tar.gz: 09b1af7a72438045fc804872a8cfa63ac8d9c2c3add1123a71f6e6117d7f61c65e005c241e35757a25b3d2ec19b877f97516ba6bae74b973083e5f013d2bb21f
data/.travis.yml CHANGED
@@ -5,20 +5,17 @@ cache: bundler
5
5
  bundler_args: --without sql benchmarks console tools
6
6
  script: "bundle exec rake ci"
7
7
  after_success:
8
- - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
8
+ - '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
9
9
  rvm:
10
- - 2.2.6
11
- - 2.3.3
12
10
  - 2.4.0
11
+ - 2.3
12
+ - 2.2
13
13
  - rbx-3
14
- - jruby-9.1.5.0
14
+ - jruby
15
15
  env:
16
16
  global:
17
17
  - JRUBY_OPTS='--dev -J-Xmx1024M'
18
18
  - COVERAGE='true'
19
- matrix:
20
- allow_failures:
21
- - rvm: rbx-3
22
19
  notifications:
23
20
  webhooks:
24
21
  urls:
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --query '@api.text != "private"'
2
+ --embed-mixins
data/Gemfile CHANGED
@@ -2,6 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ gem 'rom-mapper', git: 'https://github.com/rom-rb/rom-mapper.git', branch: 'master'
6
+
5
7
  group :console do
6
8
  gem 'pry'
7
9
  gem 'pg', platforms: [:mri]
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ task :"spec:isolation" do
11
11
  end
12
12
  end
13
13
 
14
- if RUBY_ENGINE == 'jruby'
14
+ if RUBY_ENGINE != 'ruby'
15
15
  desc "Run CI tasks"
16
16
  task ci: [:spec, :lint]
17
17
  else
data/lib/rom/command.rb CHANGED
@@ -31,7 +31,7 @@ module ROM
31
31
  extend Dry::Core::ClassAttributes
32
32
  extend ClassInterface
33
33
 
34
- defines :adapter, :relation, :result, :input, :register_as, :restrictable, :before, :after
34
+ defines :adapter, :relation, :result, :input, :register_as, :restrictable
35
35
 
36
36
  # @attr_reader [Relation] relation The command's relation
37
37
  param :relation
@@ -7,6 +7,15 @@ module ROM
7
7
  # @private
8
8
  class Command
9
9
  module ClassInterface
10
+ # This hook sets up default class state
11
+ #
12
+ # @api private
13
+ def inherited(klass)
14
+ super
15
+ klass.instance_variable_set(:'@before', before ? before.dup : [])
16
+ klass.instance_variable_set(:'@after', before ? after.dup : [])
17
+ end
18
+
10
19
  # Return adapter specific sub-class based on the adapter identifier
11
20
  #
12
21
  # This is a syntax sugar to make things consistent
@@ -106,6 +115,110 @@ module ROM
106
115
  include(relation_methods_mod(relation.class))
107
116
  end
108
117
 
118
+ # Set before-execute hooks
119
+ #
120
+ # @overload before(hook)
121
+ # Set an before hook as a method name
122
+ #
123
+ # @example
124
+ # class CreateUser < ROM::Commands::Create[:sql]
125
+ # relation :users
126
+ # register_as :create
127
+ #
128
+ # before :my_hook
129
+ #
130
+ # def my_hook(tuple, *)
131
+ # puts "hook called#
132
+ # end
133
+ # end
134
+ #
135
+ # @overload before(hook_opts)
136
+ # Set an before hook as a method name with arguments
137
+ #
138
+ # @example
139
+ # class CreateUser < ROM::Commands::Create[:sql]
140
+ # relation :users
141
+ # register_as :create
142
+ #
143
+ # before my_hook: { arg1: 1, arg1: 2 }
144
+ #
145
+ # def my_hook(tuple, arg1:, arg2:)
146
+ # puts "hook called with args: #{arg1} and #{arg2}"
147
+ # end
148
+ # end
149
+ #
150
+ # @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
151
+ #
152
+ # @return [Array<Hash, Symbol>] A list of all configured before hooks
153
+ #
154
+ # @api public
155
+ def before(*hooks)
156
+ if hooks.size > 0
157
+ set_hooks(:before, hooks)
158
+ else
159
+ @before
160
+ end
161
+ end
162
+
163
+ # Set after-execute hooks
164
+ #
165
+ # @overload after(hook)
166
+ # Set an after hook as a method name
167
+ #
168
+ # @example
169
+ # class CreateUser < ROM::Commands::Create[:sql]
170
+ # relation :users
171
+ # register_as :create
172
+ #
173
+ # after :my_hook
174
+ #
175
+ # def my_hook(tuple, *)
176
+ # puts "hook called#
177
+ # end
178
+ # end
179
+ #
180
+ # @overload after(hook_opts)
181
+ # Set an after hook as a method name with arguments
182
+ #
183
+ # @example
184
+ # class CreateUser < ROM::Commands::Create[:sql]
185
+ # relation :users
186
+ # register_as :create
187
+ #
188
+ # after my_hook: { arg1: 1, arg1: 2 }
189
+ #
190
+ # def my_hook(tuple, arg1:, arg2:)
191
+ # puts "hook called with args: #{arg1} and #{arg2}"
192
+ # end
193
+ # end
194
+ #
195
+ # @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
196
+ #
197
+ # @return [Array<Hash, Symbol>] A list of all configured after hooks
198
+ #
199
+ # @api public
200
+ def after(*hooks)
201
+ if hooks.size > 0
202
+ set_hooks(:after, hooks)
203
+ else
204
+ @after
205
+ end
206
+ end
207
+
208
+ # Set new or more hooks
209
+ #
210
+ # @api private
211
+ def set_hooks(type, hooks)
212
+ ivar = :"@#{type}"
213
+ value = instance_variable_get(ivar)
214
+
215
+ if value.empty?
216
+ instance_variable_set(ivar, hooks)
217
+ else
218
+ value.concat(hooks)
219
+ end
220
+ end
221
+
109
222
  # Return default name of the command class based on its name
110
223
  #
111
224
  # During setup phase this is used by defalut as `register_as` option
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  require 'rom/environment'
2
4
  require 'rom/setup'
3
5
  require 'rom/configuration_dsl'
@@ -12,6 +12,7 @@ require 'rom/schema'
12
12
 
13
13
  module ROM
14
14
  class Relation
15
+ # @api public
15
16
  module ClassInterface
16
17
  include Dry::Core::Constants
17
18
 
@@ -185,20 +186,45 @@ module ROM
185
186
  end
186
187
  end
187
188
 
188
- # Define a relation view with a specific header
189
+ # Define a relation view with a specific schema
189
190
  #
190
- # With headers defined all the mappers will be inferred automatically
191
+ # Explicit relation views allow relation composition with auto-mapping
192
+ # in repositories. It's useful for cases like defining custom views
193
+ # for associations where relations (even from different databases) can
194
+ # be composed together and automatically mapped in memory to structs.
191
195
  #
192
- # @example
193
- # class Users < ROM::Relation[:sql]
194
- # view(:by_name, [:id, :name]) do |name|
195
- # where(name: name)
196
+ # @overload view(name, schema, &block)
197
+ # @example View with the canonical schema
198
+ # class Users < ROM::Relation[:sql]
199
+ # view(:listing, schema) do
200
+ # order(:name)
201
+ # end
196
202
  # end
197
203
  #
198
- # view(:listing, [:id, :name, :email]) do
199
- # select(:id, :name, :email).order(:name)
204
+ # @example View with a projected schema
205
+ # class Users < ROM::Relation[:sql]
206
+ # view(:listing, schema.project(:id, :name)) do
207
+ # order(:name)
208
+ # end
209
+ # end
210
+ #
211
+ # @overload view(name, &block)
212
+ # @example View with the canonical schema and arguments
213
+ # class Users < ROM::Relation[:sql]
214
+ # view(:by_name) do |name|
215
+ # where(name: name)
216
+ # end
200
217
  # end
201
- # end
218
+ #
219
+ # @example View with projected schema and arguments
220
+ # class Users < ROM::Relation[:sql]
221
+ # view(:by_name) do
222
+ # schema { project(:id, :name) }
223
+ # relation { |name| where(name: name) }
224
+ # end
225
+ # end
226
+ #
227
+ # @return [Symbol] view method name
202
228
  #
203
229
  # @api public
204
230
  def view(*args, &block)
@@ -233,6 +259,8 @@ module ROM
233
259
  schemas[name].(instance_exec(&relation_block))
234
260
  end
235
261
  end
262
+
263
+ name
236
264
  end
237
265
 
238
266
  # Dynamically define a method that will forward to the dataset and wrap
data/lib/rom/relation.rb CHANGED
@@ -71,7 +71,7 @@ module ROM
71
71
 
72
72
  # Return schema attribute
73
73
  #
74
- # @return [Schema::Type]
74
+ # @return [Schema::Attribute]
75
75
  #
76
76
  # @api public
77
77
  def [](name)
@@ -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
data/lib/rom/schema.rb CHANGED
@@ -1,15 +1,35 @@
1
1
  require 'dry-equalizer'
2
2
 
3
- require 'rom/schema/type'
3
+ require 'rom/schema/attribute'
4
4
  require 'rom/schema/dsl'
5
5
  require 'rom/association_set'
6
6
 
7
7
  module ROM
8
8
  # Relation schema
9
9
  #
10
+ # Schemas hold detailed information about relation tuples, including their
11
+ # primitive types (String, Integer, Hash, etc. or custom classes), as well as
12
+ # various meta information like primary/foreign key and literally any other
13
+ # information that a given database adapter may need.
14
+ #
15
+ # Adapters can extend this class and it can be used in adapter-specific relations.
16
+ # In example rom-sql extends schema with Association DSL and many additional
17
+ # SQL-specific APIs in schema types.
18
+ #
19
+ # Schemas are used for projecting canonical relations into other relations and
20
+ # every relation object maintains its schema. This means that we always have
21
+ # all information about relation tuples, even when a relation was projected and
22
+ # diverged from its canonical form.
23
+ #
24
+ # Furthermore schema attributes know their source relations, which makes it
25
+ # possible to merge schemas from multiple relations and maintain information
26
+ # about the source relations. In example when two relations are joined, their
27
+ # schemas are merged, and we know which attributes belong to which relation.
28
+ #
10
29
  # @api public
11
30
  class Schema
12
31
  EMPTY_ASSOCIATION_SET = AssociationSet.new(EMPTY_HASH).freeze
32
+
13
33
  DEFAULT_INFERRER = proc { [EMPTY_ARRAY, EMPTY_ARRAY].freeze }
14
34
 
15
35
  MissingAttributesError = Class.new(StandardError) do
@@ -45,20 +65,29 @@ module ROM
45
65
 
46
66
  alias_method :to_ary, :attributes
47
67
 
68
+ # Define a relation schema from plain rom types
69
+ #
70
+ # Resulting schema will decorate plain rom types with adapter-specific types
71
+ # By default `Schema::Attribute` will be used
72
+ #
73
+ # @param [Relation::Name, Symbol] name The schema name, typically ROM::Relation::Name
74
+ #
75
+ # @return [Schema]
76
+ #
48
77
  # @api public
49
- def self.define(name, type_class: Type, attributes: EMPTY_ARRAY, associations: EMPTY_ASSOCIATION_SET, inferrer: DEFAULT_INFERRER)
78
+ def self.define(name, attr_class: Attribute, attributes: EMPTY_ARRAY, associations: EMPTY_ASSOCIATION_SET, inferrer: DEFAULT_INFERRER)
50
79
  new(
51
80
  name,
52
- attributes: attributes(attributes, type_class),
81
+ attributes: attributes(attributes, attr_class),
53
82
  associations: associations,
54
83
  inferrer: inferrer,
55
- type_class: type_class
84
+ attr_class: attr_class
56
85
  )
57
86
  end
58
87
 
59
88
  # @api private
60
- def self.attributes(attributes, type_class)
61
- attributes.map { |type| type_class.new(type) }
89
+ def self.attributes(attributes, attr_class)
90
+ attributes.map { |type| attr_class.new(type) }
62
91
  end
63
92
 
64
93
  # @api private
@@ -90,18 +119,26 @@ module ROM
90
119
 
91
120
  # Iterate over schema's attributes
92
121
  #
93
- # @yield [Schema::Type]
122
+ # @yield [Schema::Attribute]
94
123
  #
95
124
  # @api public
96
125
  def each(&block)
97
126
  attributes.each(&block)
98
127
  end
99
128
 
129
+ # Check if schema has any attributes
130
+ #
131
+ # @return [TrueClass, FalseClass]
132
+ #
100
133
  # @api public
101
134
  def empty?
102
135
  attributes.size == 0
103
136
  end
104
137
 
138
+ # Coerce schema into a <AttributeName=>Attribute> Hash
139
+ #
140
+ # @return [Hash]
141
+ #
105
142
  # @api public
106
143
  def to_h
107
144
  each_with_object({}) { |attr, h| h[attr.name] = attr }
@@ -109,6 +146,11 @@ module ROM
109
146
 
110
147
  # Return attribute
111
148
  #
149
+ # @param [Symbol] key The attribute name
150
+ # @param [Symbol, Relation::Name] src The source relation (for merged schemas)
151
+ #
152
+ # @raise KeyError
153
+ #
112
154
  # @api public
113
155
  def [](key, src = name.to_sym)
114
156
  attr =
@@ -127,7 +169,7 @@ module ROM
127
169
 
128
170
  # Project a schema to include only specified attributes
129
171
  #
130
- # @param [*Array] names Attribute names
172
+ # @param [*Array<Symbol, Schema::Attribute>] names Attribute names
131
173
  #
132
174
  # @return [Schema]
133
175
  #
@@ -174,6 +216,15 @@ module ROM
174
216
  new(map { |attr| attr.prefixed(prefix) })
175
217
  end
176
218
 
219
+ # Return new schema with all attributes marked as prefixed and wrapped
220
+ #
221
+ # This is useful when relations are joined and the right side should be marked
222
+ # as wrapped
223
+ #
224
+ # @param [Symbol] prefix The prefix used for aliasing wrapped attributes
225
+ #
226
+ # @return [Schema]
227
+ #
177
228
  # @api public
178
229
  def wrap(prefix = name.dataset)
179
230
  new(map { |attr| attr.wrapped(prefix) })
@@ -181,7 +232,7 @@ module ROM
181
232
 
182
233
  # Return FK attribute for a given relation name
183
234
  #
184
- # @return [Dry::Types::Definition]
235
+ # @return [Schema::Attribute]
185
236
  #
186
237
  # @api public
187
238
  def foreign_key(relation)
@@ -190,7 +241,7 @@ module ROM
190
241
 
191
242
  # Return primary key attributes
192
243
  #
193
- # @return [Array<Schema::Type>]
244
+ # @return [Array<Schema::Attribute>]
194
245
  #
195
246
  # @api public
196
247
  def primary_key
@@ -211,7 +262,9 @@ module ROM
211
262
 
212
263
  # Append more attributes to the schema
213
264
  #
214
- # @param [*Array<Schema::Type>]
265
+ # This returns a new schema instance
266
+ #
267
+ # @param [*Array<Schema::Attribute>]
215
268
  #
216
269
  # @return [Schema]
217
270
  #
@@ -222,7 +275,7 @@ module ROM
222
275
 
223
276
  # Return a new schema with uniq attributes
224
277
  #
225
- # @param [*Array<Schema::Type>]
278
+ # @param [*Array<Schema::Attribute>]
226
279
  #
227
280
  # @return [Schema]
228
281
  #
@@ -260,7 +313,7 @@ module ROM
260
313
  inferred, missing = inferrer.call(name, gateway)
261
314
 
262
315
  attr_names = map(&:name)
263
- inferred_attrs = self.class.attributes(inferred, type_class).
316
+ inferred_attrs = self.class.attributes(inferred, attr_class).
264
317
  reject { |attr| attr_names.include?(attr.name) }
265
318
 
266
319
  attributes.concat(inferred_attrs)
@@ -282,6 +335,12 @@ module ROM
282
335
  freeze
283
336
  end
284
337
 
338
+ # Return coercion function using attribute read types
339
+ #
340
+ # This is used for `output_schema` in relations
341
+ #
342
+ # @return [Dry::Types::Hash]
343
+ #
285
344
  # @api private
286
345
  def to_output_hash
287
346
  Types::Coercible::Hash.schema(
@@ -289,6 +348,13 @@ module ROM
289
348
  )
290
349
  end
291
350
 
351
+ # Return coercion function using attribute types
352
+ #
353
+ # This is used for `input_schema` in relations, typically commands use it
354
+ # for processing input
355
+ #
356
+ # @return [Dry::Types::Hash]
357
+ #
292
358
  # @api private
293
359
  def to_input_hash
294
360
  Types::Coercible::Hash.schema(
@@ -317,8 +383,8 @@ module ROM
317
383
  end
318
384
 
319
385
  # @api private
320
- def type_class
321
- options.fetch(:type_class)
386
+ def attr_class
387
+ options.fetch(:attr_class)
322
388
  end
323
389
 
324
390
  # @api private
data/lib/rom/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ROM
2
- VERSION = '3.0.0.beta3'.freeze
2
+ VERSION = '3.0.0.rc1'.freeze
3
3
  end
data/rom.gemspec CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
19
19
  gem.add_runtime_dependency 'dry-equalizer', '~> 0.2'
20
- gem.add_runtime_dependency 'dry-types', '~> 0.8'
20
+ gem.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.4'
21
21
  gem.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
22
22
  gem.add_runtime_dependency 'dry-initializer', '~> 0.10', '>= 0.10.2'
23
- gem.add_runtime_dependency 'rom-mapper', '~> 0.5.0.beta'
23
+ gem.add_runtime_dependency 'rom-mapper', '~> 0.5.0.rc'
24
24
 
25
25
  gem.add_development_dependency 'rake', '~> 10.3'
26
26
  gem.add_development_dependency 'rspec', '~> 3.5'
@@ -1,49 +1,58 @@
1
1
  require 'rom/command'
2
+ require 'rom/memory'
2
3
 
3
- RSpec.describe ROM::Command, 'before/after hooks' do
4
+ RSpec.describe ROM::Commands::Create[:memory], 'before/after hooks' do
4
5
  let(:relation) do
5
6
  spy(:relation)
6
7
  end
7
8
 
8
9
  describe '#before' do
9
10
  subject(:command) do
10
- Class.new(ROM::Command) do
11
+ Class.new(ROM::Commands::Create[:memory]) do
11
12
  before :init
13
+ before :normalize
12
14
 
13
15
  def init(*)
14
16
  end
15
17
 
18
+ def normalize(*)
19
+ end
20
+
16
21
  def prepare(*)
17
22
  end
18
23
  end.build(relation)
19
24
  end
20
25
 
21
26
  it 'returns a new command with configured before hooks' do
22
- expect(command.before(:prepare).before_hooks).to eql(%i[init prepare])
27
+ expect(command.before(:prepare).before_hooks).to eql(%i[init normalize prepare])
23
28
  end
24
29
  end
25
30
 
26
31
  describe '#after' do
27
32
  subject(:command) do
28
- Class.new(ROM::Command) do
33
+ Class.new(ROM::Commands::Create[:memory]) do
29
34
  after :finalize
35
+ after :filter
30
36
 
31
37
  def finalize(*)
32
38
  end
33
39
 
40
+ def filter(*)
41
+ end
42
+
34
43
  def prepare(*)
35
44
  end
36
45
  end.build(relation)
37
46
  end
38
47
 
39
48
  it 'returns a new command with configured after hooks' do
40
- expect(command.after(:prepare).after_hooks).to eql(%i[finalize prepare])
49
+ expect(command.after(:prepare).after_hooks).to eql(%i[finalize filter prepare])
41
50
  end
42
51
  end
43
52
 
44
53
  context 'without curried args' do
45
54
  subject(:command) do
46
- Class.new(ROM::Command) do
55
+ Class.new(ROM::Commands::Create[:memory]) do
47
56
  result :many
48
57
  before :prepare
49
58
  after :finalize
@@ -87,7 +96,7 @@ RSpec.describe ROM::Command, 'before/after hooks' do
87
96
 
88
97
  context 'with curried args' do
89
98
  subject(:command) do
90
- Class.new(ROM::Command) do
99
+ Class.new(ROM::Commands::Create[:memory]) do
91
100
  result :many
92
101
  before :prepare
93
102
  after :finalize
@@ -135,10 +144,10 @@ RSpec.describe ROM::Command, 'before/after hooks' do
135
144
 
136
145
  context 'with pre-set opts' do
137
146
  subject(:command) do
138
- Class.new(ROM::Command) do
147
+ Class.new(ROM::Commands::Create[:memory]) do
139
148
  result :many
140
- before [prepare: { prepared: true }]
141
- after [finalize: { finalized: true }]
149
+ before prepare: { prepared: true }
150
+ after finalize: { finalized: true }
142
151
 
143
152
  def execute(tuples)
144
153
  input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
@@ -183,10 +192,10 @@ RSpec.describe ROM::Command, 'before/after hooks' do
183
192
 
184
193
  context 'with pre-set opts for a curried command' do
185
194
  subject(:command) do
186
- Class.new(ROM::Command) do
195
+ Class.new(ROM::Commands::Create[:memory]) do
187
196
  result :many
188
- before [prepare: { prepared: true }]
189
- after [finalize: { finalized: true }]
197
+ before prepare: { prepared: true }
198
+ after finalize: { finalized: true }
190
199
 
191
200
  def execute(tuples)
192
201
  input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
@@ -231,10 +240,10 @@ RSpec.describe ROM::Command, 'before/after hooks' do
231
240
 
232
241
  context 'calling with multiple args' do
233
242
  subject(:command) do
234
- Class.new(ROM::Command) do
243
+ Class.new(ROM::Commands::Create[:memory]) do
235
244
  result :many
236
- before [prepare: { prepared: true }]
237
- after [finalize: { finalized: true }]
245
+ before prepare: { prepared: true }
246
+ after finalize: { finalized: true }
238
247
 
239
248
  def execute(tuples)
240
249
  input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
@@ -1,9 +1,19 @@
1
1
  require 'rom'
2
2
  require 'rom/memory'
3
3
 
4
- RSpec.describe ROM::Relation do
4
+ RSpec.describe ROM::Relation, '.view' do
5
5
  subject(:relation) { relation_class.new(ROM::Memory::Dataset.new([])) }
6
6
 
7
+ it 'returns view method name' do
8
+ klass = Class.new(ROM::Relation[:memory]) {
9
+ schema { attribute :id, ROM::Types::Int }
10
+ }
11
+
12
+ name = klass.view(:by_id, klass.schema) { self }
13
+
14
+ expect(name).to be(:by_id)
15
+ end
16
+
7
17
  shared_context 'relation with views' do
8
18
  before do
9
19
  relation << { id: 1, name: 'Joe' }
@@ -1,31 +1,31 @@
1
- require 'rom/schema/type'
1
+ require 'rom/schema/attribute'
2
2
 
3
- RSpec.describe ROM::Schema::Type do
3
+ RSpec.describe ROM::Schema::Attribute do
4
4
  describe '#inspect' do
5
5
  context 'with a primitive definition' do
6
6
  subject(:type) do
7
- ROM::Schema::Type.new(ROM::Types::Int).meta(name: :id, primary_key: true)
7
+ ROM::Schema::Attribute.new(ROM::Types::Int).meta(name: :id, primary_key: true)
8
8
  end
9
9
 
10
10
  specify do
11
- expect(type.inspect).to eql("#<ROM::Schema::Type[Integer] name=:id primary_key=true>")
11
+ expect(type.inspect).to eql("#<ROM::Schema::Attribute[Integer] name=:id primary_key=true>")
12
12
  end
13
13
  end
14
14
 
15
15
  context 'with a sum' do
16
16
  subject(:type) do
17
- ROM::Schema::Type.new(ROM::Types::Bool).meta(name: :admin)
17
+ ROM::Schema::Attribute.new(ROM::Types::Bool).meta(name: :admin)
18
18
  end
19
19
 
20
20
  specify do
21
- expect(type.inspect).to eql("#<ROM::Schema::Type[TrueClass | FalseClass] name=:admin>")
21
+ expect(type.inspect).to eql("#<ROM::Schema::Attribute[TrueClass | FalseClass] name=:admin>")
22
22
  end
23
23
  end
24
24
  end
25
25
 
26
26
  describe '#aliased' do
27
27
  subject(:type) do
28
- ROM::Schema::Type.new(ROM::Types::String).meta(name: :user_name)
28
+ ROM::Schema::Attribute.new(ROM::Types::String).meta(name: :user_name)
29
29
  end
30
30
 
31
31
  specify do
@@ -35,7 +35,7 @@ RSpec.describe ROM::Schema::Type do
35
35
 
36
36
  describe '#method_missing' do
37
37
  subject(:type) do
38
- ROM::Schema::Type.new(ROM::Types::Int).meta(name: :id, primary_key: true)
38
+ ROM::Schema::Attribute.new(ROM::Types::Int).meta(name: :id, primary_key: true)
39
39
  end
40
40
 
41
41
  specify do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta3
4
+ version: 3.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-19 00:00:00.000000000 Z
11
+ date: 2017-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -44,14 +44,20 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.8'
47
+ version: '0.9'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 0.9.4
48
51
  type: :runtime
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
55
  - - "~>"
53
56
  - !ruby/object:Gem::Version
54
- version: '0.8'
57
+ version: '0.9'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 0.9.4
55
61
  - !ruby/object:Gem::Dependency
56
62
  name: dry-core
57
63
  requirement: !ruby/object:Gem::Requirement
@@ -98,14 +104,14 @@ dependencies:
98
104
  requirements:
99
105
  - - "~>"
100
106
  - !ruby/object:Gem::Version
101
- version: 0.5.0.beta
107
+ version: 0.5.0.rc
102
108
  type: :runtime
103
109
  prerelease: false
104
110
  version_requirements: !ruby/object:Gem::Requirement
105
111
  requirements:
106
112
  - - "~>"
107
113
  - !ruby/object:Gem::Version
108
- version: 0.5.0.beta
114
+ version: 0.5.0.rc
109
115
  - !ruby/object:Gem::Dependency
110
116
  name: rake
111
117
  requirement: !ruby/object:Gem::Requirement
@@ -145,6 +151,7 @@ files:
145
151
  - ".rubocop.yml"
146
152
  - ".rubocop_todo.yml"
147
153
  - ".travis.yml"
154
+ - ".yardopts"
148
155
  - CHANGELOG.md
149
156
  - CODE_OF_CONDUCT.md
150
157
  - CONTRIBUTING.md
@@ -226,8 +233,8 @@ files:
226
233
  - lib/rom/relation/view_dsl.rb
227
234
  - lib/rom/relation_registry.rb
228
235
  - lib/rom/schema.rb
236
+ - lib/rom/schema/attribute.rb
229
237
  - lib/rom/schema/dsl.rb
230
- - lib/rom/schema/type.rb
231
238
  - lib/rom/setup.rb
232
239
  - lib/rom/setup/auto_registration.rb
233
240
  - lib/rom/setup/auto_registration_strategies/base.rb
@@ -1,129 +0,0 @@
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 private
17
- def [](input)
18
- type[input]
19
- end
20
-
21
- # @api private
22
- def read?
23
- ! meta[:read].nil?
24
- end
25
-
26
- def to_read_type
27
- read? ? meta[:read] : type
28
- end
29
-
30
- # @api public
31
- def primary_key?
32
- meta[:primary_key].equal?(true)
33
- end
34
-
35
- # @api public
36
- def foreign_key?
37
- meta[:foreign_key].equal?(true)
38
- end
39
-
40
- # @api public
41
- def aliased?
42
- !meta[:alias].nil?
43
- end
44
-
45
- # @api public
46
- def source
47
- meta[:source]
48
- end
49
-
50
- # @api public
51
- def target
52
- meta[:target]
53
- end
54
-
55
- # @api public
56
- def name
57
- meta[:name]
58
- end
59
-
60
- # @api public
61
- def alias
62
- meta[:alias]
63
- end
64
-
65
- # @api public
66
- def aliased(name)
67
- meta(alias: name)
68
- end
69
- alias_method :as, :aliased
70
-
71
- # @api public
72
- def prefixed(prefix = source.dataset)
73
- aliased(:"#{prefix}_#{name}")
74
- end
75
-
76
- # @api public
77
- def wrapped?
78
- meta[:wrapped].equal?(true)
79
- end
80
-
81
- # @api public
82
- def wrapped(name = source.dataset)
83
- self.class.new(prefixed(name).meta(wrapped: true))
84
- end
85
-
86
- # @api public
87
- def meta(opts = nil)
88
- if opts
89
- self.class.new(type.meta(opts))
90
- else
91
- type.meta
92
- end
93
- end
94
-
95
- # @api public
96
- def inspect
97
- %(#<#{self.class}[#{type.name}] #{meta.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}>)
98
- end
99
- alias_method :pretty_inspect, :inspect
100
-
101
- # @api public
102
- def eql?(other)
103
- other.is_a?(self.class) ? super : type.eql?(other)
104
- end
105
-
106
- # @api private
107
- def respond_to_missing?(name, include_private = false)
108
- type.respond_to?(name) || super
109
- end
110
-
111
- private
112
-
113
- # @api private
114
- def method_missing(meth, *args, &block)
115
- if type.respond_to?(meth)
116
- response = type.__send__(meth, *args, &block)
117
-
118
- if response.is_a?(type.class)
119
- self.class.new(type)
120
- else
121
- response
122
- end
123
- else
124
- super
125
- end
126
- end
127
- end
128
- end
129
- end