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
@@ -1,14 +1,12 @@
1
- require 'spec_helper'
2
-
3
1
  describe ROM::SQL::Gateway do
4
2
  describe 'migration' do
5
- let(:conn) { Sequel.connect(DB_URI) }
3
+ let(:conn) { Sequel.connect(POSTGRES_DB_URI) }
6
4
 
7
5
  context 'creating migrations inline' do
8
6
  subject(:gateway) { container.gateways[:default] }
9
7
 
10
- let(:configuration) { ROM::Configuration.new(:sql, conn) }
11
- let!(:container) { ROM.container(configuration) }
8
+ let(:conf) { ROM::Configuration.new(:sql, conn) }
9
+ let!(:container) { ROM.container(conf) }
12
10
 
13
11
  after do
14
12
  [:rabbits, :carrots].each do |name|
@@ -48,8 +46,8 @@ describe ROM::SQL::Gateway do
48
46
  end
49
47
 
50
48
  let(:migrator) { ROM::SQL::Migration::Migrator.new(conn, path: migration_dir) }
51
- let(:configuration) { ROM::Configuration.new(:sql, [conn, migrator: migrator]) }
52
- let!(:container) { ROM.container(configuration) }
49
+ let(:conf) { ROM::Configuration.new(:sql, [conn, migrator: migrator]) }
50
+ let!(:container) { ROM.container(conf) }
53
51
 
54
52
  it 'returns true for pending migrations' do
55
53
  expect(container.gateways[:default].pending_migrations?).to be_truthy
@@ -70,31 +68,25 @@ describe ROM::SQL::Gateway do
70
68
  include_context 'database setup'
71
69
 
72
70
  it 'skips settings up associations when tables are missing' do
73
- configuration = ROM::Configuration.new(:sql, uri) do |config|
74
- config.use(:macros)
75
-
71
+ conf = ROM::Configuration.new(:sql, uri) do |config|
76
72
  config.relation(:foos) do
77
73
  use :assoc_macros
78
- primary_key :id
79
74
  one_to_many :bars, key: :foo_id
80
75
  end
81
76
  end
82
- expect { ROM.container(configuration) }.not_to raise_error
77
+ expect { ROM.container(conf) }.not_to raise_error
83
78
  end
84
79
 
85
80
  it 'skips finalization a relation when table is missing' do
86
- configuration = ROM::Configuration.new(:sql, uri) do |config|
87
- config.use(:macros)
88
-
81
+ conf = ROM::Configuration.new(:sql, uri) do |config|
89
82
  class Foos < ROM::Relation[:sql]
90
83
  dataset :foos
91
84
  use :assoc_macros
92
- primary_key :id
93
85
  one_to_many :bars, key: :foo_id
94
86
  end
95
87
  end
96
88
 
97
- expect { ROM.container(configuration) }.not_to raise_error
89
+ expect { ROM.container(conf) }.not_to raise_error
98
90
  expect { Foos.model.dataset }.to raise_error(Sequel::Error, /no dataset/i)
99
91
  end
100
92
  end
@@ -1,11 +1,9 @@
1
- require 'spec_helper'
2
-
3
- describe ROM::SQL, '.migration' do
1
+ RSpec.describe ROM::SQL, '.migration' do
4
2
  let(:connection) { ROM::SQL.gateway.connection }
5
- let(:configuration) { ROM::Configuration.new(:sql, DB_URI) }
3
+ let(:conf) { ROM::Configuration.new(:sql, POSTGRES_DB_URI) }
6
4
 
7
5
  before do
8
- configuration
6
+ conf
9
7
  connection.drop_table?(:dragons)
10
8
  end
11
9
 
