rom-sql 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/.travis.yml +12 -7
  4. data/CHANGELOG.md +28 -0
  5. data/Gemfile +6 -9
  6. data/README.md +5 -4
  7. data/circle.yml +10 -0
  8. data/lib/rom/plugins/relation/sql/auto_combine.rb +16 -3
  9. data/lib/rom/plugins/relation/sql/auto_wrap.rb +3 -2
  10. data/lib/rom/sql/association.rb +75 -0
  11. data/lib/rom/sql/association/many_to_many.rb +86 -0
  12. data/lib/rom/sql/association/many_to_one.rb +60 -0
  13. data/lib/rom/sql/association/name.rb +70 -0
  14. data/lib/rom/sql/association/one_to_many.rb +9 -0
  15. data/lib/rom/sql/association/one_to_one.rb +46 -0
  16. data/lib/rom/sql/association/one_to_one_through.rb +9 -0
  17. data/lib/rom/sql/commands.rb +2 -0
  18. data/lib/rom/sql/commands/create.rb +2 -2
  19. data/lib/rom/sql/commands/delete.rb +0 -1
  20. data/lib/rom/sql/commands/postgres.rb +76 -0
  21. data/lib/rom/sql/commands/update.rb +6 -3
  22. data/lib/rom/sql/commands_ext/postgres.rb +17 -0
  23. data/lib/rom/sql/gateway.rb +23 -15
  24. data/lib/rom/sql/header.rb +7 -1
  25. data/lib/rom/sql/plugin/assoc_macros.rb +3 -3
  26. data/lib/rom/sql/plugin/associates.rb +50 -9
  27. data/lib/rom/sql/qualified_attribute.rb +53 -0
  28. data/lib/rom/sql/relation.rb +76 -25
  29. data/lib/rom/sql/relation/reading.rb +138 -35
  30. data/lib/rom/sql/relation/writing.rb +21 -0
  31. data/lib/rom/sql/schema.rb +35 -0
  32. data/lib/rom/sql/schema/associations_dsl.rb +68 -0
  33. data/lib/rom/sql/schema/dsl.rb +27 -0
  34. data/lib/rom/sql/schema/inferrer.rb +80 -0
  35. data/lib/rom/sql/support/active_support_notifications.rb +27 -17
  36. data/lib/rom/sql/types.rb +11 -0
  37. data/lib/rom/sql/types/pg.rb +26 -0
  38. data/lib/rom/sql/version.rb +1 -1
  39. data/rom-sql.gemspec +4 -2
  40. data/spec/integration/association/many_to_many_spec.rb +137 -0
  41. data/spec/integration/association/many_to_one_spec.rb +110 -0
  42. data/spec/integration/association/one_to_many_spec.rb +58 -0
  43. data/spec/integration/association/one_to_one_spec.rb +57 -0
  44. data/spec/integration/association/one_to_one_through_spec.rb +90 -0
  45. data/spec/integration/combine_spec.rb +24 -24
  46. data/spec/integration/commands/create_spec.rb +215 -168
  47. data/spec/integration/commands/delete_spec.rb +88 -46
  48. data/spec/integration/commands/update_spec.rb +141 -60
  49. data/spec/integration/commands/upsert_spec.rb +83 -0
  50. data/spec/integration/gateway_spec.rb +9 -17
  51. data/spec/integration/migration_spec.rb +3 -5
  52. data/spec/integration/plugins/associates_spec.rb +168 -0
  53. data/spec/integration/plugins/auto_wrap_spec.rb +46 -0
  54. data/spec/integration/read_spec.rb +80 -77
  55. data/spec/integration/relation_schema_spec.rb +180 -0
  56. data/spec/integration/schema_inference_spec.rb +67 -0
  57. data/spec/integration/setup_spec.rb +22 -0
  58. data/spec/{support → integration/support}/active_support_notifications_spec.rb +0 -0
  59. data/spec/{support → integration/support}/rails_log_subscriber_spec.rb +0 -0
  60. data/spec/shared/database_setup.rb +46 -8
  61. data/spec/shared/relations.rb +8 -0
  62. data/spec/shared/users_and_accounts.rb +10 -0
  63. data/spec/shared/users_and_tasks.rb +20 -2
  64. data/spec/spec_helper.rb +64 -11
  65. data/spec/support/helpers.rb +9 -0
  66. data/spec/unit/association/many_to_many_spec.rb +89 -0
  67. data/spec/unit/association/many_to_one_spec.rb +81 -0
  68. data/spec/unit/association/name_spec.rb +68 -0
  69. data/spec/unit/association/one_to_many_spec.rb +62 -0
  70. data/spec/unit/association/one_to_one_spec.rb +62 -0
  71. data/spec/unit/association/one_to_one_through_spec.rb +69 -0
  72. data/spec/unit/association_errors_spec.rb +2 -4
  73. data/spec/unit/gateway_spec.rb +12 -3
  74. data/spec/unit/migration_tasks_spec.rb +3 -3
  75. data/spec/unit/migrator_spec.rb +2 -4
  76. data/spec/unit/{combined_associations_spec.rb → plugin/assoc_macros/combined_associations_spec.rb} +13 -19
  77. data/spec/unit/{many_to_many_spec.rb → plugin/assoc_macros/many_to_many_spec.rb} +9 -15
  78. data/spec/unit/{many_to_one_spec.rb → plugin/assoc_macros/many_to_one_spec.rb} +9 -14
  79. data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +78 -0
  80. data/spec/unit/plugin/base_view_spec.rb +11 -11
  81. data/spec/unit/plugin/pagination_spec.rb +62 -62
  82. data/spec/unit/relation_spec.rb +218 -146
  83. data/spec/unit/schema_spec.rb +15 -14
  84. data/spec/unit/types_spec.rb +40 -0
  85. metadata +105 -21
  86. data/.rubocop.yml +0 -74
  87. data/.rubocop_todo.yml +0 -21
  88. data/spec/unit/one_to_many_spec.rb +0 -83
