search_object 0.2 → 1.0
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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +21 -8
- data/example/app/controllers/posts_controller.rb +1 -1
- data/example/app/models/post_search.rb +4 -1
- data/example/app/views/posts/index.html.slim +10 -0
- data/example/spec/models/post_search_spec.rb +1 -1
- data/lib/search_object/base.rb +5 -5
- data/lib/search_object/errors.rb +18 -0
- data/lib/search_object/plugin/paging.rb +24 -13
- data/lib/search_object/version.rb +1 -1
- data/lib/search_object.rb +1 -0
- data/spec/search_object/base_spec.rb +7 -6
- data/spec/search_object/plugin/kaminari_spec.rb +5 -39
- data/spec/search_object/plugin/model_spec.rb +7 -0
- data/spec/search_object/plugin/paging_spec.rb +1 -35
- data/spec/search_object/plugin/sorting_spec.rb +26 -22
- data/spec/search_object/plugin/will_paginate_spec.rb +5 -39
- data/spec/spec_helper_active_record.rb +1 -0
- data/spec/support/paging_shared_example.rb +112 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4102172d9cbe97e6fe2e066614a3136ac194a481
|
4
|
+
data.tar.gz: a01b6a7ebfa07a71a310ff32711147f378aaa337
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28e608b9331e641f3d421293b7161697da27567d8f852453b629ddd476c6a1bb2da382677c46e579beaea92d9aca96b1ee3802cad1b81231b54ae041f4481aca
|
7
|
+
data.tar.gz: 13d7e9ac89f8d9085cadf97a60ed102425a9ed877f5ecf2819c324b4003634d45584d068e52e161fafe61b5d48454452fc3fcc7926fd1a1d9ddd4a18a2c1a27b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 1.0
|
4
|
+
|
5
|
+
* Added min_per_page and max_per_page to paging plugin
|
6
|
+
|
7
|
+
* Default paging behaves more like 'kaminari' and 'will_paginate' by treating 1 page as 0 index (__backward incompatible__)
|
8
|
+
|
9
|
+
* Raise `SearchObject::MissingScopeError` when no scope is provided
|
10
|
+
|
11
|
+
* Replace position arguments with Hash of options (__backward incompatible__)
|
12
|
+
|
13
|
+
```diff
|
14
|
+
- Search.new params[:f], params[:page]
|
15
|
+
+ Search.new filters: params[:f], page: params[:page]
|
16
|
+
```
|
17
|
+
|
3
18
|
## Version 0.2
|
4
19
|
|
5
20
|
* Added `.results` shortcut for `new(*arg).results`
|
data/README.md
CHANGED
@@ -43,7 +43,7 @@ end
|
|
43
43
|
Then you can just search the given scope:
|
44
44
|
|
45
45
|
```ruby
|
46
|
-
search = PostSearch.new
|
46
|
+
search = PostSearch.new filters: params[:filters]
|
47
47
|
|
48
48
|
# accessing search options
|
49
49
|
search.name # => name option
|
@@ -83,11 +83,15 @@ class ProductSearch
|
|
83
83
|
option :category_name
|
84
84
|
|
85
85
|
# per page defaults to 25
|
86
|
-
# you can also overwrite per_page method
|
87
86
|
per_page 10
|
87
|
+
|
88
|
+
# range of values is also possible
|
89
|
+
min_per_page 5
|
90
|
+
max_per_page 100
|
88
91
|
end
|
89
92
|
|
90
|
-
search = ProductSearch.new
|
93
|
+
search = ProductSearch.new filters: params[:filters], page: params[:page], per_page: params[:per_page]
|
94
|
+
|
91
95
|
search.page # => page number
|
92
96
|
search.per_page # => per page (10)
|
93
97
|
search.results # => paginated page results
|
@@ -139,7 +143,8 @@ class ProductSearch
|
|
139
143
|
sort_by :name, :price
|
140
144
|
end
|
141
145
|
|
142
|
-
search = ProductSearch.new
|
146
|
+
search = ProductSearch.new filters: {sort: 'price desc'}
|
147
|
+
|
143
148
|
search.results # => Product sorted my price DESC
|
144
149
|
search.sort_attribute # => 'price'
|
145
150
|
search.sort_direction # => 'desc'
|
@@ -161,6 +166,14 @@ search.sort_params_for('name')
|
|
161
166
|
|
162
167
|
## Tips & Tricks
|
163
168
|
|
169
|
+
### Results shortcut
|
170
|
+
|
171
|
+
Very often you will just need results of search:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
ProductSearch.new(params).results == ProductSearch.results(params)
|
175
|
+
```
|
176
|
+
|
164
177
|
### Passing scope as argument
|
165
178
|
|
166
179
|
``` ruby
|
@@ -171,8 +184,8 @@ class ProductSearch
|
|
171
184
|
end
|
172
185
|
|
173
186
|
# first arguments is treated as scope (if no scope option is provided)
|
174
|
-
search = ProductSearch.new
|
175
|
-
search.results # => products
|
187
|
+
search = ProductSearch.new scope: Product.visible, filters: params[:f]
|
188
|
+
search.results # => includes only visible products
|
176
189
|
```
|
177
190
|
|
178
191
|
|
@@ -243,8 +256,8 @@ class ProductSearch
|
|
243
256
|
option :name
|
244
257
|
option :category_name
|
245
258
|
|
246
|
-
def initialize(user,
|
247
|
-
super Product.visible_to(user)
|
259
|
+
def initialize(user, options = {})
|
260
|
+
super options.merge(scope: Product.visible_to(user))
|
248
261
|
end
|
249
262
|
end
|
250
263
|
```
|
@@ -3,10 +3,13 @@ class PostSearch
|
|
3
3
|
|
4
4
|
scope { Post.all }
|
5
5
|
|
6
|
-
sort_by :created_at, :views_count, :likes_count, :comments_count
|
6
|
+
sort_by :id, :created_at, :views_count, :likes_count, :comments_count
|
7
7
|
|
8
8
|
per_page 15
|
9
9
|
|
10
|
+
min_per_page 10
|
11
|
+
max_per_page 100
|
12
|
+
|
10
13
|
option :user_id
|
11
14
|
option :category_name
|
12
15
|
|
@@ -9,9 +9,16 @@
|
|
9
9
|
= date_field_tag 'f[created_before]', @search.created_before
|
10
10
|
= form.submit 'Search'
|
11
11
|
|
12
|
+
div.pull-right
|
13
|
+
.btn-group
|
14
|
+
= link_to '15', root_path(per_page: 15, f: @search.params), class: "btn btn-default#{' active' if @search.per_page == 15}"
|
15
|
+
= link_to '25', root_path(per_page: 25, f: @search.params), class: "btn btn-default#{' active' if @search.per_page == 25}"
|
16
|
+
= link_to '55', root_path(per_page: 55, f: @search.params), class: "btn btn-default#{' active' if @search.per_page == 55}"
|
17
|
+
|
12
18
|
table.table.table-striped
|
13
19
|
thead
|
14
20
|
tr
|
21
|
+
th = link_to 'ID', root_path(f: @search.sort_params_for(:id)), class: @search.sort?(:id) && 'active'
|
15
22
|
th = form.label :title, 'Title'
|
16
23
|
th = form.label :user_id, 'Author'
|
17
24
|
th = form.label :category_name, 'Category'
|
@@ -21,6 +28,7 @@
|
|
21
28
|
th = form.label :published, 'Published?'
|
22
29
|
th = link_to 'Created at', root_path(f: @search.sort_params_for(:created_at)), class: @search.sort?(:created_at) && 'active'
|
23
30
|
tr
|
31
|
+
th
|
24
32
|
th = form.text_field :title
|
25
33
|
th = form.select :user_id, User.all.map { |u| [u.name, u.id] }, include_blank: true
|
26
34
|
th = form.select :category_name, Post.pluck('DISTINCT category_name'), include_blank: true
|
@@ -33,6 +41,8 @@
|
|
33
41
|
- if @search.results?
|
34
42
|
- @search.results.each do |post|
|
35
43
|
tr
|
44
|
+
td width="1%"
|
45
|
+
span.label.label-primary ##{post.id}
|
36
46
|
td = post.title
|
37
47
|
td = post.user_name
|
38
48
|
td = post.category_name
|
data/lib/search_object/base.rb
CHANGED
@@ -9,8 +9,8 @@ module SearchObject
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(
|
13
|
-
@search = self.class.build_internal_search
|
12
|
+
def initialize(options = {})
|
13
|
+
@search = self.class.build_internal_search options
|
14
14
|
end
|
15
15
|
|
16
16
|
def results
|
@@ -41,9 +41,9 @@ module SearchObject
|
|
41
41
|
|
42
42
|
module ClassMethods
|
43
43
|
# :api: private
|
44
|
-
def build_internal_search(
|
45
|
-
scope = (@scope && @scope.call
|
46
|
-
params = @defaults.merge
|
44
|
+
def build_internal_search(options)
|
45
|
+
scope = options.fetch(:scope) { @scope && @scope.call } or raise MissingScopeError
|
46
|
+
params = @defaults.merge Helper.select_keys(Helper.stringify_keys(options.fetch(:filters, {})), @actions.keys)
|
47
47
|
|
48
48
|
Search.new scope, params, @actions
|
49
49
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SearchObject
|
2
|
+
class MissingScopeError < ArgumentError
|
3
|
+
def initialize(message = 'No scope provided. Scope can be defined on a class level or passed as an option.')
|
4
|
+
super message
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class InvalidNumberError < ArgumentError
|
9
|
+
attr_reader :field, :number
|
10
|
+
|
11
|
+
def initialize(field, number)
|
12
|
+
@field = field
|
13
|
+
@number = number
|
14
|
+
|
15
|
+
super "#{field} should be more than 0. Currently '#{number}' is given."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -5,17 +5,13 @@ module SearchObject
|
|
5
5
|
base.extend ClassMethods
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
@page = args.pop.to_i.abs
|
10
|
-
super *args
|
11
|
-
end
|
8
|
+
attr_reader :page, :per_page
|
12
9
|
|
13
|
-
def
|
14
|
-
@page
|
15
|
-
|
10
|
+
def initialize(options = {})
|
11
|
+
@page = [options[:page].to_i, 0].max
|
12
|
+
@per_page = self.class.calculate_per_page options[:per_page]
|
16
13
|
|
17
|
-
|
18
|
-
self.class.get_per_page
|
14
|
+
super options
|
19
15
|
end
|
20
16
|
|
21
17
|
private
|
@@ -25,16 +21,31 @@ module SearchObject
|
|
25
21
|
end
|
26
22
|
|
27
23
|
def apply_paging(scope)
|
28
|
-
scope.limit(per_page).offset(page * per_page
|
24
|
+
scope.limit(per_page).offset ([page, 1].max - 1) * per_page
|
29
25
|
end
|
30
26
|
|
31
27
|
module ClassMethods
|
32
28
|
def per_page(number)
|
33
|
-
|
29
|
+
raise InvalidNumberError.new('Per page', number) unless number > 0
|
30
|
+
@per_page = number
|
31
|
+
end
|
32
|
+
|
33
|
+
def min_per_page(number)
|
34
|
+
raise InvalidNumberError.new('Min per page', number) unless number > 0
|
35
|
+
@min_per_page = number
|
36
|
+
end
|
37
|
+
|
38
|
+
def max_per_page(number)
|
39
|
+
raise InvalidNumberError.new('Max per page', number) unless number > 0
|
40
|
+
@max_per_page = number
|
34
41
|
end
|
35
42
|
|
36
|
-
|
37
|
-
|
43
|
+
# :api: private
|
44
|
+
def calculate_per_page(given)
|
45
|
+
per_page = (given || @per_page || 25).to_i.abs
|
46
|
+
per_page = [per_page, @max_per_page].min if @max_per_page
|
47
|
+
per_page = [per_page, @min_per_page].max if @min_per_page
|
48
|
+
per_page
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
data/lib/search_object.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'active_support/core_ext/object/blank'
|
2
3
|
|
3
4
|
module SearchObject
|
4
5
|
describe Base do
|
@@ -19,7 +20,7 @@ module SearchObject
|
|
19
20
|
end
|
20
21
|
|
21
22
|
def new_search(default_scope = [], filters = {}, &block)
|
22
|
-
search_class(default_scope, &block).new filters
|
23
|
+
search_class(default_scope, &block).new filters: filters
|
23
24
|
end
|
24
25
|
|
25
26
|
it "can had its #initialize method overwritten" do
|
@@ -63,12 +64,12 @@ module SearchObject
|
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
|
-
it "
|
67
|
-
expect(search_class.new('scope').results).to eq 'scope'
|
67
|
+
it "accepts scope as argument" do
|
68
|
+
expect(search_class.new(scope: 'scope').results).to eq 'scope'
|
68
69
|
end
|
69
70
|
|
70
|
-
it "
|
71
|
-
expect
|
71
|
+
it "raises an error if scope is not provided" do
|
72
|
+
expect { search_class.new }.to raise_error SearchObject::MissingScopeError
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
@@ -134,7 +135,7 @@ module SearchObject
|
|
134
135
|
describe ".results" do
|
135
136
|
it "shortcut for creating new search and immediately returning results" do
|
136
137
|
klass = search_class [1 ,2 ,3]
|
137
|
-
expect(klass.results(value: 1)).to eq [1]
|
138
|
+
expect(klass.results(filters: {value: 1})).to eq [1]
|
138
139
|
end
|
139
140
|
end
|
140
141
|
|
@@ -4,47 +4,13 @@ require_relative '../../support/kaminari_setup'
|
|
4
4
|
|
5
5
|
module SearchObject
|
6
6
|
module Plugin
|
7
|
-
describe
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
scope { Product }
|
13
|
-
|
14
|
-
per_page 2
|
7
|
+
describe Kaminari do
|
8
|
+
it_behaves_like "a paging plugin" do
|
9
|
+
it "uses kaminari gem" do
|
10
|
+
search = search_with_page
|
11
|
+
expect(search.results.respond_to? :total_pages).to be_true
|
15
12
|
end
|
16
13
|
end
|
17
|
-
|
18
|
-
after do
|
19
|
-
Product.delete_all
|
20
|
-
end
|
21
|
-
|
22
|
-
it "paginates" do
|
23
|
-
10.times { |i| Product.create name: "product_#{i}" }
|
24
|
-
search = search_class.new({}, 3)
|
25
|
-
expect(search.results.map(&:name)).to eq %w(product_4 product_5)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "uses will paginate" do
|
29
|
-
search = search_class.new
|
30
|
-
expect(search.results.respond_to? :total_pages).to be_true
|
31
|
-
end
|
32
|
-
|
33
|
-
it "treats nil page as 0" do
|
34
|
-
search = search_class.new({}, nil)
|
35
|
-
expect(search.page).to eq 0
|
36
|
-
end
|
37
|
-
|
38
|
-
it "treats negative page numbers as positive" do
|
39
|
-
search = search_class.new({}, -1)
|
40
|
-
expect(search.page).to eq 1
|
41
|
-
end
|
42
|
-
|
43
|
-
it "gives the real count" do
|
44
|
-
10.times { |i| Product.create name: "product_#{i}" }
|
45
|
-
search = search_class.new({}, 1)
|
46
|
-
expect(search.count).to eq 10
|
47
|
-
end
|
48
14
|
end
|
49
15
|
end
|
50
16
|
end
|
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'active_support/concern'
|
6
|
+
require 'active_model/conversion'
|
7
|
+
require 'active_model/naming'
|
3
8
|
require 'active_model/lint'
|
4
9
|
|
5
10
|
module SearchObject
|
@@ -7,6 +12,8 @@ module SearchObject
|
|
7
12
|
class ExtendedModel
|
8
13
|
include SearchObject.module(:model)
|
9
14
|
|
15
|
+
scope { [] }
|
16
|
+
|
10
17
|
# Fake errors
|
11
18
|
# Since SearchObject is focused on plain search forms,
|
12
19
|
# validations are not needed most of the time
|
@@ -3,41 +3,7 @@ require 'spec_helper_active_record'
|
|
3
3
|
module SearchObject
|
4
4
|
module Plugin
|
5
5
|
describe Paging do
|
6
|
-
|
7
|
-
Class.new do
|
8
|
-
include SearchObject.module(:paging)
|
9
|
-
|
10
|
-
scope { Product }
|
11
|
-
|
12
|
-
per_page 2
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
after do
|
17
|
-
Product.delete_all
|
18
|
-
end
|
19
|
-
|
20
|
-
it "paginates results (by offset and limit)" do
|
21
|
-
10.times { |i| Product.create name: "product_#{i}" }
|
22
|
-
search = search_class.new({}, 1)
|
23
|
-
expect(search.results.map(&:name)).to eq %w(product_2 product_3)
|
24
|
-
end
|
25
|
-
|
26
|
-
it "treats nil page as 0" do
|
27
|
-
search = search_class.new({}, nil)
|
28
|
-
expect(search.page).to eq 0
|
29
|
-
end
|
30
|
-
|
31
|
-
it "treats negative page numbers as positive" do
|
32
|
-
search = search_class.new({}, -1)
|
33
|
-
expect(search.page).to eq 1
|
34
|
-
end
|
35
|
-
|
36
|
-
it "gives the real count" do
|
37
|
-
10.times { |i| Product.create name: "product_#{i}" }
|
38
|
-
search = search_class.new({}, 1)
|
39
|
-
expect(search.count).to eq 10
|
40
|
-
end
|
6
|
+
it_behaves_like "a paging plugin"
|
41
7
|
end
|
42
8
|
end
|
43
9
|
end
|
@@ -16,6 +16,10 @@ module SearchObject
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def search_with_sort(sort = nil, filters = {})
|
20
|
+
search_class.new filters: {sort: sort}.merge(filters)
|
21
|
+
end
|
22
|
+
|
19
23
|
describe "sorting" do
|
20
24
|
after do
|
21
25
|
Product.delete_all
|
@@ -24,40 +28,40 @@ module SearchObject
|
|
24
28
|
it "sorts results based on the sort option" do
|
25
29
|
5.times { |i| Product.create! price: i }
|
26
30
|
|
27
|
-
search =
|
31
|
+
search = search_with_sort 'price desc'
|
28
32
|
expect(search.results.map(&:price)).to eq [4, 3, 2, 1, 0]
|
29
33
|
end
|
30
34
|
|
31
35
|
it "defaults to first sort by option" do
|
32
36
|
5.times { |i| Product.create! name: "Name#{i}" }
|
33
37
|
|
34
|
-
search =
|
38
|
+
search = search_with_sort
|
35
39
|
expect(search.results.map(&:name)).to eq %w(Name4 Name3 Name2 Name1 Name0)
|
36
40
|
end
|
37
41
|
|
38
42
|
it "ignores invalid sort values" do
|
39
|
-
search =
|
43
|
+
search = search_with_sort 'invalid attribute'
|
40
44
|
expect { search.results.to_a }.not_to raise_error
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
describe "#sort?" do
|
45
49
|
it "matches the sort option" do
|
46
|
-
search =
|
50
|
+
search = search_with_sort 'price desc'
|
47
51
|
|
48
52
|
expect(search.sort?(:price)).to be_true
|
49
53
|
expect(search.sort?(:name)).to be_false
|
50
54
|
end
|
51
55
|
|
52
56
|
it "matches string also" do
|
53
|
-
search =
|
57
|
+
search = search_with_sort 'price desc'
|
54
58
|
|
55
59
|
expect(search.sort?('price')).to be_true
|
56
60
|
expect(search.sort?('name')).to be_false
|
57
61
|
end
|
58
62
|
|
59
63
|
it "matches exact strings" do
|
60
|
-
search =
|
64
|
+
search = search_with_sort 'price desc'
|
61
65
|
|
62
66
|
expect(search.sort?('price desc')).to be_true
|
63
67
|
expect(search.sort?('price asc')).to be_false
|
@@ -66,72 +70,72 @@ module SearchObject
|
|
66
70
|
|
67
71
|
describe "#sort_attribute" do
|
68
72
|
it "returns sort option attribute" do
|
69
|
-
search =
|
73
|
+
search = search_with_sort 'price desc'
|
70
74
|
expect(search.sort_attribute).to eq 'price'
|
71
75
|
end
|
72
76
|
|
73
77
|
it "defaults to the first sort by option" do
|
74
|
-
search =
|
78
|
+
search = search_with_sort
|
75
79
|
expect(search.sort_attribute).to eq 'name'
|
76
80
|
end
|
77
81
|
|
78
82
|
it "rejects invalid sort options, uses defaults" do
|
79
|
-
search =
|
83
|
+
search = search_with_sort 'invalid'
|
80
84
|
expect(search.sort_attribute).to eq 'name'
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
84
88
|
describe "#sort_direction" do
|
85
89
|
it "returns asc or desc" do
|
86
|
-
expect(
|
87
|
-
expect(
|
90
|
+
expect(search_with_sort('price desc').sort_direction).to eq 'desc'
|
91
|
+
expect(search_with_sort('price asc').sort_direction).to eq 'asc'
|
88
92
|
end
|
89
93
|
|
90
94
|
it "defaults to desc" do
|
91
|
-
expect(
|
92
|
-
expect(
|
95
|
+
expect(search_with_sort.sort_direction).to eq 'desc'
|
96
|
+
expect(search_with_sort('price').sort_direction).to eq 'desc'
|
93
97
|
end
|
94
98
|
|
95
99
|
it "rejects invalid sort options, uses desc" do
|
96
|
-
expect(
|
100
|
+
expect(search_with_sort('price foo').sort_direction).to eq 'desc'
|
97
101
|
end
|
98
102
|
end
|
99
103
|
|
100
104
|
describe "#sort_direction_for" do
|
101
105
|
it "returns desc if current sort attribute is not the given attribute" do
|
102
|
-
expect(
|
106
|
+
expect(search_with_sort('price desc').sort_direction_for('name')).to eq 'desc'
|
103
107
|
end
|
104
108
|
|
105
109
|
it "returns asc if current sort attribute is the given attribute" do
|
106
|
-
expect(
|
110
|
+
expect(search_with_sort('name desc').sort_direction_for('name')).to eq 'asc'
|
107
111
|
end
|
108
112
|
|
109
113
|
it "returns desc if current sort attribute is the given attribute, but asc with direction" do
|
110
|
-
expect(
|
114
|
+
expect(search_with_sort('name asc').sort_direction_for('name')).to eq 'desc'
|
111
115
|
end
|
112
116
|
end
|
113
117
|
|
114
118
|
describe "#sort_params_for" do
|
115
119
|
it "adds sort direction" do
|
116
|
-
search =
|
120
|
+
search = search_with_sort 'name', name: 'test'
|
117
121
|
expect(search.sort_params_for(:price)).to eq 'sort' => 'price desc', 'name' => 'test'
|
118
122
|
end
|
119
123
|
|
120
124
|
it "reverses sort direction if this is the current sort attribute" do
|
121
|
-
search =
|
125
|
+
search = search_with_sort 'name desc', name: 'test'
|
122
126
|
expect(search.sort_params_for(:name)).to eq 'sort' => 'name asc', 'name' => 'test'
|
123
127
|
end
|
124
128
|
|
125
129
|
it "accepts additional options" do
|
126
|
-
search =
|
130
|
+
search = search_with_sort
|
127
131
|
expect(search.sort_params_for(:price, name: 'value')).to eq 'sort' => 'price desc', 'name' => 'value'
|
128
132
|
end
|
129
133
|
end
|
130
134
|
|
131
135
|
describe "#reverted_sort_direction" do
|
132
136
|
it "reverts sorting direction" do
|
133
|
-
expect(
|
134
|
-
expect(
|
137
|
+
expect(search_with_sort('price desc').reverted_sort_direction).to eq 'asc'
|
138
|
+
expect(search_with_sort('price asc').reverted_sort_direction).to eq 'desc'
|
135
139
|
end
|
136
140
|
end
|
137
141
|
end
|
@@ -5,47 +5,13 @@ require 'will_paginate/active_record'
|
|
5
5
|
|
6
6
|
module SearchObject
|
7
7
|
module Plugin
|
8
|
-
describe
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
scope { Product }
|
14
|
-
|
15
|
-
per_page 2
|
8
|
+
describe WillPaginate do
|
9
|
+
it_behaves_like "a paging plugin" do
|
10
|
+
it "uses will_paginate gem" do
|
11
|
+
search = search_with_page
|
12
|
+
expect(search.results.respond_to? :total_entries).to be_true
|
16
13
|
end
|
17
14
|
end
|
18
|
-
|
19
|
-
after do
|
20
|
-
Product.delete_all
|
21
|
-
end
|
22
|
-
|
23
|
-
it "paginates" do
|
24
|
-
10.times { |i| Product.create name: "product_#{i}" }
|
25
|
-
search = search_class.new({}, 2)
|
26
|
-
expect(search.results.map(&:name)).to eq %w(product_2 product_3)
|
27
|
-
end
|
28
|
-
|
29
|
-
it "uses will paginate" do
|
30
|
-
search = search_class.new
|
31
|
-
expect(search.results.respond_to? :total_entries).to be_true
|
32
|
-
end
|
33
|
-
|
34
|
-
it "treats nil page as 0" do
|
35
|
-
search = search_class.new({}, nil)
|
36
|
-
expect(search.page).to eq 0
|
37
|
-
end
|
38
|
-
|
39
|
-
it "treats negative page numbers as positive" do
|
40
|
-
search = search_class.new({}, -1)
|
41
|
-
expect(search.page).to eq 1
|
42
|
-
end
|
43
|
-
|
44
|
-
it "gives the real count" do
|
45
|
-
10.times { |i| Product.create name: "product_#{i}" }
|
46
|
-
search = search_class.new({}, 1)
|
47
|
-
expect(search.count).to eq 10
|
48
|
-
end
|
49
15
|
end
|
50
16
|
end
|
51
17
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
shared_examples_for "a paging plugin" do
|
2
|
+
after do
|
3
|
+
Product.delete_all
|
4
|
+
end
|
5
|
+
|
6
|
+
def define_search_class(&block)
|
7
|
+
plugin_name = described_class.name.demodulize.underscore.to_sym
|
8
|
+
Class.new do
|
9
|
+
include SearchObject.module(plugin_name)
|
10
|
+
|
11
|
+
if block_given?
|
12
|
+
instance_eval &block
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def search_class
|
18
|
+
define_search_class do
|
19
|
+
scope { Product }
|
20
|
+
|
21
|
+
per_page 2
|
22
|
+
|
23
|
+
min_per_page 2
|
24
|
+
max_per_page 10
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def search_with_page(page = nil, per_page = nil)
|
29
|
+
search_class.new page: page, per_page: per_page
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#results" do
|
33
|
+
it "paginates results" do
|
34
|
+
6.times { |i| Product.create name: "product_#{i}" }
|
35
|
+
search = search_with_page 2, 2
|
36
|
+
|
37
|
+
expect(search.results.map(&:name)).to eq %w(product_2 product_3)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#page" do
|
42
|
+
it "treats nil page as 0" do
|
43
|
+
search = search_with_page nil
|
44
|
+
expect(search.page).to eq 0
|
45
|
+
end
|
46
|
+
|
47
|
+
it "treats negative page numbers as positive" do
|
48
|
+
search = search_with_page -1
|
49
|
+
expect(search.page).to eq 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#per_page" do
|
54
|
+
it "returns the class defined per page" do
|
55
|
+
search = search_class.new
|
56
|
+
expect(search.per_page).to eq 2
|
57
|
+
end
|
58
|
+
|
59
|
+
it "can be overwritten as option" do
|
60
|
+
search = search_class.new per_page: 3
|
61
|
+
expect(search.per_page).to eq 3
|
62
|
+
end
|
63
|
+
|
64
|
+
it "respects min per page" do
|
65
|
+
search = search_class.new per_page: 1
|
66
|
+
expect(search.per_page).to eq 2
|
67
|
+
end
|
68
|
+
|
69
|
+
it "respects max per page" do
|
70
|
+
search = search_class.new per_page: 100
|
71
|
+
expect(search.per_page).to eq 10
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "#count" do
|
76
|
+
it "gives the real count" do
|
77
|
+
10.times { |i| Product.create name: "product_#{i}" }
|
78
|
+
search = search_with_page 1
|
79
|
+
expect(search.count).to eq 10
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe ".per_page" do
|
84
|
+
it "doesn't accept 0" do
|
85
|
+
expect { define_search_class { per_page 0 } }.to raise_error SearchObject::InvalidNumberError
|
86
|
+
end
|
87
|
+
|
88
|
+
it "doesn't accept negative number" do
|
89
|
+
expect { define_search_class { per_page -1 } }.to raise_error SearchObject::InvalidNumberError
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe ".min_per_page" do
|
94
|
+
it "doesn't accept 0" do
|
95
|
+
expect { define_search_class { min_per_page 0 } }.to raise_error SearchObject::InvalidNumberError
|
96
|
+
end
|
97
|
+
|
98
|
+
it "doesn't accept negative number" do
|
99
|
+
expect { define_search_class { min_per_page -1 } }.to raise_error SearchObject::InvalidNumberError
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe ".max_per_page" do
|
104
|
+
it "doesn't accept 0" do
|
105
|
+
expect { define_search_class { max_per_page 0 } }.to raise_error SearchObject::InvalidNumberError
|
106
|
+
end
|
107
|
+
|
108
|
+
it "doesn't accept negative number" do
|
109
|
+
expect { define_search_class { max_per_page -1 } }.to raise_error SearchObject::InvalidNumberError
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: search_object
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0
|
4
|
+
version: '1.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Radoslav Stankov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -192,6 +192,7 @@ files:
|
|
192
192
|
- example/spec/spec_helper.rb
|
193
193
|
- lib/search_object.rb
|
194
194
|
- lib/search_object/base.rb
|
195
|
+
- lib/search_object/errors.rb
|
195
196
|
- lib/search_object/helper.rb
|
196
197
|
- lib/search_object/plugin/kaminari.rb
|
197
198
|
- lib/search_object/plugin/model.rb
|
@@ -212,6 +213,7 @@ files:
|
|
212
213
|
- spec/spec_helper.rb
|
213
214
|
- spec/spec_helper_active_record.rb
|
214
215
|
- spec/support/kaminari_setup.rb
|
216
|
+
- spec/support/paging_shared_example.rb
|
215
217
|
homepage: https://github.com/RStankov/SearchObject
|
216
218
|
licenses:
|
217
219
|
- MIT
|
@@ -248,4 +250,5 @@ test_files:
|
|
248
250
|
- spec/spec_helper.rb
|
249
251
|
- spec/spec_helper_active_record.rb
|
250
252
|
- spec/support/kaminari_setup.rb
|
253
|
+
- spec/support/paging_shared_example.rb
|
251
254
|
has_rdoc:
|