rom-sql 0.9.1 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +4 -1
  5. data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
  6. data/lib/rom/sql/association.rb +33 -14
  7. data/lib/rom/sql/association/many_to_many.rb +17 -10
  8. data/lib/rom/sql/association/many_to_one.rb +29 -13
  9. data/lib/rom/sql/association/name.rb +12 -4
  10. data/lib/rom/sql/association/one_to_many.rb +21 -10
  11. data/lib/rom/sql/commands/create.rb +0 -1
  12. data/lib/rom/sql/commands/update.rb +1 -49
  13. data/lib/rom/sql/dsl.rb +29 -0
  14. data/lib/rom/sql/expression.rb +26 -0
  15. data/lib/rom/sql/function.rb +23 -0
  16. data/lib/rom/sql/gateway.rb +24 -9
  17. data/lib/rom/sql/migration.rb +6 -7
  18. data/lib/rom/sql/migration/migrator.rb +7 -8
  19. data/lib/rom/sql/order_dsl.rb +20 -0
  20. data/lib/rom/sql/plugin/associates.rb +58 -45
  21. data/lib/rom/sql/plugin/pagination.rb +8 -11
  22. data/lib/rom/sql/plugins.rb +0 -2
  23. data/lib/rom/sql/projection_dsl.rb +41 -0
  24. data/lib/rom/sql/qualified_attribute.rb +2 -2
  25. data/lib/rom/sql/relation.rb +35 -67
  26. data/lib/rom/sql/relation/reading.rb +77 -25
  27. data/lib/rom/sql/restriction_dsl.rb +24 -0
  28. data/lib/rom/sql/schema.rb +73 -7
  29. data/lib/rom/sql/schema/associations_dsl.rb +4 -3
  30. data/lib/rom/sql/schema/dsl.rb +5 -2
  31. data/lib/rom/sql/schema/inferrer.rb +21 -11
  32. data/lib/rom/sql/transaction.rb +19 -0
  33. data/lib/rom/sql/type.rb +76 -0
  34. data/lib/rom/sql/version.rb +1 -1
  35. data/rom-sql.gemspec +3 -4
  36. data/spec/extensions/postgres/inferrer_spec.rb +19 -9
  37. data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
  38. data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
  39. data/spec/integration/association/many_to_many_spec.rb +2 -2
  40. data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
  41. data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
  42. data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
  43. data/spec/integration/association/many_to_one_spec.rb +4 -2
  44. data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
  45. data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
  46. data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
  47. data/spec/integration/association/one_to_many_spec.rb +1 -1
  48. data/spec/integration/association/one_to_one_spec.rb +1 -1
  49. data/spec/integration/association/one_to_one_through_spec.rb +2 -2
  50. data/spec/integration/commands/create_spec.rb +11 -27
  51. data/spec/integration/commands/update_spec.rb +54 -109
  52. data/spec/integration/gateway_spec.rb +31 -17
  53. data/spec/integration/plugins/associates_spec.rb +27 -0
  54. data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
  55. data/spec/integration/schema/call_spec.rb +24 -0
  56. data/spec/integration/schema/prefix_spec.rb +18 -0
  57. data/spec/integration/schema/qualified_spec.rb +18 -0
  58. data/spec/integration/schema/rename_spec.rb +23 -0
  59. data/spec/integration/schema/view_spec.rb +29 -0
  60. data/spec/integration/schema_inference_spec.rb +31 -14
  61. data/spec/spec_helper.rb +2 -2
  62. data/spec/support/helpers.rb +7 -0
  63. data/spec/unit/gateway_spec.rb +5 -4
  64. data/spec/unit/projection_dsl_spec.rb +54 -0
  65. data/spec/unit/relation/dataset_spec.rb +3 -3
  66. data/spec/unit/relation/distinct_spec.rb +8 -7
  67. data/spec/unit/relation/exclude_spec.rb +2 -4
  68. data/spec/unit/relation/having_spec.rb +6 -4
  69. data/spec/unit/relation/inner_join_spec.rb +47 -2
  70. data/spec/unit/relation/invert_spec.rb +2 -3
  71. data/spec/unit/relation/left_join_spec.rb +44 -3
  72. data/spec/unit/relation/order_spec.rb +40 -0
  73. data/spec/unit/relation/prefix_spec.rb +2 -0
  74. data/spec/unit/relation/project_spec.rb +3 -1
  75. data/spec/unit/relation/qualified_columns_spec.rb +2 -0
  76. data/spec/unit/relation/rename_spec.rb +2 -0
  77. data/spec/unit/relation/right_join_spec.rb +59 -0
  78. data/spec/unit/relation/select_append_spec.rb +21 -0
  79. data/spec/unit/relation/select_spec.rb +41 -0
  80. data/spec/unit/relation/where_spec.rb +28 -0
  81. data/spec/unit/restriction_dsl_spec.rb +34 -0
  82. metadata +62 -40
  83. data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
  84. data/lib/rom/sql/header.rb +0 -61
  85. data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
  86. data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
  87. data/spec/integration/read_spec.rb +0 -111
  88. data/spec/unit/association_errors_spec.rb +0 -19
  89. data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
  90. data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
  91. data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
  92. data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
  93. data/spec/unit/plugin/base_view_spec.rb +0 -18