@@ -0,0 +1,168 @@
1
+ RSpec.describe 'Plugins / :associates' do
2
+ include_context 'relations'
3
+
4
+ with_adapters do
5
+ context 'with Create command' do
6
+ let(:users) { container.commands[:users] }
7
+ let(:tasks) { container.commands[:tasks] }
8
+ let(:tags) { container.commands[:tags] }
9
+
10
+ before do
11
+ conf.commands(:users) do
12
+ define(:create) { result :one }
13
+ end
14
+ end
15
+
16
+ shared_context 'automatic FK setting' do
17
+ it 'sets foreign key prior execution for many tuples' do
18
+ create_user = users[:create].with(name: 'Jade')
19
+ create_task = tasks[:create_many].with([{ title: 'Task one' }, { title: 'Task two' }])
20
+
21
+ command = create_user >> create_task
22
+
23
+ result = command.call
24
+
25
+ expect(result).to match_array([
26
+ { id: 1, user_id: 1, title: 'Task one' },
27
+ { id: 2, user_id: 1, title: 'Task two' }
28
+ ])
29
+ end
30
+
31
+ it 'sets foreign key prior execution for one tuple' do
32
+ create_user = users[:create].with(name: 'Jade')
33
+ create_task = tasks[:create_one].with(title: 'Task one')
34
+
35
+ command = create_user >> create_task
36
+
37
+ result = command.call
38
+
39
+ expect(result).to match_array(id: 1, user_id: 1, title: 'Task one')
40
+ end
41
+ end
42
+
43
+ context 'without a schema' do
44
+ include_context 'automatic FK setting' do
45
+ before do
46
+ conf.commands(:tasks) do
47
+ define(:create) do
48
+ register_as :create_many
49
+ associates :user, key: [:user_id, :id]
50
+ end
51
+
52
+ define(:create) do
53
+ register_as :create_one
54
+ result :one
55
+ associates :user, key: [:user_id, :id]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ context 'with a schema' do
63
+ include_context 'automatic FK setting'
64
+
65
+ before do
66
+ conf.relation_classes[1].class_eval do
67
+ schema do
68
+ attribute :id, ROM::SQL::Types::Serial
69
+ attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
70
+ attribute :title, ROM::SQL::Types::String
71
+
72
+ associations do
73
+ many_to_one :users, as: :user
74
+ one_to_many :task_tags
75
+ one_to_many :tags, through: :task_tags
76
+ end
77
+ end
78
+ end
79
+
80
+ conf.commands(:tasks) do
81
+ define(:create) do
82
+ register_as :create_many
83
+ associates :user
84
+ end
85
+
86
+ define(:create) do
87
+ register_as :create_one
88
+ result :one
89
+ associates :user
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'with many-to-many association' do
95
+ before do
96
+ conf.relation(:tags) do
97
+ schema do
98
+ attribute :id, ROM::SQL::Types::Serial
99
+ attribute :name, ROM::SQL::Types::String
100
+
101
+ associations do
102
+ one_to_many :task_tags
103
+ one_to_many :tasks, through: :task_tags
104
+ end
105
+ end
106
+ end
107
+
108
+ conf.relation(:task_tags) do
109
+ schema do
110
+ attribute :tag_id, ROM::SQL::Types::ForeignKey(:tags)
111
+ attribute :task_id, ROM::SQL::Types::ForeignKey(:tasks)
112
+
113
+ primary_key :tag_id, :task_id
114
+
115
+ associations do
116
+ many_to_one :tags
117
+ many_to_one :tasks
118
+ end
119
+ end
120
+ end
121
+
122
+ conf.commands(:tasks) do
123
+ define(:create) do
124
+ result :one
125
+ associates :user
126
+ end
127
+ end
128
+
129
+ conf.commands(:tags) do
130
+ define(:create) do
131
+ associates :tasks
132
+ end
133
+ end
134
+ end
135
+
136
+ it 'sets FKs for the join table' do
137
+ create_user = users[:create].with(name: 'Jade')
138
+ create_task = tasks[:create].with(title: "Jade's task")
139
+ create_tags = tags[:create].with([{ name: 'red' }, { name: 'blue' }])
140
+
141
+ command = create_user >> create_task >> create_tags
142
+
143
+ result = command.call
144
+ tags = relations[:tasks].associations[:tags].call(relations).to_a
145
+
146
+ expect(result).to eql([
147
+ { id: 1, task_id: 1, name: 'red' }, { id: 2, task_id: 1, name: 'blue' }
148
+ ])
149
+
150
+ expect(tags).to eql(result)
151
+ end
152
+ end
153
+ end
154
+
155
+ it 'raises when already defined' do
156
+ expect {
157
+ conf.commands(:tasks) do
158
+ define(:create) do
159
+ result :one
160
+ associates :user, key: [:user_id, :id]
161
+ associates :user, key: [:user_id, :id]
162
+ end
163
+ end
164
+ }.to raise_error(ArgumentError, /user/)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,46 @@
1
+ RSpec.describe 'Plugins / :auto_wrap' do
2
+ with_adapters do
3
+ include_context 'users and tasks'
4
+
5
+ describe '#for_wrap' do
6
+ shared_context 'joined tuple' do
7
+ it 'returns joined tuples' do
8
+ task_with_user = tasks
9
+ .for_wrap({ id: :user_id }, users.name.relation)
10
+ .where(tasks__id: 2)
11
+ .one
12
+
13
+ expect(task_with_user).to eql(
14
+ id: 2, user_id: 1, title: "Jane's task", users_name: "Jane", users_id: 1
15
+ )
16
+ end
17
+ end
18
+
19
+ context 'when parent relation is registered under dataset name' do
20
+ subject(:tasks) { container.relations[:tasks] }
21
+
22
+ let(:users) { container.relations[:users] }
23
+
24
+ before do
25
+ conf.relation(:tasks)
26
+ conf.relation(:users)
27
+ end
28
+
29
+ include_context 'joined tuple'
30
+ end
31
+
32
+ context 'when parent relation is registered under a custom name' do
33
+ subject(:tasks) { container.relations[:tasks] }
34
+
35
+ let(:users) { container.relations[:authors] }
36
+
37
+ before do
38
+ conf.relation(:tasks)
39
+ conf.relation(:authors) { dataset :users }
40
+ end
41
+
42
+ include_context 'joined tuple'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,116 +1,119 @@
1
- require 'spec_helper'
2
1
  require 'virtus'
