order_query 0.3.0 → 0.3.1

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: 02a08c0101e3e15512098357d8e9159941b2606f
4
- data.tar.gz: a11460b00d5f8e3dc16dc28f0fc264d4c17f1fef
3
+ metadata.gz: be66ba349bb0a4819d39ee8f01d8bfa12e82177e
4
+ data.tar.gz: 56637889ea73d1f90e70dcd72e09431eb357f1e1
5
5
  SHA512:
6
- metadata.gz: 9e450de5cb30e692f068ae9551459879a3315c9feed3527809a50f82412954ce258a495f54bbefa18f04e1a823310fd1eba55d44caf1d623bb94ac7712e0f905
7
- data.tar.gz: b2c92a65bdb70275836a7fede9c457056fdbb9ba7fe768a8fe3b5608790264dec600ad857f504135a7d465905fa3201d7c740f03b71546d68bbd8ea1e92090af
6
+ metadata.gz: c8f9fe60869ae746f9d095dfc549f651b994ded8b75875cd60761e9e7683cdcb6968834916ae6eb881b3dc684f26220adf4e873948923b8e41daee90b11d6671
7
+ data.tar.gz: b068a5d7bcc50af28dbdff396adca897f8f502640fcad0f162f1ea9ee4424c9a9cf1afde85837261908bd984a9339bbb6fdce50477d0aa9ed99b394cabf2e53a
data/CHANGES.md CHANGED
@@ -1,6 +1,12 @@
1
+ ## 0.3.1
2
+
3
+ * Automatically add primary key when there is no unique column for the order
4
+ * Remove `complete` option
5
+ * Fix Rubinius compatibility
6
+
1
7
  ## 0.3.0
2
8
 
3
- * `order_query` now accepts conditions as varargs. Array form is still supported.
9
+ * `order_query` now accepts columns as varargs. Array form is still supported.
4
10
  * `order_by` renamed to `seek`
5
11
 
6
12
  ## 0.2.1
