rom 3.0.0.beta2 → 3.0.0.beta3

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: 7783cc66c1b765933756f1ac1aa1c90d37e457c3
4
- data.tar.gz: b7303d0198ff50269246903fe532500a94fd4829
3
+ metadata.gz: 98cf29125e7e877f9eadf9603755d7bacf9603af
4
+ data.tar.gz: 596ae65060eb04d7f52be2406908a4541b758c6e
5
5
  SHA512:
6
- metadata.gz: 228c165ca9052fc4a50963fc17929ff0f1eb6ee94a856df11d0d9dc58eae5118f908c9dbb58db4f50071a9398bf92afbd28e247f8f15753d31edc25ea76494a9
7
- data.tar.gz: 836531fb06c233cdee08a8cad06c648bc498095a7914d50f3dc25553dab7e761d912b38dde9f9fcbd9638db5381e6a0506798b1e455d77c58af6360e8dc29ea3
6
+ metadata.gz: 8cf082f7aa10bc96e0b186f8bf42b6d64fa3a703780cf763292f2393374009a5a26f5387b4b0df2381371d82cf49bdb210438c07fb0b9303a72d5065e3952fd8
7
+ data.tar.gz: 864cd037917d3df2ded0efca14a7a27aa53023ce296532df70aa28f7cb5ad1b917b41b6ada18e6be6ec566f321cd6d4920d9f954096330e07e11ab89539f40b4
@@ -6,13 +6,14 @@
6
6
  * Schemas have their own types with adapter-specific APIs (solnic)
7
7
  * Schema attributes include meta properties `source` and `target` (for FKs) (solnic)
8
8
  * Inferred schemas can have explicit attribute definitions for cases where inference didn't work (solnic)
9
- * New schema APIs: `#project`, `#rename`, `#exclude`, `#prefix`, `#wrap` and `#merge` (solnic)
9
+ * New schema APIs: `#project`, `#rename`, `#exclude`, `#prefix`, `#wrap`, `#merge`, `#append` and `#uniq` (solnic)
10
10
  * New schema attribute APIs: `#name`, `#aliased`, `#aliased?`, `#prefixed`, `#prefixed?`, `#wrapped`, `#wrapped?`, `#source`, `#target`, `#primary_key?`, `#foreign_key?` (solnic)
11
11
  * Schemas are coercible to arrays that include all attribute types (solnic)
12
12
  * Automatic relation view projection via `Schema#call` (abstract method for adapters) (solnic)
13
13
  * `Relation#new(dataset, new_opts)` interface (solnic)
14
14
  * `Relation#[]` interface for convenient access to schema attributes (solnic)
15
15
  * `Command` has now support for `before` and `after` hooks (solnic)
16
+ * Support for `read` types in schemas, these are used when relation loads its tuples (solnic)
16
17
  * New `Command#before` method for creating a command with before hook(s) at run-time (solnic)
17
18
  * New `Command#after` method for creating a command with after hook(s) at run-time (solnic)
18
19
  * New `Gateway#transaction` method runs code inside a transaction (flash-gordon)
@@ -170,14 +170,19 @@ module ROM
170
170
  self.class.build(new_relation, options.merge(source: relation))
171
171
  end
172
172
 
173
+ # @api public
174
+ def with_opts(new_opts)
175
+ self.class.new(relation, options.merge(new_opts))
176
+ end
177
+
173
178
  # @api public
174
179
  def before(*hooks)
175
- self.class.new(relation, options.merge(before: hooks))
180
+ self.class.new(relation, options.merge(before: before_hooks + hooks))
176
181
  end
177
182
 
178
183
  # @api public
179
184
  def after(*hooks)
180
- self.class.new(relation, options.merge(after: hooks))
185
+ self.class.new(relation, options.merge(after: after_hooks + hooks))
181
186
  end
182
187
 
183
188
  private
@@ -20,9 +20,9 @@ module ROM
20
20
 
21
21
  input_handler =
22
22
  if default_input != Hash && relation.schema?
23
- -> tuple { relation.schema_hash[input[tuple]] }
23
+ -> tuple { relation.input_schema[input[tuple]] }
24
24
  elsif relation.schema?
25
- relation.schema_hash
25
+ relation.input_schema
26
26
  else
27
27
  default_input
28
28
  end
@@ -30,6 +30,8 @@ module ROM
30
30
  #
31
31
  # @api public
32
32
  class Relation
33
+ NOOP_OUTPUT_SCHEMA = -> tuple { tuple }.freeze
34
+
33
35
  extend Initializer
34
36
  extend ClassInterface
35
37
 
@@ -51,13 +53,20 @@ module ROM
51
53
  # schema (if it was defined) and sets an empty one as
52
54
  # the fallback
53
55
  # @api public