3
2
 
4
- describe 'Reading relations' do
3
+ RSpec.describe 'Reading relations' do
5
4
  include_context 'users and tasks'
6
5
 
7
- before :each do
8
- class Goal
9
- include Virtus.value_object(coerce: true)
6
+ # FIXME: one example fails on :mysql
7
+ with_adapters(:postgres, :sqlite) do
8
+ before :each do
9
+ class Goal
10
+ include Virtus.value_object(coerce: true)
10
11
 
11
- values do
12
- attribute :id, Integer
13
- attribute :title, String
12
+ values do
13
+ attribute :id, Integer
14
+ attribute :title, String
15
+ end
14
16
  end
15
- end
16
17
 
17
- class User
18
- include Virtus.value_object(coerce: true)
18
+ class User
19
+ include Virtus.value_object(coerce: true)
19
20
 
20
- values do
21
- attribute :id, Integer
22
- attribute :name, String
23
- attribute :goals, Array[Goal]
21
+ values do
22
+ attribute :id, Integer
23
+ attribute :name, String
24
+ attribute :goals, Array[Goal]
25
+ end
24
26
  end
25
- end
26
27
 
27
- class UserGoalCount
28
- include Virtus.value_object(coerce: true)
28
+ class UserGoalCount
29
+ include Virtus.value_object(coerce: true)
29
30
 
30
- values do
31
- attribute :id, Integer
32
- attribute :name, String
33
- attribute :goal_count, Integer
31
+ values do
32
+ attribute :id, Integer
33
+ attribute :name, String
34
+ attribute :goal_count, Integer
35
+ end
34
36
  end
35
- end
36
37
 
37
- configuration.relation(:goals) do
38
- use :assoc_macros
38
+ conf.relation(:goals) do
39
+ use :assoc_macros
39
40
 
40
- register_as :goals
41
- dataset :tasks
42
- end
41
+ register_as :goals
42
+ dataset :tasks
43
+ end
43
44
 
44
- configuration.relation(:users) do
45
- use :assoc_macros
45
+ conf.relation(:users) do
46
+ use :assoc_macros
46
47
 
