rom 0.5.0 → 0.6.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -15
  3. data/.rubocop_todo.yml +28 -0
  4. data/.travis.yml +8 -1
  5. data/CHANGELOG.md +40 -0
  6. data/Gemfile +10 -2
  7. data/Guardfile +12 -10
  8. data/README.md +42 -43
  9. data/Rakefile +13 -23
  10. data/lib/rom.rb +19 -27
  11. data/lib/rom/command.rb +118 -0
  12. data/lib/rom/command_registry.rb +13 -27
  13. data/lib/rom/commands.rb +1 -59
  14. data/lib/rom/commands/abstract.rb +147 -0
  15. data/lib/rom/commands/composite.rb +47 -0
  16. data/lib/rom/commands/create.rb +2 -17
  17. data/lib/rom/commands/delete.rb +5 -25
  18. data/lib/rom/commands/result.rb +5 -5
  19. data/lib/rom/commands/update.rb +3 -27
  20. data/lib/rom/constants.rb +19 -0
  21. data/lib/rom/env.rb +85 -35
  22. data/lib/rom/global.rb +173 -42
  23. data/lib/rom/header.rb +5 -5
  24. data/lib/rom/header/attribute.rb +2 -2
  25. data/lib/rom/lint/enumerable_dataset.rb +52 -0
  26. data/lib/rom/lint/linter.rb +64 -0
  27. data/lib/rom/lint/repository.rb +78 -0
  28. data/lib/rom/lint/spec.rb +20 -0
  29. data/lib/rom/lint/test.rb +98 -0
  30. data/lib/rom/mapper.rb +32 -5
  31. data/lib/rom/mapper/attribute_dsl.rb +240 -0
  32. data/lib/rom/mapper/dsl.rb +100 -0
  33. data/lib/rom/mapper/model_dsl.rb +55 -0
  34. data/lib/rom/mapper_registry.rb +8 -1
  35. data/lib/rom/memory.rb +4 -0
  36. data/lib/rom/memory/commands.rb +46 -0
  37. data/lib/rom/memory/dataset.rb +72 -0
  38. data/lib/rom/memory/relation.rb +44 -0
  39. data/lib/rom/memory/repository.rb +62 -0
  40. data/lib/rom/memory/storage.rb +57 -0
  41. data/lib/rom/model_builder.rb +44 -5
  42. data/lib/rom/processor.rb +1 -1
  43. data/lib/rom/processor/transproc.rb +109 -16
  44. data/lib/rom/reader.rb +91 -39
  45. data/lib/rom/relation.rb +165 -26
  46. data/lib/rom/relation/composite.rb +132 -0
  47. data/lib/rom/relation/curried.rb +48 -0
  48. data/lib/rom/relation/lazy.rb +173 -0
  49. data/lib/rom/relation/loaded.rb +75 -0
  50. data/lib/rom/relation/registry_reader.rb +23 -0
  51. data/lib/rom/repository.rb +93 -34
  52. data/lib/rom/setup.rb +54 -98
  53. data/lib/rom/setup/finalize.rb +85 -76
  54. data/lib/rom/setup_dsl/command.rb +36 -0
  55. data/lib/rom/setup_dsl/command_dsl.rb +34 -0
  56. data/lib/rom/setup_dsl/mapper.rb +32 -0
  57. data/lib/rom/setup_dsl/mapper_dsl.rb +30 -0
  58. data/lib/rom/setup_dsl/relation.rb +21 -0
  59. data/lib/rom/setup_dsl/setup.rb +75 -0
  60. data/lib/rom/support/array_dataset.rb +38 -0
  61. data/lib/rom/support/class_builder.rb +44 -0
  62. data/lib/rom/support/class_macros.rb +56 -0
  63. data/lib/rom/support/data_proxy.rb +102 -0
  64. data/lib/rom/support/enumerable_dataset.rb +58 -0
  65. data/lib/rom/support/inflector.rb +73 -0
  66. data/lib/rom/support/options.rb +188 -0
  67. data/lib/rom/support/registry.rb +4 -8
  68. data/lib/rom/version.rb +1 -1
  69. data/rakelib/benchmark.rake +13 -0
  70. data/rakelib/mutant.rake +16 -0
  71. data/rakelib/rubocop.rake +18 -0
  72. data/rom.gemspec +4 -7
  73. data/spec/integration/commands/create_spec.rb +32 -24
  74. data/spec/integration/commands/delete_spec.rb +15 -7
  75. data/spec/integration/commands/update_spec.rb +13 -11
  76. data/spec/integration/mappers/deep_embedded_spec.rb +4 -11
  77. data/spec/integration/mappers/definition_dsl_spec.rb +31 -44
  78. data/spec/integration/mappers/embedded_spec.rb +9 -24
  79. data/spec/integration/mappers/group_spec.rb +22 -30
  80. data/spec/integration/mappers/prefixing_attributes_spec.rb +18 -23
  81. data/spec/integration/mappers/renaming_attributes_spec.rb +23 -38
  82. data/spec/integration/mappers/symbolizing_attributes_spec.rb +18 -24
  83. data/spec/integration/mappers/wrap_spec.rb +22 -30
  84. data/spec/integration/multi_repo_spec.rb +15 -37
  85. data/spec/integration/relations/reading_spec.rb +82 -14
  86. data/spec/integration/repositories/extending_relations_spec.rb +50 -0
  87. data/spec/integration/{adapters → repositories}/setting_logger_spec.rb +6 -5
  88. data/spec/integration/setup_spec.rb +59 -62
  89. data/spec/shared/enumerable_dataset.rb +49 -0
  90. data/spec/shared/one_behavior.rb +26 -0
  91. data/spec/shared/users_and_tasks.rb +11 -23
  92. data/spec/spec_helper.rb +16 -7
  93. data/spec/support/constant_leak_finder.rb +14 -0
  94. data/spec/test/memory_repository_lint_test.rb +27 -0
  95. data/spec/unit/rom/command_registry_spec.rb +44 -0
  96. data/spec/unit/rom/commands/result_spec.rb +14 -0
  97. data/spec/unit/rom/commands_spec.rb +174 -0
  98. data/spec/unit/rom/env_spec.rb +40 -7
  99. data/spec/unit/rom/global_spec.rb +14 -0
  100. data/spec/unit/rom/{mapper_builder_spec.rb → mapper/dsl_spec.rb} +52 -38
  101. data/spec/unit/rom/mapper_spec.rb +51 -10
  102. data/spec/unit/rom/{adapter/memory → memory}/dataset_spec.rb +6 -4
  103. data/spec/unit/rom/memory/repository_spec.rb +12 -0
  104. data/spec/unit/rom/memory/storage_spec.rb +45 -0
  105. data/spec/unit/rom/model_builder_spec.rb +4 -3
  106. data/spec/unit/rom/processor/transproc_spec.rb +1 -0
  107. data/spec/unit/rom/reader_spec.rb +97 -24
  108. data/spec/unit/rom/relation/composite_spec.rb +65 -0
  109. data/spec/unit/rom/relation/lazy_spec.rb +145 -0
  110. data/spec/unit/rom/relation/loaded_spec.rb +28 -0
  111. data/spec/unit/rom/relation_spec.rb +111 -6
  112. data/spec/unit/rom/repository_spec.rb +59 -9
  113. data/spec/unit/rom/setup_spec.rb +99 -11
  114. data/spec/unit/rom/support/array_dataset_spec.rb +59 -0
  115. data/spec/unit/rom/support/class_builder_spec.rb +42 -0
  116. data/spec/unit/rom/support/enumerable_dataset_spec.rb +17 -0
  117. data/spec/unit/rom/support/inflector_spec.rb +89 -0
  118. data/spec/unit/rom/support/options_spec.rb +119 -0
  119. metadata +74 -112
  120. data/lib/rom/adapter.rb +0 -191
  121. data/lib/rom/adapter/memory.rb +0 -32
  122. data/lib/rom/adapter/memory/commands.rb +0 -31
  123. data/lib/rom/adapter/memory/dataset.rb +0 -67
  124. data/lib/rom/adapter/memory/storage.rb +0 -26
  125. data/lib/rom/commands/with_options.rb +0 -18
  126. data/lib/rom/config.rb +0 -70
  127. data/lib/rom/mapper_builder.rb +0 -52
  128. data/lib/rom/mapper_builder/mapper_dsl.rb +0 -114
  129. data/lib/rom/mapper_builder/model_dsl.rb +0 -29
  130. data/lib/rom/reader_builder.rb +0 -48
  131. data/lib/rom/relation_builder.rb +0 -62
  132. data/lib/rom/setup/base_relation_dsl.rb +0 -46
  133. data/lib/rom/setup/command_dsl.rb +0 -46
  134. data/lib/rom/setup/mapper_dsl.rb +0 -19
  135. data/lib/rom/setup/relation_dsl.rb +0 -20
  136. data/lib/rom/setup/schema_dsl.rb +0 -33
  137. data/spec/integration/adapters/extending_relations_spec.rb +0 -41
  138. data/spec/integration/commands/try_spec.rb +0 -27
  139. data/spec/integration/schema_spec.rb +0 -77
  140. data/spec/unit/config_spec.rb +0 -60
  141. data/spec/unit/rom/adapter_spec.rb +0 -79
  142. data/spec/unit/rom_spec.rb +0 -14
