order_query 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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