rom 3.0.0.beta3 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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