data/MIT-LICENSE CHANGED
@@ -6,7 +6,7 @@ a copy of this software and associated documentation files (the
6
6
  without limitation the rights to use, copy, modify, merge, publish,
7
7
  distribute, sublicense, and/or sell copies of the Software, and to
8
8
  permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
9
+ the following columns:
10
10
 
11
11
  The above copyright notice and this permission notice shall be
12
12
  included in all copies or substantial portions of the Software.
data/README.md CHANGED
@@ -12,12 +12,12 @@ 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.0'
15
+ gem 'order_query', '~> 0.3.1'
16
16
  ```
17
17
 
18
18
  ## Usage
19
19
 
20
- Define named order conditions with `order_query`:
20
+ Define named order columns with `order_query`:
21
21
 
22
22
  ```ruby
23
23
  class Post < ActiveRecord::Base
@@ -29,7 +29,7 @@ class Post < ActiveRecord::Base
29
29
  end
30
30
  ```
31
31
 
32
- Order query accepts a list of order conditions as varargs or one array, each one specified as:
32
+ Order query accepts a list of order columns as varargs or one array, each one specified as:
33
33
 
34
34
  ```ruby
35
35
  [<attribute name>, (attribute values in order), (:asc or :desc), (options hash)]
@@ -40,7 +40,6 @@ Available options:
40
40
  | option | description |
41
41
  |------------|----------------------------------------------------------------------------|
42
42
  | unique | Unique attribute. Default: `true` for primary key, `false` otherwise. |
43
- | complete | Specified attribute values are the only possible values. Default: `true`. |
44
43
  | sql | Customize attribute value SQL |
45
44
 
46
45
 
@@ -87,9 +86,9 @@ post = posts.find(42)
87
86
  post.order_home(posts) #=> #<OrderQuery::Point>
88
87
  ```
89
88
 
90
- ### Dynamic conditions
89
+ ### Dynamic columns
91
90
 
92
- Query with dynamic order conditions using the `seek(*spec)` class method:
91
+ Query with dynamic order columns using the `seek(*spec)` class method:
93
92
 
94
93
  ```ruby
95
94
  space = Post.visible.seek([:id, :desc]) #=> #<OrderQuery::Space>
@@ -124,7 +123,7 @@ class Post < ActiveRecord::Base
124
123
  [:priority, %w(high medium low)],
125
124
  # A method and custom SQL can be used instead of an attribute
126
125
  [:valid_votes_count, :desc, sql: '(votes - suspicious_votes)'],
127
- # Default sort order for non-array conditions is :asc, just like SQL
126
+ # Default sort order for non-array columns is :asc, just like SQL
128
127
  [:updated_at, :desc],
129
128
  # pass unique: true for unique attributes to get more optimized queries
130
129
  # unique is true by default for primary_key
@@ -153,7 +152,7 @@ ORDER BY
153
152
  LIMIT 1
154
153
  ```
155
154
 
156
- The actual query is a bit different because `order_query` wraps the top-level `OR` with a (redundant) non-strict condition `x0' AND (x0 OR ...)`
155
+ The actual query is a bit different because `order_query` wraps the top-level `OR` with a (redundant) non-strict column `x0' AND (x0 OR ...)`
157
156
  for [performance reasons](https://github.com/glebm/order_query/issues/3).
158
157
  This can be disabled with `OrderQuery.wrap_top_level_or = false`.
159
158
 
@@ -1,13 +1,12 @@
1
1
  # coding: utf-8
2
- require 'order_query/sql/condition'
2
+ require 'order_query/sql/column'
3
3
  module OrderQuery
4
- # An order condition (sort column)
5
- class Condition
4
+ # An order column (sort column)
5
+ class Column
6
6
  attr_reader :name, :order, :order_enum, :options
7
7
  delegate :column_name, :quote, to: :@sql
8
8
 
9
- # @option spec [String] :unique Mark the attribute as unique to avoid redundant conditions
10
- # @option spec [String] :complete Mark the condition's domain as complete to avoid redundant conditions (only for array conditions)
9
+ # @option spec [String] :unique Mark the attribute as unique to avoid redundant columns
11
10
  def initialize(spec, scope)
12
11
  spec = spec.dup
13
12
  options = spec.extract_options!
@@ -24,18 +23,13 @@ module OrderQuery
24
23
  complete: true
25
24
  )
26
25
  @unique = @options[:unique]
27
- @complete = @options[:complete]
28
- @sql = SQL::Condition.new(self, scope)
26
+ @sql = SQL::Column.new(self, scope)
29
27
  end
30
28
 
31
29
  def unique?
32
30
  @unique
33
31
  end
34
32
 
35
- def complete?
36
- @complete
37
- end
38
-
39
33
  # @param [Object] value
40
34
  # @param [:before, :after] side
41
35
  # @return [Array] valid order values before / after passed (depending on the side)
@@ -54,7 +48,7 @@ module OrderQuery
54
48
  end
55
49
  else
56
50
  # default to all if current is not in sort order values
57
- ord
51
+ []
58
52
  end
59
53
  end
60
54
 
@@ -63,7 +57,6 @@ module OrderQuery
63
57
  @name,
64
58
  (@order_enum.inspect if order_enum),
65
59
  ('unique' if @unique),
66
- ('complete' if order_enum && complete?),
67
60
  (column_name if options[:sql]),
68
61
  {desc: '▼', asc: '▲'}[@order]
69
62
  ].compact
@@ -1,17 +1,22 @@
1
- require 'order_query/condition'
1
+ require 'order_query/column'
2
2
  require 'order_query/sql/order_by'
3
3
  module OrderQuery
4
4
  # Order specification and a scope
5
5
  class Space
6
- # @return [Array<OrderQuery::Condition>]
7
- attr_reader :conditions
6
+ # @return [Array<OrderQuery::Column>]
7
+ attr_reader :columns
8
8
 
9
9
  # @param [ActiveRecord::Relation] base_scope
10
10
  # @param [Array<Array<Symbol,String>>, OrderQuery::Spec] order_spec
11
11
  def initialize(base_scope, order_spec)
12
12
  @base_scope = base_scope
13
- @conditions = order_spec.map { |cond_spec| Condition.new(cond_spec, base_scope) }
14
- @order_by_sql = SQL::OrderBy.new(@conditions)
13
+ @columns = order_spec.map { |cond_spec| Column.new(cond_spec, base_scope) }
14
+ # add primary key if columns are not unique
15
+ unless @columns.last.unique?
16
+ raise ArgumentError.new('Unique column must be last') if @columns.detect(&:unique?)
17
+ @columns << Column.new([base_scope.primary_key], base_scope)
18
+ end
19
+ @order_by_sql = SQL::OrderBy.new(@columns)
15
20
  end
16
21
 
17
22
  # @return [Point]
@@ -19,12 +24,12 @@ module OrderQuery
19
24
  Point.new(record, self)
