search_object 0.2 → 1.0

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