rom-sql 0.7.0 → 0.8.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 (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