rom-sql 0.6.1 → 0.7.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -5
  3. data/.rubocop_todo.yml +12 -6
  4. data/.travis.yml +3 -2
  5. data/CHANGELOG.md +27 -0
  6. data/Gemfile +1 -0
  7. data/lib/rom/plugins/relation/sql/auto_combine.rb +45 -0
  8. data/lib/rom/plugins/relation/sql/auto_wrap.rb +48 -0
  9. data/lib/rom/plugins/relation/sql/base_view.rb +31 -0
  10. data/lib/rom/sql.rb +17 -10
  11. data/lib/rom/sql/commands/error_wrapper.rb +1 -1
  12. data/lib/rom/sql/commands/update.rb +1 -1
  13. data/lib/rom/sql/gateway.rb +8 -2
  14. data/lib/rom/sql/header.rb +1 -1
  15. data/lib/rom/sql/migration.rb +11 -20
  16. data/lib/rom/sql/migration/migrator.rb +4 -0
  17. data/lib/rom/sql/{relation/associations.rb → plugin/assoc_macros.rb} +17 -2
  18. data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +128 -0
  19. data/lib/rom/sql/plugin/associates.rb +2 -4
  20. data/lib/rom/sql/plugin/pagination.rb +1 -1
  21. data/lib/rom/sql/plugins.rb +4 -2
  22. data/lib/rom/sql/relation.rb +46 -6
  23. data/lib/rom/sql/relation/reading.rb +63 -0
  24. data/lib/rom/sql/tasks/migration_tasks.rake +37 -16
  25. data/lib/rom/sql/version.rb +1 -1
  26. data/rom-sql.gemspec +2 -2
  27. data/spec/fixtures/migrations/20150403090603_create_carrots.rb +1 -1
  28. data/spec/integration/combine_spec.rb +6 -6
  29. data/spec/integration/commands/create_spec.rb +25 -25
  30. data/spec/integration/commands/delete_spec.rb +6 -6
  31. data/spec/integration/commands/update_spec.rb +5 -5
  32. data/spec/integration/{repository_spec.rb → gateway_spec.rb} +34 -21
  33. data/spec/integration/migration_spec.rb +3 -3
  34. data/spec/integration/read_spec.rb +13 -7
  35. data/spec/shared/database_setup.rb +3 -5
  36. data/spec/spec_helper.rb +3 -6
  37. data/spec/support/active_support_notifications_spec.rb +1 -1
  38. data/spec/support/rails_log_subscriber_spec.rb +1 -1
  39. data/spec/unit/association_errors_spec.rb +4 -3
  40. data/spec/unit/combined_associations_spec.rb +7 -5
  41. data/spec/unit/gateway_spec.rb +2 -2
  42. data/spec/unit/logger_spec.rb +1 -1
  43. data/spec/unit/many_to_many_spec.rb +7 -4
  44. data/spec/unit/many_to_one_spec.rb +14 -8
  45. data/spec/unit/migration_tasks_spec.rb +7 -6
  46. data/spec/unit/one_to_many_spec.rb +16 -10
  47. data/spec/unit/plugin/base_view_spec.rb +18 -0
  48. data/spec/unit/plugin/pagination_spec.rb +10 -10
  49. data/spec/unit/relation_spec.rb +69 -3
  50. data/spec/unit/schema_spec.rb +5 -3
  51. metadata +19 -26
  52. data/lib/rom/sql/relation/class_methods.rb +0 -116
  53. data/lib/rom/sql/relation/inspection.rb +0 -16
@@ -64,13 +64,11 @@ module ROM
64
64
  # @api public
65
65
  def associates(name, options)
66
66
  if associations.include?(name)
67
- raise(
68
- ArgumentError,
67
+ raise ArgumentError,
69
68
  "#{name} association is already defined for #{self.class}"
70
- )
71
69
  end
72
70
 
73
- option :association, reader: true, default: -> command { options }
71
+ option :association, reader: true, default: -> _command { options }
74
72
  include InstanceMethods
75
73
 
76
74
  associations << name
@@ -4,7 +4,7 @@ module ROM
4
4
  module Pagination
5
5
  class Pager
6
6
  include Options
7
- include Equalizer.new(:dataset, :options)
7
+ include Dry::Equalizer(:dataset, :options)
8
8
 
9
9
  option :current_page, reader: true, default: 1
10
10
  option :per_page, reader: true
