rom-sql 0.8.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +16 -12
- data/CHANGELOG.md +23 -0
- data/Gemfile +11 -3
- data/README.md +1 -7
- data/lib/rom/sql.rb +4 -7
- data/lib/rom/sql/association.rb +1 -1
- data/lib/rom/sql/association/one_to_many.rb +44 -1
- data/lib/rom/sql/association/one_to_one.rb +1 -38
- data/lib/rom/sql/commands.rb +0 -3
- data/lib/rom/sql/commands/error_wrapper.rb +1 -1
- data/lib/rom/sql/errors.rb +4 -1
- data/lib/rom/sql/extensions.rb +19 -0
- data/lib/rom/sql/{support → extensions}/active_support_notifications.rb +0 -0
- data/lib/rom/sql/extensions/postgres.rb +3 -0
- data/lib/rom/sql/{commands/postgres.rb → extensions/postgres/commands.rb} +38 -0
- data/lib/rom/sql/extensions/postgres/inferrer.rb +64 -0
- data/lib/rom/sql/extensions/postgres/types.rb +65 -0
- data/lib/rom/sql/{support → extensions}/rails_log_subscriber.rb +0 -0
- data/lib/rom/sql/gateway.rb +15 -4
- data/lib/rom/sql/relation.rb +6 -2
- data/lib/rom/sql/relation/reading.rb +18 -0
- data/lib/rom/sql/schema/dsl.rb +7 -4
- data/lib/rom/sql/schema/inferrer.rb +44 -31
- data/lib/rom/sql/types.rb +5 -1
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +14 -13
- data/spec/extensions/postgres/inferrer_spec.rb +40 -0
- data/spec/extensions/postgres/integration_spec.rb +38 -0
- data/spec/extensions/postgres/types_spec.rb +115 -0
- data/spec/integration/association/many_to_many_spec.rb +2 -1
- data/spec/integration/association/one_to_one_spec.rb +6 -4
- data/spec/integration/combine_spec.rb +1 -1
- data/spec/integration/commands/create_spec.rb +46 -21
- data/spec/integration/commands/delete_spec.rb +13 -38
- data/spec/integration/commands/update_spec.rb +19 -41
- data/spec/integration/commands/upsert_spec.rb +1 -1
- data/spec/integration/gateway_spec.rb +5 -9
- data/spec/integration/migration_spec.rb +6 -7
- data/spec/integration/read_spec.rb +30 -38
- data/spec/integration/schema_inference_spec.rb +211 -49
- data/spec/integration/setup_spec.rb +5 -5
- data/spec/integration/support/active_support_notifications_spec.rb +4 -3
- data/spec/integration/support/rails_log_subscriber_spec.rb +5 -4
- data/spec/shared/database_setup.rb +21 -6
- data/spec/spec_helper.rb +44 -35
- data/spec/unit/association/one_to_many_spec.rb +20 -0
- data/spec/unit/association/one_to_one_spec.rb +23 -2
- data/spec/unit/association_errors_spec.rb +1 -1
- data/spec/unit/gateway_spec.rb +9 -8
- data/spec/unit/logger_spec.rb +1 -1
- data/spec/unit/migration_tasks_spec.rb +3 -3
- data/spec/unit/migrator_spec.rb +3 -2
- data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +1 -1
- data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +1 -1
- data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +1 -1
- data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +1 -1
- data/spec/unit/relation/associations_spec.rb +27 -0
- data/spec/unit/relation/avg_spec.rb +11 -0
- data/spec/unit/relation/by_pk_spec.rb +15 -0
- data/spec/unit/relation/dataset_spec.rb +48 -0
- data/spec/unit/relation/distinct_spec.rb +14 -0
- data/spec/unit/relation/exclude_spec.rb +13 -0
- data/spec/unit/relation/fetch_spec.rb +21 -0
- data/spec/unit/relation/having_spec.rb +20 -0
- data/spec/unit/relation/inner_join_spec.rb +22 -0
- data/spec/unit/relation/inspect_spec.rb +11 -0
- data/spec/unit/relation/invert_spec.rb +12 -0
- data/spec/unit/relation/left_join_spec.rb +16 -0
- data/spec/unit/relation/map_spec.rb +16 -0
- data/spec/unit/relation/max_spec.rb +11 -0
- data/spec/unit/relation/min_spec.rb +11 -0
- data/spec/unit/relation/pluck_spec.rb +11 -0
- data/spec/unit/relation/prefix_spec.rb +27 -0
- data/spec/unit/relation/primary_key_spec.rb +27 -0
- data/spec/unit/relation/project_spec.rb +22 -0
- data/spec/unit/relation/qualified_columns_spec.rb +27 -0
- data/spec/unit/relation/rename_spec.rb +21 -0
- data/spec/unit/relation/sum_spec.rb +11 -0
- data/spec/unit/relation/union_spec.rb +19 -0
- data/spec/unit/relation/unique_predicate_spec.rb +18 -0
- data/spec/unit/schema_spec.rb +1 -1
- data/spec/unit/types_spec.rb +4 -21
- metadata +79 -11
- data/lib/rom/sql/commands_ext/postgres.rb +0 -45
- data/lib/rom/sql/types/pg.rb +0 -26
- data/spec/unit/relation_spec.rb +0 -272
@@ -8,6 +8,26 @@ RSpec.describe ROM::SQL::Association::OneToMany, helpers: true do
|
|
8
8
|
let(:users) { double(:users, primary_key: :id) }
|
9
9
|
let(:tasks) { double(:tasks) }
|
10
10
|
|
11
|
+
describe '#associate' do
|
12
|
+
let(:source) { :users }
|
13
|
+
let(:target) { :tasks }
|
14
|
+
|
15
|
+
let(:relations) do
|
16
|
+
{ users: users, tasks: tasks }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns child tuple with FK set' do
|
20
|
+
expect(tasks).to receive(:foreign_key).with(:users).and_return(:user_id)
|
21
|
+
|
22
|
+
task_tuple = { title: 'Task' }
|
23
|
+
user_tuple = { id: 3 }
|
24
|
+
|
25
|
+
expect(assoc.associate(relations, task_tuple, user_tuple)).to eql(
|
26
|
+
user_id: 3, title: 'Task'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
11
31
|
shared_examples_for 'one-to-many association' do
|
12
32
|
describe '#combine_keys' do
|
13
33
|
it 'returns a hash with combine keys' do
|
@@ -5,8 +5,29 @@ RSpec.describe ROM::SQL::Association::OneToOne, helpers: true do
|
|
5
5
|
|
6
6
|
let(:options) { {} }
|
7
7
|
|
8
|
-
let(:users)
|
9
|
-
let(:tasks)
|
8
|
+
let(:users) { double(:users, primary_key: :id) }
|
9
|
+
let(:tasks) { double(:tasks) }
|
10
|
+
let(:avatars) { double(:avatars) }
|
11
|
+
|
12
|
+
describe '#associate' do
|
13
|
+
let(:source) { :users }
|
14
|
+
let(:target) { :avatar }
|
15
|
+
|
16
|
+
let(:relations) do
|
17
|
+
{ users: users, avatar: avatars }
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns child tuple with FK set' do
|
21
|
+
expect(avatars).to receive(:foreign_key).with(:users).and_return(:user_id)
|
22
|
+
|
23
|
+
avatar_tuple = { url: 'http://rom-rb.org/images/logo.svg' }
|
24
|
+
user_tuple = { id: 3 }
|
25
|
+
|
26
|
+
expect(assoc.associate(relations, avatar_tuple, user_tuple)).to eql(
|
27
|
+
user_id: 3, url: 'http://rom-rb.org/images/logo.svg'
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
10
31
|
|
11
32
|
shared_examples_for 'one-to-one association' do
|
12
33
|
describe '#combine_keys' do
|
data/spec/unit/gateway_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
require 'rom/lint/spec'
|
4
4
|
|
5
|
-
describe ROM::SQL::Gateway do
|
5
|
+
RSpec.describe ROM::SQL::Gateway, :postgres do
|
6
6
|
include_context 'users and tasks'
|
7
7
|
|
8
8
|
let(:gateway) { container.gateways[:default] }
|
@@ -10,13 +10,14 @@ describe ROM::SQL::Gateway do
|
|
10
10
|
it_behaves_like 'a rom gateway' do
|
11
11
|
let(:identifier) { :sql }
|
12
12
|
let(:gateway) { ROM::SQL::Gateway }
|
13
|
-
let(:uri) { POSTGRES_DB_URI }
|
14
13
|
end
|
15
14
|
|
16
|
-
describe 'sqlite with a file db' do
|
15
|
+
describe 'sqlite with a file db', :sqlite, postgres: false do
|
16
|
+
before do
|
17
|
+
Tempfile.new('test.sqlite')
|
18
|
+
end
|
19
|
+
|
17
20
|
it 'establishes an sqlite connection' do
|
18
|
-
db_file = Tempfile.new('test.sqlite')
|
19
|
-
uri = "#{defined?(JRUBY_VERSION) ? 'jdbc:sqlite' : 'sqlite'}://#{db_file.path}"
|
20
21
|
gateway = ROM::SQL::Gateway.new(uri)
|
21
22
|
expect(gateway).to be_instance_of(ROM::SQL::Gateway)
|
22
23
|
end
|
@@ -37,10 +38,10 @@ describe ROM::SQL::Gateway do
|
|
37
38
|
migrator = double('migrator')
|
38
39
|
|
39
40
|
expect(Sequel).to receive(:connect)
|
40
|
-
.with(
|
41
|
+
.with(uri, host: '127.0.0.1')
|
41
42
|
.and_return(conn)
|
42
43
|
|
43
|
-
gateway = ROM::SQL::Gateway.new(
|
44
|
+
gateway = ROM::SQL::Gateway.new(uri, migrator: migrator, host: '127.0.0.1')
|
44
45
|
|
45
46
|
expect(gateway.options).to eql(migrator: migrator)
|
46
47
|
end
|
@@ -49,7 +50,7 @@ describe ROM::SQL::Gateway do
|
|
49
50
|
extensions = [:pg_array, :pg_enum]
|
50
51
|
connection = Sequel.connect uri
|
51
52
|
|
52
|
-
expect(connection).to receive(:extension).with(:pg_array, :pg_enum)
|
53
|
+
expect(connection).to receive(:extension).with(:pg_array, :pg_json, :pg_enum)
|
53
54
|
|
54
55
|
ROM::SQL::Gateway.new(connection, extensions: extensions)
|
55
56
|
end
|
data/spec/unit/logger_spec.rb
CHANGED
@@ -6,9 +6,9 @@ namespace :db do
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
describe 'MigrationTasks' do
|
10
|
-
|
11
|
-
|
9
|
+
RSpec.describe 'MigrationTasks', :postgres, skip_tables: true do
|
10
|
+
include_context 'database setup'
|
11
|
+
|
12
12
|
let(:migrator) { container.gateways[:default].migrator }
|
13
13
|
|
14
14
|
before do
|
data/spec/unit/migrator_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
RSpec.describe ROM::SQL::Migration::Migrator do
|
1
|
+
RSpec.describe ROM::SQL::Migration::Migrator, :postgres, skip_tables: true do
|
2
|
+
include_context 'database setup'
|
3
|
+
|
2
4
|
subject(:migrator) { ROM::SQL::Migration::Migrator.new(conn, options) }
|
3
5
|
|
4
|
-
let(:conn) { Sequel.connect(POSTGRES_DB_URI) }
|
5
6
|
let(:options) { { path: TMP_PATH.join('test/migrations') } }
|
6
7
|
|
7
8
|
describe '#create_file' do
|
@@ -0,0 +1,27 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#associations' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
context 'with schema' do
|
8
|
+
it 'returns configured primary key from the schema' do
|
9
|
+
conf.relation(:users) do
|
10
|
+
schema(infer: true) do
|
11
|
+
associations do
|
12
|
+
has_many :tasks
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
expect(relation.associations[:tasks]).to be(container.relations.users.schema.associations[:tasks])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'without schema' do
|
22
|
+
it 'returns an empty association set' do
|
23
|
+
expect(relation.associations.elements).to be_empty
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#by_pk' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
it 'restricts a relation by its PK' do
|
8
|
+
expect(relation.by_pk(1).to_a).to eql([id: 1, name: 'Jane'])
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'is available as a view' do
|
12
|
+
expect(relation.by_pk).to be_curried
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#dataset' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
let(:dataset) { container.gateways[:default].dataset(:users) }
|
7
|
+
|
8
|
+
with_adapters do
|
9
|
+
context 'with schema' do
|
10
|
+
before do
|
11
|
+
conf.relation(:users) do
|
12
|
+
schema do
|
13
|
+
attribute :id, ROM::SQL::Types::Serial
|
14
|
+
attribute :name, ROM::SQL::Types::String
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'uses schema to infer default dataset' do
|
20
|
+
expect(relation.dataset).to eql(dataset.select(:id, :name).order(:users__id))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with cherry-picked attributes in schema' do
|
25
|
+
before do
|
26
|
+
conf.relation(:users) do
|
27
|
+
schema do
|
28
|
+
attribute :id, ROM::SQL::Types::Serial
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'uses schema to infer default dataset' do
|
34
|
+
expect(relation.dataset).to eql(dataset.select(:id).order(:users__id))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'without schema' do
|
39
|
+
before do
|
40
|
+
conf.relation(:users)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'selects all qualified columns and sorts by pk' do
|
44
|
+
expect(relation.dataset).to eql(dataset.select(*relation.columns).order(:users__id))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#distinct' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
if !metadata[:sqlite]
|
8
|
+
it 'delegates to dataset and returns a new relation' do
|
9
|
+
expect(relation.dataset).to receive(:distinct).with(:name).and_call_original
|
10
|
+
expect(relation.distinct(:name)).to_not eql(relation)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#exclude' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
it 'delegates to dataset and returns a new relation' do
|
8
|
+
expect(relation.dataset)
|
9
|
+
.to receive(:exclude).with(name: 'Jane').and_call_original
|
10
|
+
expect(relation.exclude(name: 'Jane')).to_not eq(relation)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#fetch' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
describe '#fetch' do
|
8
|
+
it 'returns a single tuple identified by the pk' do
|
9
|
+
expect(relation.fetch(1)).to eql(id: 1, name: 'Jane')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'raises when tuple was not found' do
|
13
|
+
expect { relation.fetch(535315412) }.to raise_error(ROM::TupleCountMismatchError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'raises when more tuples were returned' do
|
17
|
+
expect { relation.fetch([1, 2]) }.to raise_error(ROM::TupleCountMismatchError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#having' do
|
2
|
+
subject(:relation) do
|
3
|
+
container.relations.users
|
4
|
+
.inner_join(:tasks, user_id: :id)
|
5
|
+
.select_group(:users__id, :users__name)
|
6
|
+
.select_append { count(:tasks).as(:task_count) }
|
7
|
+
end
|
8
|
+
|
9
|
+
include_context 'users and tasks'
|
10
|
+
|
11
|
+
with_adapters :postgres do
|
12
|
+
before do
|
13
|
+
conn[:tasks].insert(id: 3, user_id: 2, title: "Joe's another task")
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'restricts a relation using HAVING clause' do
|
17
|
+
expect(relation.having { count(:tasks__id) >= 2 }.to_a).to eq([{ id: 2, name: 'Joe', task_count: 2 }])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#inner_join' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
it 'joins relations using inner join' do
|
8
|
+
result = relation.inner_join(:tasks, user_id: :id).select(:name, :title)
|
9
|
+
|
10
|
+
expect(result.to_a).to eql([
|
11
|
+
{ name: 'Jane', title: "Jane's task" },
|
12
|
+
{ name: 'Joe', title: "Joe's task" }
|
13
|
+
])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'raises error when column names are ambiguous' do
|
17
|
+
expect {
|
18
|
+
relation.inner_join(:tasks, user_id: :id).to_a
|
19
|
+
}.to raise_error(Sequel::DatabaseError, /ambiguous/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#invert' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
it 'delegates to dataset and returns a new relation' do
|
8
|
+
expect(relation.dataset).to receive(:invert).and_call_original
|
9
|
+
expect(relation.invert).to_not eq(relation)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#left_join' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
it 'joins relations using left outer join' do
|
8
|
+
result = relation.left_join(:tasks, user_id: :id).select(:name, :title)
|
9
|
+
|
10
|
+
expect(result.to_a).to match_array([
|
11
|
+
{ name: 'Joe', title: "Joe's task" },
|
12
|
+
{ name: 'Jane', title: "Jane's task" }
|
13
|
+
])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#map' do
|
2
|
+
subject(:relation) { container.relations.users }
|
3
|
+
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
with_adapters do
|
7
|
+
it 'yields tuples' do
|
8
|
+
result = relation.map { |tuple| tuple[:name] }
|
9
|
+
expect(result).to eql(%w(Jane Joe))
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'plucks value' do
|
13
|
+
expect(relation.map(:name)).to eql(%w(Jane Joe))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|