rom-sql 0.3.2 → 0.4.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/.gitignore +1 -0
  3. data/.rubocop.yml +55 -18
  4. data/.rubocop_todo.yml +15 -0
  5. data/.travis.yml +10 -5
  6. data/CHANGELOG.md +18 -0
  7. data/Gemfile +8 -1
  8. data/Guardfile +24 -0
  9. data/README.md +14 -22
  10. data/Rakefile +13 -5
  11. data/lib/rom/sql.rb +5 -5
  12. data/lib/rom/sql/commands.rb +7 -49
  13. data/lib/rom/sql/commands/create.rb +29 -0
  14. data/lib/rom/sql/commands/delete.rb +18 -0
  15. data/lib/rom/sql/commands/transaction.rb +17 -0
  16. data/lib/rom/sql/commands/update.rb +54 -0
  17. data/lib/rom/sql/commands_ext/postgres.rb +24 -0
  18. data/lib/rom/sql/header.rb +8 -9
  19. data/lib/rom/sql/migration.rb +26 -0
  20. data/lib/rom/sql/plugin/pagination.rb +93 -0
  21. data/lib/rom/sql/rake_task.rb +2 -0
  22. data/lib/rom/sql/relation.rb +320 -0
  23. data/lib/rom/sql/relation/associations.rb +104 -0
  24. data/lib/rom/sql/relation/class_methods.rb +47 -0
  25. data/lib/rom/sql/relation/inspection.rb +16 -0
  26. data/lib/rom/sql/repository.rb +59 -0
  27. data/lib/rom/sql/support/rails_log_subscriber.rb +1 -1
  28. data/lib/rom/sql/tasks/migration_tasks.rake +56 -0
  29. data/lib/rom/sql/version.rb +1 -1
  30. data/rom-sql.gemspec +2 -3
  31. data/spec/integration/commands/create_spec.rb +66 -8
  32. data/spec/integration/commands/delete_spec.rb +22 -3
  33. data/spec/integration/commands/update_spec.rb +57 -6
  34. data/spec/integration/read_spec.rb +42 -1
  35. data/spec/shared/database_setup.rb +10 -5
  36. data/spec/spec_helper.rb +17 -0
  37. data/spec/support/active_support_notifications_spec.rb +5 -4
  38. data/spec/support/rails_log_subscriber_spec.rb +2 -2
  39. data/spec/unit/logger_spec.rb +5 -3
  40. data/spec/unit/many_to_many_spec.rb +2 -2
  41. data/spec/unit/migration_spec.rb +34 -0
  42. data/spec/unit/migration_tasks_spec.rb +99 -0
  43. data/spec/unit/one_to_many_spec.rb +0 -2
  44. data/spec/unit/plugin/pagination_spec.rb +73 -0
  45. data/spec/unit/relation_spec.rb +49 -3
  46. data/spec/unit/repository_spec.rb +33 -0
  47. data/spec/unit/schema_spec.rb +5 -17
  48. metadata +32 -35
  49. data/lib/rom/sql/adapter.rb +0 -100
  50. data/lib/rom/sql/relation_inclusion.rb +0 -149
  51. data/lib/rom/sql/support/sequel_dataset_ext.rb +0 -33
  52. data/spec/unit/adapter_spec.rb +0 -48
  53. data/spec/unit/config_spec.rb +0 -54
@@ -1,26 +1,77 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'Commands / Update' do
4
- include_context 'users and tasks'
4
+ include_context 'database setup'
5
5
 
6
6
  subject(:users) { rom.commands.users }
7
7
 
8
- it 'works' do
8
+ let(:relation) { rom.relations.users }
9
+ let(:piotr) { relation.by_name('Piotr').first }
10
+ let(:peter) { { name: 'Peter' } }
11
+
12
+ before do
9
13
  setup.relation(:users) do
14
+ def by_id(id)
15
+ where(id: id).limit(1)
16
+ end
17
+
10
18
  def by_name(name)
11
19
  where(name: name)
12
20
  end
13
21
  end
14
22
 
15
23
  setup.commands(:users) do
