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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc173628e2536b5964d951dbf6dd41b193cd52b8
4
- data.tar.gz: 82b714d47dc48c540c64503589d50872f9156446
3
+ metadata.gz: 4102172d9cbe97e6fe2e066614a3136ac194a481
4
+ data.tar.gz: a01b6a7ebfa07a71a310ff32711147f378aaa337
5
5
  SHA512:
6
- metadata.gz: e3e9f0fae74e4a5a6d620c6f6db1f8fbc744ac85bc918b98ed30c2c91920b06d7213c94af0f1fb823e54ea681c162018db4c0073513f60e8eec11ade31db443d
7
- data.tar.gz: 4c5fed9dc2f8dae633b1cf8507ff66a79c620e356bdde2a9ebddcc455256ecf49823acc93cf2e9f4d24e8fc76fcea05d064e902707a6f88f67f2a5768a9d01d4
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(params[:filters])
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(params[:filters], params[:page]) # page number is required
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(sort: 'price desc')
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(Product.visible, params[:f])
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, filters)
247
- super Product.visible_to(user), filters
259
+ def initialize(user, options = {})
260
+ super options.merge(scope: Product.visible_to(user))
248
261
  end
249
262
  end
250
263
  ```
@@ -1,5 +1,5 @@
1
1
  class PostsController < ApplicationController
2
2
  def index
3
- @search = PostSearch.new params[:f], params[:page]
3
+ @search = PostSearch.new filters: params[:f], page: params[:page], per_page: params[:per_page]
4
4
  end
5
5
  end
@@ -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
@@ -18,7 +18,7 @@ describe PostSearch do
18
18
  end
19
19
 
20
20
  def expect_search(options)
21
- expect(PostSearch.new(options, 0).results)
21
+ expect(PostSearch.new(filters: options, page: 0).results)
22
22
  end
23
23
 
24
24
  it "can search by category name" do
@@ -9,8 +9,8 @@ module SearchObject
9
9
  end
10
10
  end
11
11
 
12
- def initialize(*args)
13
- @search = self.class.build_internal_search args
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(args)
45
- scope = (@scope && @scope.call) || args.shift
46
- params = @defaults.merge(Helper.select_keys Helper.stringify_keys(args.shift || {}), @actions.keys)
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
- def initialize(*args)
9
- @page = args.pop.to_i.abs
10
- super *args
11
- end
8
+ attr_reader :page, :per_page
12
9
 
13
- def page
14
- @page
15
- end
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
- def per_page
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
- @per_page = number.to_i.abs
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
- def get_per_page
37
- @per_page ||= 25
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
@@ -1,3 +1,3 @@
1
1
  module SearchObject
2
- VERSION = '0.2'
2
+ VERSION = '1.0'
3
3
  end
data/lib/search_object.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'search_object/version'
2
+ require 'search_object/errors'
2
3
  require 'search_object/helper'
3
4
  require 'search_object/base'
4
5
  require 'search_object/search'
@@ -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 "treats first argument as scope" do
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 "treats second argument as filters" do
71
- expect(search_class.new('scope', name: 'name').params).to eq 'name' => 'name'
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 Paging do
8
- def search_class
9
- Class.new do
10
- include SearchObject.module(:kaminari)
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
- def search_class
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 = search_class.new sort: 'price desc'
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 = search_class.new
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 = search_class.new sort: 'invalid attribute'
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 = search_class.new sort: 'price desc'
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 = search_class.new sort: 'price desc'
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 = search_class.new sort: 'price desc'
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 = search_class.new sort: 'price desc'
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 = search_class.new
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 = search_class.new sort: 'invalid'
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(search_class.new(sort: 'price desc').sort_direction).to eq 'desc'
87
- expect(search_class.new(sort: 'price asc').sort_direction).to eq 'asc'
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(search_class.new.sort_direction).to eq 'desc'
92
- expect(search_class.new(sort: 'price').sort_direction).to eq 'desc'
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(search_class.new(sort: 'price foo').sort_direction).to eq 'desc'
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(search_class.new(sort: 'price desc').sort_direction_for('name')).to eq 'desc'
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(search_class.new(sort: 'name desc').sort_direction_for('name')).to eq 'asc'
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(search_class.new(sort: 'name asc').sort_direction_for('name')).to eq 'desc'
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 = search_class.new sort: 'name', name: 'test'
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 = search_class.new sort: 'name desc', name: 'test'
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 = search_class.new
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(search_class.new(sort: 'price desc').reverted_sort_direction).to eq 'asc'
134
- expect(search_class.new(sort: 'price asc').reverted_sort_direction).to eq 'desc'
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 Paging do
9
- def search_class
10
- Class.new do
11
- include SearchObject.module(:will_paginate)
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
@@ -1,4 +1,5 @@
1
1
  require_relative 'spec_helper'
2
+ require_relative 'support/paging_shared_example'
2
3
  require 'active_record'
3
4
 
4
5
  ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
@@ -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.2'
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-04 00:00:00.000000000 Z
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: