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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +55 -18
- data/.rubocop_todo.yml +15 -0
- data/.travis.yml +10 -5
- data/CHANGELOG.md +18 -0
- data/Gemfile +8 -1
- data/Guardfile +24 -0
- data/README.md +14 -22
- data/Rakefile +13 -5
- data/lib/rom/sql.rb +5 -5
- data/lib/rom/sql/commands.rb +7 -49
- data/lib/rom/sql/commands/create.rb +29 -0
- data/lib/rom/sql/commands/delete.rb +18 -0
- data/lib/rom/sql/commands/transaction.rb +17 -0
- data/lib/rom/sql/commands/update.rb +54 -0
- data/lib/rom/sql/commands_ext/postgres.rb +24 -0
- data/lib/rom/sql/header.rb +8 -9
- data/lib/rom/sql/migration.rb +26 -0
- data/lib/rom/sql/plugin/pagination.rb +93 -0
- data/lib/rom/sql/rake_task.rb +2 -0
- data/lib/rom/sql/relation.rb +320 -0
- data/lib/rom/sql/relation/associations.rb +104 -0
- data/lib/rom/sql/relation/class_methods.rb +47 -0
- data/lib/rom/sql/relation/inspection.rb +16 -0
- data/lib/rom/sql/repository.rb +59 -0
- data/lib/rom/sql/support/rails_log_subscriber.rb +1 -1
- data/lib/rom/sql/tasks/migration_tasks.rake +56 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +2 -3
- data/spec/integration/commands/create_spec.rb +66 -8
- data/spec/integration/commands/delete_spec.rb +22 -3
- data/spec/integration/commands/update_spec.rb +57 -6
- data/spec/integration/read_spec.rb +42 -1
- data/spec/shared/database_setup.rb +10 -5
- data/spec/spec_helper.rb +17 -0
- data/spec/support/active_support_notifications_spec.rb +5 -4
- data/spec/support/rails_log_subscriber_spec.rb +2 -2
- data/spec/unit/logger_spec.rb +5 -3
- data/spec/unit/many_to_many_spec.rb +2 -2
- data/spec/unit/migration_spec.rb +34 -0
- data/spec/unit/migration_tasks_spec.rb +99 -0
- data/spec/unit/one_to_many_spec.rb +0 -2
- data/spec/unit/plugin/pagination_spec.rb +73 -0
- data/spec/unit/relation_spec.rb +49 -3
- data/spec/unit/repository_spec.rb +33 -0
- data/spec/unit/schema_spec.rb +5 -17
- metadata +32 -35
- data/lib/rom/sql/adapter.rb +0 -100
- data/lib/rom/sql/relation_inclusion.rb +0 -149
- data/lib/rom/sql/support/sequel_dataset_ext.rb +0 -33
- data/spec/unit/adapter_spec.rb +0 -48
- 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 '
|
4
|
+
include_context 'database setup'
|
5
5
|
|
6
6
|
subject(:users) { rom.commands.users }
|
7
7
|
|
8
|
-
|
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)
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
-
|
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(:
|
5
|
-
let(:conn) {
|
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
|
-
|
13
|
+
conn.loggers << LOGGER
|
9
14
|
|
10
|
-
|
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.
|
10
|
+
rom.repositories[:default].use_logger(LOGGER)
|
11
11
|
|
12
12
|
sql = nil
|
13
13
|
|
14
|
-
ActiveSupport::Notifications.subscribe('sql.rom') do
|
14
|
+
ActiveSupport::Notifications.subscribe('sql.rom') do |*, payload|
|
15
15
|
sql = payload[:sql]
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
query = %(SELECT * FROM "users" WHERE name = 'notification test')
|
19
|
+
conn.run(query)
|
19
20
|
|
20
|
-
expect(sql).to eql(
|
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.
|
21
|
+
rom.repositories[:default].use_logger(logger)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'works' do
|
25
|
-
|
25
|
+
conn.run(test_query)
|
26
26
|
|
27
27
|
expect(logger.logged(:debug).last).to include(test_query)
|
28
28
|
end
|
data/spec/unit/logger_spec.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe 'Logger' do
|
4
|
-
include_context '
|
4
|
+
include_context 'database setup'
|
5
5
|
|
6
6
|
it 'sets up a logger for sequel' do
|
7
|
-
repository = rom.
|
7
|
+
repository = rom.repositories[:default]
|
8
|
+
|
9
|
+
repository.use_logger(LOGGER)
|
8
10
|
|
9
11
|
expect(repository.logger).to be(LOGGER)
|
10
|
-
expect(
|
12
|
+
expect(conn.loggers).to include(LOGGER)
|
11
13
|
end
|
12
14
|
end
|
@@ -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
|
@@ -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
|