16
- define(:update) do
17
- input Hash
18
- validator proc {}
24
+ define(:update)
25
+ end
26
+
27
+ relation.insert(name: 'Piotr')
28
+ end
29
+
30
+ context '#transaction' do
31
+ it 'update record if there was no errors' do
32
+ result = users.update.transaction do
33
+ users.update.by_id(piotr[:id]).set(peter)
19
34
  end
35
+
36
+ expect(result.value).to eq([{ id: 1, name: 'Peter' }])
20
37
  end
21
38
 
22
- result = users.try { update(:by_name, 'Piotr').set(name: 'Peter') }
39
+ it 'updates nothing if error was raised' do
40
+ users.update.transaction do
41
+ users.update.by_id(piotr[:id]).set(peter)
42
+ raise ROM::SQL::Rollback
43
+ end
44
+
45
+ expect(relation.first[:name]).to eq('Piotr')
46
+ end
47
+ end
48
+
49
+ it 'updates everything when there is no original tuple' do
50
+ result = users.try do
51
+ users.update.by_id(piotr[:id]).set(peter)
52
+ end
53
+
54
+ expect(result.value.to_a).to match_array([{ id: 1, name: 'Peter' }])
55
+ end
56
+
57
+ it 'updates when attributes changed' do
58
+ result = users.try do
59
+ users.update.by_id(piotr[:id]).change(piotr).to(peter)
60
+ end
23
61
 
24
62
  expect(result.value.to_a).to match_array([{ id: 1, name: 'Peter' }])
25
63
  end
64
+
65
+ it 'does not update when attributes did not change' do
66
+ piotr_rel = double('piotr_rel').as_null_object
67
+
68
+ expect(relation).to receive(:by_id).with(piotr[:id]).and_return(piotr_rel)
69
+ expect(piotr_rel).not_to receive(:update)
70
+
71
+ result = users.try do
72
+ users.update.by_id(piotr[:id]).change(piotr).to(name: piotr[:name])
73
+ end
74
+
75
+ expect(result.value.to_a).to be_empty
76
+ end
26
77
  end
@@ -4,7 +4,7 @@ require 'virtus'
4
4
  describe 'Reading relations' do
5
5
  include_context 'users and tasks'
6
6
 
7
- it 'loads domain objects' do
7
+ before :each do
8
8
  class Task
9
9
  include Virtus.value_object(coerce: true)
10
10
 
@@ -24,6 +24,16 @@ describe 'Reading relations' do
24
24
  end
25
25
  end
26
26
 
27
+ class UserTaskCount
28
+ include Virtus.value_object(coerce: true)
29
+
30
+ values do
31
+ attribute :id, Integer
32
+ attribute :name, String
33
+ attribute :task_count, Integer
34
+ end
35
+ end
36
+
27
37
  setup.relation(:tasks)
28
38
 
29
39
  setup.relation(:users) do
@@ -42,6 +52,22 @@ describe 'Reading relations' do
42
52
  end
43
53
  end
44
54
 
55
+ setup.relation(:user_task_counts) do
56
+ dataset :users
57
+ register_as :user_task_counts
58
+ one_to_many :tasks, key: :user_id
59
+
60
+ def all
61
+ with_tasks.select_group(:users__id, :users__name).select_append {
62
+ count(:tasks).as(:task_count)
63
+ }
64
+ end
65
+
66
+ def with_tasks
67
+ association_left_join(:tasks, select: [:id, :title])
68
+ end
69
+ end
70
+
45
71
  setup.mappers do
46
72
  define(:users) do
47
73
  model User
@@ -53,8 +79,14 @@ describe 'Reading relations' do
53
79
  attribute :title
54
80
  end
55
81
  end
82
+
83
+ define(:user_task_counts) do
84
+ model UserTaskCount
85
+ end
56
86
  end
87
+ end
57
88
 
89
+ it 'loads domain objects' do
58
90
  user = rom.read(:users).with_tasks.by_name('Piotr').to_a.first
59
91
 
60
92
  expect(user).to eql(
@@ -62,4 +94,13 @@ describe 'Reading relations' do
62
94
  id: 1, name: 'Piotr', tasks: [Task.new(id: 1, title: 'Finish ROM')]
63
95
  ))
64
96
  end
