sortable-by 0.9.0 → 0.12.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
- SHA1:
3
- metadata.gz: c69dc4027db827d7339df15f6b6a82a00dd106c3
4
- data.tar.gz: 6542aa11412834bd4360db4f6f989e2fb56358de
2
+ SHA256:
3
+ metadata.gz: 7e89dd7025ad14bfd9a012b10d68a70183692121625d14e56feea7e3484d8398
4
+ data.tar.gz: 931017877555f573485ebbaecb5501ba36e5e4c3a2c7d0842ada6ff2f68cd5fb
5
5
  SHA512:
6
- metadata.gz: 12165f68bf353d543f8701ed328586822a8c43d729cd514b8695821214a424926507e53e0fb443639a2051e28626733e6edcb47a5f5530949ae9bd6e9e80aa0a
7
- data.tar.gz: 9008ae98a2a8aacb2bf807b9db1eb428fc6934e39e84ae11369919f46cbe75463c3e63ceb3897f88819cd5b125071c47591290bb0c2d33f950114c0966cd2219
6
+ metadata.gz: 45ef18f0d53caa91e2e9cb0c8bcbae4bf4cdc3da59b844e1ddae2f5fa16454905d925a7212a31e4355f077fe7436b25303f17422c47f25592a3036cd623caba3
7
+ data.tar.gz: 724fd46c5ea0fd379980dcfe64a58ea579d9d03edab02516862d654ccc5a472d10bc810ccf48a32d4cc96e70ed5608100596425091c7a541dae5f380f8f4a173
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  spec/tmp
2
2
  pkg
3
3
  *.gem
4
+ .rake_tasks~
5
+ .rubocop-*
@@ -0,0 +1,10 @@
1
+ inherit_from:
2
+ - https://gitlab.com/bsm/misc/raw/master/rubocop/default.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.5
6
+
7
+ Naming/MemoizedInstanceVariableName:
8
+ Enabled: false
9
+ Naming/FileName:
10
+ Exclude: [lib/sortable-by.rb]
@@ -1,8 +1,8 @@
1
+ sudo: false
1
2
  rvm:
2
- - 1.9.3
3
- - 2.1.2
4
- - 2.2.2
5
- - 2.3.2
6
- - 2.4.0
3
+ - 2.5
4
+ - 2.6
7
5
  gemfile:
8
6
  - Gemfile
7
+ before_install:
8
+ - gem install bundler
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
  gemspec
@@ -1,47 +1,66 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sortable-by (0.9.0)
4
+ sortable-by (0.12.1)
5
5
  activerecord
6
6
  activesupport
7
7
 
8
8
  GEM
9
9
  remote: http://rubygems.org/
10
10
  specs:
11
- activemodel (5.0.1)
12
- activesupport (= 5.0.1)
13
- activerecord (5.0.1)
14
- activemodel (= 5.0.1)
15
- activesupport (= 5.0.1)
16
- arel (~> 7.0)
17
- activesupport (5.0.1)
11
+ activemodel (6.0.3.1)
12
+ activesupport (= 6.0.3.1)
13
+ activerecord (6.0.3.1)
14
+ activemodel (= 6.0.3.1)
15
+ activesupport (= 6.0.3.1)
16
+ activesupport (6.0.3.1)
18
17
  concurrent-ruby (~> 1.0, >= 1.0.2)
19
- i18n (~> 0.7)
18
+ i18n (>= 0.7, < 2)
20
19
  minitest (~> 5.1)
21
20
  tzinfo (~> 1.1)
22
- arel (7.1.4)
23
- concurrent-ruby (1.0.4)
21
+ zeitwerk (~> 2.2, >= 2.2.2)
22
+ ast (2.4.0)
23
+ concurrent-ruby (1.1.6)
24
24
  diff-lcs (1.3)
