rom-sql 0.9.1 → 1.0.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 (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