rom-sql 1.0.0.rc2 → 1.0.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/CHANGELOG.md +3 -1
- data/lib/rom/sql/association.rb +5 -0
- data/lib/rom/sql/association/many_to_many.rb +7 -0
- data/lib/rom/sql/attribute.rb +57 -0
- data/lib/rom/sql/function.rb +10 -0
- data/lib/rom/sql/group_dsl.rb +19 -0
- data/lib/rom/sql/relation.rb +26 -7
- data/lib/rom/sql/relation/reading.rb +58 -10
- data/lib/rom/sql/schema.rb +15 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +1 -1
- data/spec/integration/plugins/auto_wrap_spec.rb +10 -0
- data/spec/unit/attribute_spec.rb +25 -0
- data/spec/unit/function_spec.rb +6 -0
- data/spec/unit/relation/assoc_spec.rb +68 -19
- data/spec/unit/relation/by_pk_spec.rb +20 -6
- data/spec/unit/relation/group_spec.rb +46 -0
- data/spec/unit/relation/inner_join_spec.rb +21 -1
- data/spec/unit/relation/order_spec.rb +11 -0
- data/spec/unit/relation/qualified_spec.rb +27 -0
- data/spec/unit/relation/select_spec.rb +0 -8
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83c439ad1fcf4ee22b0a4f8d92f924b41b2a0d19
|
4
|
+
data.tar.gz: ebd68c2d754a959b7f669cbcc1269d23108a663c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a8081d7fb278e307d71b3be25fe4d386e2a33aa6794b4706a1d7096daf2a29364a867e52e945a41c388f211ecbc0c280af23fe1a9ab9b9c0e730a0a7e5991cc
|
7
|
+
data.tar.gz: 8de227092aa395b288d281cbfc69c5370424d76c856ef24f96ef308a4f1f110633556c9021207f74f96b01ee279f0916fc66dcbc20d8d9f4a5d7a99992c5391e
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
## v1.0.0
|
1
|
+
## v1.0.0 2017-01-29
|
2
2
|
|
3
3
|
This release is based on rom core 3.0.0 with its improved Schema API, which is extended with SQL-specific features.
|
4
4
|
|
@@ -23,6 +23,7 @@ Please refer to [the upgrading guide](https://github.com/rom-rb/rom-sql/wiki/Upg
|
|
23
23
|
* Support for inferring more PG types: `macaddr`, `xml` (flash-gordon)
|
24
24
|
* `ROM::SQL::Relation::SequelAPI` extension for backward-compatible query API (this will be deprecated in 1.1.0 and removed in 2.0.0) (solnic)
|
25
25
|
* Added `Object` type for `SQLite` which is used by the inferrer for columns without a type affinity (flash-gordon)
|
26
|
+
* Support for composite PKs in the default `by_pk` view (solnic)
|
26
27
|
|
27
28
|
### Changed
|
28
29
|
|
@@ -32,6 +33,7 @@ Please refer to [the upgrading guide](https://github.com/rom-rb/rom-sql/wiki/Upg
|
|
32
33
|
* [BREAKING] Deprecated `Commands::Update#change` has been removed (solnic)
|
33
34
|
* [BREAKING] Deprecated `Commands.validator` has been removed (solnic)
|
34
35
|
* [BREAKING] `assoc_macros` plugin has been removed, please use associations from now (solnic)
|
36
|
+
* Default `by_pk` uses schema attributes, it will raise exception if PK attribute is missing in the schema (solnic)
|
35
37
|
* [internal] Associations use schemas for relation projections (solnic)
|
36
38
|
* [internal] `select`, `select_append`, `project`, `inner_join` and `left_join` use schemas internally (solnic)
|
37
39
|
* [internal] Deprecation and constants are now based on dry-core (flash-gordon)
|
data/lib/rom/sql/association.rb
CHANGED
@@ -58,6 +58,11 @@ module ROM
|
|
58
58
|
)
|
59
59
|
end
|
60
60
|
|
61
|
+
# @api public
|
62
|
+
def join(relations, type, source = relations[self.source], target = relations[self.target])
|
63
|
+
source.__send__(type, target.name.dataset, join_keys(relations)).qualified
|
64
|
+
end
|
65
|
+
|
61
66
|
# Returns a qualified attribute name for a given dataset
|
62
67
|
#
|
63
68
|
# This is compatible with Sequel's SQL generator and can be used in query
|
@@ -48,6 +48,13 @@ module ROM
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
# @api public
|
52
|
+
def join(relations, type, source = relations[self.source], target = relations[self.target])
|
53
|
+
through_assoc = source.associations[through]
|
54
|
+
joined = through_assoc.join(relations, type, source)
|
55
|
+
joined.__send__(type, target.name.dataset, join_keys(relations)).qualified
|
56
|
+
end
|
57
|
+
|
51
58
|
# @api public
|
52
59
|
def join_keys(relations)
|
53
60
|
with_keys(relations) { |source_key, target_key|
|
data/lib/rom/sql/attribute.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
require 'sequel/core'
|
2
|
+
|
1
3
|
require 'rom/schema/attribute'
|
4
|
+
require 'rom/sql/projection_dsl'
|
2
5
|
|
3
6
|
module ROM
|
4
7
|
module SQL
|
@@ -22,6 +25,17 @@ module ROM
|
|
22
25
|
end
|
23
26
|
alias_method :as, :aliased
|
24
27
|
|
28
|
+
# Return a new attribute in its canonical form
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
def canonical
|
32
|
+
if aliased?
|
33
|
+
meta(alias: nil, sql_expr: nil)
|
34
|
+
else
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
25
39
|
# Return a new attribute marked as qualified
|
26
40
|
#
|
27
41
|
# @example
|
@@ -124,6 +138,49 @@ module ROM
|
|
124
138
|
end
|
125
139
|
end
|
126
140
|
|
141
|
+
# Return a boolean expression with `=` operator
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# users.where { id.is(1) }
|
145
|
+
#
|
146
|
+
# users.where(users[:id].is(1))
|
147
|
+
#
|
148
|
+
# @param [Object] other Any SQL-compatible object type
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
def is(other)
|
152
|
+
Sequel::SQL::BooleanExpression.new(:'=', self, other)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Create a function DSL from the attribute
|
156
|
+
#
|
157
|
+
# @example
|
158
|
+
# users[:id].func { int::count(id).as(:count) }
|
159
|
+
#
|
160
|
+
# @return [SQL::Function]
|
161
|
+
#
|
162
|
+
# @api public
|
163
|
+
def func(&block)
|
164
|
+
ProjectionDSL.new(name => self).call(&block).first
|
165
|
+
end
|
166
|
+
|
167
|
+
# Create a CONCAT function from the attribute
|
168
|
+
#
|
169
|
+
# @example with default separator (' ')
|
170
|
+
# users[:id].concat(users[:name])
|
171
|
+
#
|
172
|
+
# @example with custom separator
|
173
|
+
# users[:id].concat(users[:name], '-')
|
174
|
+
#
|
175
|
+
# @param [SQL::Attribute] other
|
176
|
+
#
|
177
|
+
# @return [SQL::Function]
|
178
|
+
#
|
179
|
+
# @api public
|
180
|
+
def concat(other, sep = ' ')
|
181
|
+
Function.new(type).concat(self, sep, other)
|
182
|
+
end
|
183
|
+
|
127
184
|
# Sequel calls this method to coerce an attribute into SQL string
|
128
185
|
#
|
129
186
|
# @param [Sequel::Dataset]
|
data/lib/rom/sql/function.rb
CHANGED
@@ -16,6 +16,16 @@ module ROM
|
|
16
16
|
meta[:alias] || super
|
17
17
|
end
|
18
18
|
|
19
|
+
def qualified
|
20
|
+
meta(
|
21
|
+
func: ::Sequel::SQL::Function.new(func.name, *func.args.map { |arg| arg.respond_to?(:qualified) ? arg.qualified : arg })
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def is(other)
|
26
|
+
::Sequel::SQL::BooleanExpression.new(:'=', func, other)
|
27
|
+
end
|
28
|
+
|
19
29
|
private
|
20
30
|
|
21
31
|
def func
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rom/sql/dsl'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module SQL
|
5
|
+
# @api private
|
6
|
+
class GroupDSL < DSL
|
7
|
+
private
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
def method_missing(meth, *args, &block)
|
11
|
+
if schema.key?(meth)
|
12
|
+
schema[meth].canonical
|
13
|
+
else
|
14
|
+
::Sequel::VIRTUAL_ROW.__send__(meth.to_s.upcase, *args, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rom/sql/relation.rb
CHANGED
@@ -67,13 +67,32 @@ module ROM
|
|
67
67
|
|
68
68
|
# @api private
|
69
69
|
def self.define_default_views!
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
70
|
+
if schema.primary_key.size > 1
|
71
|
+
# @!method by_pk(val1, val2)
|
72
|
+
# Return a relation restricted by its composite primary key
|
73
|
+
#
|
74
|
+
# @param [Array] args A list with composite pk values
|
75
|
+
#
|
76
|
+
# @return [SQL::Relation]
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
80
|
+
def by_pk(#{schema.primary_key.map(&:name).join(', ')})
|
81
|
+
where(#{schema.primary_key.map { |attr| "schema[:#{attr.name}] => #{attr.name}" }.join(', ')})
|
82
|
+
end
|
83
|
+
RUBY
|
84
|
+
else
|
85
|
+
# @!method by_pk(pk)
|
86
|
+
# Return a relation restricted by its primary key
|
87
|
+
#
|
88
|
+
# @param [Object] pk The primary key value
|
89
|
+
#
|
90
|
+
# @return [SQL::Relation]
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
define_method(:by_pk) do |pk|
|
94
|
+
where(schema[primary_key] => pk)
|
95
|
+
end
|
77
96
|
end
|
78
97
|
end
|
79
98
|
|
@@ -640,13 +640,68 @@ module ROM
|
|
640
640
|
# @example
|
641
641
|
# tasks.group(tasks[:id], tasks[:title])
|
642
642
|
#
|
643
|
-
# @param [Array<SQL::Attribute>] columns A list with
|
643
|
+
# @param [Array<SQL::Attribute>] columns A list with relation attributes
|
644
|
+
#
|
645
|
+
# @overload group(*attributes, &block)
|
646
|
+
# Return a new relation grouped by provided attributes from a block
|
647
|
+
#
|
648
|
+
# @example
|
649
|
+
# tasks.group(tasks[:id]) { title.qualified }
|
650
|
+
#
|
651
|
+
# @param [Array<SQL::Attributes>] attributes A list with relation attributes
|
644
652
|
#
|
645
653
|
# @return [Relation]
|
646
654
|
#
|
647
655
|
# @api public
|
648
656
|
def group(*args, &block)
|
649
|
-
|
657
|
+
if block
|
658
|
+
if args.size > 0
|
659
|
+
group(*args).group_append(&block)
|
660
|
+
else
|
661
|
+
new(dataset.__send__(__method__, *schema.group(&block)))
|
662
|
+
end
|
663
|
+
else
|
664
|
+
new(dataset.__send__(__method__, *schema.project(*args).canonical))
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
# Group by more columns
|
669
|
+
#
|
670
|
+
# @overload group_append(*columns)
|
671
|
+
# Return a new relation grouped by provided columns
|
672
|
+
#
|
673
|
+
# @example
|
674
|
+
# tasks.group_append(:user_id)
|
675
|
+
#
|
676
|
+
# @param [Array<Symbol>] columns A list with column names
|
677
|
+
#
|
678
|
+
# @overload group_append(*attributes)
|
679
|
+
# Return a new relation grouped by provided schema attributes
|
680
|
+
#
|
681
|
+
# @example
|
682
|
+
# tasks.group_append(tasks[:id], tasks[:title])
|
683
|
+
#
|
684
|
+
# @overload group_append(*attributes, &block)
|
685
|
+
# Return a new relation grouped by provided schema attributes from a block
|
686
|
+
#
|
687
|
+
# @example
|
688
|
+
# tasks.group_append(tasks[:id]) { id.qualified }
|
689
|
+
#
|
690
|
+
# @param [Array<SQL::Attribute>] columns A list with column names
|
691
|
+
#
|
692
|
+
# @return [Relation]
|
693
|
+
#
|
694
|
+
# @api public
|
695
|
+
def group_append(*args, &block)
|
696
|
+
if block
|
697
|
+
if args.size > 0
|
698
|
+
group_append(*args).group_append(&block)
|
699
|
+
else
|
700
|
+
new(dataset.group_append(*schema.group(&block)))
|
701
|
+
end
|
702
|
+
else
|
703
|
+
new(dataset.group_append(*args))
|
704
|
+
end
|
650
705
|
end
|
651
706
|
|
652
707
|
# Group by specific columns and count by group
|
@@ -742,18 +797,11 @@ module ROM
|
|
742
797
|
when Symbol, Association::Name
|
743
798
|
new(dataset.__send__(type, other.to_sym, join_cond, opts, &block))
|
744
799
|
when Relation
|
745
|
-
|
800
|
+
associations[other.name.dataset].join(__registry__, type, self, other)
|
746
801
|
else
|
747
802
|
raise ArgumentError, "+other+ must be either a symbol or a relation, #{other.class} given"
|
748
803
|
end
|
749
804
|
end
|
750
|
-
|
751
|
-
# Return join key conditions for the provided relation
|
752
|
-
#
|
753
|
-
# @api private
|
754
|
-
def join_keys(other)
|
755
|
-
other.associations[name].join_keys(__registry__)
|
756
|
-
end
|
757
805
|
end
|
758
806
|
end
|
759
807
|
end
|
data/lib/rom/sql/schema.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rom/schema'
|
2
2
|
require 'rom/sql/order_dsl'
|
3
|
+
require 'rom/sql/group_dsl'
|
3
4
|
require 'rom/sql/projection_dsl'
|
4
5
|
require 'rom/sql/restriction_dsl'
|
5
6
|
|
@@ -31,6 +32,11 @@ module ROM
|
|
31
32
|
OrderDSL.new(self).call(&block)
|
32
33
|
end
|
33
34
|
|
35
|
+
# @api public
|
36
|
+
def group(&block)
|
37
|
+
GroupDSL.new(self).call(&block)
|
38
|
+
end
|
39
|
+
|
34
40
|
# Return a new schema with attributes marked as qualified
|
35
41
|
#
|
36
42
|
# @return [Schema]
|
@@ -40,6 +46,15 @@ module ROM
|
|
40
46
|
new(map(&:qualified))
|
41
47
|
end
|
42
48
|
|
49
|
+
# Return a new schema with attributes restored to canonical form
|
50
|
+
#
|
51
|
+
# @return [Schema]
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def canonical
|
55
|
+
new(map(&:canonical))
|
56
|
+
end
|
57
|
+
|
43
58
|
# @api public
|
44
59
|
def project(*names, &block)
|
45
60
|
if block
|
data/lib/rom/sql/version.rb
CHANGED
data/rom-sql.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
|
23
23
|
spec.add_runtime_dependency 'dry-types', '~> 0.9', '>= 0.9.4'
|
24
24
|
spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
|
25
|
-
spec.add_runtime_dependency 'rom', '~> 3.0
|
25
|
+
spec.add_runtime_dependency 'rom', '~> 3.0'
|
26
26
|
|
27
27
|
spec.add_development_dependency 'bundler'
|
28
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
@@ -14,6 +14,16 @@ RSpec.describe 'Plugins / :auto_wrap' do
|
|
14
14
|
id: 2, user_id: 1, title: "Jane's task", users_name: "Jane", users_id: 1
|
15
15
|
)
|
16
16
|
end
|
17
|
+
|
18
|
+
it 'works with by_pk' do
|
19
|
+
task_with_user = tasks
|
20
|
+
.for_wrap({ id: :user_id }, users.name.relation)
|
21
|
+
.by_pk(1)
|
22
|
+
.one
|
23
|
+
|
24
|
+
expect(task_with_user).
|
25
|
+
to eql(id: 1, user_id: 2, title: "Joe's task", users_name: "Joe", users_id: 2)
|
26
|
+
end
|
17
27
|
end
|
18
28
|
|
19
29
|
context 'when parent relation is registered under dataset name' do
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe ROM::SQL::Attribute, :postgres do
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
let(:users) { relations[:users] }
|
7
|
+
let(:ds) { users.dataset }
|
8
|
+
|
9
|
+
describe '#is' do
|
10
|
+
it 'returns a boolean expression' do
|
11
|
+
expect(users[:id].is(1).sql_literal(ds)).to eql('("id" = 1)')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns a boolean expression for qualified attribute' do
|
15
|
+
expect((users[:id].qualified.is(1)).sql_literal(ds)).to eql('("users"."id" = 1)')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#concat' do
|
20
|
+
it 'returns a concat function attribute' do
|
21
|
+
expect(users[:id].concat(users[:name]).as(:uid).sql_literal(ds)).
|
22
|
+
to eql(%(CONCAT("id", ' ', "name") AS "uid"))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/unit/function_spec.rb
CHANGED
@@ -22,6 +22,12 @@ RSpec.describe ROM::SQL::Function, :postgres do
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
describe '#is' do
|
26
|
+
it 'returns an sql boolean expression' do
|
27
|
+
expect((func.count(:id).is(1)).sql_literal(ds)).to eql(%((COUNT("id") = 1)))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
25
31
|
describe '#method_missing' do
|
26
32
|
it 'responds to anything when not set' do
|
27
33
|
expect(func.count(:id)).to be_instance_of(func.class)
|
@@ -1,38 +1,87 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
RSpec.describe ROM::SQL::Relation
|
3
|
+
RSpec.describe ROM::SQL::Relation do
|
4
4
|
include_context 'users and tasks'
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
context 'with has_many' do
|
7
|
+
subject(:users) { relations[:users] }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
let(:tasks) { relations[:tasks] }
|
10
|
+
|
11
|
+
before do
|
12
|
+
conf.relation(:users) do
|
13
|
+
schema(infer: true) do
|
14
|
+
associations do
|
15
|
+
has_many :tasks
|
16
|
+
end
|
14
17
|
end
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
with_adapters do
|
22
|
+
it 'returns child tuples for a relation' do
|
23
|
+
expect(users.assoc(:tasks).where(name: 'Jane').to_a).
|
24
|
+
to eql([{ id: 2, user_id: 1, title: "Jane's task" }])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with has_many-through' do
|
30
|
+
subject(:tasks) { relations[:tasks] }
|
31
|
+
|
32
|
+
before do
|
33
|
+
conf.relation(:task_tags) do
|
34
|
+
schema(infer: true) do
|
35
|
+
associations do
|
36
|
+
belongs_to :tasks
|
37
|
+
belongs_to :tags
|
38
|
+
end
|
22
39
|
end
|
23
40
|
end
|
41
|
+
|
42
|
+
conf.relation(:tasks) do
|
43
|
+
schema(infer: true) do
|
44
|
+
associations do
|
45
|
+
has_many :tags, through: :task_tags
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
conn[:tags].insert id: 2, name: 'whatevah'
|
51
|
+
conn[:task_tags].insert(tag_id: 2, task_id: 2)
|
52
|
+
end
|
53
|
+
|
54
|
+
with_adapters do
|
55
|
+
it 'returns child tuples for a relation' do
|
56
|
+
expect(tasks.assoc(:tags).to_a).
|
57
|
+
to eql([{ id: 1, name: 'important', task_id: 1 }, { id: 2, name: 'whatevah', task_id: 2 }])
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns child tuples for a restricted relation' do
|
61
|
+
expect(tasks.assoc(:tags).where(title: "Jane's task").to_a).
|
62
|
+
to eql([{ id: 2, name: 'whatevah', task_id: 2 }])
|
63
|
+
end
|
24
64
|
end
|
25
65
|
end
|
26
66
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
67
|
+
context 'with belongs_to' do
|
68
|
+
subject(:tasks) { relations[:tasks] }
|
69
|
+
|
70
|
+
before do
|
71
|
+
conf.relation(:tasks) do
|
72
|
+
schema(infer: true) do
|
73
|
+
associations do
|
74
|
+
belongs_to :users, as: :user
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
31
78
|
end
|
32
79
|
|
33
|
-
|
34
|
-
|
35
|
-
|
80
|
+
with_adapters do
|
81
|
+
it 'returns parent tuples for a relation' do
|
82
|
+
expect(tasks.assoc(:user).where(title: "Jane's task").to_a).
|
83
|
+
to eql([{ id: 1, task_id: 2, name: 'Jane' }])
|
84
|
+
end
|
36
85
|
end
|
37
86
|
end
|
38
87
|
end
|
@@ -1,15 +1,29 @@
|
|
1
1
|
RSpec.describe ROM::Relation, '#by_pk' do
|
2
|
-
subject(:relation) { container.relations.users }
|
3
|
-
|
4
2
|
include_context 'users and tasks'
|
5
3
|
|
6
4
|
with_adapters do
|
7
|
-
|
8
|
-
|
5
|
+
context 'with a single PK' do
|
6
|
+
subject(:relation) { relations[:users] }
|
7
|
+
|
8
|
+
it 'restricts a relation by its PK' do
|
9
|
+
expect(relation.by_pk(1).to_a).to eql([id: 1, name: 'Jane'])
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'is available as a view' do
|
13
|
+
expect(relation.by_pk).to be_curried
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'qualifies pk attr' do
|
17
|
+
expect(relation.qualified.by_pk(1).select(:id).join(:tasks, user_id: :id).one).to eql(id: 1)
|
18
|
+
end
|
9
19
|
end
|
10
20
|
|
11
|
-
|
12
|
-
|
21
|
+
context 'with a composite PK' do
|
22
|
+
subject(:relation) { relations[:task_tags] }
|
23
|
+
|
24
|
+
it 'restricts a relation by is PK' do
|
25
|
+
expect(relation.by_pk(1, 1).to_a).to eql([{ tag_id: 1, task_id: 1 }])
|
26
|
+
end
|
13
27
|
end
|
14
28
|
end
|
15
29
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#group' do
|
2
|
+
subject(:relation) { relations[:users] }
|
3
|
+
|
4
|
+
let(:tasks) { relations[:tasks] }
|
5
|
+
|
6
|
+
include_context 'users and tasks'
|
7
|
+
|
8
|
+
with_adapters do
|
9
|
+
it 'groups by provided attribute name' do
|
10
|
+
grouped = relation.
|
11
|
+
qualified.
|
12
|
+
left_join(:tasks, tasks[:user_id].qualified => relation[:id].qualified).
|
13
|
+
group(:id)
|
14
|
+
|
15
|
+
expect(grouped.to_a).to eql([{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe'}])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'groups by provided attribute name in a block' do
|
19
|
+
grouped = relation.
|
20
|
+
qualified.
|
21
|
+
left_join(:tasks, tasks[:user_id].qualified => relation[:id].qualified).
|
22
|
+
group { id.qualified }
|
23
|
+
|
24
|
+
expect(grouped.to_a).to eql([{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe'}])
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'groups by aliased attributes' do
|
28
|
+
grouped = relation.
|
29
|
+
select { id.as(:user_id) }.
|
30
|
+
group(:id)
|
31
|
+
|
32
|
+
expect(grouped.to_a).to eql([{ user_id: 1 }, { user_id: 2 }])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
with_adapters :postgres do
|
37
|
+
it 'groups by provided attribute name in and attributes from a block' do
|
38
|
+
grouped = relation.
|
39
|
+
qualified.
|
40
|
+
left_join(:tasks, tasks[:user_id].qualified => relation[:id].qualified).
|
41
|
+
group(tasks[:title]) { id.qualified }
|
42
|
+
|
43
|
+
expect(grouped.to_a).to eql([{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe'}])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -2,6 +2,7 @@ RSpec.describe ROM::Relation, '#inner_join' do
|
|
2
2
|
subject(:relation) { relations[:users] }
|
3
3
|
|
4
4
|
let(:tasks) { relations[:tasks] }
|
5
|
+
let(:tags) { relations[:tags] }
|
5
6
|
|
6
7
|
include_context 'users and tasks'
|
7
8
|
|
@@ -44,9 +45,22 @@ RSpec.describe ROM::Relation, '#inner_join' do
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
48
|
+
conf.relation(:task_tags) do
|
49
|
+
schema(infer: true) do
|
50
|
+
associations do
|
51
|
+
belongs_to :tasks
|
52
|
+
belongs_to :tags
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
47
57
|
conf.relation(:tasks) do
|
48
58
|
schema(infer: true) do
|
49
|
-
associations
|
59
|
+
associations do
|
60
|
+
belongs_to :user
|
61
|
+
has_many :task_tags
|
62
|
+
has_many :tags, through: :task_tags
|
63
|
+
end
|
50
64
|
end
|
51
65
|
end
|
52
66
|
|
@@ -65,6 +79,12 @@ RSpec.describe ROM::Relation, '#inner_join' do
|
|
65
79
|
{ name: 'Joe', title: "Joe's task" }
|
66
80
|
])
|
67
81
|
end
|
82
|
+
|
83
|
+
it 'joins relation with join keys inferred for m:m-through' do
|
84
|
+
result = tasks.inner_join(tags)
|
85
|
+
|
86
|
+
expect(result.to_a).to eql([{ id: 1, user_id: 2, title: "Joe's task" }])
|
87
|
+
end
|
68
88
|
end
|
69
89
|
|
70
90
|
it 'raises error when column names are ambiguous' do
|
@@ -37,4 +37,15 @@ RSpec.describe ROM::Relation, '#order' do
|
|
37
37
|
to eql([{ id: 2, name: 'Joe' }, { id: 1, name: 'Jane' }, { id: 3, name: 'Jade' }])
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
with_adapters :postgres, :mysql do
|
42
|
+
it 'orders by virtual attributes' do
|
43
|
+
ordered = relation.
|
44
|
+
select { string::concat(id, '-', name).as(:uid) }.
|
45
|
+
order(:uid)
|
46
|
+
|
47
|
+
expect(ordered.to_a).
|
48
|
+
to eql([{ uid: '1-Jane' }, { uid: '2-Joe' }, { uid: '3-Jade' }])
|
49
|
+
end
|
50
|
+
end
|
40
51
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
RSpec.describe ROM::Relation, '#qualified' do
|
2
|
+
subject(:relation) { relations[:users] }
|
3
|
+
|
4
|
+
let(:tasks) { relations[:tasks] }
|
5
|
+
|
6
|
+
include_context 'users and tasks'
|
7
|
+
|
8
|
+
with_adapters do
|
9
|
+
it 'qualifies all attributes' do
|
10
|
+
qualified = relation.qualified
|
11
|
+
|
12
|
+
expect(qualified.schema.all?(&:qualified)).to be(true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'qualifies virtual attributes' do
|
16
|
+
qualified = relation.
|
17
|
+
left_join(:tasks, tasks[:user_id].qualified => relation[:id].qualified).
|
18
|
+
select(:id, tasks[:id].func { int::count(id).as(:count) }).
|
19
|
+
qualified.
|
20
|
+
group(:id)
|
21
|
+
|
22
|
+
expect(qualified.schema.all?(&:qualified)).to be(true)
|
23
|
+
|
24
|
+
expect(qualified.to_a).to eql([{ id: 1, count: 1 }, { id: 2, count: 1 }])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -29,13 +29,5 @@ RSpec.describe ROM::Relation, '#select' do
|
|
29
29
|
it 'supports blocks' do
|
30
30
|
expect(relation.select { [id, title] }.schema.map(&:name)).to eql(%i[id title])
|
31
31
|
end
|
32
|
-
|
33
|
-
it 'supports blocks with custom expressions' do
|
34
|
-
selected = relation
|
35
|
-
.select { [int::count(id).as(:id_count), title.prefixed(:task)] }
|
36
|
-
.group { [id, title] }
|
37
|
-
|
38
|
-
expect(selected.first).to eql(id_count: 1, task_title: "Joe's task")
|
39
|
-
end
|
40
32
|
end
|
41
33
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rom-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -84,14 +84,14 @@ dependencies:
|
|
84
84
|
requirements:
|
85
85
|
- - "~>"
|
86
86
|
- !ruby/object:Gem::Version
|
87
|
-
version: 3.0
|
87
|
+
version: '3.0'
|
88
88
|
type: :runtime
|
89
89
|
prerelease: false
|
90
90
|
version_requirements: !ruby/object:Gem::Requirement
|
91
91
|
requirements:
|
92
92
|
- - "~>"
|
93
93
|
- !ruby/object:Gem::Version
|
94
|
-
version: 3.0
|
94
|
+
version: '3.0'
|
95
95
|
- !ruby/object:Gem::Dependency
|
96
96
|
name: bundler
|
97
97
|
requirement: !ruby/object:Gem::Requirement
|
@@ -173,6 +173,7 @@ files:
|
|
173
173
|
- lib/rom/sql/extensions/sqlite/types.rb
|
174
174
|
- lib/rom/sql/function.rb
|
175
175
|
- lib/rom/sql/gateway.rb
|
176
|
+
- lib/rom/sql/group_dsl.rb
|
176
177
|
- lib/rom/sql/migration.rb
|
177
178
|
- lib/rom/sql/migration/migrator.rb
|
178
179
|
- lib/rom/sql/migration/template.rb
|
@@ -253,6 +254,7 @@ files:
|
|
253
254
|
- spec/unit/association/one_to_many_spec.rb
|
254
255
|
- spec/unit/association/one_to_one_spec.rb
|
255
256
|
- spec/unit/association/one_to_one_through_spec.rb
|
257
|
+
- spec/unit/attribute_spec.rb
|
256
258
|
- spec/unit/function_spec.rb
|
257
259
|
- spec/unit/gateway_spec.rb
|
258
260
|
- spec/unit/logger_spec.rb
|
@@ -270,6 +272,7 @@ files:
|
|
270
272
|
- spec/unit/relation/distinct_spec.rb
|
271
273
|
- spec/unit/relation/exclude_spec.rb
|
272
274
|
- spec/unit/relation/fetch_spec.rb
|
275
|
+
- spec/unit/relation/group_spec.rb
|
273
276
|
- spec/unit/relation/having_spec.rb
|
274
277
|
- spec/unit/relation/inner_join_spec.rb
|
275
278
|
- spec/unit/relation/inspect_spec.rb
|
@@ -284,6 +287,7 @@ files:
|
|
284
287
|
- spec/unit/relation/primary_key_spec.rb
|
285
288
|
- spec/unit/relation/project_spec.rb
|
286
289
|
- spec/unit/relation/qualified_columns_spec.rb
|
290
|
+
- spec/unit/relation/qualified_spec.rb
|
287
291
|
- spec/unit/relation/read_spec.rb
|
288
292
|
- spec/unit/relation/rename_spec.rb
|
289
293
|
- spec/unit/relation/right_join_spec.rb
|
@@ -311,9 +315,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
311
315
|
version: '0'
|
312
316
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
313
317
|
requirements:
|
314
|
-
- - "
|
318
|
+
- - ">="
|
315
319
|
- !ruby/object:Gem::Version
|
316
|
-
version:
|
320
|
+
version: '0'
|
317
321
|
requirements: []
|
318
322
|
rubyforge_project:
|
319
323
|
rubygems_version: 2.5.1
|
@@ -374,6 +378,7 @@ test_files:
|
|
374
378
|
- spec/unit/association/one_to_many_spec.rb
|
375
379
|
- spec/unit/association/one_to_one_spec.rb
|
376
380
|
- spec/unit/association/one_to_one_through_spec.rb
|
381
|
+
- spec/unit/attribute_spec.rb
|
377
382
|
- spec/unit/function_spec.rb
|
378
383
|
- spec/unit/gateway_spec.rb
|
379
384
|
- spec/unit/logger_spec.rb
|
@@ -391,6 +396,7 @@ test_files:
|
|
391
396
|
- spec/unit/relation/distinct_spec.rb
|
392
397
|
- spec/unit/relation/exclude_spec.rb
|
393
398
|
- spec/unit/relation/fetch_spec.rb
|
399
|
+
- spec/unit/relation/group_spec.rb
|
394
400
|
- spec/unit/relation/having_spec.rb
|
395
401
|
- spec/unit/relation/inner_join_spec.rb
|
396
402
|
- spec/unit/relation/inspect_spec.rb
|
@@ -405,6 +411,7 @@ test_files:
|
|
405
411
|
- spec/unit/relation/primary_key_spec.rb
|
406
412
|
- spec/unit/relation/project_spec.rb
|
407
413
|
- spec/unit/relation/qualified_columns_spec.rb
|
414
|
+
- spec/unit/relation/qualified_spec.rb
|
408
415
|
- spec/unit/relation/read_spec.rb
|
409
416
|
- spec/unit/relation/rename_spec.rb
|
410
417
|
- spec/unit/relation/right_join_spec.rb
|