@@ -0,0 +1,81 @@
1
+ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
2
+ subject(:assoc) do
3
+ relations[:users].associations[:solved_puzzles]
4
+ end
5
+
6
+ include_context 'database setup'
7
+
8
+ with_adapters do
9
+ before do
10
+ conn.create_table(:puzzles) do
11
+ primary_key :id
12
+ column :text, String, null: false
13
+ column :solved, TrueClass, null: false, default: false
14
+ end
15
+
16
+ conn.create_table(:puzzle_solvers) do
17
+ foreign_key :user_id, :users, null: false
18
+ foreign_key :puzzle_id, :puzzles, null: false
19
+ primary_key [:user_id, :puzzle_id]
20
+ end
21
+
22
+ conf.relation(:puzzles) do
23
+ schema(infer: true)
24
+
25
+ view(:solved, schema) do
26
+ where(solved: true)
27
+ end
28
+ end
29
+
30
+ conf.relation(:puzzle_solvers) do
31
+ schema(infer: true) do
32
+ associations do
33
+ belongs_to :user
34
+ belongs_to :puzzle
35
+ end
36
+ end
37
+ end
38
+
39
+ conf.relation(:users) do
40
+ schema(infer: true) do
41
+ associations do
42
+ has_many :puzzle_solvers
43
+ has_many :puzzles, through: :puzzle_solvers
44
+ has_many :puzzles, through: :puzzle_solvers, as: :solved_puzzles, view: :solved
45
+ end
46
+ end
47
+ end
48
+
49
+ joe_id = relations[:users].insert(name: 'Joe')
50
+ jane_id = relations[:users].insert(name: 'Jane')
51
+
52
+ p1_id = relations[:puzzles].insert(text: 'P1')
53
+ p2_id = relations[:puzzles].insert(text: 'P2', solved: true)
54
+ p3_id = relations[:puzzles].insert(text: 'P3')
55
+
56
+ relations[:puzzle_solvers].insert(user_id: joe_id, puzzle_id: p2_id)
57
+ relations[:puzzle_solvers].insert(user_id: jane_id, puzzle_id: p2_id)
58
+
59
+ relations[:puzzle_solvers].insert(user_id: joe_id, puzzle_id: p1_id)
60
+ relations[:puzzle_solvers].insert(user_id: jane_id, puzzle_id: p3_id)
61
+ end
62
+
63
+ after do
64
+ conn.drop_table?(:puzzle_solvers)
65
+ conn.drop_table?(:puzzles)
66
+ end
67
+
68
+ it 'prepares joined relations using custom FK' do
69
+ relation = assoc.call(relations).order(:puzzles__text)
70
+
71
+ expect(relation.schema.map(&:to_sym)).
72
+ to eql(%i[puzzles__id puzzles__text puzzles__solved puzzle_solvers__user_id])
73
+
74
+ expect(relation.to_a).
75
+ to eql([
76
+ { id: 2, user_id: 1, solved: true, text: 'P2' },
77
+ { id: 2, user_id: 2, solved: true, text: 'P2' }
78
+ ])
79
+ end
80
+ end
81
+ end
@@ -109,7 +109,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
109
109
  it 'prepares joined relations' do
110
110
  relation = assoc.call(container.relations)
111
111
 
112
- expect(relation.attributes).to eql(%i[id name task_id])
112
+ expect(relation.schema.map(&:to_sym)).to eql(%i[tags__id tags__name task_tags__task_id])
113
113
  expect(relation.to_a).to eql([id: 1, name: 'important', task_id: 1])