25
- i18n (0.8.1)
26
- minitest (5.10.1)
27
- rake (12.0.0)
28
- rspec (3.5.0)
29
- rspec-core (~> 3.5.0)
30
- rspec-expectations (~> 3.5.0)
31
- rspec-mocks (~> 3.5.0)
32
- rspec-core (3.5.4)
33
- rspec-support (~> 3.5.0)
34
- rspec-expectations (3.5.0)
25
+ i18n (1.8.2)
26
+ concurrent-ruby (~> 1.0)
27
+ minitest (5.14.1)
28
+ parallel (1.19.1)
29
+ parser (2.7.1.3)
30
+ ast (~> 2.4.0)
31
+ rainbow (3.0.0)
32
+ rake (13.0.1)
33
+ rexml (3.2.4)
34
+ rspec (3.9.0)
35
+ rspec-core (~> 3.9.0)
36
+ rspec-expectations (~> 3.9.0)
37
+ rspec-mocks (~> 3.9.0)
38
+ rspec-core (3.9.2)
39
+ rspec-support (~> 3.9.3)
40
+ rspec-expectations (3.9.2)
35
41
  diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.5.0)
37
- rspec-mocks (3.5.0)
42
+ rspec-support (~> 3.9.0)
43
+ rspec-mocks (3.9.1)
38
44
  diff-lcs (>= 1.2.0, < 2.0)
39
- rspec-support (~> 3.5.0)
40
- rspec-support (3.5.0)
41
- sqlite3 (1.3.13)
45
+ rspec-support (~> 3.9.0)
46
+ rspec-support (3.9.3)
47
+ rubocop (0.84.0)
48
+ parallel (~> 1.10)
49
+ parser (>= 2.7.0.1)
50
+ rainbow (>= 2.2.2, < 4.0)
51
+ rexml
52
+ rubocop-ast (>= 0.0.3)
53
+ ruby-progressbar (~> 1.7)
54
+ unicode-display_width (>= 1.4.0, < 2.0)
55
+ rubocop-ast (0.0.3)
56
+ parser (>= 2.7.0.1)
57
+ ruby-progressbar (1.10.1)
58
+ sqlite3 (1.4.2)
42
59
  thread_safe (0.3.6)
43
- tzinfo (1.2.2)
60
+ tzinfo (1.2.7)
44
61
  thread_safe (~> 0.1)
62
+ unicode-display_width (1.7.0)
63
+ zeitwerk (2.3.0)
45
64
 
46
65
  PLATFORMS
47
66
  ruby
@@ -50,8 +69,9 @@ DEPENDENCIES
50
69
  bundler
51
70
  rake
52
71
  rspec
72
+ rubocop
53
73
  sortable-by!
54
74
  sqlite3
55
75
 
56
76
  BUNDLED WITH
57
- 1.14.3
77
+ 2.1.2
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015-2018 Black Square Media
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Sortable By
2
2
 