20
25
  end
21
26
 
22
- # @return [ActiveRecord::Relation] scope ordered by conditions
27
+ # @return [ActiveRecord::Relation] scope ordered by columns
23
28
  def scope
24
29
  @scope ||= @base_scope.order(@order_by_sql.build)
25
30
  end
26
31
 
27
- # @return [ActiveRecord::Relation] scope ordered by conditions in reverse
32
+ # @return [ActiveRecord::Relation] scope ordered by columns in reverse
28
33
  def scope_reverse
29
34
  @scope_reverse ||= @base_scope.order(@order_by_sql.build_reverse)
30
35
  end
@@ -42,7 +47,7 @@ module OrderQuery
42
47
  delegate :count, :empty?, to: :@base_scope
43
48
 
44
49
  def inspect
45
- "#<OrderQuery::Space @conditions=#{@conditions.inspect} @base_scope=#{@base_scope.inspect}>"
50
+ "#<OrderQuery::Space @columns=#{@columns.inspect} @base_scope=#{@base_scope.inspect}>"
46
51
  end
47
52
  end
48
53
  end
@@ -1,20 +1,20 @@
1
1
  module OrderQuery
2
2
  module SQL
3
- class Condition
4
- attr_reader :condition, :scope
3
+ class Column
4
+ attr_reader :column, :scope
5
5
 
6
- def initialize(condition, scope)
7
- @condition = condition
6
+ def initialize(column, scope)
7
+ @column = column
8
8
  @scope = scope
9
9
  end
10
10
 
11
11
  def column_name
12
12
  @column_name ||= begin
13
- sql = condition.options[:sql]
13
+ sql = column.options[:sql]
14
14
  if sql
15
15
  sql.respond_to?(:call) ? sql.call : sql
16
16
  else
17
- connection.quote_table_name(scope.table_name) + '.' + connection.quote_column_name(condition.name)
17
+ connection.quote_table_name(scope.table_name) + '.' + connection.quote_column_name(column.name)
18
18
  end
19
19
  end
20
20
  end
@@ -1,9 +1,9 @@
1
1
  module OrderQuery
2
2
  module SQL
3
3
  class OrderBy
4
- # @param [Array<Condition>]
5
- def initialize(conditions)
6
- @conditions = conditions
4
+ # @param [Array<Column>]
5
+ def initialize(columns)
6
+ @columns = columns
7
7
  end
8
8
 
9
9
  # @return [String]
@@ -20,10 +20,10 @@ module OrderQuery
20
20
 
21
21
  # @return [Array<String>]
22
22
  def order_by_sql_clauses
23
- @conditions.map { |cond| condition_clause cond }
23
+ @columns.map { |cond| column_clause cond }
24
24
  end
25
25
 
26
- def condition_clause(cond)
26
+ def column_clause(cond)
27
27
  dir_sql = sort_direction_sql cond.order
28
28
  col_sql = cond.column_name
29
29
  if cond.order_enum
@@ -7,13 +7,13 @@ module OrderQuery
7
7
 
8
8
  # @param [OrderQuery::Point] point
9
9
  def initialize(point)
10
- @point = point
11
- @conditions = point.space.conditions
10
+ @point = point
11
+ @columns = point.space.columns
12
12
  end
13
13
 
14
- # Join condition pairs with OR, and nest within each other with AND
14
+ # Join column pairs with OR, and nest within each other with AND
15
15
  # @param [:before or :after] side
16
- # @return [query, parameters] WHERE conditions matching records strictly before / after this one
16
+ # @return [query, parameters] WHERE columns matching records strictly before / after this one
17
17
  # sales < 5 OR
18
18
  # sales = 5 AND (
19
19
  # invoice < 3 OR
@@ -21,14 +21,14 @@ module OrderQuery
21
21
  # ... ))
22
22
  def build(side)
23
23
  # generate pairs of terms such as sales < 5, sales = 5
