arel 0.1.0

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 (132) hide show
  1. data/.gitignore +6 -0
  2. data/README.markdown +184 -0
  3. data/Rakefile +60 -0
  4. data/VERSION +1 -0
  5. data/arel.gemspec +233 -0
  6. data/doc/CONVENTIONS +17 -0
  7. data/doc/TODO +118 -0
  8. data/lib/arel.rb +10 -0
  9. data/lib/arel/algebra.rb +4 -0
  10. data/lib/arel/algebra/extensions.rb +4 -0
  11. data/lib/arel/algebra/extensions/class.rb +32 -0
  12. data/lib/arel/algebra/extensions/hash.rb +11 -0
  13. data/lib/arel/algebra/extensions/object.rb +17 -0
  14. data/lib/arel/algebra/extensions/symbol.rb +9 -0
  15. data/lib/arel/algebra/predicates.rb +41 -0
  16. data/lib/arel/algebra/primitives.rb +5 -0
  17. data/lib/arel/algebra/primitives/attribute.rb +150 -0
  18. data/lib/arel/algebra/primitives/expression.rb +43 -0
  19. data/lib/arel/algebra/primitives/ordering.rb +23 -0
  20. data/lib/arel/algebra/primitives/value.rb +14 -0
  21. data/lib/arel/algebra/relations.rb +14 -0
  22. data/lib/arel/algebra/relations/operations/alias.rb +7 -0
  23. data/lib/arel/algebra/relations/operations/group.rb +12 -0
  24. data/lib/arel/algebra/relations/operations/join.rb +64 -0
  25. data/lib/arel/algebra/relations/operations/order.rb +18 -0
  26. data/lib/arel/algebra/relations/operations/project.rb +20 -0
  27. data/lib/arel/algebra/relations/operations/skip.rb +6 -0
  28. data/lib/arel/algebra/relations/operations/take.rb +10 -0
  29. data/lib/arel/algebra/relations/operations/where.rb +16 -0
  30. data/lib/arel/algebra/relations/relation.rb +136 -0
  31. data/lib/arel/algebra/relations/row.rb +26 -0
  32. data/lib/arel/algebra/relations/utilities/compound.rb +30 -0
  33. data/lib/arel/algebra/relations/utilities/externalization.rb +24 -0
  34. data/lib/arel/algebra/relations/utilities/nil.rb +7 -0
  35. data/lib/arel/algebra/relations/writes.rb +36 -0
  36. data/lib/arel/engines.rb +2 -0
  37. data/lib/arel/engines/memory.rb +4 -0
  38. data/lib/arel/engines/memory/engine.rb +16 -0
  39. data/lib/arel/engines/memory/predicates.rb +35 -0
  40. data/lib/arel/engines/memory/primitives.rb +27 -0
  41. data/lib/arel/engines/memory/relations.rb +5 -0
  42. data/lib/arel/engines/memory/relations/array.rb +25 -0
  43. data/lib/arel/engines/memory/relations/compound.rb +9 -0
  44. data/lib/arel/engines/memory/relations/operations.rb +61 -0
  45. data/lib/arel/engines/memory/relations/writes.rb +7 -0
  46. data/lib/arel/engines/sql.rb +7 -0
  47. data/lib/arel/engines/sql/christener.rb +13 -0
  48. data/lib/arel/engines/sql/engine.rb +37 -0
  49. data/lib/arel/engines/sql/extensions.rb +4 -0
  50. data/lib/arel/engines/sql/extensions/array.rb +16 -0
  51. data/lib/arel/engines/sql/extensions/nil_class.rb +11 -0
  52. data/lib/arel/engines/sql/extensions/object.rb +15 -0
  53. data/lib/arel/engines/sql/extensions/range.rb +15 -0
  54. data/lib/arel/engines/sql/formatters.rb +113 -0
  55. data/lib/arel/engines/sql/predicates.rb +51 -0
  56. data/lib/arel/engines/sql/primitives.rb +85 -0
  57. data/lib/arel/engines/sql/relations.rb +9 -0
  58. data/lib/arel/engines/sql/relations/operations/alias.rb +5 -0
  59. data/lib/arel/engines/sql/relations/operations/join.rb +33 -0
  60. data/lib/arel/engines/sql/relations/relation.rb +50 -0
  61. data/lib/arel/engines/sql/relations/table.rb +52 -0
  62. data/lib/arel/engines/sql/relations/utilities/compound.rb +10 -0
  63. data/lib/arel/engines/sql/relations/utilities/externalization.rb +14 -0
  64. data/lib/arel/engines/sql/relations/utilities/nil.rb +6 -0
  65. data/lib/arel/engines/sql/relations/utilities/recursion.rb +13 -0
  66. data/lib/arel/engines/sql/relations/writes.rb +39 -0
  67. data/lib/arel/session.rb +48 -0
  68. data/spec/arel/algebra/unit/predicates/binary_spec.rb +33 -0
  69. data/spec/arel/algebra/unit/predicates/equality_spec.rb +27 -0
  70. data/spec/arel/algebra/unit/predicates/in_spec.rb +10 -0
  71. data/spec/arel/algebra/unit/primitives/attribute_spec.rb +183 -0
  72. data/spec/arel/algebra/unit/primitives/expression_spec.rb +45 -0
  73. data/spec/arel/algebra/unit/primitives/value_spec.rb +15 -0
  74. data/spec/arel/algebra/unit/relations/alias_spec.rb +16 -0
  75. data/spec/arel/algebra/unit/relations/delete_spec.rb +9 -0
  76. data/spec/arel/algebra/unit/relations/group_spec.rb +10 -0
  77. data/spec/arel/algebra/unit/relations/insert_spec.rb +9 -0
  78. data/spec/arel/algebra/unit/relations/join_spec.rb +26 -0
  79. data/spec/arel/algebra/unit/relations/order_spec.rb +21 -0
  80. data/spec/arel/algebra/unit/relations/project_spec.rb +34 -0
  81. data/spec/arel/algebra/unit/relations/relation_spec.rb +188 -0
  82. data/spec/arel/algebra/unit/relations/skip_spec.rb +10 -0
  83. data/spec/arel/algebra/unit/relations/table_spec.rb +39 -0
  84. data/spec/arel/algebra/unit/relations/take_spec.rb +10 -0
  85. data/spec/arel/algebra/unit/relations/update_spec.rb +9 -0
  86. data/spec/arel/algebra/unit/relations/where_spec.rb +18 -0
  87. data/spec/arel/algebra/unit/session/session_spec.rb +84 -0
  88. data/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb +48 -0
  89. data/spec/arel/engines/memory/unit/relations/array_spec.rb +32 -0
  90. data/spec/arel/engines/memory/unit/relations/insert_spec.rb +28 -0
  91. data/spec/arel/engines/memory/unit/relations/join_spec.rb +31 -0
  92. data/spec/arel/engines/memory/unit/relations/order_spec.rb +27 -0
  93. data/spec/arel/engines/memory/unit/relations/project_spec.rb +27 -0
  94. data/spec/arel/engines/memory/unit/relations/skip_spec.rb +26 -0
  95. data/spec/arel/engines/memory/unit/relations/take_spec.rb +26 -0
  96. data/spec/arel/engines/memory/unit/relations/where_spec.rb +39 -0
  97. data/spec/arel/engines/sql/integration/joins/with_adjacency_spec.rb +209 -0
  98. data/spec/arel/engines/sql/integration/joins/with_aggregations_spec.rb +167 -0
  99. data/spec/arel/engines/sql/integration/joins/with_compounds_spec.rb +107 -0
  100. data/spec/arel/engines/sql/unit/engine_spec.rb +45 -0
  101. data/spec/arel/engines/sql/unit/predicates/binary_spec.rb +117 -0
  102. data/spec/arel/engines/sql/unit/predicates/equality_spec.rb +46 -0
  103. data/spec/arel/engines/sql/unit/predicates/in_spec.rb +86 -0
  104. data/spec/arel/engines/sql/unit/predicates/predicates_spec.rb +65 -0
  105. data/spec/arel/engines/sql/unit/primitives/attribute_spec.rb +32 -0
  106. data/spec/arel/engines/sql/unit/primitives/expression_spec.rb +24 -0
  107. data/spec/arel/engines/sql/unit/primitives/literal_spec.rb +23 -0
  108. data/spec/arel/engines/sql/unit/primitives/value_spec.rb +29 -0
  109. data/spec/arel/engines/sql/unit/relations/alias_spec.rb +43 -0
  110. data/spec/arel/engines/sql/unit/relations/delete_spec.rb +63 -0
  111. data/spec/arel/engines/sql/unit/relations/group_spec.rb +56 -0
  112. data/spec/arel/engines/sql/unit/relations/insert_spec.rb +107 -0
  113. data/spec/arel/engines/sql/unit/relations/join_spec.rb +57 -0
  114. data/spec/arel/engines/sql/unit/relations/order_spec.rb +113 -0
  115. data/spec/arel/engines/sql/unit/relations/project_spec.rb +110 -0
  116. data/spec/arel/engines/sql/unit/relations/skip_spec.rb +32 -0
  117. data/spec/arel/engines/sql/unit/relations/table_spec.rb +69 -0
  118. data/spec/arel/engines/sql/unit/relations/take_spec.rb +32 -0
  119. data/spec/arel/engines/sql/unit/relations/update_spec.rb +151 -0
  120. data/spec/arel/engines/sql/unit/relations/where_spec.rb +56 -0
  121. data/spec/connections/mysql_connection.rb +16 -0
  122. data/spec/connections/postgresql_connection.rb +15 -0
  123. data/spec/connections/sqlite3_connection.rb +25 -0
  124. data/spec/doubles/hash.rb +23 -0
  125. data/spec/matchers/be_like.rb +24 -0
  126. data/spec/matchers/disambiguate_attributes.rb +28 -0
  127. data/spec/matchers/hash_the_same_as.rb +26 -0
  128. data/spec/schemas/mysql_schema.rb +18 -0
  129. data/spec/schemas/postgresql_schema.rb +18 -0
  130. data/spec/schemas/sqlite3_schema.rb +18 -0
  131. data/spec/spec_helper.rb +47 -0
  132. metadata +250 -0
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Skip do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ @skipped = 4
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Table do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ end
8
+
9
+ describe '[]' do
10
+ describe 'when given a', Symbol do
11
+ it "manufactures an attribute if the symbol names an attribute within the relation" do
12
+ @relation[:id].should == Attribute.new(@relation, :id)
13
+ @relation[:does_not_exist].should be_nil
14
+ end
15
+ end
16
+
17
+ describe 'when given an', Attribute do
18
+ it "returns the attribute if the attribute is within the relation" do
19
+ @relation[@relation[:id]].should == @relation[:id]
20
+ end
21
+
22
+ it "returns nil if the attribtue is not within the relation" do
23
+ another_relation = Table.new(:photos)
24
+ @relation[another_relation[:id]].should be_nil
25
+ end
26
+ end
27
+
28
+ describe 'when given an', Expression do
29
+ before do
30
+ @expression = @relation[:id].count
31
+ end
32
+
33
+ it "returns the Expression if the Expression is within the relation" do
34
+ @relation[@expression].should be_nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Take do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ @taken = 4
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Update do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Where do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ @predicate = @relation[:id].eq(1)
8
+ end
9
+
10
+ describe '#initialize' do
11
+ it "manufactures nested where relations if multiple predicates are provided" do
12
+ another_predicate = @relation[:name].lt(2)
13
+ Where.new(@relation, @predicate, another_predicate). \
14
+ should == Where.new(Where.new(@relation, another_predicate), @predicate)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,84 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Session do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ @session = Session.new
8
+ end
9
+
10
+ describe '::start' do
11
+ describe '::instance' do
12
+ it "it is a singleton within the started session" do
13
+ Session.start do
14
+ Session.new.should == Session.new
15
+ end
16
+ end
17
+
18
+ it "is a singleton across nested sessions" do
19
+ Session.start do
20
+ outside = Session.new
21
+ Session.start do
22
+ Session.new.should == outside
23
+ end
24
+ end
25
+ end
26
+
27
+ it "manufactures new sessions outside of the started session" do
28
+ Session.new.should_not == Session.new
29
+ end
30
+ end
31
+ end
32
+
33
+ describe Session::CRUD do
34
+ before do
35
+ @insert = Insert.new(@relation, @relation[:name] => 'nick')
36
+ @update = Update.new(@relation, @relation[:name] => 'nick')
37
+ @delete = Deletion.new(@relation)
38
+ @read = @relation
39
+ end
40
+
41
+ describe '#create' do
42
+ it "executes an insertion on the connection" do
43
+ mock(@insert).call
44
+ @session.create(@insert)
45
+ end
46
+ end
47
+
48
+ describe '#read' do
49
+ it "executes an selection on the connection" do
50
+ mock(@read).call
51
+ @session.read(@read)
52
+ end
53
+
54
+ it "is memoized" do
55
+ mock(@read).call.once
56
+ @session.read(@read)
57
+ @session.read(@read)
58
+ end
59
+ end
60
+
61
+ describe '#update' do
62
+ it "executes an update on the connection" do
63
+ mock(@update).call
64
+ @session.update(@update)
65
+ end
66
+ end
67
+
68
+ describe '#delete' do
69
+ it "executes a delete on the connection" do
70
+ mock(@delete).call
71
+ @session.delete(@delete)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe 'Transactions' do
77
+ describe '#begin' do
78
+ end
79
+
80
+ describe '#end' do
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Join do
5
+ before do
6
+ @users = Array.new([
7
+ [1, 'bryan' ],
8
+ [2, 'emilio' ],
9
+ [3, 'nick']
10
+ ], [:id, :name])
11
+ @photos = Table.new(:photos)
12
+ @photos.delete
13
+ @photos \
14
+ .insert(@photos[:id] => 1, @photos[:user_id] => 1, @photos[:camera_id] => 6) \
15
+ .insert(@photos[:id] => 2, @photos[:user_id] => 2, @photos[:camera_id] => 42)
16
+ end
17
+
18
+ describe 'when the in memory relation is on the left' do
19
+ it 'joins across engines' do
20
+ @users \
21
+ .join(@photos) \
22
+ .on(@users[:id].eq(@photos[:user_id])) \
23
+ .project(@users[:name], @photos[:camera_id]) \
24
+ .let do |relation|
25
+ relation.call.should == [
26
+ Row.new(relation, ['bryan', '6']),
27
+ Row.new(relation, ['emilio', '42'])
28
+ ]
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'when the in memory relation is on the right' do
34
+ it 'joins across engines' do
35
+ @photos \
36
+ .join(@users) \
37
+ .on(@users[:id].eq(@photos[:user_id])) \
38
+ .project(@users[:name], @photos[:camera_id]) \
39
+ .let do |relation|
40
+ relation.call.should == [
41
+ Row.new(relation, ['bryan', '6']),
42
+ Row.new(relation, ['emilio', '42'])
43
+ ]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Array do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#attributes' do
14
+ it 'manufactures attributes corresponding to the names given on construction' do
15
+ @relation.attributes.should == [
16
+ Attribute.new(@relation, :id),
17
+ Attribute.new(@relation, :name)
18
+ ]
19
+ end
20
+ end
21
+
22
+ describe '#call' do
23
+ it "manufactures an array of hashes of attributes to values" do
24
+ @relation.call.should == [
25
+ Row.new(@relation, [1, 'duck']),
26
+ Row.new(@relation, [2, 'duck']),
27
+ Row.new(@relation, [3, 'goose'])
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Insert do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#call' do
14
+ it "manufactures an array of hashes of attributes to values" do
15
+ @relation \
16
+ .insert(@relation[:id] => 4, @relation[:name] => 'guinea fowl') \
17
+ .let do |relation|
18
+ relation.call.should == [
19
+ Row.new(relation, [1, 'duck']),
20
+ Row.new(relation, [2, 'duck']),
21
+ Row.new(relation, [3, 'goose']),
22
+ Row.new(relation, [4, 'guinea fowl'])
23
+ ]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Join do
5
+ before do
6
+ @relation1 = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ @relation2 = @relation1.alias
12
+ end
13
+
14
+ describe InnerJoin do
15
+ describe '#call' do
16
+ it 'combines the two tables where the predicate obtains' do
17
+ @relation1 \
18
+ .join(@relation2) \
19
+ .on(@relation1[:id].eq(@relation2[:id])) \
20
+ .let do |relation|
21
+ relation.call.should == [
22
+ Row.new(relation, [1, 'duck', 1, 'duck' ]),
23
+ Row.new(relation, [2, 'duck', 2, 'duck' ]),
24
+ Row.new(relation, [3, 'goose', 3, 'goose'])
25
+ ]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Order do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'sorts the relation with the provided ordering' do
15
+ @relation \
16
+ .order(@relation[:id].desc) \
17
+ .let do |relation|
18
+ relation.call.should == [
19
+ Row.new(relation, [3, 'goose']),
20
+ Row.new(relation, [2, 'duck' ]),
21
+ Row.new(relation, [1, 'duck' ])
22
+ ]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Project do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'retains only the attributes that are provided' do
15
+ @relation \
16
+ .project(@relation[:id]) \
17
+ .let do |relation|
18
+ relation.call.should == [
19
+ Row.new(relation, [1]),
20
+ Row.new(relation, [2]),
21
+ Row.new(relation, [3])
22
+ ]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Skip do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'removes the first n rows' do
15
+ @relation \
16
+ .skip(1) \
17
+ .let do |relation|
18
+ relation.call.should == [
19
+ Row.new(relation, [2, 'duck']),
20
+ Row.new(relation, [3, 'goose']),
21
+ ]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Take do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'removes the rows after the first n' do
15
+ @relation \
16
+ .take(2) \
17
+ .let do |relation|
18
+ relation.call.should == [
19
+ Row.new(relation, [1, 'duck']),
20
+ Row.new(relation, [2, 'duck']),
21
+ ]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Where do
5
+ before do
6
+ @relation = Array.new([
7
+ [1, 'duck' ],
8
+ [2, 'duck' ],
9
+ [3, 'goose']
10
+ ], [:id, :name])
11
+ end
12
+
13
+ describe '#call' do
14
+ it 'filters the relation with the provided predicate' do
15
+ @relation \
16
+ .where(@relation[:id].lt(3)) \
17
+ .let do |relation|
18
+ relation.call.should == [
19
+ Row.new(relation, [1, 'duck']),
20
+ Row.new(relation, [2, 'duck']),
21
+ ]
22
+ end
23
+ end
24
+
25
+ describe 'when filtering a where relation' do
26
+ it 'further filters the already-filtered relation with the provided predicate' do
27
+ @relation \
28
+ .where(@relation[:id].gt(1)) \
29
+ .where(@relation[:id].lt(3)) \
30
+ .let do |relation|
31
+ relation.call.should == [
32
+ Row.new(relation, [2, 'duck'])
33
+ ]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end