@@ -1,8 +1,10 @@
1
- require "rom/sql/plugin/associates"
2
- require "rom/sql/plugin/pagination"
1
+ require 'rom/sql/plugin/assoc_macros'
2
+ require 'rom/sql/plugin/associates'
3
+ require 'rom/sql/plugin/pagination'
3
4
 
4
5
  ROM.plugins do
5
6
  adapter :sql do
7
+ register :assoc_macros, ROM::SQL::Plugin::AssocMacros, type: :relation
6
8
  register :pagination, ROM::SQL::Plugin::Pagination, type: :relation
7
9
  register :associates, ROM::SQL::Plugin::Associates, type: :command
8
10
  end
@@ -1,10 +1,13 @@
1
1
  require 'rom/sql/header'
2
2
 
3
- require 'rom/sql/relation/class_methods'
4
3
  require 'rom/sql/relation/reading'
5
4
  require 'rom/sql/relation/writing'
6
- require 'rom/sql/relation/inspection'
7
- require 'rom/sql/relation/associations'
5
+
6
+ require 'rom/plugins/relation/view'
7
+ require 'rom/plugins/relation/key_inference'
8
+ require 'rom/plugins/relation/sql/base_view'
9
+ require 'rom/plugins/relation/sql/auto_combine'
10
+ require 'rom/plugins/relation/sql/auto_wrap'
8
11
 
9
12
  module ROM
10
13
  module SQL
@@ -13,10 +16,12 @@ module ROM
13
16
  class Relation < ROM::Relation
14
17
  adapter :sql
15
18
 
16
- extend ClassMethods
19
+ use :key_inference
20
+ use :view
21
+ use :base_view
22
+ use :auto_combine
23
+ use :auto_wrap
17
24
 
18
- include Inspection
19
- include Associations
20
25
  include Writing
21
26
  include Reading
22
27
 
@@ -28,6 +33,41 @@ module ROM
28
33
  # @attr_reader [Symbol] table
29
34
  attr_reader :table
30
35
 
36
+ # Set default dataset for a relation sub-class
37
+ #
38
+ # @api private
39
+ def self.inherited(klass)
40
+ super
41
+
42
+ klass.class_eval do
43
+ dataset do
44
+ table = opts[:from].first
45
+
46
+ if db.table_exists?(table)
47
+ # quick fix for dbs w/o primary_key inference
48
+ #
49
+ # TODO: add a way of setting a pk explicitly on a relation
50
+ pk =
51
+ if db.respond_to?(:primary_key)
52
+ Array(db.primary_key(table))
53
+ else
54
+ [:id]
55
+ end.map { |name| :"#{table}__#{name}" }
56
+
57
+ select(*columns).order(*pk)
58
+ else
59
+ self
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.primary_key(value)
66
+ option :primary_key, reader: true, default: value
67
+ end
68
+
69
+ primary_key :id
70
+
31
71
  # @api private
32
72
  def initialize(dataset, registry = {})
33
73
  super
@@ -175,6 +175,54 @@ module ROM
175
175
  __new__(dataset.__send__(__method__, *args, &block))
176
176
  end
177
177
 
178
+ # Returns a result of SQL SUM clause.
179
+ #
180
+ # @example
181
+ # users.sum(:age)
182
+ #
183
+ # @return Number
184
+ #
185
+ # @api public
186
+ def sum(*args)
187
+ dataset.__send__(__method__, *args)
188
+ end
189
+
190
+ # Returns a result of SQL MIN clause.
191
+ #
192
+ # @example
193
+ # users.min(:age)
194
+ #
195
+ # @return Number
196
+ #
197
+ # @api public
198
+ def min(*args)
199
+ dataset.__send__(__method__, *args)
200
+ end
201
+
202
+ # Returns a result of SQL MAX clause.
203
+ #
204
+ # @example
205
+ # users.max(:age)
206
+ #
207
+ # @return Number
208
+ #
209
+ # @api public
210
+ def max(*args)
211
+ dataset.__send__(__method__, *args)
212
+ end
213
+
214
+ # Returns a result of SQL AVG clause.
215
+ #
216
+ # @example
217
+ # users.avg(:age)
218
+ #
219
+ # @return Number
220
+ #
221
+ # @api public
222
+ def avg(*args)
223
+ dataset.__send__(__method__, *args)
224
+ end
225
+
178
226
  # Restrict a relation to match criteria
179
227
  #
180
228
  # @example
@@ -323,6 +371,21 @@ module ROM
323
371
  def select_group(*args, &block)
324
372
  __new__(dataset.__send__(__method__, *args, &block))