24
- parts = @conditions.map { |cond|
25
- [where_side(cond, side, true), where_tie(cond)].reject { |x| x == WHERE_IDENTITY }
24
+ terms = @columns.map { |col|
25
+ [where_side(col, side, true), where_tie(col)].reject { |x| x == WHERE_IDENTITY }
26
26
  }
27
27
  # group pairwise with OR, and nest with AND
28
- query = foldr_terms parts.map { |terms| join_terms 'OR'.freeze, *terms }, 'AND'.freeze
28
+ query = foldr_terms terms.map { |pair| join_terms 'OR'.freeze, *pair }, 'AND'.freeze
29
29
  if ::OrderQuery.wrap_top_level_or
30
30
  # wrap in a redundant AND clause for performance
31
- query = wrap_top_level_or query, parts, side
31
+ query = wrap_top_level_or query, terms, side
32
32
  end
33
33
  query
34
34
  end
@@ -44,7 +44,7 @@ module OrderQuery
44
44
  end
45
45
  end
46
46
 
47
- # joins terms with an operator
47
+ # joins terms with an operator, empty terms are skipped
48
48
  # @return [query, parameters]
49
49
  def join_terms(op, *terms)
50
50
  [terms.map(&:first).reject(&:empty?).join(" #{op} "), terms.map(&:second).reduce([], :+)]
@@ -63,61 +63,55 @@ module OrderQuery
63
63
  # (sales < 5 OR
64
64
  # (sales = 5 AND ...)))
65
65
  # Read more at https://github.com/glebm/order_query/issues/3
66
- def wrap_top_level_or(query, pairs, side)
67
- top_pair_idx = pairs.index(&:present?)
68
- if top_pair_idx &&
69
- (top_pair = pairs[top_pair_idx]).length == 2 &&
70
- top_pair.first != (redundant_cond = where_side(@conditions[top_pair_idx], side, false))
71
- join_terms 'AND'.freeze, redundant_cond, wrap_term_with_parens(query)
66
+ def wrap_top_level_or(query, terms, side)
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)
72
71
  else
73
72
  query
74
73
  end
75
74
  end
76
75
 
77
- # @return [query, params] tie-breaker unless condition is unique
78
- def where_tie(cond)
79
- if cond.unique?
76
+ # @return [query, params] tie-breaker unless column is unique
77
+ def where_tie(col)
78
+ if col.unique?
80
79
  WHERE_IDENTITY
81
80
  else
82
- where_eq(cond)
81
+ where_eq(col)
83
82
  end
84
83
  end
85
84
 
86
85
  # @param [:before or :after] side
87
- # @return [query, params] return query conditions for attribute values before / after the current one
88
- def where_side(cond, side, strict = true, value = point.value(cond))
89
- if cond.order_enum
90
- values = cond.enum_side(value, side, strict)
91
- if cond.complete? && values.length == cond.order_enum.length
92
- WHERE_IDENTITY
93
- else
94
- where_in cond, values
95
- end
86
+ # @return [query, params] return query fragment for column values before / after the current one
87
+ def where_side(col, side, strict = true, value = point.value(col))
88
+ if col.order_enum
89
+ where_in col, col.enum_side(value, side, strict)
96
90
  else
97
- where_ray cond, value, side, strict
91
+ where_ray col, value, side, strict
98
92
  end
99
93
  end
100
94
 
101
- def where_in(cond, values)
95
+ def where_in(col, values)
102
96
  case values.length
103
97
  when 0
104
98
  WHERE_IDENTITY
105
99
  when 1
106
- where_eq cond, values[0]
100
+ where_eq col, values[0]
107
101
  else
108
- ["#{cond.column_name} IN (?)".freeze, [values]]
102
+ ["#{col.column_name} IN (?)".freeze, [values]]
109
103
  end
110
104
  end
111
105
 