@@ -1,20 +1,23 @@
1
1
  require 'spec_helper'
2
+ require 'rom/memory/dataset'
2
3
 
3
4
  require 'ostruct'
4
5
 
5
6
  describe ROM::Mapper do
6
- subject(:mapper) do
7
- ROM::Mapper.build(ROM::Header.coerce(relation.header.zip, user_model))
8
- end
7
+ subject(:mapper) { mapper_class.build }
9
8
 
10
- let(:relation) do
11
- ROM::Relation.new(dataset, dataset.header)
9
+ let(:mapper_class) do
10
+ user_model = self.user_model
11
+
12
+ Class.new(ROM::Mapper) do
13
+ attribute :id
14
+ attribute :name
15
+ model user_model
16
+ end
12
17
  end
13
18
 
14
- let(:dataset) do
15
- ROM::Adapter::Memory::Dataset.new(
16
- [{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }], [:id, :name]
17
- )
19
+ let(:relation) do
20
+ [{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }]
18
21
  end
19
22
 
20
23
  let(:user_model) do
@@ -24,11 +27,49 @@ describe ROM::Mapper do
24
27
  let(:jane) { user_model.new(id: 1, name: 'Jane') }
25
28
  let(:joe) { user_model.new(id: 2, name: 'Joe') }