97
+
98
+ it 'works with grouping and aggregates' do
99
+ rom.relations[:tasks].insert(id: 2, user_id: 1, title: 'Get Milk')
100
+
101
+ users_with_task_count = rom.read(:user_task_counts).all
102
+ expect(users_with_task_count.to_a).to eq([
103
+ UserTaskCount.new(id: 1, name: "Piotr", task_count: 2)
104
+ ])
105
+ end
65
106
  end
@@ -1,17 +1,22 @@
1
1
  shared_context 'database setup' do
2
2
  subject(:rom) { setup.finalize }
3
3
 
4
- let(:setup) { ROM.setup(postgres: 'postgres://localhost/rom') }
5
- let(:conn) { setup.postgres.connection }
4
+ let(:uri) { 'postgres://localhost/rom' }
5
+ let(:conn) { Sequel.connect(uri) }
6
+ let(:setup) { ROM.setup(:sql, uri) }
7
+
8
+ def drop_tables
9
+ [:users, :tasks, :tags, :task_tags].each { |name| conn.drop_table?(name) }
10
+ end
6
11
 
7
12
  before do
8
- setup.postgres.use_logger(LOGGER)
13
+ conn.loggers << LOGGER
9
14
 
10
- [:users, :tasks, :tags, :task_tags].each { |name| conn.drop_table?(name) }
15
+ drop_tables
11
16
 
12
17
  conn.create_table :users do
13
18
  primary_key :id
14
- String :name
19
+ String :name, null: false
15
20
  index :name, unique: true
16
21
  end
17
22
 
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,17 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'bundler'
4
+ Bundler.setup
5
+
3
6
  if RUBY_ENGINE == 'rbx'
4
7
  require "codeclimate-test-reporter"
5
8
  CodeClimate::TestReporter.start
6
9
  end
7
10
 
8
11
  require 'rom-sql'
12
+ require 'rom/sql/rake_task'
13
+ # FIXME: why do we need to require it manually??
14
+ require 'sequel/adapters/postgres'
9
15
  require 'logger'
10
16
 
11
17
  LOGGER = Logger.new(File.open('./log/test.log', 'a'))
@@ -13,3 +19,14 @@ LOGGER = Logger.new(File.open('./log/test.log', 'a'))
13
19
  root = Pathname(__FILE__).dirname
14
20
 
15
21
  Dir[root.join('shared/*.rb').to_s].each { |f| require f }
22
+
23
+ RSpec.configure do |config|
24
+ config.before do
25
+ @constants = Object.constants
26
+ end
27
+
28
+ config.after do
29
+ added_constants = Object.constants - @constants
30
+ added_constants.each { |name| Object.send(:remove_const, name) }
31
+ end
32
+ end
@@ -7,16 +7,17 @@ describe 'ActiveSupport::Notifications support' do
7
7
  include_context 'database setup'
8
8
 
9
9
  it 'works' do
10
- rom.postgres.use_logger(LOGGER)
10
+ rom.repositories[:default].use_logger(LOGGER)
11
11
 
12
12
  sql = nil
13
13
 
14
- ActiveSupport::Notifications.subscribe('sql.rom') do |_name, _start, _finish, _id, payload|
14
+ ActiveSupport::Notifications.subscribe('sql.rom') do |*, payload|
15
15
  sql = payload[:sql]
16
16
  end
17
17
 
18
- rom.postgres.connection.run(%(SELECT * FROM "users" WHERE name = 'notification test'))
18
+ query = %(SELECT * FROM "users" WHERE name = 'notification test')
19
+ conn.run(query)
19
20
 
20
- expect(sql).to eql(%(SELECT * FROM "users" WHERE name = 'notification test'))
21
+ expect(sql).to eql(query)
21
22
  end
22
23
  end
@@ -18,11 +18,11 @@ describe 'Rails log subscriber' do
18
18
 
19
19
  before do
20
20
  set_logger(logger)
21
- rom.postgres.use_logger(logger)
21
+ rom.repositories[:default].use_logger(logger)
22
22
  end
23
23
 
24
24
  it 'works' do
25
- rom.postgres.connection.run(test_query)
25
+ conn.run(test_query)
26
26
 