112
- def where_eq(cond, value = point.value(cond))
113
- [%Q(#{cond.column_name} = ?).freeze, [value]]
106
+ def where_eq(col, value = point.value(col))
107
+ [%Q(#{col.column_name} = ?).freeze, [value]]
114
108
  end
115
109
 
116
- def where_ray(cond, from, mode, strict = true)
110
+ def where_ray(col, from, mode, strict = true)
117
111
  ops = %w(< >)
118
112
  ops = ops.reverse if mode == :after
119
- op = {asc: ops[0], desc: ops[1]}[cond.order || :asc]
120
- ["#{cond.column_name} #{op}#{'=' unless strict} ?".freeze, [from]]
113
+ op = {asc: ops[0], desc: ops[1]}[col.order || :asc]
114
+ ["#{col.column_name} #{op}#{'=' unless strict} ?".freeze, [from]]
121
115
  end
122
116
 
123
117
  WHERE_IDENTITY = [''.freeze, [].freeze].freeze
@@ -1,3 +1,3 @@
1
1
  module OrderQuery
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1'
3
3
  end
data/lib/order_query.rb CHANGED
@@ -66,10 +66,10 @@ module OrderQuery
66
66
  scope name, -> {
67
67
  send(space_method).scope
68
68
  }
69
- scope :"#{name}_reverse", -> {
69
+ scope "#{name}_reverse", -> {
70
70
  send(space_method).scope_reverse
71
71
  }
72
- define_singleton_method "#{name}_at", -> (record) {
72
+ define_singleton_method "#{name}_at", ->(record) {
73
73
  send(space_method).at(record)
74
74
  }
75
75
  define_method(name) { |scope = nil|
@@ -81,6 +81,6 @@ module OrderQuery
81
81
  class << self
82
82
  attr_accessor :wrap_top_level_or
83
83
  end
84
- # Wrap top-level or with an AND and a redundant condition for performance
84
+ # Wrap top-level or with an AND and a redundant column for performance
85
85
  self.wrap_top_level_or = true
86
86
  end
@@ -9,7 +9,7 @@ end
9
9
  class Post < ActiveRecord::Base
10
10
  include OrderQuery
11
11
  order_query :order_list,
12
- [:pinned, [true, false], complete: true],
12
+ [:pinned, [true, false]],
13
13
  [:published_at, :desc],
14
14
  [:id, :desc]
15
15
  end
@@ -21,7 +21,8 @@ end
21
21
  # Advanced model
22
22
  class Issue < ActiveRecord::Base
23
23
  DISPLAY_ORDER = [
24
- [:priority, %w(high medium low), complete: true],
24
+ [:pinned, [true, false]],
25
+ [:priority, %w(high medium low)],
25
26
  [:valid_votes_count, :desc, sql: '(votes - suspicious_votes)'],
26
27
  [:updated_at, :desc],
27
28
  [:id, :desc]
@@ -63,9 +64,11 @@ describe 'OrderQuery' do
63
64
  t = Time.now
64
65
  datasets = [
65
66
  [
67
+ ['high', 5, 0, t, true],
68
+ ['high', 5, 1, t, true],
66
69
  ['high', 5, 0, t],
70
+ ['high', 5, 0, t - 1.day],
67
71
  ['high', 5, 1, t],
68
- ['high', 5, 1, t - 1.day],
69
72
  ['medium', 10, 0, t],
70
73
  ['medium', 10, 5, t - 12.hours],
71
74
  ['low', 30, 0, t + 1.day]
@@ -85,7 +88,7 @@ describe 'OrderQuery' do
85
88
  datasets.each_with_index do |ds, i|
86
89
  it "is ordered correctly (test data #{i})" do
87
90
  issues = ds.map do |attr|
88
- Issue.new(priority: attr[0], votes: attr[1], suspicious_votes: attr[2], updated_at: attr[3])
91
+ Issue.new(priority: attr[0], votes: attr[1], suspicious_votes: attr[2], updated_at: attr[3], pinned: attr[4] || false)
89
92
  end
90
93
  issues.shuffle.reverse_each(&:save!)
91
94
  expect(Issue.display_order.to_a).to eq(issues)
@@ -161,7 +164,7 @@ describe 'OrderQuery' do
161
164
  end
162
165
  end
163
166
 
164
- it '#seek falls back to scope when order condition is missing self' do
167
+ it '#seek falls back to scope when order column is missing self' do
165
168
  a = create_issue(priority: 'medium')
166
169
  b = create_issue(priority: 'high')
167
170
  expect(a.seek(Issue.display_order, [[:priority, ['wontfix', 'askbob']], [:id, :desc]]).next).to eq(b)
@@ -176,6 +179,7 @@ describe 'OrderQuery' do
176
179
  self.verbose = false
177
180
 
178
181
  create_table :issues do |t|
182
+ t.column :pinned, :boolean, null: false, default: false
179
183
  t.column :priority, :string
180
184
  t.column :votes, :integer
181
185
  t.column :suspicious_votes, :integer
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: order_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Mazovetskiy
@@ -78,10 +78,10 @@ files:
78
78
  - README.md
79
79
  - Rakefile
80
80
  - lib/order_query.rb
81
- - lib/order_query/condition.rb
81
+ - lib/order_query/column.rb
82
82
  - lib/order_query/point.rb
83
83
  - lib/order_query/space.rb
84
- - lib/order_query/sql/condition.rb
84
+ - lib/order_query/sql/column.rb
85
85
  - lib/order_query/sql/order_by.rb
86
86
  - lib/order_query/sql/where.rb
87
87
  - lib/order_query/version.rb