26
29
 
30
+ describe '.registry' do
31
+ it 'builds mapper class registry for base and virtual relations' do
32
+ users = Class.new(ROM::Mapper) { relation(:users) }
33
+ active = Class.new(users) { relation(:active) }
34
+ admins = Class.new(users) { relation(:admins) }
35
+ custom = Class.new(users) { register_as(:custom) }
36
+
37
+ registry = ROM::Mapper.registry([users, active, admins, custom])
38
+
39
+ expect(registry).to eql(
40
+ users: {
41
+ users: users.build,
42
+ active: active.build,
43
+ admins: admins.build,
44
+ custom: custom.build
45
+ }
46
+ )
47
+ end
48
+ end
49
+
50
+ describe '.relation' do
51
+ it 'inherits from parent' do
52
+ base = Class.new(ROM::Mapper) { relation(:users) }
53
+ virt = Class.new(base)
54
+
55
+ expect(virt.relation).to be(:users)
56
+ expect(virt.base_relation).to be(:users)
57
+ end
58
+
59
+ it 'allows overriding' do
60
+ base = Class.new(ROM::Mapper) { relation(:users) }
61
+ virt = Class.new(base) { relation(:active) }
62
+
63
+ expect(virt.relation).to be(:active)
64
+ expect(virt.base_relation).to be(:users)
65
+ end
66
+ end
67
+
27
68
  describe "#each" do