114
114
  end
115
115
  end
@@ -122,7 +122,7 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
122
122
  it 'prepares joined relations through other association' do
123
123
  relation = assoc.call(container.relations)
124
124
 
125
- expect(relation.attributes).to eql(%i[id name user_id])
125
+ expect(relation.schema.map(&:to_sym)).to eql(%i[tags__id tags__name tasks__user_id])
126
126
  expect(relation.to_a).to eql([id: 1, name: 'important', user_id: 2])
127
127
  end
128
128
  end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::SQL::Association::ManyToOne, '#call' do
4
+ let(:assoc_from) { relations[:flights].associations[:from] }
5
+ let(:assoc_to) { relations[:flights].associations[:to] }
6
+
7
+ include_context 'database setup'
8
+
9
+ with_adapters do
10
+ before do
11
+ conn.create_table(:destinations) do
12
+ primary_key :id
13
+ column :name, String, null: false
14
+ end
15
+
16
+ conn.create_table(:flights) do
17
+ primary_key :id
18
+ foreign_key :from_id, :destinations, null: false
19
+ foreign_key :to_id, :destinations, null: false
20
+ column :code, String, null: false
21
+ end
22
+
23
+ conf.relation(:flights) do
24
+ schema(infer: true) do
25
+ associations do
26
+ belongs_to :destination, as: :from, foreign_key: :from_id
27
+ belongs_to :destination, as: :to, foreign_key: :to_id
28
+ end
29
+ end
30
+ end
31
+
32
+ from_id = relations[:destinations].insert(name: 'FROM')
33
+ to_id = relations[:destinations].insert(name: 'TO')
34
+
35
+ relations[:flights].insert(code: 'F1', from_id: from_id, to_id: to_id)
36
+ end
37
+
38
+ after do
39
+ conn.drop_table(:flights)
40
+ conn.drop_table(:destinations)
41
+ end
42
+
43
+ it 'prepares joined relations using correct FKs based on association aliases' do
44
+ relation = assoc_from.call(relations)
45
+
46
+ expect(relation.schema.map(&:to_sym)).
47
+ to eql(%i(destinations__id destinations__name flights__id___flight_id))
48
+
49
+ expect(relation.first).to eql(id: 1, name: 'FROM', flight_id: 1)
50
+
51
+ relation = assoc_to.call(relations)
52
+
53
+ expect(relation.schema.map(&:to_sym)).
54
+ to eql(%i(destinations__id destinations__name flights__id___flight_id))
55
+
56
+ expect(relation.first).to eql(id: 2, name: 'TO', flight_id: 1)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::SQL::Association::ManyToOne, '#call' do
4
+ let(:assoc_inter) { relations[:flights].associations[:inter_destination] }
5
+ let(:assoc_final) { relations[:flights].associations[:final_destination] }
6
+
7
+ include_context 'database setup'
8
+
9
+ with_adapters do
10
+ before do
11
+ conn.create_table(:destinations) do
12
+ primary_key :id
13
+ column :name, String, null: false
14
+ column :intermediate, TrueClass, null: false, default: false
15
+ end
16
+
17
+ conn.create_table(:flights) do
18
+ primary_key :id
19
+ foreign_key :destination_id, :destinations, null: false
20
+ column :code, String, null: false
21
+ end
22
+
23
+ conf.relation(:destinations) do
24
+ schema(infer: true)
25
+
26
+ view(:intermediate, schema) do
27
+ where(intermediate: true)
28
+ end
29
+
30
+ view(:final, schema) do
31
+ where(intermediate: false)
32
+ end
33
+ end
34
+
35
+ conf.relation(:flights) do
36
+ schema(infer: true) do
37
+ associations do
38
+ belongs_to :destination, as: :inter_destination, view: :intermediate
39
+ belongs_to :destination, as: :final_destination, view: :final
40
+ end
41
+ end
42
+ end
43
+
44
+ final_id = relations[:destinations].insert(name: 'Final')
45
+ inter_id = relations[:destinations].insert(name: 'Intermediate', intermediate: true)
46
+
47
+ relations[:flights].insert(code: 'F1', destination_id: inter_id)
48
+ relations[:flights].insert(code: 'F2', destination_id: final_id)
49
+ end
50
+
51
+ after do
52
+ conn.drop_table(:flights)
53
+ conn.drop_table(:destinations)
54
+ end
55
+
56
+ it 'prepares joined relations using custom view in target relation' do
57
+ relation = assoc_inter.call(relations)
58
+
59
+ expect(relation.schema.map(&:to_sym)).
60
+ to eql(%i(destinations__id destinations__name destinations__intermediate flights__id___flight_id))
61
+
62
+ expect(relation.first).to eql(id: 2, intermediate: true, name: 'Intermediate', flight_id: 1)
63
+ expect(relation.count).to be(1)
64
+
65
+ relation = assoc_final.call(relations)
66
+
67
+ expect(relation.schema.map(&:to_sym)).
68
+ to eql(%i(destinations__id destinations__name destinations__intermediate flights__id___flight_id))
69
+
70
+ expect(relation.first).to eql(id: 1, intermediate: false, name: 'Final', flight_id: 2)
71
+ expect(relation.count).to be(1)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
4
+ subject(:assoc) do
5
+ relations[:categories].associations[:parent]
6
+ end
7
+
8
+ include_context 'database setup'
9
+
10
+ with_adapters do
11
+ before do
12
+ conn.create_table(:categories) do
13
+ primary_key :id
14
+ foreign_key :parent_id, :categories, null: true
15
+ column :name, String, null: false
16
+ end
17
+
18
+ conf.relation(:categories) do
19
+ schema(infer: true) do
20
+ associations do
21
+ belongs_to :categories, as: :parent, foreign_key: :parent_id
22
+ end
23
+ end
24
+ end
25
+
26
+ p1_id = relations[:categories].insert(name: 'P1')
27
+ p2_id = relations[:categories].insert(name: 'P2')
28
+ c1_id = relations[:categories].insert(name: 'C3', parent_id: p2_id)
29
+ c2_id = relations[:categories].insert(name: 'C4', parent_id: p1_id)
30
+ c3_id = relations[:categories].insert(name: 'C5', parent_id: p1_id)
31
+ end
32
+
33
+ after do
34
+ conn.drop_table(:categories)
35
+ end
36
+
37
+ it 'prepares joined relations using custom FK for a self-ref association' do
38
+ relation = assoc.call(relations)
39
+
40
+ expect(relation.schema.map(&:to_sym)).
41
+ to eql(%i[categories__id categories__parent_id categories__name])
42
+
43
+ expect(relation.to_a).
44
+ to eql([
45
+ { id: 1, parent_id: nil, name: 'P1' },
46
+ { id: 1, parent_id: nil, name: 'P1' },
47
+ { id: 2, parent_id: nil, name: 'P2' }
48
+ ])
49
+ end
50
+ end
51
+ end
@@ -43,7 +43,8 @@ RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
43
43
  it 'prepares joined relations' do
