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
@@ -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