47
- one_to_many :goals, key: :user_id
48
+ one_to_many :goals, key: :user_id
48
49
 
49
- def by_name(name)
50
- where(name: name)
51
- end
50
+ def by_name(name)
51
+ where(name: name)
52
+ end
52
53
 
53
- def with_goals
54
- association_left_join(:goals, select: [:id, :title])
55
- end
54
+ def with_goals
55
+ association_left_join(:goals, select: [:id, :title])
56
+ end
56
57
 
57
- def all
58
- select(:id, :name)
58
+ def all
59
+ select(:id, :name)
60
+ end
59
61
  end
60
- end
61
62
 
62
- configuration.relation(:user_goal_counts) do
63
- use :assoc_macros
63
+ conf.relation(:user_goal_counts) do
64
+ use :assoc_macros
64
65
 
65
- dataset :users
66
- register_as :user_goal_counts
67
- one_to_many :goals, key: :user_id
66
+ dataset :users
67
+ register_as :user_goal_counts
68
+ one_to_many :goals, key: :user_id
68
69
 
69
- def all
70
- with_goals.select_group(:users__id, :users__name).select_append {
71
- count(:tasks).as(:goal_count)
72
- }
73
- end
70
+ def all
71
+ with_goals.select_group(:users__id, :users__name).select_append {
72
+ count(:tasks).as(:goal_count)
73
+ }
74
+ end
74
75
 
75
- def with_goals
76
- association_left_join(:goals, select: [:id, :title])
76
+ def with_goals
77
+ association_left_join(:goals, select: [:id, :title])
78
+ end
77
79
  end
78
- end
79
80
 
80
- configuration.mappers do
81
- define(:users) do
82
- model User
81
+ conf.mappers do
82
+ define(:users) do
83
+ model User
83
84
 
84
- group :goals do
85
- model Goal
85
+ group :goals do
86
+ model Goal
86
87
 
87
- attribute :id, from: :tasks_id
88
- attribute :title
88
+ attribute :id, from: :tasks_id
89
+ attribute :title
90
+ end
89
91
  end
90
- end
91
92
 
92
- define(:user_goal_counts) do
93
- model UserGoalCount
93
+ define(:user_goal_counts) do
94
+ model UserGoalCount
95
+ end
94
96
  end
95
97
  end
96
- end
97
98
 
98
- it 'loads domain objects' do
99
- user = container.relation(:users).as(:users).with_goals.by_name('Piotr').to_a.first
99
+ it 'loads domain objects' do
100
+ user = container.relation(:users).as(:users).with_goals.by_name('Jane').to_a.first
100
101
 
101
- expect(user).to eql(
102
- User.new(
103
- id: 1, name: 'Piotr', goals: [Goal.new(id: 1, title: 'Finish ROM')]
104
- ))
105
- end
102
+ expect(user).to eql(
103
+ User.new(
104
+ id: 1, name: 'Jane', goals: [Goal.new(id: 2, title: "Jane's task")]
105
+ ))
106
+ end
106
107
 
107
- it 'works with grouping and aggregates' do
108
- container.relations[:goals].insert(id: 2, user_id: 1, title: 'Get Milk')
108
+ it 'works with grouping and aggregates' do
109
+ container.relations[:goals].insert(id: 3, user_id: 1, title: 'Get Milk')
109
110
 
110
- users_with_goal_count = container.relation(:user_goal_counts).as(:user_goal_counts).all
111
+ users_with_goal_count = container.relation(:user_goal_counts).as(:user_goal_counts).all
111
112
 
112
- expect(users_with_goal_count.to_a).to eq([
113
- UserGoalCount.new(id: 1, name: "Piotr", goal_count: 2)
114
- ])
113
+ expect(users_with_goal_count.to_a).to eq([
114
+ UserGoalCount.new(id: 1, name: "Jane", goal_count: 2),
115
+ UserGoalCount.new(id: 2, name: "Joe", goal_count: 1)
116
+ ])
117
+ end
115
118
  end
116
119
  end