rom 0.5.0 → 0.6.0.beta1

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