27
27
  expect(logger.logged(:debug).last).to include(test_query)
28
28
  end
@@ -1,12 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe 'Logger' do
4
- include_context 'users and tasks'
4
+ include_context 'database setup'
5
5
 
6
6
  it 'sets up a logger for sequel' do
7
- repository = rom.postgres
7
+ repository = rom.repositories[:default]
8
+
9
+ repository.use_logger(LOGGER)
8
10
 
9
11
  expect(repository.logger).to be(LOGGER)
10
- expect(repository.connection.loggers).to include(LOGGER)
12
+ expect(conn.loggers).to include(LOGGER)
11
13
  end
12
14
  end
@@ -20,8 +20,8 @@ describe 'Defining many-to-one association' do
20
20
 
21
21
  def with_tags_and_tag_id
22
22
  association_left_join(:tags, select: {
23
- tags: [:name], task_tags: [:tag_id]
24
- })
23
+ tags: [:name], task_tags: [:tag_id]
24
+ })
25
25
  end
26
26
 
27
27
  def by_tag(name)
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::SQL::Migration do
4
+ let(:migration) { ROM::SQL::Migration }
5
+
6
+ context '#run' do
7
+ it 'calls Sequel migration code' do
8
+ migration.path = 'foo/bar'
9
+ migration.connection = double
10
+ opts = { foo: 'bar' }
11
+
12
+ expect(Sequel::Migrator).to receive(:run)
13
+ .with(migration.connection, migration.path, opts)
14
+
15
+ migration.run(opts)
16
+ end
17
+ end
18
+
19
+ context '#path' do
20
+ it 'returns default path if non provided' do
21
+ migration.path = nil
22
+
23
+ expect(migration.path).to eq ROM::SQL::Migration::DEFAULT_PATH
24
+ end
25
+ end
26
+
27
+ context '.create' do
28
+ it 'calls Sequel migration block' do
29
+ expect(Sequel).to receive(:migration)
30
+
31
+ ROM::SQL::Migration.create {}
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ namespace :db do
4
+ task :load_setup do
5
+ end
6
+ end
7
+
8
+ describe 'MigrationTasks' do
9
+ context 'db:reset' do
10
+ it 'calls proper commands' do
11
+ expect(ROM::SQL::Migration).to receive(:run).with(target: 0)
12
+ expect(ROM::SQL::Migration).to receive(:run)
13
+
14
+ expect {
15
+ Rake::Task["db:reset"].invoke
16
+ }.to output("<= db:reset executed\n").to_stdout
17
+ end
18
+ end
19
+
20
+ context 'db:migrate' do
21
+ context 'with VERSION' do
22
+ it 'calls proper commands' do
23
+ expect(ROM::SQL::Migration).to receive(:run).with(target: 1)
24
+
25
+ expect {
26
+ Rake::Task["db:migrate"].invoke(1)
27
+ }.to output("<= db:migrate version=[1] executed\n").to_stdout
28
+ end
29
+ end
30
+
31
+ context 'without VERSION' do
32
+ it 'calls proper commands' do
33
+ expect(ROM::SQL::Migration).to receive(:run)
34
+
35
+ expect {
36
+ Rake::Task["db:migrate"].execute
37
+ }.to output("<= db:migrate executed\n").to_stdout
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'db:clean' do
43
+ it 'calls proper commands' do
44
+ expect(ROM::SQL::Migration).to receive(:run).with(target: 0)
45
+
46
+ expect {
47
+ Rake::Task["db:clean"].invoke
48
+ }.to output("<= db:clean executed\n").to_stdout
49
+ end
50
+ end
51
+
52
+ context 'db:create_migration' do
53
+ context 'without NAME' do
54
+ it 'exit without creating any file' do
55
+ expect(File).to_not receive(:write)
56
+
57
+ expect {
58
+ expect {
59
+ Rake::Task["db:create_migration"].execute
60
+ }.to output(/No NAME specified/).to_stdout
61
+ }.to raise_error(SystemExit)
62
+ end
63
+ end
64
+
65
+ context 'with NAME' do
66
+ let(:dirname) { 'db/migration' }
67
+ let(:name) { 'foo_bar' }
68
+ let(:version) { '001' }
69
+ let(:filename) { "#{version}_#{name}.rb" }
70
+ let(:path) { File.join(dirname, filename) }
71
+
72
+ before do
73
+ expect(ROM::SQL::Migration).to receive(:path).and_return(dirname)
74
+ end
75
+
76
+ it 'calls proper commands with default VERSION' do
77
+ time = double(utc: double(strftime: '001'))
78
+ expect(Time).to receive(:now).and_return(time)
79
+ expect(FileUtils).to receive(:mkdir_p).with(dirname)
80
+ expect(File).to receive(:write).with(path, /ROM::SQL::Migration/)
81
+
82
+ expect {
83
+ Rake::Task["db:create_migration"].execute(
84
+ Rake::TaskArguments.new([:name], [name]))
85
+ }.to output(path+"\n").to_stdout
86
+ end
87
+
88
+ it 'calls proper commands with manualy set VERSION' do
89
+ expect(FileUtils).to receive(:mkdir_p).with(dirname)
90
+ expect(File).to receive(:write).with(path, /ROM::SQL::Migration/)
91
+
92
+ expect {
93
+ Rake::Task["db:create_migration"].execute(
94
+ Rake::TaskArguments.new([:name, :version], [name, version]))
95
+ }.to output(path+"\n").to_stdout
96
+ end
97
+ end
98
+ end
99
+ end
@@ -4,8 +4,6 @@ describe 'Defining one-to-many association' do
4
4
  include_context 'users and tasks'