44
44
  relation = assoc.call(container.relations)
45
45
 
46
- expect(relation.attributes).to eql(%i[id name task_id])
46
+ expect(relation.schema.map(&:to_sym))
47
+ .to eql(%i[users__id users__name tasks__id___task_id])
47
48
 
48
49
  expect(relation.where(user_id: 1).one).to eql(id: 1, task_id: 2, name: 'Jane')
49
50
 
@@ -89,7 +90,8 @@ RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
89
90
  it 'prepares joined relations' do
90
91
  relation = assoc.call(container.relations)
91
92
 
92
- expect(relation.attributes).to eql(%i[id name post_id])
93
+ expect(relation.schema.map(&:to_sym))
94
+ .to eql(%i[users__id users__name posts__post_id])
93
95
 
94
96
  expect(relation.order(:id).to_a).to eql([
95
97
  { id: 1, name: 'Jane', post_id: 2 },
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
4
+ subject(:assoc) do
5
+ relations[:users].associations[:solved_puzzles]
6
+ end
7
+
8
+ include_context 'database setup'
9
+
10
+ with_adapters do
11
+ before do
12
+ conn.create_table(:puzzles) do
13
+ primary_key :id
14
+ foreign_key :author_id, :users, null: false
15
+ foreign_key :solver_id, :users, null: true
16
+ column :text, String, null: false
17
+ end
18
+
19
+ conf.relation(:users) do
20
+ schema(infer: true) do
21
+ associations do
22
+ has_many :puzzles, as: :created_puzzles, foreign_key: :author_id
23
+ has_many :puzzles, as: :solved_puzzles, foreign_key: :solver_id
24
+ end
25
+ end
26
+ end
27
+
28
+ joe_id = relations[:users].insert(name: 'Joe')
29
+ jane_id = relations[:users].insert(name: 'Jane')
30
+
31
+ relations[:puzzles].insert(author_id: joe_id, text: 'P1')
32
+ relations[:puzzles].insert(author_id: joe_id, solver_id: jane_id, text: 'P2')
33
+ end
34
+
35
+ after do
36
+ conn.drop_table(:puzzles)
37
+ end
38
+
39
+ it 'prepares joined relations using custom FK' do
40
+ relation = assoc.call(relations)
41
+
42
+ expect(relation.schema.map(&:to_sym)).
43
+ to eql(%i[puzzles__id puzzles__author_id puzzles__solver_id puzzles__text])
44
+
45
+ expect(relation.first).to eql(id: 2, author_id: 1, solver_id: 2, text: 'P2')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
4
+ subject(:assoc) do
5
+ relations[:users].associations[:solved_puzzles]
6
+ end
7
+
8
+ include_context 'database setup'
9
+
10
+ with_adapters do
11
+ before do
12
+ conn.create_table(:puzzles) do
13
+ primary_key :id
14
+ foreign_key :user_id, :users, null: false
15
+ column :text, String, null: false
16
+ column :solved, TrueClass, null: false, default: false
17
+ end
18
+
19
+ conf.relation(:users) do
20
+ schema(infer: true) do
21
+ associations do
22
+ has_many :puzzles
23
+ has_many :puzzles, as: :solved_puzzles, view: :solved
24
+ end
25
+ end
26
+ end
27
+
28
+ conf.relation(:puzzles) do
29
+ schema(infer: true)
30
+
31
+ view(:solved, schema) do
32
+ where(solved: true)
33
+ end
34
+ end
35
+
36
+ joe_id = relations[:users].insert(name: 'Joe')
37
+ jane_id = relations[:users].insert(name: 'Jane')
38
+
39
+ relations[:puzzles].insert(user_id: joe_id, text: 'P1')
40
+ relations[:puzzles].insert(user_id: joe_id, solved: true, text: 'P2')
41
+ end
42
+
43
+ after do
44
+ conn.drop_table(:puzzles)
45
+ end
46
+
47
+ it 'prepares joined relations using custom view' do
48
+ relation = assoc.call(relations)
49
+
50
+ expect(relation.schema.map(&:to_sym)).
51
+ to eql(%i[puzzles__id puzzles__user_id puzzles__text puzzles__solved])
52
+
53
+ expect(relation.count).to be(1)
54
+ expect(relation.first).to eql(id: 2, user_id: 1, solved: true, text: 'P2')
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
4
+ subject(:assoc) do
5
+ relations[:categories].associations[:children]
6
+ end
7
+
8
+ include_context 'database setup'
9
+
10
+ with_adapters do
11
+ before do
12
+ conn.create_table(:categories) do
13
+ primary_key :id
14
+ foreign_key :parent_id, :categories, null: true
15
+ column :name, String, null: false
16
+ end
17
+
18
+ conf.relation(:categories) do
19
+ schema(infer: true) do
20
+ associations do
21
+ belongs_to :categories, as: :parent, foreign_key: :parent_id
22
+ has_many :categories, as: :children, foreign_key: :parent_id
23
+ end
24
+ end
25
+ end
26
+
27
+ p1_id = relations[:categories].insert(name: 'P1')
28
+ p2_id = relations[:categories].insert(name: 'P2')
29
+ c1_id = relations[:categories].insert(name: 'C3', parent_id: p2_id)
30
+ c2_id = relations[:categories].insert(name: 'C4', parent_id: p1_id)
31
+ c3_id = relations[:categories].insert(name: 'C5', parent_id: p1_id)
32
+ end
33
+
34
+ after do
35
+ conn.drop_table(:categories)
36
+ end
37
+
38
+ it 'prepares joined relations using custom FK for a self-ref association' do
39
+ relation = assoc.call(relations)
40
+
41
+ expect(relation.schema.map(&:to_sym)).
42
+ to eql(%i[categories__id categories__parent_id categories__name])
43
+
44
+ expect(relation.to_a).
45
+ to eql([
46
+ { id: 3, parent_id: 2, name: 'C3' },
47
+ { id: 4, parent_id: 1, name: 'C4' },
48
+ { id: 5, parent_id: 1, name: 'C5' }
49
+ ])
50
+ end
51
+ end
52
+ end