@@ -0,0 +1,9 @@
1
+ module Helpers
2
+ def qualified_attribute(*args)
3
+ ROM::SQL::QualifiedAttribute[*args]
4
+ end
5
+
6
+ def assoc_name(*args)
7
+ ROM::SQL::Association::Name[*args]
8
+ end
9
+ end
@@ -0,0 +1,89 @@
1
+ RSpec.describe ROM::SQL::Association::ManyToMany, helpers: true do
2
+ subject(:assoc) do
3
+ ROM::SQL::Association::ManyToMany.new(source, target, options)
4
+ end
5
+
6
+ let(:options) { { through: :tasks_tags } }
7
+
8
+ let(:tags) { double(:tags, primary_key: :id) }
9
+ let(:tasks) { double(:tasks, primary_key: :id) }
10
+ let(:tasks_tags) { double(:tasks, primary_key: [:task_id, :tag_id]) }
11
+
12
+ let(:source) { :tasks }
13
+ let(:target) { :tags }
14
+
15
+ let(:relations) do
16
+ { tasks: tasks, tags: tags, tasks_tags: tasks_tags }
17
+ end
18
+
19
+ shared_examples_for 'many-to-many association' do
20
+ describe '#combine_keys' do
21
+ it 'returns a hash with combine keys' do
22
+ expect(tasks_tags).to receive(:foreign_key).with(:tasks).and_return(:tag_id)
23
+
24
+ expect(assoc.combine_keys(relations)).to eql(id: :tag_id)
25
+ end
26
+ end
27
+ end
28
+
29
+ describe '#result' do
30
+ it 'is :many' do
31
+ expect(assoc.result).to be(:many)
32
+ end
33
+ end
34
+
35
+ describe '#associate' do
36
+ let(:join_assoc) { double(:join_assoc) }
37
+
38
+ let(:source) { :tags }
39
+ let(:target) { :tasks }
40
+
41
+ it 'returns a list of join keys for given child tuples' do
42
+ expect(tasks_tags).to receive(:associations).and_return(assoc.target => join_assoc)
43
+ expect(join_assoc).to receive(:join_key_map).with(relations).and_return([:task_id, :id])
44
+ expect(tasks_tags).to receive(:foreign_key).with(:tags).and_return(:tag_id)
45
+
46
+ task_tuple = { id: 3 }
47
+ tag_tuples = [{ id: 1 }, { id: 2 }]
48
+
49
+ expect(assoc.associate(relations, tag_tuples, task_tuple)).to eql([
50
+ { tag_id: 1, task_id: 3 }, { tag_id: 2, task_id: 3 }
51
+ ])
52
+ end
53
+ end
54
+
55
+ context 'with default names' do
56
+ it_behaves_like 'many-to-many association'
57
+
58
+ describe '#join_keys' do
59
+ it 'returns a hash with combine keys' do
60
+ expect(tasks_tags).to receive(:foreign_key).with(:tasks).and_return(:tag_id)
61
+
62
+ expect(assoc.join_keys(relations)).to eql(
63
+ qualified_attribute(:tasks, :id) => qualified_attribute(:tasks_tags, :tag_id)
64
+ )
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'with custom relation names' do
70
+ let(:source) { assoc_name(:tasks, :user_tasks) }
71
+ let(:target) { assoc_name(:tags, :user_tags) }
72
+
73
+ let(:relations) do
74
+ { tasks: tasks, tags: tags, tasks_tags: tasks_tags }
75
+ end
76
+
77
+ it_behaves_like 'many-to-many association'
78
+
79
+ describe '#join_keys' do
80
+ it 'returns a hash with combine keys' do
81
+ expect(tasks_tags).to receive(:foreign_key).with(:tasks).and_return(:tag_id)
82
+
83
+ expect(assoc.join_keys(relations)).to eql(
84
+ qualified_attribute(:user_tasks, :id) => qualified_attribute(:tasks_tags, :tag_id)
85
+ )
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,81 @@
1
+ RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
2
+ subject(:assoc) do
3
+ ROM::SQL::Association::ManyToOne.new(source, target, options)
4
+ end
5
+
6
+ let(:options) { {} }
7
+
8
+ let(:users) { double(:users, primary_key: :id) }
9
+ let(:tasks) { double(:tasks) }
10
+
11
+ let(:source) { :tasks }
12
+ let(:target) { :users }
13
+
14
+ let(:relations) do
15
+ { users: users, tasks: tasks }
16
+ end
17
+
18
+ describe '#result' do
19
+ it 'is :one' do
20
+ expect(assoc.result).to be(:one)
21
+ end
22
+ end
23
+
24
+ describe '#associate' do
25
+ it 'returns child tuple with FK set' do
26
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
27
+
28
+ task_tuple = { title: 'Task' }
29
+ user_tuple = { id: 3 }
30
+
31
+ expect(assoc.associate(relations, task_tuple, user_tuple)).to eql(
32
+ user_id: 3, title: 'Task'
33
+ )
34
+ end
35
+ end
36
+
37
+ shared_examples_for 'many-to-many association' do
38
+ describe '#combine_keys' do
39
+ it 'returns a hash with combine keys' do
40
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
41
+
42
+ expect(assoc.combine_keys(relations)).to eql(user_id: :id)
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'with default names' do
48
+ it_behaves_like 'many-to-many association'
49
+
50
+ describe '#join_keys' do
51
+ it 'returns a hash with combine keys' do
52
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
53
+
54
+ expect(assoc.join_keys(relations)).to eql(
55
+ qualified_attribute(:tasks, :user_id) => qualified_attribute(:users, :id)
56
+ )
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'with custom relation names' do
62
+ let(:source) { assoc_name(:tasks, :user_tasks) }
63
+ let(:target) { assoc_name(:users, :people) }
64
+
65
+ let(:relations) do
66
+ { users: users, tasks: tasks }
67
+ end
68
+
69
+ it_behaves_like 'many-to-many association'
70
+
71
+ describe '#join_keys' do
72
+ it 'returns a hash with combine keys' do
73
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
74
+
75
+ expect(assoc.join_keys(relations)).to eql(
76
+ qualified_attribute(:user_tasks, :user_id) => qualified_attribute(:people, :id)
77
+ )
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,68 @@
1
+ RSpec.describe ROM::SQL::Association::Name do
2
+ describe '.[]' do
3
+ it 'returns a name object from a relation name' do
4
+ rel_name = ROM::Relation::Name[:users]
5
+ assoc_name = ROM::SQL::Association::Name[rel_name]
6
+
7
+ expect(assoc_name).to eql(ROM::SQL::Association::Name.new(rel_name, :users))
8
+ end
9
+
10
+ it 'returns a name object from a relation and a dataset symbols' do
11
+ rel_name = ROM::Relation::Name[:users, :people]
12
+ assoc_name = ROM::SQL::Association::Name[:users, :people]
13
+
14
+ expect(assoc_name).to eql(ROM::SQL::Association::Name.new(rel_name, :people))
15
+ end
16
+
17
+ it 'returns a name object from a relation and a dataset symbols and an alias' do
18
+ rel_name = ROM::Relation::Name[:users, :people]
19
+ assoc_name = ROM::SQL::Association::Name[:users, :people, :author]
20
+
21
+ expect(assoc_name).to eql(ROM::SQL::Association::Name.new(rel_name, :author))
22
+ end
23
+
24
+ it 'caches names' do
25
+ name = ROM::SQL::Association::Name[:users]
26
+
27
+ expect(name).to be(ROM::SQL::Association::Name[:users])
28
+
29
+ name = ROM::SQL::Association::Name[:users, :people]
30
+
31
+ expect(name).to be(ROM::SQL::Association::Name[:users, :people])
32
+
33
+ name = ROM::SQL::Association::Name[:users, :people, :author]
34
+
35
+ expect(name).to be(ROM::SQL::Association::Name[:users, :people, :author])
36
+ end
37
+ end
38
+
39
+ describe '#aliased?' do
40
+ it 'returns true if a name has an alias' do
41
+ expect(ROM::SQL::Association::Name[:users, :people, :author]).to be_aliased
42
+ end
43
+
44
+ it 'returns false if a name has no alias' do
45
+ expect(ROM::SQL::Association::Name[:users, :people]).to_not be_aliased
46
+ end
47
+ end
48
+
49
+ describe '#inspect' do
50
+ it 'includes info about the relation name' do
51
+ expect(ROM::SQL::Association::Name[:users].inspect).to eql(
52
+ "ROM::SQL::Association::Name(users)"
53
+ )
54
+ end
55
+
56
+ it 'includes info about the relation name and its dataset' do
57
+ expect(ROM::SQL::Association::Name[:users, :people].inspect).to eql(
58
+ "ROM::SQL::Association::Name(users on people)"
59
+ )
60
+ end
61
+
62
+ it 'includes info about the relation name, its dataset and alias' do
63
+ expect(ROM::SQL::Association::Name[:users, :people, :author].inspect).to eql(
64
+ "ROM::SQL::Association::Name(users on people as author)"
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ RSpec.describe ROM::SQL::Association::OneToMany, helpers: true do
2
+ subject(:assoc) do
3
+ ROM::SQL::Association::OneToMany.new(source, target, options)
4
+ end
5
+
6
+ let(:options) { {} }
7
+
8
+ let(:users) { double(:users, primary_key: :id) }
9
+ let(:tasks) { double(:tasks) }
10
+
11
+ shared_examples_for 'one-to-many association' do
12
+ describe '#combine_keys' do
13
+ it 'returns a hash with combine keys' do
14
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
15
+
16
+ expect(assoc.combine_keys(relations)).to eql(id: :user_id)
17
+ end
18
+ end
19
+ end
20
+
21
+ context 'with default names' do
22
+ let(:source) { :users }
23
+ let(:target) { :tasks }
24
+
25
+ let(:relations) do
26
+ { users: users, tasks: tasks }
27
+ end
28
+
29
+ it_behaves_like 'one-to-many association'
30
+
31
+ describe '#join_keys' do
32
+ it 'returns a hash with combine keys' do
33
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
34
+
35
+ expect(assoc.join_keys(relations)).to eql(
36
+ qualified_attribute(:users, :id) => qualified_attribute(:tasks, :user_id)
37
+ )
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'with custom relation names' do
43
+ let(:source) { assoc_name(:users, :people) }
44
+ let(:target) { assoc_name(:tasks, :user_tasks) }
45
+
46
+ let(:relations) do
47
+ { users: users, tasks: tasks }
48
+ end
49
+
50
+ it_behaves_like 'one-to-many association'
51
+
52
+ describe '#join_keys' do
53
+ it 'returns a hash with combine keys' do
54
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
55
+
56
+ expect(assoc.join_keys(relations)).to eql(
57
+ qualified_attribute(:people, :id) => qualified_attribute(:user_tasks, :user_id)
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,62 @@
1
+ RSpec.describe ROM::SQL::Association::OneToOne, helpers: true do
2
+ subject(:assoc) do
3
+ ROM::SQL::Association::OneToOne.new(source, target, options)
4
+ end
5
+
6
+ let(:options) { {} }
7
+
8
+ let(:users) { double(:users, primary_key: :id) }
9
+ let(:tasks) { double(:tasks) }
10
+
11
+ shared_examples_for 'one-to-one association' do
12
+ describe '#combine_keys' do
13
+ it 'returns a hash with combine keys' do
14
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
15
+
16
+ expect(assoc.combine_keys(relations)).to eql(id: :user_id)
17
+ end
18
+ end
19
+ end
20
+
21
+ context 'with default names' do
22
+ let(:source) { :users }
23
+ let(:target) { :tasks }
24
+
25
+ let(:relations) do
26
+ { users: users, tasks: tasks }
27
+ end
28
+
29
+ it_behaves_like 'one-to-one association'
30
+
31
+ describe '#join_keys' do
32
+ it 'returns a hash with combine keys' do
33
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
34
+
35
+ expect(assoc.join_keys(relations)).to eql(
36
+ qualified_attribute(:users, :id) => qualified_attribute(:tasks, :user_id)
37
+ )
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'with custom relation names' do
43
+ let(:source) { assoc_name(:users, :people) }
44
+ let(:target) { assoc_name(:tasks, :user_tasks) }
45
+
46
+ let(:relations) do
47
+ { users: users, tasks: tasks }
48
+ end
49
+
50
+ it_behaves_like 'one-to-one association'
51
+
52
+ describe '#join_keys' do
53
+ it 'returns a hash with combine keys' do
54
+ expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
55
+
56
+ expect(assoc.join_keys(relations)).to eql(
57
+ qualified_attribute(:people, :id) => qualified_attribute(:user_tasks, :user_id)
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,69 @@
1
+ RSpec.describe ROM::SQL::Association::OneToOneThrough, helpers: true do
2
+ subject(:assoc) do
3
+ ROM::SQL::Association::OneToOneThrough.new(source, target, options)
4
+ end
5
+
6
+ let(:options) { { through: :tasks_tags } }
7
+
8
+ let(:tags) { double(:tags, primary_key: :id) }
9
+ let(:tasks) { double(:tasks, primary_key: :id) }
10
+ let(:tasks_tags) { double(:tasks, primary_key: [:task_id, :tag_id]) }
11
+
12
+ let(:source) { :tasks }
13
+ let(:target) { :tags }
14
+
15
+ describe '#result' do
16
+ it 'is :one' do
17
+ expect(assoc.result).to be(:one)
18
+ end
19
+ end
20
+
21
+ shared_examples_for 'many-to-many association' do
22
+ describe '#combine_keys' do
23
+ it 'returns a hash with combine keys' do
24
+ expect(tasks_tags).to receive(:foreign_key).with(:tasks).and_return(:tag_id)
25
+
26
+ expect(assoc.combine_keys(relations)).to eql(id: :tag_id)
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'with default names' do
32
+ let(:relations) do
33
+ { tasks: tasks, tags: tags, tasks_tags: tasks_tags }
34
+ end
35
+
36
+ it_behaves_like 'many-to-many association'
37
+
38
+ describe '#join_keys' do
39
+ it 'returns a hash with combine keys' do
40
+ expect(tasks_tags).to receive(:foreign_key).with(:tasks).and_return(:tag_id)
41
+
42
+ expect(assoc.join_keys(relations)).to eql(
43
+ qualified_attribute(:tasks, :id) => qualified_attribute(:tasks_tags, :tag_id)
44
+ )
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'with custom relation names' do
50
+ let(:source) { assoc_name(:tasks, :user_tasks) }
51
+ let(:target) { assoc_name(:tags, :user_tags) }
52
+
53
+ let(:relations) do
54
+ { tasks: tasks, tags: tags, tasks_tags: tasks_tags }
55
+ end
56
+
57
+ it_behaves_like 'many-to-many association'
58
+
59
+ describe '#join_keys' do
60
+ it 'returns a hash with combine keys' do
61
+ expect(tasks_tags).to receive(:foreign_key).with(:tasks).and_return(:tag_id)
62
+
63
+ expect(assoc.join_keys(relations)).to eql(
64
+ qualified_attribute(:user_tasks, :id) => qualified_attribute(:tasks_tags, :tag_id)
65
+ )
66
+ end
67
+ end
68
+ end
69
+ end