order_query 0.3.1 → 0.3.2

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: be66ba349bb0a4819d39ee8f01d8bfa12e82177e
4
- data.tar.gz: 56637889ea73d1f90e70dcd72e09431eb357f1e1
3
+ metadata.gz: 6e69a0283a9dcc95fe70d2fd18c02f5aa62120d2
4
+ data.tar.gz: d1d0a0975074175ff4eb15d7fa5efdd20fc91c3f
5
5
  SHA512:
6
- metadata.gz: c8f9fe60869ae746f9d095dfc549f651b994ded8b75875cd60761e9e7683cdcb6968834916ae6eb881b3dc684f26220adf4e873948923b8e41daee90b11d6671
7
- data.tar.gz: b068a5d7bcc50af28dbdff396adca897f8f502640fcad0f162f1ea9ee4424c9a9cf1afde85837261908bd984a9339bbb6fdce50477d0aa9ed99b394cabf2e53a
6
+ metadata.gz: 0a5d6a00cd9096587584e9c32a1daf05f26577175d9e0bf6a783b8bc189a710482143b18166b82620b3ef315928c51ed837f6776e719ff53c53c644cfc278fa6
7
+ data.tar.gz: 6ae64249791c2c8ac500b10b3a50c9ed6cf439056301ca1d361f1d8f5559fa1180bcdf72113698922a08ad6fd85f5e3adcd3c25c5b9e9f50776225ce51c616f2
data/CHANGES.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.3.2
2
+
3
+ * Optimization: do not wrap top-level disjunctive in `AND` when the column has an enumerated order. [Read more](https://github.com/glebm/order_query/issues/3#issuecomment-54764638).
4
+ * Boolean enum columns (e.g. `[:pinned, [true, false]]`) are now automatically collapsed to `ORDER by column ASC|DESC`.
5
+
1
6
  ## 0.3.1
2
7
 
3
8
  * Automatically add primary key when there is no unique column for the order
data/README.md CHANGED
@@ -12,7 +12,7 @@ It uses [keyset pagination](http://use-the-index-luke.com/no-offset) to achieve
12
12
  Add to Gemfile:
13
13
 
14
14
  ```ruby
15
- gem 'order_query', '~> 0.3.1'
15
+ gem 'order_query', '~> 0.3.2'
16
16
  ```
17
17
 
18
18
  ## Usage
@@ -1,29 +1,32 @@
1
1
  # coding: utf-8
2
+ require 'order_query/direction'
2
3
  require 'order_query/sql/column'
3
4
  module OrderQuery
4
5
  # An order column (sort column)
5
6
  class Column
6
- attr_reader :name, :order, :order_enum, :options
7
+ attr_reader :name, :order_enum, :options
7
8
  delegate :column_name, :quote, to: :@sql
8
9
 
9
10
  # @option spec [String] :unique Mark the attribute as unique to avoid redundant columns
10
11
  def initialize(spec, scope)
11
- spec = spec.dup
12
- options = spec.extract_options!
13
- @name = spec[0]
14
- case spec[1]
15
- when Array
16
- @order_enum = spec[1]
17
- @order = spec[2] || :desc
18
- else
19
- @order = spec[1] || :asc
12
+ spec = spec.dup
13
+ options = spec.extract_options!
14
+ @name = spec[0]
15
+ if spec[1].is_a?(Array)
16
+ @order_enum = spec.delete_at(1)
17
+ spec[1] ||= :desc
20
18
  end
21
- @options = options.reverse_merge(
19
+ @direction = Direction.parse!(spec[1] || :asc)
20
+ @options = options.reverse_merge(
22
21
  unique: name.to_s == scope.primary_key,
23
22
  complete: true
24
23
  )
25
- @unique = @options[:unique]
26
- @sql = SQL::Column.new(self, scope)
24
+ @unique = @options[:unique]
25
+ @sql = SQL::Column.new(self, scope)
26
+ end
27
+
28
+ def direction(reverse = false)
29
+ reverse ? Direction.reverse(@direction) : @direction
27
30
  end
28
31
 
29
32
  def unique?
@@ -40,7 +43,7 @@ module OrderQuery
40
43
  ord = order_enum
41
44
  pos = ord.index(value)
42
45
  if pos
43
- dir = order
46
+ dir = direction
44
47
  if side == :after && dir == :desc || side == :before && dir == :asc
45
48
  ord.from pos + (strict ? 1 : 0)
46
49
  else
@@ -0,0 +1,32 @@
1
+ module OrderQuery
2
+ # Responsible for handling :asc and :desc
3
+ module Direction
4
+ extend self
5
+
6
+ DIRECTIONS = [:asc, :desc].freeze
7
+ DIRECTIONS_S = DIRECTIONS.map { |d| d.to_s.freeze }.freeze
8
+
9
+ def all
10
+ DIRECTIONS
11
+ end
12
+
13
+ # @param [:asc, :desc] direction
14
+ # @return [:asc, :desc]
15
+ def reverse(direction)
16
+ all[(all.index(direction) + 1) % 2].to_sym
17
+ end
18
+
19
+ # @param [:asc, :desc, String] direction
20
+ # @raise [ArgumentError]
21
+ # @return [:asc, :desc]
22
+ def parse!(direction)
23
+ if direction.is_a?(Symbol)
24
+ direction if DIRECTIONS.include?(direction)
25
+ else
26
+ direction = direction.to_s.downcase
27
+ direction.to_sym if DIRECTIONS_S.include?(direction)
28
+ end or
29
+ raise ArgumentError.new("sort direction must be in #{all.map(&:inspect).join(', ')}, is #{direction.inspect}")
30
+ end
31
+ end
32
+ end
@@ -13,48 +13,52 @@ module OrderQuery
13
13
 
14
14
  # @return [String]
15
15
  def build_reverse
16
- @reverse_sql ||= join_order_by_clauses order_by_reverse_sql_clauses
16
+ @reverse_sql ||= join_order_by_clauses order_by_sql_clauses(true)
17
17
  end
18
18
 
19
19
  protected
20
20
 
21
21
  # @return [Array<String>]
22
- def order_by_sql_clauses
23
- @columns.map { |cond| column_clause cond }
22
+ def order_by_sql_clauses(reverse = false)
23
+ @columns.map { |col| column_clause col, reverse }
24
24
  end
25
25
 
26
- def column_clause(cond)
27
- dir_sql = sort_direction_sql cond.order
28
- col_sql = cond.column_name
29
- if cond.order_enum
30
- cond.order_enum.map { |v| "#{col_sql}=#{cond.quote v} #{dir_sql}" }.join(', ').freeze
26
+ def column_clause(col, reverse = false)
27
+ if col.order_enum
28
+ column_clause_enum col, reverse
31
29
  else
32
- "#{col_sql} #{dir_sql}".freeze
30
+ column_clause_ray col, reverse
33
31
  end
34
32
  end
35
33
 
36
- SORT_DIRECTIONS = [:asc, :desc].freeze
37
- # @return [String]
38
- def sort_direction_sql(direction)
39
- if SORT_DIRECTIONS.include?(direction)
40
- direction.to_s.upcase.freeze
41
- else
42
- raise ArgumentError.new("sort direction must be in #{SORT_DIRECTIONS.map(&:inspect).join(', ')}, is #{direction.inspect}")
34
+ def column_clause_ray(col, reverse = false)
35
+ "#{col.column_name} #{sort_direction_sql(col, reverse)}".freeze
36
+ end
37
+
38
+ def column_clause_enum(col, reverse = false)
39
+ enum = col.order_enum
40
+ # Collapse boolean enum to `ORDER BY column ASC|DESC`
41
+ if enum == [false, true] || enum == [true, false]
42
+ return column_clause_ray col, reverse ^ enum.last
43
43
  end
44
+ enum.map { |v|
45
+ "#{order_by_value_sql col, v} #{sort_direction_sql(col, reverse)}"
46
+ }.join(', ').freeze
47
+ end
48
+
49
+ def order_by_value_sql(col, v)
50
+ "#{col.column_name}=#{col.quote v}"
51
+ end
52
+
53
+ # @return [String]
54
+ def sort_direction_sql(col, reverse = false)
55
+ col.direction(reverse).to_s.upcase.freeze
44
56
  end
45
57
 
46
58
  # @param [Array<String>] clauses
47
59
  def join_order_by_clauses(clauses)
48
60
  clauses.join(', ').freeze
49
61
  end
50
-
51
- # @return [Array<String>]
52
- def order_by_reverse_sql_clauses
53
- swap = {'DESC' => 'ASC', 'ASC' => 'DESC'}
54
- order_by_sql_clauses.map { |s|
55
- s.gsub(/DESC|ASC/) { |m| swap[m] }
56
- }
57
- end
58
62
  end
59
63
  end
60
64
  end
@@ -65,9 +65,8 @@ module OrderQuery
65
65
  # Read more at https://github.com/glebm/order_query/issues/3
66
66
  def wrap_top_level_or(query, terms, side)
67
67
  top_term_i = terms.index(&:present?)
68
- if top_term_i && terms[top_term_i].length == 2 &&
69
- (cond = where_side(@columns[top_term_i], side, false)) != WHERE_IDENTITY
70
- join_terms 'AND'.freeze, cond, wrap_term_with_parens(query)
68
+ if top_term_i && terms[top_term_i].length == 2 && !(col = @columns[top_term_i]).order_enum
69
+ join_terms 'AND'.freeze, where_side(col, side, false), wrap_term_with_parens(query)
71
70
  else
72
71
  query
73
72
  end
@@ -107,11 +106,9 @@ module OrderQuery
107
106
  [%Q(#{col.column_name} = ?).freeze, [value]]
108
107
  end
109
108
 
109
+ RAY_OP = {asc: '>'.freeze, desc: '<'.freeze}.freeze
110
110
  def where_ray(col, from, mode, strict = true)
111
- ops = %w(< >)
112
- ops = ops.reverse if mode == :after
113
- op = {asc: ops[0], desc: ops[1]}[col.order || :asc]
114
- ["#{col.column_name} #{op}#{'=' unless strict} ?".freeze, [from]]
111
+ ["#{col.column_name} #{RAY_OP[col.direction(mode == :before)]}#{'=' unless strict} ?".freeze, [from]]
115
112
  end
116
113
 
117
114
  WHERE_IDENTITY = [''.freeze, [].freeze].freeze
@@ -1,3 +1,3 @@
1
1
  module OrderQuery
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
@@ -57,7 +57,7 @@ end
57
57
  describe 'OrderQuery' do
58
58
 
59
59
  [false, true].each do |wrap_top_level_or|
60
- context "(wrap_top_level_or: #{wrap_top_level_or})" do
60
+ context "(wtlo: #{wrap_top_level_or})" do
61
61
  wrap_top_level_or wrap_top_level_or
62
62
 
63
63
  context 'Issue test model' do
@@ -210,6 +210,42 @@ describe 'OrderQuery' do
210
210
  expect(o2.next(true)).to eq(p1)
211
211
  end
212
212
 
213
+ context 'boolean enum order' do
214
+ before do
215
+ create_post pinned: true
216
+ create_post pinned: false
217
+ end
218
+ after do
219
+ Post.delete_all
220
+ end
221
+ it 'ORDER BY is collapsed' do
222
+ expect(Post.seek([:pinned, [true, false]]).scope.to_sql).to include('ORDER BY "posts"."pinned" DESC')
223
+ end
224
+ it 'enum asc' do
225
+ expect(Post.seek([:pinned, [false, true], :asc]).scope.pluck(:pinned)).to eq([true, false])
226
+ expect(Post.seek([:pinned, [true, false], :asc]).scope.pluck(:pinned)).to eq([false, true])
227
+ end
228
+ it 'enum desc' do
229
+ expect(Post.seek([:pinned, [false, true], :desc]).scope.pluck(:pinned)).to eq([false, true])
230
+ expect(Post.seek([:pinned, [true, false], :desc]).scope.pluck(:pinned)).to eq([true, false])
231
+ end
232
+ end
233
+
234
+ xcontext 'nil in enum' do
235
+ states = [nil, false, true]
236
+ let!(:posts) { states.map { |state| create_post(pinned: state) } }
237
+ states.permutation do |p|
238
+ # There is no cross-DB SQL that can be generated to position nil results
239
+ # http://use-the-index-luke.com/sql/sorting-grouping/order-by-asc-desc-nulls-last
240
+ next unless p.first.nil? || p.last.nil?
241
+ # Positioning NULLs first or last can be achieved, but remains on the ToDo / contributions welcome list
242
+ it "nil in enum works for #{p}" do
243
+ expect(Post.seek([:pinned, p]).scope.all.map(&:pinned)).to eq(p)
244
+ expect(Post.seek([:pinned, p, :asc]).scope.all.map(&:pinned)).to eq(p.reverse)
245
+ end
246
+ end
247
+ end
248
+
213
249
  before do
214
250
  Post.delete_all
215
251
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: order_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Mazovetskiy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-07 00:00:00.000000000 Z
11
+ date: 2014-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -79,6 +79,7 @@ files:
79
79
  - Rakefile
80
80
  - lib/order_query.rb
81
81
  - lib/order_query/column.rb
82
+ - lib/order_query/direction.rb
82
83
  - lib/order_query/point.rb
83
84
  - lib/order_query/space.rb
84
85
  - lib/order_query/sql/column.rb