order_query 0.3.0 → 0.3.1

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: 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