54
- option :schema, reader: true, default: method(:default_schema).to_proc
56
+ option :schema, reader: true, optional: true, default: method(:default_schema).to_proc
55
57
 
56
- # @!attribute [r] schema_hash
58
+ # @!attribute [r] input_schema
57
59
  # @return [Object#[]] tuple processing function, uses schema or defaults to Hash[]
58
60
  # @api private
59
- option :schema_hash, reader: true, default: -> relation {
60
- relation.schema? ? Types::Coercible::Hash.schema(relation.schema.to_h) : Hash
61
+ option :input_schema, reader: true, default: -> relation {
62
+ relation.schema? ? schema.to_input_hash : Hash
63
+ }
64
+
65
+ # @!attribute [r] output_schema
66
+ # @return [Object#[]] tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA
67
+ # @api private
68
+ option :output_schema, reader: true, optional: true, default: -> relation {
69
+ relation.schema.any?(&:read?) ? schema.to_output_hash : NOOP_OUTPUT_SCHEMA
61
70
  }
62
71
 
63
72
  # Return schema attribute
@@ -77,7 +86,7 @@ module ROM
77
86
  # @api public
78
87
  def each(&block)
79
88
  return to_enum unless block
80
- dataset.each { |tuple| yield(tuple) }
89
+ dataset.each { |tuple| yield(output_schema[tuple]) }
81
90
  end
82
91
 
83
92
  # Composes with other relations
@@ -205,10 +205,36 @@ module ROM
205
205
  #
206
206
  # @api public
207
207
  def merge(other)
208
- new(attributes + other.attributes)
208
+ append(*other)
209
209
  end
210
210
  alias_method :+, :merge
211
211
 
212
+ # Append more attributes to the schema
213
+ #
214
+ # @param [*Array<Schema::Type>]
215
+ #
216
+ # @return [Schema]
217
+ #
218
+ # @api public
219
+ def append(*new_attributes)
220
+ new(attributes + new_attributes)
221
+ end
222
+
223
+ # Return a new schema with uniq attributes
224
+ #
225
+ # @param [*Array<Schema::Type>]
226
+ #
227
+ # @return [Schema]
228
+ #
229
+ # @api public
230
+ def uniq(&block)
231
+ if block
232
+ new(attributes.uniq(&block))
233
+ else
234
+ new(attributes.uniq(&:name))
235
+ end
236
+ end
237
+
212
238
  # Return if a schema includes an attribute with the given name
213
239
  #
214
240
  # @param [Symbol] name The name of the attribute
@@ -256,6 +282,20 @@ module ROM
256
282
  freeze
257
283
  end
258
284
 
285
+ # @api private
286
+ def to_output_hash
287
+ Types::Coercible::Hash.schema(
288
+ map { |attr| [attr.name, attr.to_read_type] }.to_h
289
+ )
290
+ end
291
+
292
+ # @api private
293
+ def to_input_hash
294
+ Types::Coercible::Hash.schema(
295
+ map { |attr| [attr.name, attr] }.to_h
296
+ )
297
+ end
298
+
259
299
  private
260
300
 
261
301
  # @api private
@@ -32,9 +32,15 @@ module ROM
32
32
  # @see Relation.schema
33
33
  #
34
34
  # @api public
35
- def attribute(name, type)
35
+ def attribute(name, type, options = EMPTY_HASH)
36
36
  @attributes ||= {}
37
- @attributes[name] = type.meta(name: name, source: relation)
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
38
44
  end
39
45
 
40
46
  # Specify which key(s) should be the primary key
@@ -13,6 +13,20 @@ module ROM
13
13
  @type = type
14
14
  end
15
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
+
16
30
  # @api public
17
31
  def primary_key?
18
32
  meta[:primary_key].equal?(true)
@@ -1,3 +1,3 @@
1
1
  module ROM
2
- VERSION = '3.0.0.beta2'.freeze
2
+ VERSION = '3.0.0.beta3'.freeze
3
3
  end
@@ -8,26 +8,36 @@ RSpec.describe ROM::Command, 'before/after hooks' do
8
8
  describe '#before' do
9
9
  subject(:command) do
10
10
  Class.new(ROM::Command) do
11
+ before :init
12
+
13
+ def init(*)
14
+ end
15
+
11
16
  def prepare(*)
12
17
  end
13
18
  end.build(relation)
14
19
  end
15
20
 
16
21
  it 'returns a new command with configured before hooks' do
17
- expect(command.before(:prepare).before_hooks).to include(:prepare)
22
+ expect(command.before(:prepare).before_hooks).to eql(%i[init prepare])
18
23
  end
19
24
  end
20
25
 
21
26
  describe '#after' do
22
27
  subject(:command) do
23
28
  Class.new(ROM::Command) do
29
+ after :finalize
30
+
31
+ def finalize(*)
32
+ end
33
+
24
34
  def prepare(*)
25
35
  end
26
36
  end.build(relation)
27
37
  end
28
38
 
29
39
  it 'returns a new command with configured after hooks' do
30
- expect(command.after(:prepare).after_hooks).to include(:prepare)
40
+ expect(command.after(:prepare).after_hooks).to eql(%i[finalize prepare])
31
41
  end
32
42
  end
33
43
 
@@ -168,4 +168,21 @@ RSpec.describe 'Commands' do
168
168
  expect(command.call('foo')).to be(ROM::EMPTY_ARRAY)
169
169
  end
170
170
  end
171
+
172
+ describe '#with_opts' do
173
+ subject(:command) do
174
+ Class.new(ROM::Command::Create).build(relation, options)
175
+ end
176
+
177
+ let(:relation) { double(:relation) }
178
+ let(:options) { { result: :one } }
179
+
180
+ it 'returns a new command with updated options' do
181
+ new_command = command.with_opts(before: :test)
182
+
183
+ expect(new_command.relation).to be(relation)
184
+ expect(new_command.result).to be(:one)
185
+ expect(new_command.before_hooks).to eql([:test])
186
+ end
187
+ end
171
188
  end
@@ -38,17 +38,17 @@ RSpec.describe ROM::Plugins::Command::Schema do
38
38
 
39
39
  context 'when relation has a schema' do
40
40
  let(:relation) do
41
- instance_double(ROM::Relation, schema?: true, schema_hash: schema_hash)
41
+ instance_double(ROM::Relation, schema?: true, input_schema: input_schema)
42
42
  end
43
43
 
44
- let(:schema_hash) do
45
- double(:schema_hash)
44
+ let(:input_schema) do
45
+ double(:input_schema)
46
46
  end
47
47
 
48
48
  it 'sets schema hash as input handler' do
49
49
  command = Class.new(command_class).build(relation)
50
50
 
51
- expect(command.input).to be(schema_hash)
51
+ expect(command.input).to be(input_schema)
52
52
  end
53
53
 
54
54
  it 'sets a composed input handler with schema hash and a custom one' do
@@ -57,7 +57,7 @@ RSpec.describe ROM::Plugins::Command::Schema do
57
57
  command = Class.new(command_class) { input my_handler }.build(relation)
58
58
 
59
59
  expect(my_handler).to receive(:[]).with('some value').and_return('my handler')
60
- expect(schema_hash).to receive(:[]).with('my handler').and_return('a tuple')
60
+ expect(input_schema).to receive(:[]).with('my handler').and_return('a tuple')
61
61
 
62
62
  expect(command.input['some value']).to eql('a tuple')
63
63
  end
@@ -0,0 +1,51 @@
1
+ require 'rom/relation'
2
+
3
+ RSpec.describe ROM::Relation, '#call' do
4
+ subject(:relation) do
5
+ relation_class.new(data)
6
+ end
7
+
8
+ context 'without read types in schema' do
9
+ let(:relation_class) do
10
+ Class.new(ROM::Relation[:memory]) do
11
+ schema do
12
+ attribute :id, ROM::Types::Int
13
+ attribute :name, ROM::Types::String
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:data) do
19
+ [{ id: '1', name: 'Jane' }, { id: '2', name: 'John'} ]
20
+ end
21
+
22
+ it 'has noop output_schema' do
23
+ expect(relation.output_schema).to be(ROM::Relation::NOOP_OUTPUT_SCHEMA)
24
+ end
25
+
26
+ it 'returns loaded relation with data' do
27
+ expect(relation.call.collection).
28
+ to eql(data)
29
+ end
30
+ end
31
+
32
+ context 'with read types in schema' do
33
+ let(:relation_class) do
34
+ Class.new(ROM::Relation[:memory]) do
35
+ schema do
36
+ attribute :id, ROM::Types::String, read: ROM::Types::Coercible::Int
37
+ attribute :name, ROM::Types::String
38
+ end
39
+ end
40
+ end
41
+
42
+ let(:data) do
43
+ [{ id: '1', name: 'Jane' }, { id: '2', name: 'John'} ]
44
+ end
45
+
46
+ it 'returns loaded relation with coerced data' do
47
+ expect(relation.call.collection).
48
+ to eql([{ id: 1, name: 'Jane' }, { id: 2, name: 'John'} ])
49
+ end
50
+ end
51
+ end
@@ -29,6 +29,26 @@ RSpec.describe ROM::Relation, '.schema' do
29
29
  expect(Test::Users.schema).to eql(schema)
