rom 3.0.0.beta2 → 3.0.0.beta3

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: 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