325
373
  end
374
+
375
+ # Adds a UNION clause for relation dataset using second relation dataset
376
+ #
377
+ # @param [Relation] relation object
378
+ #
379
+ # @example
380
+ # users.where(id: 1).union(users.where(id: 2))
381
+ # # => [{ id: 1, name: 'Piotr' }, { id: 2, name: 'Jane' }]
382
+ #
383
+ # @return [Relation]
384
+ #
385
+ # @api public
386
+ def union(relation, *args, &block)
387
+ __new__(dataset.__send__(__method__, relation.dataset, *args, &block))
388
+ end
326
389
  end
327
390
  end
328
391
  end
@@ -1,41 +1,62 @@
1
1
  require "pathname"
2
2
  require "fileutils"
3
3
 
4
+ module ROM
5
+ module SQL
6
+ module RakeSupport
7
+ class << self
8
+ def run_migrations(options = {})
9
+ gateway.run_migrations(options)
10
+ end
11
+
12
+ def create_migration(*args)
13
+ gateway.migrator.create_file(*args)
14
+ end
15
+
16
+ private
17
+
18
+ def gateway
19
+ ROM::SQL::Gateway.instance
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+
4
26
  namespace :db do
5
27
  desc "Perform migration reset (full erase and migration up)"
6
- task reset: :setup do
7
- gateway = ROM::SQL.gateway
8
- gateway.run_migrations(target: 0)
9
- gateway.run_migrations
28
+ task :rom_configuration do
29
+ Rake::Task["db:setup"].invoke
30
+ end
31
+
32
+ task reset: :rom_configuration do
33
+ ROM::SQL::RakeSupport.run_migrations(target: 0)
34
+ ROM::SQL::RakeSupport.run_migrations
10
35
  puts "<= db:reset executed"
11
36
  end
12
37
 
13
38
  desc "Migrate the database (options [version_number])]"
14
- task :migrate, [:version] => :setup do |_, args|
15
- gateway = ROM::SQL.gateway
39
+ task :migrate, [:version] => :rom_configuration do |_, args|
16
40
  version = args[:version]
17
41
 
18
42
  if version.nil?
19
- gateway.run_migrations
43
+ ROM::SQL::RakeSupport.run_migrations
20
44
  puts "<= db:migrate executed"
21
45
  else
22
- gateway.run_migrations(target: version.to_i)
46
+ ROM::SQL::RakeSupport.run_migrations(target: version.to_i)
23
47
  puts "<= db:migrate version=[#{version}] executed"
24
48
  end
25
49
  end
26
50
 
27
51
  desc "Perform migration down (erase all data)"
28
- task clean: :setup do
29
- gateway = ROM::SQL.gateway
30
-
31
- gateway.run_migrations(target: 0)
52
+ task clean: :rom_configuration do
53
+ ROM::SQL::RakeSupport.run_migrations(target: 0)
32
54
  puts "<= db:clean executed"
33
55
  end
34
56
 
35
57
  desc "Create a migration (parameters: NAME, VERSION)"
36
- task :create_migration, [:name, :version] => :setup do |_, args|
37
- gateway = ROM::SQL.gateway
38
- name, version = args[:name], args[:version]
58
+ task :create_migration, [:name, :version] => :rom_configuration do |_, args|
59
+ name, version = args.values_at(:name, :version)
39
60
 
40
61
  if name.nil?
41
62
  puts "No NAME specified. Example usage:
@@ -43,7 +64,7 @@ namespace :db do
43
64
  exit
44
65
  end
45
66
 
46
- path = gateway.migrator.create_file(*[name, version].compact)
67
+ path = ROM::SQL::RakeSupport.create_migration(*[name, version].compact)
47
68
 
48
69
  puts "<= migration file created #{path}"
49
70
  end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '0.6.1'.freeze
3
+ VERSION = '0.7.0.beta1'.freeze
4
4
  end
5
5
  end
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_runtime_dependency "sequel", "~> 4.18"
22
- spec.add_runtime_dependency "equalizer", "~> 0.0", ">= 0.0.9"
23
- spec.add_runtime_dependency "rom", "~> 0.9", ">= 0.9.0"
22
+ spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
23
+ spec.add_runtime_dependency "rom", "~> 1.0.0.beta1"
24
24
 
25
25
  spec.add_development_dependency "bundler"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
@@ -1,4 +1,4 @@
1
- ROM.env.gateways[:default].migration do
1
+ ROM::SQL.migration do
2
2
  change do
