rom-sql 0.3.2 → 0.4.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/.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