30
30
  end
31
31
 
32
+ it 'allows defining types for reading tuples' do
33
+ module Test
34
+ module Types
35
+ CoercibleDate = ROM::Types::Date.constructor(Date.method(:parse))
36
+ end
37
+ end
38
+
39
+ class Test::Users < ROM::Relation[:memory]
40
+ schema do
41
+ attribute :id, Types::Int
42
+ attribute :date, Types::Coercible::String, read: Test::Types::CoercibleDate
43
+ end
44
+ end
45
+
46
+ schema = Test::Users.schema
47
+
48
+ expect(schema.to_output_hash).
49
+ to eql(ROM::Types::Coercible::Hash.schema(id: schema[:id].type, date: schema[:date].meta[:read]))
50
+ end
51
+
32
52
  it 'allows setting composite primary key' do
33
53
  class Test::Users < ROM::Relation[:memory]
34
54
  schema do
@@ -232,13 +232,13 @@ RSpec.describe ROM::Relation do
232
232
  end
233
233
  end
234
234
 
235
- describe '#schema_hash' do
235
+ describe '#input_schema' do
236
236
  it 'returns a schema hash type' do
237
237
  relation = Class.new(ROM::Relation[:memory]) do
238
238
  schema { attribute :id, ROM::Types::Coercible::Int }