28
69
  it "yields all mapped objects" do
29
70
  result = []
30
71
 
31
- mapper.process(relation).each do |tuple|
72
+ mapper.call(relation).each do |tuple|
32
73
  result << tuple
33
74
  end
34
75
 
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
+ require 'rom/lint/spec'
3
+ require 'rom/memory/dataset'
2
4
 
3
- describe ROM::Adapter::Memory::Dataset do
4
- subject(:dataset) do
5
- ROM::Adapter::Memory::Dataset.new(data, [:name, :email, :age])
6
- end
5
+ describe ROM::Memory::Dataset do
6
+ subject(:dataset) { described_class.new(data) }
7
7
 
8
8
  let(:data) do
9
9
  [
@@ -13,6 +13,8 @@ describe ROM::Adapter::Memory::Dataset do
13
13
  ]
14
14
  end
15
15
 
16
+ it_behaves_like "a rom enumerable dataset"
17
+
16
18
  describe '#project' do
17
19
  it 'projects tuples with the provided keys' do
18
20
  expect(dataset.project(:name, :age)).to match_array([
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'rom/lint/spec'
3
+ require 'rom/memory'
4
+
5
+ describe ROM::Memory::Repository do
6
+ let(:repository) { ROM::Memory::Repository }
7
+ let(:uri) { nil }
8
+
9
+ it_behaves_like "a rom repository" do
10
+ let(:identifier) { :memory }
11
+ end
12
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'rom/memory/storage'
3
+
4
+ describe ROM::Memory::Storage do
5
+ describe 'thread safe' do
6
+ let(:threads) { 4 }
7
+ let(:operations) { 5000 }
8
+
9
+ describe 'data' do
10
+ it 'create datasets properly' do
11
+ storage = ROM::Memory::Storage.new
12
+
13
+ threaded_operations do |thread, operation|
14
+ key = "#{thread}:#{operation}"
15
+ storage.create_dataset(key)
16
+ end
17
+
18
+ expect(storage.size).to eql(threads * operations)
19
+ end
20
+ end
21
+
22
+ describe 'dataset' do
23
+ it 'inserts data in proper order' do
24
+ storage = ROM::Memory::Storage.new
25
+ dataset = storage.create_dataset(:ary)
26
+
27
+ threaded_operations do
28
+ dataset << :data
29
+ end
30
+
31
+ expect(dataset.size).to eql(threads * operations)
32
+ end
33
+ end
34
+
35
+ def threaded_operations
36
+ threads.times.map do |thread|
37
+ Thread.new do
38
+ operations.times do |operation|
39
+ yield thread, operation
40
+ end
41
+ end
42
+ end.each(&:join)
43
+ end
44
+ end
45
+ end
@@ -28,16 +28,17 @@ describe ROM::ModelBuilder do
28
28
  builder.call([:name, :email])
29
29
 
30
30
  expect(Object.const_defined?(:User)).to be(true)
31
+ Object.send(:remove_const, :User)
31
32
  end
32
33
 
33
34
  it 'defines a constant within a namespace for the model' do
34
- module MyApp; module Entities; end; end
35
+ module Test::MyApp; module Entities; end; end
35
36
 
36
- builder = ROM::ModelBuilder::PORO.new(name: 'MyApp::Entities::User')
37
+ builder = ROM::ModelBuilder::PORO.new(name: 'Test::MyApp::Entities::User')
37
38
 
38
39
  builder.call([:name, :email])
39
40
 
40
- expect(MyApp::Entities.const_defined?(:User)).to be(true)
41
+ expect(Test::MyApp::Entities.const_defined?(:User)).to be(true)
41
42
  expect(Object.const_defined?(:User)).to be(false)
42
43
  end
43
44
  end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'virtus'
2
3
 
3
4
  describe ROM::Processor::Transproc do
4
5
  subject(:transproc) { ROM::Processor::Transproc.build(header) }
@@ -10,27 +10,10 @@ describe ROM::Reader do
10
10
  let(:mappers) { ROM::MapperRegistry.new(users: mapper) }
11
11
  let(:mapper) { double('mapper', header: []) }
12
12
 
13
- describe '#initialize' do
14
- it 'raises error when mapper cannot be found' do
15
- expect { ROM::Reader.new(:not_here, relation, mappers) }
16
- .to raise_error(ROM::Reader::MapperMissingError, /not_here/)
17
- end
18
- end
19
-
20
- describe '#each' do
21
- it 'yields mapped tuples from relations' do
22
- expect(mapper).to receive(:process)
23
- .with(relation)
24
- .and_yield(jane).and_yield(joe)
25
-
26
- result = []
27
- reader.each { |user| result << user }
28
- expect(result).to eql([jane, joe])
29
- end
30
- end
31
-
32
13
  describe '.build' do
33
- subject(:reader) { ROM::Reader.build(name, relation, mappers, [:all]) }
14
+ subject(:reader) do
15
+ ROM::Reader.build(name, relation, mappers, [:all])
16
+ end
34
17
 
35
18
  before do
36
19
  relation.instance_exec do
@@ -51,15 +34,17 @@ describe ROM::Reader do
51
34
  it 'defines methods from relation' do
52
35
  block = proc {}
53
36
 
37
+ user_id = 1
38
+
54
39
  expect(relation).to receive(:all)
55
- .with(1, &block)
40
+ .with(user_id, &block)
56
41
  .and_return([joe])
57
42
 
58
- expect(mapper).to receive(:process)
43
+ expect(mapper).to receive(:call)
59
44
  .with([joe])
60
- .and_yield(joe)
45
+ .and_return([joe])
61
46
 
62
- result = reader.all(1, &block)
47
+ result = reader.all(user_id, &block)
63
48
 
64
49
  expect(result.path).to eql('users.all')
65
50
  expect(result.to_a).to eql([joe])
@@ -69,5 +54,93 @@ describe ROM::Reader do
69
54
  expect { reader.not_here }
70
55
  .to raise_error(ROM::NoRelationError, /not_here/)
71
56
  end
57
+
58
+ it 'raises error when relation does not respond to the method with args' do
59
+ expect { reader.find_by_id(1) }
60
+ .to raise_error(ROM::NoRelationError, /find_by_id/)
61
+ end
62
+ end
63
+
64
+ describe '#initialize' do
65
+ it 'raises error when mapper cannot be found' do
66
+ expect { ROM::Reader.new(:not_here, relation, mappers) }
67
+ .to raise_error(ROM::MapperMissingError, /not_here/)
68
+ end
69
+ end
70
+
71
+ describe '#each' do
72
+ it 'yields mapped tuples from relations' do
73
+ expect(mapper).to receive(:call)
74
+ .with(relation)
75
+ .and_return(relation)
76
+
77
+ result = []
78
+ reader.each { |user| result << user }
79
+ expect(result).to eql([jane, joe])
80
+ end
81
+ end
82
+
83
+ shared_examples_for 'one and one!' do |method|
84
+ context 'with a single tuple' do
85
+ let(:relation) { [jane] }
86
+
87
+ it 'returns a single tuple' do
88
+ expect(mapper).to receive(:call)
89
+ .with(relation)
90
+ .and_return(relation)
91
+
92
+ expect(reader.public_send(method)).to eql(jane)
93
+ end
94
+ end
95
+
96
+ context 'with more than one tuple' do
97
+ it 'raises an error' do
98
+ expect { reader.public_send(method) }
99
+ .to raise_error(ROM::TupleCountMismatchError)
100
+ end
101
+ end
102
+ end
103
+
104
+ describe '#one' do
105
+ it_should_behave_like 'one and one!', :one
106
+
107
+ context 'without any tuple' do
108
+ let(:relation) { [] }
109
+
110
+ it 'returns nil' do
111
+ expect(mapper).to receive(:call)
112
+ .with(relation)
113
+ .and_return(relation)
114
+
115
+ expect(reader.one).to be_nil
116
+ end
117
+ end
118
+ end
119
+
120
+ describe '#one!' do
121
+ it_should_behave_like 'one and one!', :one!
122
+
123
+ context 'without any tuple' do
124
+ let(:relation) { [] }
125
+
126
+ it 'raises an error' do
127
+ expect(mapper).to receive(:call)
128
+ .with(relation)
129
+ .and_return(relation)
130
+
131
+ expect { reader.one! }.to raise_error(ROM::TupleCountMismatchError)
132
+ end
133
+ end
134
+ end
135
+
136
+ describe '#to_ary' do
137
+ it 'casts relation to an array with loaded objects' do
138
+ expect(mapper).to receive(:call)
139
+ .with(relation)
140
+ .and_return(relation)
141
+
142
+ result = reader.to_ary
143
+ expect(result).to eql(relation)
144
+ end
72
145
  end
73
146
  end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Relation::Composite do
4
+ include_context 'users and tasks'
5
+
6
+ let(:users) { rom.relation(:users) }
7
+
8
+ let(:name_list) { proc { |r| r.map { |t| t[:name] } } }
9
+ let(:upcaser) { proc { |r| r.map(&:upcase) } }
10
+
11
+ before do
12
+ setup.relation(:users) do
13
+ def by_name(name)
14
+ restrict(name: name)
15
+ end
16
+
17
+ def sorted(other)
18
+ other.sort_by { |t| t[:name] }
19
+ end
20
+ end
21
+ end
22
+
23
+ describe '#call' do
24
+ it 'sends a relation through mappers' do
25
+ relation = users >> name_list >> upcaser
26
+ loaded = relation.call
27
+
28
+ expect(loaded.source).to eql(users.relation)
29
+ expect(loaded).to match_array(%w(JANE JOE))
30
+ end
31
+
32
+ it 'sends a relation through another relation' do
33
+ relation = users >> users.sorted
34
+ loaded = relation.call
35
+
36
+ expect(loaded.source).to eql(users.relation)
37
+ expect(loaded).to match_array([
38
+ { name: 'Jane', email: 'jane@doe.org' },
39
+ { name: 'Joe', email: 'joe@doe.org' }
40
+ ])
41
+ end
42
+ end
43
+
44
+ describe '#each' do
45
+ let(:relation) { users >> name_list >> upcaser }
46
+
47
+ it 'calls and iterates' do
48
+ result = []
49
+ relation.each { |object| result << object }
50
+ expect(result).to match_array(%w(JANE JOE))
51
+ end
52
+
53
+ it 'returns enumerator if block is not provided' do
54
+ expect(relation.each.to_a).to match_array(%w(JANE JOE))
55
+ end
56
+ end
57
+
58
+ describe '#first' do
59
+ let(:relation) { users >> name_list >> upcaser }
60
+
61
+ it 'calls and returns the first object' do
62
+ expect(relation.first).to eql('JOE')
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Relation::Lazy do
4
+ include_context 'users and tasks'
5
+
6
+ let(:users) { rom.relations.users.to_lazy }
7
+ let(:tasks) { rom.relations.tasks.to_lazy }
8
+
9
+ before do
10
+ setup.relation(:users) do
11
+ def repository
12
+ :default
13
+ end
14
+
15
+ def by_name(name)
16
+ restrict(name: name)
17
+ end
18
+
19
+ def by_email(email)
20
+ restrict(name: email)
21
+ end
22
+
23
+ def by_name_and_email(name, email)
24
+ by_name(name).by_email(email)
25
+ end
26
+
27
+ def by_name_and_email_sorted(name, email, order_by)
28
+ by_name_and_email(name, email).order(order_by)
29
+ end
30
+
31
+ def all(*args)
32
+ if args.any?
33
+ restrict(*args)
34
+ else
35
+ self
36
+ end
37
+ end
38
+ end
39
+
40
+ setup.relation(:tasks) do
41
+ def for_users(users)
42
+ names = users.map { |u| u[:name] }
43
+ restrict { |t| names.include?(t[:name]) }
44
+ end
45
+ end
46
+ end
47
+
48
+ it_behaves_like 'a relation that returns one tuple' do
49
+ let(:relation) { users }
50
+ end
51
+
52
+ describe '#method_missing' do
53
+ it 'forwards to relation and auto-curries' do
54
+ relation = users.by_name_and_email_sorted('Jane')
55
+
56
+ expect(relation.name).to eql(:by_name_and_email_sorted)
57
+ expect(relation.curry_args).to eql(['Jane'])
58
+
59
+ relation = relation['jane@doe.org']
60
+
61
+ expect(relation.name).to eql(:by_name_and_email_sorted)
62
+ expect(relation.curry_args).to eql(['Jane', 'jane@doe.org'])
63
+
64
+ expect(relation[:email]).to match_array(
65
+ rom.relations.users.by_name_and_email_sorted('Jane', 'jane@doe.org', :email)
66
+ )
67
+ end
68
+
69
+ it 'forwards to relation and does not auto-curry when it is not needed' do
70
+ relation = users.by_name('Jane')
71
+
72
+ expect(relation).to_not be_curried
73
+ expect(relation).to match_array(rom.relations.users.by_name('Jane'))
74
+ end
75
+
76
+ it 'forwards to relation and return lazy when arity is unknown' do
77
+ relation = users.all(name: 'Jane')
78
+ expect(relation).to_not be_curried
79
+ expect(relation).to match_array(rom.relations.users.by_name('Jane').to_a)
80
+ end
81
+
82
+ it 'returns original response if it is not a relation' do
83
+ expect(users.repository).to be(:default)
84
+ end
85
+
86
+ it 'raises NoMethodError when relation does not respond to a method' do
87
+ expect { users.not_here }.to raise_error(NoMethodError, /not_here/)
88
+ end
89
+ end
90
+
91
+ describe '#call' do
92
+ it 'auto-curries' do
93
+ relation = users.by_name
94
+
95
+ expect(relation.name).to eql(:by_name)
96
+ expect(relation['Jane'].to_a).to eql(rom.relations.users.by_name('Jane').to_a)
97
+ end
98
+
99
+ it 'returns relation' do
100
+ expect(users.call.to_a).to eql(rom.relations.users.to_a)
101
+ end
102
+
103
+ it 'does not allow currying on already curried relation' do
104
+ expect { users.by_name.by_email }.to raise_error(NoMethodError, /by_email/)
105
+ end
106
+
107
+ describe 'using mappers' do
108
+ subject(:users) { rom.relations.users.to_lazy(mappers: mappers) }
109
+
110
+ let(:name_list) { proc { |r| r.map { |t| t[:name] } } }
111
+ let(:upcaser) { proc { |r| r.map(&:upcase) } }
112
+ let(:mappers) { { name_list: name_list, upcaser: upcaser } }
113
+
114
+ it 'sends relation through mappers' do
115
+ relation = users.map_with(:name_list, :upcaser).by_name('Jane')
116
+
117
+ expect(relation.call.to_a).to eql(['JANE'])
118
+ end
119
+ end
120
+ end
121
+
122
+ describe '#>>' do
123
+ it 'composes two relations' do
124
+ other = users.by_name('Jane') >> tasks.for_users
125
+
126
+ expect(other).to match_array([
127
+ { name: 'Jane', title: 'be cool', priority: 2 }
128
+ ])
129
+ end
130
+
131
+ it_behaves_like 'a relation that returns one tuple' do
132
+ let(:relation) { rom.relation(:users) >> proc { |r| r } }
133
+
134
+ describe 'using a mapper' do
135
+ it 'returns one mapped tuple' do
136
+ mapper = proc { |r| r.map { |t| t[:name].upcase } }
137
+ relation = users.by_name('Jane') >> mapper
138
+
139
+ expect(relation.one).to eql('JANE')
140
+ expect(relation.one!).to eql('JANE')
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end