rom-sql 0.6.1 → 0.7.0.beta1

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 (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]