3
+ [![Build Status](https://travis-ci.org/bsm/sortable-by.png?branch=master)](https://travis-ci.org/bsm/sortable-by)
4
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+
3
6
  ActiveRecord plugin to parse the sort order from a query parameter, match against a white-list and generate a scope. Useful for [JSON-API][jsonapi] compatibility.
4
7
 
5
8
  [jsonapi]: http://jsonapi.org/format/#fetching-sorting
@@ -10,39 +13,75 @@ Add `gem 'sortable-by'` to your Gemfile.
10
13
 
11
14
  ## Usage
12
15
 
13
- Simple use cases:
16
+ Simple:
14
17
 
15
18
  ```ruby
16
- class Foo < ActiveRecord::Base
17
- sortable_by :title, :updated_at, default: { updated_at: :desc }
19
+ class Post < ActiveRecord::Base
20
+ sortable_by :title, :id
18
21
  end
19
22
 
20
- Foo.sorted_by "-updated_at,title" # => ORDER BY updated_at DESC, title ASC
21
- Foo.sorted_by "bad,title" # => ORDER BY title ASC
22
- Foo.sorted_by nil # => ORDER BY updated_at DESC
23
+ Post.sorted_by('title') # => ORDER BY posts.title ASC
24
+ Post.sorted_by('-title') # => ORDER BY posts.title DESC
25
+ Post.sorted_by('bad,title') # => ORDER BY posts.title ASC
26
+ Post.sorted_by(nil) # => ORDER BY posts.title ASC
23
27
  ```
24
28
 
25
- ## LICENCE
29
+ Case-insensitive:
30
+
31
+ ```ruby
32
+ class Post < ActiveRecord::Base
33
+ sortable_by do |x|
34
+ x.field :title, as: arel_table[:title].lower
35
+ x.field :id
36
+ end
37
+ end
26
38
 
39
+ Post.sorted_by('title') # => ORDER BY LOWER(posts.title) ASC
27
40
  ```
28
- Copyright (c) 2015 Black Square Media
29
-
30
- Permission is hereby granted, free of charge, to any person obtaining
31
- a copy of this software and associated documentation files (the
32
- "Software"), to deal in the Software without restriction, including
33
- without limitation the rights to use, copy, modify, merge, publish,
34
- distribute, sublicense, and/or sell copies of the Software, and to
35
- permit persons to whom the Software is furnished to do so, subject to
36
- the following conditions:
37
-
38
- The above copyright notice and this permission notice shall be
39
- included in all copies or substantial portions of the Software.
40
-
41
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
42
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
44
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
45
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
46
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
47
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
+
42
+ With custom default:
43
+
44
+ ```ruby
45
+ class Post < ActiveRecord::Base
46
+ sortable_by :id, :topic, :created_at, default: 'topic,-created_at'
47
+ end
48
+
49
+ Post.sorted_by(nil) # => ORDER BY posts.topic ASC, posts.created_at DESC
50
+ ```
51
+
52
+ Composition:
53
+
54
+ ```ruby
55
+ class App < ActiveRecord::Base
56
+ sortable_by :name, default: '-version' do |x|
57
+ x.field :version, as: %i[major minor patch]]
58
+ end
59
+ end
60
+
61
+ App.sorted_by('version') # => ORDER BY apps.major ASC, apps.minor ASC, apps.patch ASC
62
+ App.sorted_by(nil) # => ORDER BY apps.major DESC, apps.minor DESC, apps.patch DESC
63
+ ```
64
+
65
+ Associations (eager load):
66
+
67
+ ```ruby
68
+ class Product < ActiveRecord::Base
69
+ belongs_to :shop
70
+ sortable_by do |x|
71
+ x.field :name, as: arel_table[:name].lower
72
+ x.field :shop, as: Shop.arel_table[:name].lower, eager_load: :shop
73
+ x.default 'shop,name'
74
+ end
75
+ end
76
+ ```
77
+
78
+ Associations (custom scope):
79
+
80
+ ```
81
+ class Product < ActiveRecord::Base
82
+ belongs_to :shop
83
+ sortable_by do |x|
84
+ x.field :shop, as: Shop.arel_table[:name].lower, scope: -> { joins(:shop) }
85
+ end
86
+ end
48
87
  ```
data/Rakefile CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'bundler/setup'
2
2
  require 'bundler/gem_tasks'
3
3
  require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
4
5
 
5
6
  RSpec::Core::RakeTask.new(:spec)
6
- task default: :spec
7
+ RuboCop::RakeTask.new(:rubocop)
8
+
9
+ task default: %i[spec rubocop]
@@ -1,42 +1,184 @@
1
1
  require 'active_record'
2
- require 'active_support/concern'
3
- require 'set'
4
2
 
5
- module ActiveRecord
6
- module SortableByHelper
7
- extend ActiveSupport::Concern
3
+ module ActiveRecord # :nodoc:
4
+ module SortableBy # :nodoc:
5
+ class Config # :nodoc:
6
+ attr_reader :_fields, :_default
8
7
 
9
- included do
10
- class_attribute :_sortable_by_scope_options, instance_accessor: false
11
- sortable_by
12
- end
8
+ def initialize
9
+ @_fields = {}
10
+ @_default = nil
11
+ end
12
+
13
+ def dup
14
+ duplicate = self.class.new
15
+ duplicate.instance_variable_set :@_fields, _fields.deep_dup
16
+ duplicate.instance_variable_set :@_default, _default.deep_dup
17
+ duplicate
18
+ end
13
19
 
14
- module ClassMethods
20
+ def field(name, opts = {})
21
+ name = name.to_s
22
+ @_fields[name] = Field.new(name, opts)
23
+ @_default ||= name
24
+ end
25
+
26
+ def default(expr)
27
+ # legacy support
28
+ if expr.is_a?(Hash)
29
+ expr = expr.map do |field, dir|
30
+ [(dir == :desc ? '-' : ''), field].join
31
+ end.join(',')
32
+ end
15
33
 
16
- def sortable_by(*whitelist)
17
- opts = whitelist.extract_options!
18
- opts[:default] ||= {id: :asc}
19
- self._sortable_by_scope_options = opts.merge(whitelist: whitelist.map(&:to_s).to_set)
34
+ @_default = expr.to_s
20
35
  end
21
36
 
22
- # @param [String] expr the sort expr
23
- # @return [ActiveRecord::Relation] the scoped relation
24
- def sorted_by(expr)
25
- options = {}
37
+ protected
38
+
39
+ def order(relation, expr, fallback = true)
40
+ matched = false
26
41
  expr.to_s.split(',').each do |name|
27
42
  name.strip!
43
+
28
44
  rank = :asc
29
- rank, name = :desc, name[1..-1] if name[0] == '-'
30
- options[name.to_sym] = rank if self._sortable_by_scope_options[:whitelist].include?(name)
45
+ if name[0] == '-'
46
+ rank = :desc
47
+ name = name[1..-1]
48
+ end
49
+
50
+ field = _fields[name]
51
+ next unless field
52
+
53
+ matched = true
54
+ relation = field.order(relation, rank)
31
55
  end
32
- options = self._sortable_by_scope_options[:default] if options.empty?
33
- order(options)
56
+
57
+ relation = order(relation, _default, false) if fallback && !matched && _default
58
+ relation
34
59
  end
60
+ end
61
+
62
+ class Field # :nodoc:
63
+ def initialize(name, opts = {})
64
+ @cols = Array.wrap(opts[:as])
65
+ @eager_load = Array.wrap(opts[:eager_load]).presence
66
+
67
+ # validate custom_scope
68
+ @custom_scope = opts[:scope]
69
+ raise ArgumentError, "Invalid sortable-by field '#{name}': scope must be a Proc." if @custom_scope && !@custom_scope.is_a?(Proc)
70
+
71
+ # normalize cols
72
+ @cols.push name if @cols.empty?
73
+ @cols.each do |col|
74
+ case col
75
+ when String, Symbol, Arel::Attributes::Attribute, Arel::Nodes::Node
76
+ next
77
+ when Proc
78
+ raise ArgumentError, "Invalid sortable-by field '#{name}': proc must accept 2 arguments." unless col.arity == 2
79
+ else
80
+ raise ArgumentError, "Invalid sortable-by field '#{name}': invalid type #{col.class}."
81
+ end
82
+ end
83
+ end
84
+
85
+ def order(relation, rank)
86
+ @cols.each do |col|
87
+ case col
88
+ when String, Symbol
89
+ relation = relation.order(col => rank)
90
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
91
+ relation = relation.order(col.send(rank))
92
+ when Proc
93
+ relation = col.call(relation, rank)
94
+ end
95
+ end
96
+
97
+ relation = relation.eager_load(*@eager_load) if @eager_load
98
+ relation = relation.instance_eval(&@custom_scope) if @custom_scope
99
+ relation
100
+ end
101
+ end
102
+
103
+ def self.extended(base) # :nodoc:
104
+ base.class_attribute :_sortable_by_config, instance_accessor: false, instance_predicate: false
105
+ base._sortable_by_config = Config.new
106
+ end
107
+
108
+ def inherited(base) # :nodoc:
109
+ base._sortable_by_config = _sortable_by_config.deep_dup
110
+ super
111
+ end
112
+
113
+ # Declare sortable attributes and scopes. Examples:
114
+ #
115
+ # # Simple
116
+ # class Post < ActiveRecord::Base
117
+ # sortable_by :title, :id
118
+ # end
119
+ #
120
+ # # Case-insensitive
121
+ # class Post < ActiveRecord::Base
122
+ # sortable_by do |x|
123
+ # x.field :title, as: arel_table[:title].lower
124
+ # x.field :id
125
+ # end
126
+ # end
127
+ #
128
+ # # With default
129
+ # class Post < ActiveRecord::Base
130
+ # sortable_by :id, :topic, :created_at,
131
+ # default: 'topic,-created_at'
132
+ # end
133
+ #
134
+ # # Composition
135
+ # class App < ActiveRecord::Base
136
+ # sortable_by :name, default: '-version' do |x|
137
+ # x.field :version, as: %i[major minor patch]]
138
+ # end
139
+ # end
140
+ #
141
+ # # Associations (eager load)
142
+ # class Product < ActiveRecord::Base
143
+ # belongs_to :shop
144
+ #
145
+ # sortable_by do |x|
146
+ # x.field :name
147
+ # x.field :shop, as: Shop.arel_table[:name].lower, eager_load: :shop
148
+ # x.default 'shop,name'
149
+ # end
150
+ # end
151
+ #
152
+ # # Associations (custom scope)
153
+ # class Product < ActiveRecord::Base
154
+ # belongs_to :shop
155
+ #
156
+ # sortable_by do |x|
157
+ # x.field :shop, as: Shop.arel_table[:name].lower, scope: -> { joins(:shop) }
158
+ # end
159
+ # end
160
+ #
161
+ def sortable_by(*attributes)
162
+ config = _sortable_by_config
163
+ opts = attributes.extract_options!
164
+ default = opts.delete(:default)
165
+
166
+ attributes.each do |name|
167
+ config.field(name, opts)
168
+ end
169
+ config.default(default) if default
170
+ yield config if block_given?
171
+ config
172
+ end
35
173
 
174
+ # @param [String] expr the sort expr
175
+ # @return [ActiveRecord::Relation] the scoped relation
176
+ def sorted_by(expr)
177
+ _sortable_by_config.send :order, self, expr
36
178
  end
37
179
  end
38
180
 
39
- class Base
40
- include SortableByHelper
181
+ class Base # :nodoc:
182
+ extend SortableBy
41
183
  end
42
184
  end
@@ -1,7 +1,6 @@
1
- # -*- encoding: utf-8 -*-
2
1
  Gem::Specification.new do |s|
3
2
  s.name = 'sortable-by'
4
- s.version = '0.9.0'
3
+ s.version = '0.12.1'
5
4
  s.authors = ['Dimitrij Denissenko']
6
5
  s.email = ['dimitrij@blacksquaremedia.com']
7
6
  s.summary = 'Generate white-listed sort scopes from URL parameter values'
@@ -12,13 +11,14 @@ Gem::Specification.new do |s|
12
11
  s.files = `git ls-files -z`.split("\x0").reject {|f| f.match(%r{^spec/}) }
13
12
  s.test_files = `git ls-files -z -- spec/*`.split("\x0")
14
13
  s.require_paths = ['lib']
15
- s.required_ruby_version = '>= 1.9.3'
14
+ s.required_ruby_version = '>= 2.5'
16
15
 
17
- s.add_dependency 'activesupport'
18
16
  s.add_dependency 'activerecord'
17
+ s.add_dependency 'activesupport'
19
18
 
20
19
  s.add_development_dependency 'bundler'
21
20
  s.add_development_dependency 'rake'
22
21
  s.add_development_dependency 'rspec'
22
+ s.add_development_dependency 'rubocop'
23
23
  s.add_development_dependency 'sqlite3'
24
24
  end
@@ -1,23 +1,86 @@
1
- require File.dirname(__FILE__) + '/spec_helper'
1
+ require File.expand_path('./spec_helper', __dir__)
2
2
 
3
- describe ActiveRecord::SortableByHelper do
3
+ describe ActiveRecord::SortableBy do
4
+ before do
5
+ c = ActiveRecord::Base.connection
6
+ c.tables.each do |t|
7
+ c.update "DELETE FROM #{t}"
8
+ end
9
+ end
4
10
 
5
11
  it 'should have config' do
6
- expect(Foo._sortable_by_scope_options).to eq(default: {title: :asc}, whitelist: Set.new(["title", "age"]))
7
- expect(Bar._sortable_by_scope_options).to eq(default: {id: :asc}, whitelist: Set.new)
12
+ expect(Post._sortable_by_config._fields.keys).to match_array(%w[title created])
13
+ expect(Post._sortable_by_config._default).to eq('-created')
14
+ expect(SubPost._sortable_by_config._fields.keys).to match_array(%w[title created])
15
+ expect(SubPost._sortable_by_config._default).to eq('-created')
16
+
17
+ expect(App._sortable_by_config._fields.keys).to match_array(%w[name version])
18
+ expect(App._sortable_by_config._default).to eq('name')
19
+
20
+ expect(Shop._sortable_by_config._fields.keys).to be_empty
21
+ expect(Shop._sortable_by_config._default).to be_nil
22
+
23
+ expect(Product._sortable_by_config._fields.keys).to match_array(%w[name shop])
24
+ expect(Product._sortable_by_config._default).to eq('shop,name')
8
25
  end
9
26
 
10
- it 'should generate scopes' do
11
- expect(Foo.sorted_by("title").pluck(:title)).to eq(["A", "B", "B", "C"])
12
- expect(Foo.sorted_by("-title").pluck(:title)).to eq(["C", "B", "B", "A"])
13
- expect(Foo.sorted_by("age,title").pluck(:title)).to eq(["B", "A", "B", "C"])
14
- expect(Foo.sorted_by("invalid , -age").pluck(:title)).to eq(["C", "B", "A", "B"])
15
- expect(Foo.sorted_by("").pluck(:title)).to eq(["A", "B", "B", "C"])
16
- expect(Foo.sorted_by(nil).pluck(:title)).to eq(["A", "B", "B", "C"])
17
-
18
- expect(Bar.sorted_by("").pluck(:title)).to eq(["Y", "X"])
19
- expect(Bar.sorted_by("title").pluck(:title)).to eq(["Y", "X"])
20
- expect(Bar.where(title: "X").sorted_by("title").pluck(:title)).to eq(["X"])
27
+ it 'should simply order' do
28
+ Post.create! title: 'A', created_at: Time.at(1515151500)
29
+ Post.create! title: 'b', created_at: Time.at(1515151600)
30
+ Post.create! title: 'C', created_at: Time.at(1515151400)
31
+
32
+ expect(Post.sorted_by(nil).pluck(:title)).to eq(%w[b A C])
33
+ expect(Post.sorted_by('').pluck(:title)).to eq(%w[b A C])
34
+ expect(Post.sorted_by('invalid').pluck(:title)).to eq(%w[b A C])
35
+
36
+ expect(Post.sorted_by('-created').pluck(:title)).to eq(%w[b A C])
37
+ expect(Post.sorted_by('created').pluck(:title)).to eq(%w[C A b])
38
+
39
+ expect(Post.sorted_by('title').pluck(:title)).to eq(%w[A C b])
40
+ expect(Post.sorted_by('-title').pluck(:title)).to eq(%w[b C A])
41
+ expect(Post.sorted_by(' title ').pluck(:title)).to eq(%w[A C b])
21
42
  end
22
43
 
44
+ it 'should support STI inheritance and overrides' do
45
+ SubPost.create! title: 'A', created_at: Time.at(1515151700)
46
+ SubPost.create! title: 'b', created_at: Time.at(1515151600)
47
+ Post.create! title: 'C', created_at: Time.at(1515151400)
48
+ SubPost.create! title: 'D', created_at: Time.at(1515151500)
49
+
50
+ expect(Post.sorted_by(nil).pluck(:title)).to eq(%w[A b D C])
51
+ expect(SubPost.sorted_by(nil).pluck(:title)).to eq(%w[A b D])
52
+ expect(SubPost.sorted_by('-created').pluck(:title)).to eq(%w[A b D])
53
+ expect(SubPost.sorted_by('created').pluck(:title)).to eq(%w[D b A])
54
+
55
+ expect(Post.sorted_by('title').pluck(:title)).to eq(%w[A C D b])
56
+ expect(SubPost.sorted_by('title').pluck(:title)).to eq(%w[A b D])
57
+ end
58
+
59
+ it 'should support composition' do
60
+ App.create! name: 'E', major: 0, minor: 9, patch: 2
61
+ App.create! name: 'A', major: 1, minor: 0, patch: 1
62
+ App.create! name: 'D', major: 1, minor: 0, patch: 6
63
+ App.create! name: 'C', major: 1, minor: 1, patch: 0
64
+ App.create! name: 'B', major: 2, minor: 2, patch: 0
65
+
66
+ expect(App.sorted_by(nil).pluck(:name)).to eq(%w[A B C D E])
67
+ expect(App.sorted_by('version').pluck(:name)).to eq(%w[E A D C B])
68
+ expect(App.sorted_by('-version').pluck(:name)).to eq(%w[B C D A E])
69
+ end
70
+
71
+ it 'should support associations' do
72
+ y = Shop.create! name: 'Y'
73
+ x = Shop.create! name: 'X'
74
+
75
+ Product.create! name: 'a', shop_id: y.id
76
+ Product.create! name: 'B', shop_id: y.id, active: false
77
+ Product.create! name: 'c', shop_id: x.id
78
+ Product.create! name: 'D', shop_id: y.id
79
+ Product.create! name: 'e', shop_id: x.id
80
+ Product.create! name: 'f', shop_id: x.id, active: false
81
+
82
+ expect(Product.sorted_by(nil).pluck(:name)).to eq(%w[c e f a B D])
83
+ expect(Product.where(active: true).sorted_by(nil).pluck(:name)).to eq(%w[c e a D])
84
+ expect(Product.sorted_by('name').pluck(:name)).to eq(%w[a B c D e f])
85
+ end
23
86
  end
@@ -1,29 +1,72 @@
1
- ENV['RACK_ENV'] ||= "test"
1
+ ENV['RACK_ENV'] ||= 'test'
2
2
  require 'sortable-by'
3
3
  require 'rspec'
4
4
 
5
- ActiveRecord::Base.configurations["test"] = { 'adapter' => 'sqlite3', 'database' => ":memory:" }
5
+ ActiveRecord::Base.configurations = {
6
+ 'test' => {
7
+ 'adapter' => 'sqlite3',
8
+ 'database' => ':memory:',
9
+ },
10
+ }
6
11
  ActiveRecord::Base.establish_connection :test
7
- ActiveRecord::Base.connection.create_table :foos do |t|
8
- t.string :title
9
- t.integer :age
12
+
13
+ ActiveRecord::Base.connection.create_table :posts do |t|
14
+ t.string :type
15
+ t.string :title, null: false
16
+ t.timestamp :created_at, null: false
17
+ end
18
+
19
+ class Post < ActiveRecord::Base
20
+ sortable_by :title, default: '-created' do |s|
21
+ s.field :created, as: arel_table[:created_at]
22
+ end
10
23
  end
11
24
 
12
- ActiveRecord::Base.connection.create_table :bars do |t|
13
- t.string :title
25
+ class SubPost < Post
26
+ sortable_by do |s|
27
+ s.field :title, as: arel_table[:title].lower
28
+ end
14
29
  end
15
30
 
16
- class Foo < ActiveRecord::Base
17
- sortable_by :title, :age, default: {title: :asc}
31
+ # ---------------------------------------------------------------------
32
+
33
+ ActiveRecord::Base.connection.create_table :apps do |t|
34
+ t.string :name, null: false
35
+ t.integer :major, null: false
36
+ t.integer :minor, null: false
37
+ t.integer :patch, null: false
18
38
  end
19
39
 
20
- class Bar < ActiveRecord::Base
40
+ class App < ActiveRecord::Base
41
+ sortable_by :name do |s|
42
+ s.field :version, as: %i[major minor patch]
43
+ end
21
44
  end
22
45
 
23
- Foo.create! title: 'A', age: 25
24
- Foo.create! title: 'B', age: 24
25
- Foo.create! title: 'B', age: 26
26
- Foo.create! title: 'C', age: 27
46
+ # ---------------------------------------------------------------------
27
47
 
28
- Bar.create! title: 'Y'
29
- Bar.create! title: 'X'
48
+ ActiveRecord::Base.connection.create_table :shops do |t|
49
+ t.string :name, null: false
50
+ end
51
+
52
+ class Shop < ActiveRecord::Base
53
+ end
54
+
55
+ # ---------------------------------------------------------------------
56
+
57
+ ActiveRecord::Base.connection.create_table :products do |t|
58
+ t.string :name, null: false
59
+ t.integer :shop_id, null: false
60
+ t.boolean :active, null: false, default: true
61
+ t.foreign_key :shops
62
+ end
63
+
64
+ class Product < ActiveRecord::Base
65
+ belongs_to :shop
66
+
67
+ sortable_by do |s|
68
+ s.field :name, as: arel_table[:name].lower
69
+ s.field :shop, as: Shop.arel_table[:name].lower, eager_load: :shop
70
+ s.default 'shop,name'
71
+ end
72
+ end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sortable-by
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitrij Denissenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-23 00:00:00.000000000 Z
11
+ date: 2020-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,7 +25,7 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: activerecord
28
+ name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: sqlite3
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -102,9 +116,11 @@ extensions: []
102
116
  extra_rdoc_files: []
103
117
  files:
104
118
  - ".gitignore"
119
+ - ".rubocop.yml"
105
120
  - ".travis.yml"
106
121
  - Gemfile
107
122
  - Gemfile.lock
123
+ - LICENSE
108
124
  - README.md
109
125
  - Rakefile
110
126
  - lib/sortable-by.rb
@@ -124,15 +140,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
124
140
  requirements:
125
141
  - - ">="
126
142
  - !ruby/object:Gem::Version
127
- version: 1.9.3
143
+ version: '2.5'
128
144
  required_rubygems_version: !ruby/object:Gem::Requirement
129
145
  requirements:
130
146
  - - ">="
131
147
  - !ruby/object:Gem::Version
132
148
  version: '0'
133
149
  requirements: []
134
- rubyforge_project:
135
- rubygems_version: 2.6.8
150
+ rubygems_version: 3.1.2
136
151
  signing_key:
137
152
  specification_version: 4
138
153
  summary: Generate white-listed sort scopes from URL parameter values