3
3
  create_table :carrots do
4
4
  primary_key :id
@@ -4,19 +4,19 @@ describe 'Eager loading' do
4
4
  include_context 'users and tasks'
5
5
 
6
6
  before do
7
- setup.relation(:users) do
7
+ configuration.relation(:users) do
8
8
  def by_name(name)
9
9
  where(name: name)
10
10
  end
11
11
  end
12
12
 
13
- setup.relation(:tasks) do
13
+ configuration.relation(:tasks) do
14
14
  def for_users(users)
15
15
  where(user_id: users.map { |tuple| tuple[:id] })
16
16
  end
17
17
  end
18
18
 
19
- setup.relation(:tags) do
19
+ configuration.relation(:tags) do
20
20
  def for_tasks(tasks)
21
21
  inner_join(:task_tags, task_id: :id)
22
22
  .where(task_id: tasks.map { |tuple| tuple[:id] })
@@ -25,9 +25,9 @@ describe 'Eager loading' do
25
25
  end
26
26
 
27
27
  it 'issues 3 queries for 3 combined relations' do
28
- users = rom.relation(:users).by_name('Piotr')
29
- tasks = rom.relation(:tasks)
30
- tags = rom.relation(:tags)
28
+ users = container.relation(:users).by_name('Piotr')
29
+ tasks = container.relation(:tasks)
30
+ tags = container.relation(:tags)
31
31
 
32
32
  relation = users.combine(tasks.for_users.combine(tags.for_tasks))
33
33
 
@@ -4,8 +4,8 @@ require 'virtus'
4
4
  describe 'Commands / Create' do
5
5
  include_context 'database setup'
6
6
 
7
- subject(:users) { rom.commands.users }
8
- subject(:tasks) { rom.commands.tasks }
7
+ subject(:users) { container.commands.users }
8
+ subject(:tasks) { container.commands.tasks }
9
9
 
10
10
  before do
11
11
  class Params
@@ -18,12 +18,12 @@ describe 'Commands / Create' do
18
18
  end
19
19
  end
20
20
 
21
- setup.commands(:users) do
21
+ configuration.commands(:users) do
22
22
  define(:create) do
23
23
  input Params
24
24
 
25
25
  validator -> tuple {
26
- raise ROM::CommandError.new('name cannot be empty') if tuple[:name] == ''
26
+ raise ROM::CommandError, 'name cannot be empty' if tuple[:name] == ''
27
27
  }
28
28
 
29
29
  result :one
@@ -34,12 +34,12 @@ describe 'Commands / Create' do
34
34
  end
35
35
  end
36
36
 
37
- setup.commands(:tasks) do
37
+ configuration.commands(:tasks) do
38
38
  define(:create)
39
39
  end
40
40
 
41
- setup.relation(:users)
42
- setup.relation(:tasks)
41
+ configuration.relation(:users)
42
+ configuration.relation(:tasks)
43
43
  end
44
44
 
45
45
  context '#transaction' do
@@ -53,7 +53,7 @@ describe 'Commands / Create' do
53
53
 
54
54
  it 'creates multiple records if nothing was raised' do
55
55
  result = users.create.transaction {
56
- users.create_many.call([{name: 'Jane'}, {name: 'Jack'}])
56
+ users.create_many.call([{ name: 'Jane' }, { name: 'Jack' }])
57
57
  }
58
58
 
59
59
  expect(result.value).to match_array([
@@ -78,14 +78,14 @@ describe 'Commands / Create' do
78
78
  result = users.create.transaction {
79
79
  users.create.call(name: 'Jane')
80
80
  users.create.call(name: '')
81
- } >-> value {
81
+ } >-> _value {
82
82
  passed = true
83
83
  }
84
84
 
85
85
  expect(result.value).to be(nil)
86
86
  expect(result.error.message).to eql('name cannot be empty')
87
87
  expect(passed).to be(false)
88
- }.to_not change { rom.relations.users.count }
88
+ }.to_not change { container.relations.users.count }
89
89
  end
90
90
 
91
91
  it 'creates nothing if rollback was raised' do
@@ -96,14 +96,14 @@ describe 'Commands / Create' do
96
96
  users.create.call(name: 'Jane')
97
97
  users.create.call(name: 'John')
98
98
  raise ROM::SQL::Rollback
99
- } >-> value {
99
+ } >-> _value {
100
100
  passed = true
101
101
  }
102
102
 
103
103
  expect(result.value).to be(nil)
104
104
  expect(result.error).to be(nil)
