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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93ca7d5248744905dadd655401fb8e9534b466d2
4
- data.tar.gz: 0712f79b9575ff7da7017d5bbf7329daae35dc3b
3
+ metadata.gz: 83c439ad1fcf4ee22b0a4f8d92f924b41b2a0d19
4
+ data.tar.gz: ebd68c2d754a959b7f669cbcc1269d23108a663c
5
5
  SHA512:
6
- metadata.gz: 2c8c09bf8ec81e1f6c5452c79047ef46529e5fbb9aa8b2c017797120d6c6ef74d2e8ce279fb490d9844a640bc4a117aa0a75d2ef428d0aeba20b79244288c358
7
- data.tar.gz: fac22ae5b662fbc8bc4bf7529a84efcb9bb0328a62ebd5ab91ca8e0662aaf4697cdfc6b40936211e26d7806bab8e9d717d83beb7a5d19ac5e044300ec7072f26
6
+ metadata.gz: 4a8081d7fb278e307d71b3be25fe4d386e2a33aa6794b4706a1d7096daf2a29364a867e52e945a41c388f211ecbc0c280af23fe1a9ab9b9c0e730a0a7e5991cc
7
+ data.tar.gz: 8de227092aa395b288d281cbfc69c5370424d76c856ef24f96ef308a4f1f110633556c9021207f74f96b01ee279f0916fc66dcbc20d8d9f4a5d7a99992c5391e
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ## v1.0.0 to-be-released
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)
@@ -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|
@@ -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]
@@ -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
@@ -67,13 +67,32 @@ module ROM
67
67
 
68
68
  # @api private
69
69
  def self.define_default_views!
70
- # @!method by_pk(pk)
71
- # Return a relation restricted by its primary key
72
- # @param [Object] pk The primary key value
73
- # @return [SQL::Relation]
74
- # @api public
75
- view(:by_pk, schema.map(&:name)) do |pk|
76
- where(primary_key => pk)
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 column names
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
- new(dataset.__send__(__method__, *args, &block))
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
- __send__(type, other.name.dataset, join_keys(other))
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '1.0.0.rc2'.freeze
3
+ VERSION = '1.0.0'.freeze
4
4
  end
5
5
  end
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.0.rc'
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
@@ -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, :sqlite do
3
+ RSpec.describe ROM::SQL::Relation do
4
4
  include_context 'users and tasks'
5
5
 
6
- let(:users) { relations[:users] }
7
- let(:tasks) { relations[:tasks] }
6
+ context 'with has_many' do
7
+ subject(:users) { relations[:users] }
8
8
 
9
- before do
10
- conf.relation(:users) do
11
- schema(infer: true) do
12
- associations do
13
- has_many :tasks
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
- conf.relation(:tasks) do
19
- schema(infer: true) do
20
- associations do
21
- belongs_to :users, as: :user
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
- with_adapters(:sqlite) do
28
- it 'returns child tuples for a relation' do
29
- expect(users.assoc(:tasks).where(name: 'Jane').to_a).
30
- to eql([{ id: 2, user_id: 1, title: "Jane's task" }])
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
- it 'returns parent tuples for a relation' do
34
- expect(tasks.assoc(:user).where(title: "Jane's task").to_a).
35
- to eql([{ id: 1, task_id: 2, name: 'Jane' }])
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
- it 'restricts a relation by its PK' do
8
- expect(relation.by_pk(1).to_a).to eql([id: 1, name: 'Jane'])
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
- it 'is available as a view' do
12
- expect(relation.by_pk).to be_curried
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 { belongs_to :user }
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.rc2
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-27 00:00:00.000000000 Z
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.0.rc
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.0.rc
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: 1.3.1
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