5
5
 
6
6
  it 'extends relation with association methods' do
7
- setup.relation(:tasks)
8
-
9
7
  setup.relation(:users) do
10
8
  one_to_many :tasks, key: :user_id
11
9
 
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rom/sql/plugin/pagination'
4
+
5
+ describe 'Plugin / Pagination' do
6
+ include_context 'database setup'
7
+
8
+ before do
9
+ 9.times { |i| conn[:users].insert(name: "User #{i}") }
10
+
11
+ setup.relation(:users) do
12
+ include ROM::SQL::Plugin::Pagination # meh such constants not wow
13
+
14
+ per_page 4
15
+ end
16
+ end
17
+
18
+ describe '#page' do
19
+ it 'allow to call with stringify number' do
20
+ expect {
21
+ rom.relation(:users).page('5')
22
+ }.to_not raise_error
23
+ end
24
+ end
25
+
26
+ describe '#per_page' do
27
+ it 'allow to call with stringify number' do
28
+ expect {
29
+ rom.relation(:users).per_page('5')
30
+ }.to_not raise_error
31
+ end
32
+
33
+ it 'returns paginated relation with provided limit' do
34
+ users = rom.relation(:users).page(2).per_page(5)
35
+
36
+ expect(users.relation.dataset.opts[:offset]).to eql(5)
37
+ expect(users.relation.dataset.opts[:limit]).to eql(5)
38
+
39
+ expect(users.pager.current_page).to eql(2)
40
+
41
+ expect(users.pager.total).to be(9)
42
+ expect(users.pager.total_pages).to be(2)
43
+
44
+ expect(users.pager.next_page).to be(nil)
45
+ expect(users.pager.prev_page).to be(1)
46
+ expect(users.pager.limit_value).to eql(5)
47
+ end
48
+ end
49
+
50
+ describe '#pager' do
51
+ it 'returns a pager with pagination meta-info' do
52
+ users = rom.relation(:users).page(1)
53
+
54
+ expect(users.pager.total).to be(9)
55
+ expect(users.pager.total_pages).to be(3)
56
+
57
+ expect(users.pager.current_page).to be(1)
58
+ expect(users.pager.next_page).to be(2)
59
+ expect(users.pager.prev_page).to be(nil)
60
+
61
+ users = rom.relation(:users).page(2)
62
+
63
+ expect(users.pager.current_page).to be(2)
64
+ expect(users.pager.next_page).to be(3)
65
+ expect(users.pager.prev_page).to be(1)
66
+
67
+ users = rom.relation(:users).page(3)
68
+
69
+ expect(users.pager.next_page).to be(nil)
70
+ expect(users.pager.prev_page).to be(2)
71
+ end
72
+ end
73
+ end