105
105
  expect(passed).to be(false)
106
- }.to_not change { rom.relations.users.count }
106
+ }.to_not change { container.relations.users.count }
107
107
  end
108
108
 
109
109
  it 'creates nothing if constraint error was raised' do
@@ -114,14 +114,14 @@ describe 'Commands / Create' do
114
114
  users.create.transaction {
115
115
  users.create.call(name: 'Jane')
116
116
  users.create.call(name: 'Jane')
117
- } >-> value {
117
+ } >-> _value {
118
118
  passed = true
119
119
  }
120
120
  rescue => error
121
121
  expect(error).to be_instance_of(ROM::SQL::UniqueConstraintError)
122
122
  expect(passed).to be(false)
123
123
  end
124
- }.to_not change { rom.relations.users.count }
124
+ }.to_not change { container.relations.users.count }
125
125
  end
126
126
 
127
127
  it 'creates nothing if anything was raised in any nested transaction' do
@@ -135,7 +135,7 @@ describe 'Commands / Create' do
135
135
  }
136
136
  }
137
137
  }.to raise_error(Exception)
138
- }.to_not change { rom.relations.users.count }
138
+ }.to_not change { container.relations.users.count }
139
139
  end
140
140
  end
141
141
 
@@ -158,7 +158,7 @@ describe 'Commands / Create' do
158
158
  it 're-raises not-null constraint violation error' do
159
159
  expect {
160
160
  users.try { users.create.call(name: nil) }
161
- }.to raise_error(ROM::SQL::NotNullConstraintError, /not-null/)
161
+ }.to raise_error(ROM::SQL::NotNullConstraintError)
162
162
  end
163
163
 
164
164
  it 're-raises uniqueness constraint violation error' do
@@ -168,7 +168,7 @@ describe 'Commands / Create' do
168
168
  } >-> user {
169
169
  users.try { users.create.call(name: user[:name]) }
170
170
  }
171
- }.to raise_error(ROM::SQL::UniqueConstraintError, /unique/)
171
+ }.to raise_error(ROM::SQL::UniqueConstraintError)
172
172
  end
173
173
 
174
174
  it 're-raises check constraint violation error' do
@@ -182,7 +182,7 @@ describe 'Commands / Create' do
182
182
  it 're-raises fk constraint violation error' do
183
183
  expect {
184
184
  tasks.try {
185
- tasks.create.call(user_id: 918273645)
185
+ tasks.create.call(user_id: 918_273_645)
186
186
  }
187
187
  }.to raise_error(ROM::SQL::ForeignKeyConstraintError, /user_id/)
188
188
  end
@@ -203,15 +203,15 @@ describe 'Commands / Create' do
203
203
 
204
204
  describe '.associates' do
205
205
  it 'sets foreign key prior execution for many tuples' do
206
- setup.commands(:tasks) do
206
+ configuration.commands(:tasks) do
207
207
  define(:create) do
208
208
  associates :user, key: [:user_id, :id]
209
209
  end
210
210
  end
211
211
 
212
- create_user = rom.command(:users).create.with(name: 'Jade')
212
+ create_user = container.command(:users).create.with(name: 'Jade')
213
213
 
214
- create_task = rom.command(:tasks).create.with([
214
+ create_task = container.command(:tasks).create.with([
215
215
  { title: 'Task one' }, { title: 'Task two' }
216
216
  ])
217
217
 
@@ -226,15 +226,15 @@ describe 'Commands / Create' do
226
226
  end
227
227
 
228
228
  it 'sets foreign key prior execution for one tuple' do
229
- setup.commands(:tasks) do
229
+ configuration.commands(:tasks) do
230
230
  define(:create) do
231
231
  result :one
232
232
  associates :user, key: [:user_id, :id]
233
233
  end
234
234
  end
235
235
 
236
- create_user = rom.command(:users).create.with(name: 'Jade')
237
- create_task = rom.command(:tasks).create.with(title: 'Task one')
236
+ create_user = container.command(:users).create.with(name: 'Jade')
237
+ create_task = container.command(:tasks).create.with(title: 'Task one')
238
238
 
239
239
  command = create_user >> create_task
240
240
 
@@ -245,7 +245,7 @@ describe 'Commands / Create' do
245
245
 
246
246
  it 'raises when already defined' do
247
247
  expect {
248
- setup.commands(:tasks) do
248
+ configuration.commands(:tasks) do
249
249
  define(:create) do
250
250
  result :one
251
251
  associates :user, key: [:user_id, :id]