239
239
  end.new([])
240
240
 
241
- expect(relation.schema_hash[id: '1']).to eql(id: 1)
241
+ expect(relation.input_schema[id: '1']).to eql(id: 1)
242
242
  end
243
243
 
244
244
  it 'returns a plain Hash coercer when there is no schema' do
@@ -246,7 +246,7 @@ RSpec.describe ROM::Relation do
246
246
 
247
247
  tuple = [[:id, '1']]
248
248
 
249
- expect(relation.schema_hash[tuple]).to eql(id: '1')
249
+ expect(relation.input_schema[tuple]).to eql(id: '1')
250
250
  end
251
251
  end
252
252
  end
@@ -0,0 +1,17 @@
1
+ require 'rom/schema'
2
+
3
+ RSpec.describe ROM::Schema, '#append' do
4
+ subject(:schema) { left.append(*right) }
5
+
6
+ let(:left) do
7
+ define_schema(:users, id: :Int, name: :String)
8
+ end
9
+
10
+ let(:right) do
11
+ define_schema(:tasks, user_id: :Int)
12
+ end
13
+
14
+ it 'returns a new schema with attributes from two schemas' do
15
+ expect(schema.map(&:name)).to eql(%i[id name user_id])
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ require 'rom/schema'
2
+
3
+ RSpec.describe ROM::Schema, '#uniq' do
4
+ subject(:schema) { left.merge(right) }
5
+
6
+ let(:left) do
7
+ define_schema(:users, id: :Int, name: :String)
8
+ end
9
+
10
+ let(:right) do
11
+ define_schema(:tasks, id: :Int, user_id: :Int)
12
+ end
13
+
14
+ it 'returns a new schema with unique attributes from two schemas' do
15
+ expect(schema.uniq.map(&:name)).to eql(%i[id name user_id])
16
+ end
17
+
18
+ it 'accepts a block' do
19
+ expect(schema.uniq(&:primitive).map(&:name)).to eql(%i[id name])
20
+ end
21
+ end
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.beta2
4
+ version: 3.0.0.beta3
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-12 00:00:00.000000000 Z
11
+ date: 2017-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -345,6 +345,7 @@ files:
345
345
  - spec/unit/rom/plugins/relation/key_inference_spec.rb
346
346
  - spec/unit/rom/registry_spec.rb
347
347
  - spec/unit/rom/relation/attribute_reader_spec.rb
348
+ - spec/unit/rom/relation/call_spec.rb
348
349
  - spec/unit/rom/relation/composite_spec.rb
349
350
  - spec/unit/rom/relation/curried_spec.rb
350
351
  - spec/unit/rom/relation/graph_spec.rb
@@ -356,6 +357,7 @@ files:
356
357
  - spec/unit/rom/relation/view_spec.rb
357
358
  - spec/unit/rom/relation_spec.rb
358
359
  - spec/unit/rom/schema/accessing_attributes_spec.rb
360
+ - spec/unit/rom/schema/append_spec.rb
359
361
  - spec/unit/rom/schema/exclude_spec.rb
360
362
  - spec/unit/rom/schema/finalize_spec.rb
361
363
  - spec/unit/rom/schema/key_predicate_spec.rb
@@ -364,6 +366,7 @@ files:
364
366
  - spec/unit/rom/schema/project_spec.rb
365
367
  - spec/unit/rom/schema/rename_spec.rb
366
368
  - spec/unit/rom/schema/type_spec.rb
369
+ - spec/unit/rom/schema/uniq_spec.rb
367
370
  - spec/unit/rom/schema/wrap_spec.rb
368
371
  - spec/unit/rom/schema_spec.rb
369
372
  - spec/unit/rom/setup/auto_registration_spec.rb