search_object 1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +24 -0
- data/.travis.yml +4 -2
- data/CHANGELOG.md +40 -0
- data/README.md +38 -0
- data/example/Gemfile +2 -2
- data/example/app/assets/images/favicon.png +0 -0
- data/example/app/views/layouts/application.html.slim +1 -0
- data/example/config/environments/test.rb +1 -1
- data/example/screenshot.png +0 -0
- data/example/spec/models/post_search_spec.rb +11 -9
- data/example/spec/spec_helper.rb +1 -2
- data/lib/search_object/base.rb +22 -14
- data/lib/search_object/helper.rb +12 -4
- data/lib/search_object/plugin/kaminari.rb +0 -1
- data/lib/search_object/plugin/paging.rb +10 -10
- data/lib/search_object/plugin/sorting.rb +4 -4
- data/lib/search_object/search.rb +12 -0
- data/lib/search_object/version.rb +1 -1
- data/search_object.gemspec +2 -0
- data/spec/search_object/base_spec.rb +93 -57
- data/spec/search_object/helper_spec.rb +7 -7
- data/spec/search_object/plugin/kaminari_spec.rb +3 -3
- data/spec/search_object/plugin/model_spec.rb +6 -2
- data/spec/search_object/plugin/paging_spec.rb +1 -1
- data/spec/search_object/plugin/sorting_spec.rb +38 -33
- data/spec/search_object/plugin/will_paginate_spec.rb +3 -3
- data/spec/search_object/search_spec.rb +25 -26
- data/spec/spec_helper.rb +0 -1
- data/spec/spec_helper_active_record.rb +1 -1
- data/spec/support/kaminari_setup.rb +0 -1
- data/spec/support/paging_shared_example.rb +35 -32
- metadata +56 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24b14c3e7ec2492dc899f0a81a022089073be982
|
4
|
+
data.tar.gz: 8bee82cac11549acebcc9d35cc9cbb2aac25f456
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba172f5b05a65e43bbbb3c3ab28e4b62c397757a6e9e1cb2bbe9e1b78a1fd94bba980d7d86ed1c286471adefe2bc99bacbb82c37f085f541ce2f407ecb2aa410
|
7
|
+
data.tar.gz: e5dc94ed98f6f947dc7a2f44b854ebc1a4bfe98000d00d6451200855860ce0ccc51a4b844e6d09638137038f3f3786c12d6a46a0d8d29bc80ef01558ed2ebb1d
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
RunRailsCops: true
|
5
|
+
Exclude:
|
6
|
+
- example/db/**/*
|
7
|
+
- example/config/**/*
|
8
|
+
- search_object.gemspec
|
9
|
+
|
10
|
+
# Disables "Line is too long"
|
11
|
+
LineLength:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
# Disables "Missing top-level class documentation comment"
|
15
|
+
Documentation:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
# Disables "Use each_with_object instead of inject"
|
19
|
+
Style/EachWithObject:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
# Disables "Prefer reduce over inject."
|
23
|
+
Style/CollectionMethods:
|
24
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,45 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 1.1
|
4
|
+
|
5
|
+
* Search objects now can be inherited
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class BaseSearch
|
9
|
+
include SearchObject.module
|
10
|
+
|
11
|
+
# ... options and configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
class ProductSearch < BaseSearch
|
15
|
+
scope { Product }
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
* Using instance method for straight dispatch
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class ProductSearch
|
23
|
+
include SearchObject.module
|
24
|
+
|
25
|
+
scope { Product.all }
|
26
|
+
|
27
|
+
option :name
|
28
|
+
option :category_name
|
29
|
+
|
30
|
+
attr_reader :page
|
31
|
+
|
32
|
+
def initialize(filters = {}, page = 0)
|
33
|
+
super filters
|
34
|
+
@page = page.to_i.abc
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_results
|
38
|
+
super.paginate page: @page
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
3
43
|
## Version 1.0
|
4
44
|
|
5
45
|
* Added min_per_page and max_per_page to paging plugin
|
data/README.md
CHANGED
@@ -232,6 +232,24 @@ class ProductSearch
|
|
232
232
|
end
|
233
233
|
```
|
234
234
|
|
235
|
+
### Using instance method for straight dispatch
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class ProductSearch
|
239
|
+
include SearchObject.module
|
240
|
+
|
241
|
+
scope { Product.all }
|
242
|
+
|
243
|
+
option :date, with: :parse_dates
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def parse_dates(scope, value)
|
248
|
+
# some "magic" method to parse dates
|
249
|
+
end
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
235
253
|
### Active Record is not required at all
|
236
254
|
|
237
255
|
```ruby
|
@@ -286,6 +304,26 @@ class ProductSearch
|
|
286
304
|
end
|
287
305
|
```
|
288
306
|
|
307
|
+
### Extracting basic module
|
308
|
+
|
309
|
+
You can extarct a basic search class for your application.
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
class BaseSearch
|
313
|
+
include SearchObject.module
|
314
|
+
|
315
|
+
# ... options and configuration
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
Then use it like:
|
320
|
+
|
321
|
+
```ruby
|
322
|
+
class ProductSearch < BaseSearch
|
323
|
+
scope { Product }
|
324
|
+
end
|
325
|
+
```
|
326
|
+
|
289
327
|
## Contributing
|
290
328
|
|
291
329
|
1. Fork it
|
data/example/Gemfile
CHANGED
Binary file
|
@@ -13,7 +13,7 @@ Example::Application.configure do
|
|
13
13
|
config.eager_load = false
|
14
14
|
|
15
15
|
# Configure static asset server for tests with Cache-Control for performance.
|
16
|
-
config.
|
16
|
+
config.serve_static_files = true
|
17
17
|
config.static_cache_control = "public, max-age=3600"
|
18
18
|
|
19
19
|
# Show full error reports and disable caching.
|
data/example/screenshot.png
CHANGED
Binary file
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# rubocop:disable Lint/UselessAssignment:%s
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe PostSearch do
|
@@ -13,7 +15,7 @@ describe PostSearch do
|
|
13
15
|
title: 'Title',
|
14
16
|
body: 'Body',
|
15
17
|
category_name: 'Tech',
|
16
|
-
published: true
|
18
|
+
published: true
|
17
19
|
)
|
18
20
|
end
|
19
21
|
|
@@ -21,35 +23,35 @@ describe PostSearch do
|
|
21
23
|
expect(PostSearch.new(filters: options, page: 0).results)
|
22
24
|
end
|
23
25
|
|
24
|
-
it
|
26
|
+
it 'can search by category name' do
|
25
27
|
post = create category_name: 'Personal'
|
26
28
|
other = create category_name: 'Other'
|
27
29
|
|
28
30
|
expect_search(category_name: 'Personal').to eq [post]
|
29
31
|
end
|
30
32
|
|
31
|
-
it
|
33
|
+
it 'can search by user_id' do
|
32
34
|
post = create user: create_user
|
33
35
|
other = create user: create_user
|
34
36
|
|
35
37
|
expect_search(user_id: post.user_id).to eq [post]
|
36
38
|
end
|
37
39
|
|
38
|
-
it
|
40
|
+
it 'can search by title' do
|
39
41
|
post = create title: 'Title'
|
40
42
|
other = create title: 'Other'
|
41
43
|
|
42
44
|
expect_search(title: 'itl').to eq [post]
|
43
45
|
end
|
44
46
|
|
45
|
-
it
|
47
|
+
it 'can search by published' do
|
46
48
|
post = create published: true
|
47
49
|
other = create published: false
|
48
50
|
|
49
51
|
expect_search(published: true).to eq [post]
|
50
52
|
end
|
51
53
|
|
52
|
-
it
|
54
|
+
it 'can search by term' do
|
53
55
|
post_with_body = create body: 'pattern'
|
54
56
|
post_with_title = create title: 'pattern'
|
55
57
|
other = create
|
@@ -57,21 +59,21 @@ describe PostSearch do
|
|
57
59
|
expect_search(term: 'pattern').to eq [post_with_title, post_with_body]
|
58
60
|
end
|
59
61
|
|
60
|
-
it
|
62
|
+
it 'can search by created after' do
|
61
63
|
post = create created_at: 1.month.ago
|
62
64
|
other = create created_at: 3.month.ago
|
63
65
|
|
64
66
|
expect_search(created_after: 2.month.ago.strftime('%Y-%m-%d')).to eq [post]
|
65
67
|
end
|
66
68
|
|
67
|
-
it
|
69
|
+
it 'can search by created before' do
|
68
70
|
post = create created_at: 3.month.ago
|
69
71
|
other = create created_at: 1.month.ago
|
70
72
|
|
71
73
|
expect_search(created_before: 2.month.ago.strftime('%Y-%m-%d')).to eq [post]
|
72
74
|
end
|
73
75
|
|
74
|
-
it
|
76
|
+
it 'can sort by views count' do
|
75
77
|
post_3 = create views_count: 3
|
76
78
|
post_2 = create views_count: 2
|
77
79
|
post_1 = create views_count: 1
|
data/example/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
2
|
ENV['RAILS_ENV'] ||= 'test'
|
3
3
|
|
4
|
-
require File.expand_path(
|
4
|
+
require File.expand_path('../../config/environment', __FILE__)
|
5
5
|
require 'rspec/rails'
|
6
|
-
require 'rspec/autorun'
|
7
6
|
|
8
7
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
9
8
|
# in spec/support/ and its subdirectories.
|
data/lib/search_object/base.rb
CHANGED
@@ -3,14 +3,16 @@ module SearchObject
|
|
3
3
|
def self.included(base)
|
4
4
|
base.extend ClassMethods
|
5
5
|
base.instance_eval do
|
6
|
-
@
|
7
|
-
|
8
|
-
|
6
|
+
@config = {
|
7
|
+
defaults: {},
|
8
|
+
actions: {},
|
9
|
+
scope: nil
|
10
|
+
}
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
14
|
def initialize(options = {})
|
13
|
-
@search = self.class.
|
15
|
+
@search = Search.build_for self.class.config, options
|
14
16
|
end
|
15
17
|
|
16
18
|
def results
|
@@ -40,23 +42,29 @@ module SearchObject
|
|
40
42
|
end
|
41
43
|
|
42
44
|
module ClassMethods
|
43
|
-
|
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)
|
45
|
+
attr_reader :config
|
47
46
|
|
48
|
-
|
47
|
+
def inherited(base)
|
48
|
+
new_config = config.dup
|
49
|
+
|
50
|
+
base.instance_eval do
|
51
|
+
@config = new_config
|
52
|
+
end
|
49
53
|
end
|
50
54
|
|
51
55
|
def scope(&block)
|
52
|
-
|
56
|
+
config[:scope] = block
|
53
57
|
end
|
54
58
|
|
55
|
-
def option(name,
|
56
|
-
|
59
|
+
def option(name, options = nil, &block)
|
60
|
+
options = { default: options } unless options.is_a?(Hash)
|
61
|
+
|
62
|
+
name = name.to_s
|
63
|
+
default = options[:default]
|
64
|
+
handler = options[:with] || block
|
57
65
|
|
58
|
-
|
59
|
-
|
66
|
+
config[:defaults][name] = default unless default.nil?
|
67
|
+
config[:actions][name] = Helper.normalize_search_handler(handler, name)
|
60
68
|
|
61
69
|
define_method(name) { @search.param name }
|
62
70
|
end
|
data/lib/search_object/helper.rb
CHANGED
@@ -2,18 +2,18 @@ module SearchObject
|
|
2
2
|
module Helper
|
3
3
|
class << self
|
4
4
|
def stringify_keys(hash)
|
5
|
-
Hash[(hash || {}).map { |k, v| [k.to_s, v]}]
|
5
|
+
Hash[(hash || {}).map { |k, v| [k.to_s, v] }]
|
6
6
|
end
|
7
7
|
|
8
8
|
def select_keys(hash, keys)
|
9
9
|
keys.inject({}) do |memo, key|
|
10
|
-
memo[key] = hash[key] if hash.
|
10
|
+
memo[key] = hash[key] if hash.key? key
|
11
11
|
memo
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
15
|
def camelize(text)
|
16
|
-
text.to_s.gsub(
|
16
|
+
text.to_s.gsub(/(?:^|_)(.)/) { Regexp.last_match[1].upcase }
|
17
17
|
end
|
18
18
|
|
19
19
|
def ensure_included(item, collection)
|
@@ -27,10 +27,18 @@ module SearchObject
|
|
27
27
|
def define_module(&block)
|
28
28
|
Module.new do
|
29
29
|
define_singleton_method :included do |base|
|
30
|
-
base.class_eval
|
30
|
+
base.class_eval(&block)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
|
+
|
35
|
+
def normalize_search_handler(handler, name)
|
36
|
+
case handler
|
37
|
+
when Symbol then ->(scope, value) { method(handler).call scope, value }
|
38
|
+
when Proc then handler
|
39
|
+
else ->(scope, value) { scope.where name => value unless value.blank? }
|
40
|
+
end
|
41
|
+
end
|
34
42
|
end
|
35
43
|
end
|
36
44
|
end
|
@@ -21,30 +21,30 @@ module SearchObject
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def apply_paging(scope)
|
24
|
-
scope.limit(per_page).offset
|
24
|
+
scope.limit(per_page).offset(([page, 1].max - 1) * per_page)
|
25
25
|
end
|
26
26
|
|
27
27
|
module ClassMethods
|
28
28
|
def per_page(number)
|
29
|
-
|
30
|
-
|
29
|
+
fail InvalidNumberError.new('Per page', number) unless number > 0
|
30
|
+
config[:per_page] = number
|
31
31
|
end
|
32
32
|
|
33
33
|
def min_per_page(number)
|
34
|
-
|
35
|
-
|
34
|
+
fail InvalidNumberError.new('Min per page', number) unless number > 0
|
35
|
+
config[:min_per_page] = number
|
36
36
|
end
|
37
37
|
|
38
38
|
def max_per_page(number)
|
39
|
-
|
40
|
-
|
39
|
+
fail InvalidNumberError.new('Max per page', number) unless number > 0
|
40
|
+
config[:max_per_page] = number
|
41
41
|
end
|
42
42
|
|
43
43
|
# :api: private
|
44
44
|
def calculate_per_page(given)
|
45
|
-
per_page = (given ||
|
46
|
-
per_page = [per_page,
|
47
|
-
per_page = [per_page,
|
45
|
+
per_page = (given || config[:per_page] || 25).to_i.abs
|
46
|
+
per_page = [per_page, config[:max_per_page]].min if config[:max_per_page]
|
47
|
+
per_page = [per_page, config[:min_per_page]].max if config[:min_per_page]
|
48
48
|
per_page
|
49
49
|
end
|
50
50
|
end
|
@@ -4,7 +4,7 @@ module SearchObject
|
|
4
4
|
def self.included(base)
|
5
5
|
base.extend ClassMethods
|
6
6
|
base.instance_eval do
|
7
|
-
option :sort do |scope,
|
7
|
+
option :sort do |scope, _|
|
8
8
|
scope.order "#{sort_attribute} #{sort_direction}"
|
9
9
|
end
|
10
10
|
end
|
@@ -41,12 +41,12 @@ module SearchObject
|
|
41
41
|
|
42
42
|
module ClassMethods
|
43
43
|
def sort_by(*attributes)
|
44
|
-
|
45
|
-
|
44
|
+
config[:sort_attributes] = attributes.map(&:to_s)
|
45
|
+
config[:defaults]['sort'] = "#{config[:sort_attributes].first} desc"
|
46
46
|
end
|
47
47
|
|
48
48
|
def sort_attributes
|
49
|
-
|
49
|
+
config[:sort_attributes] ||= []
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
data/lib/search_object/search.rb
CHANGED
@@ -2,6 +2,18 @@ module SearchObject
|
|
2
2
|
class Search
|
3
3
|
attr_reader :params
|
4
4
|
|
5
|
+
class << self
|
6
|
+
def build_for(config, options)
|
7
|
+
scope = options.fetch(:scope) { config[:scope] && config[:scope].call }
|
8
|
+
filters = Helper.stringify_keys(options.fetch(:filters, {}))
|
9
|
+
params = config[:defaults].merge Helper.select_keys(filters, config[:actions].keys)
|
10
|
+
|
11
|
+
fail MissingScopeError unless scope
|
12
|
+
|
13
|
+
new scope, params, config[:actions]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
5
17
|
def initialize(scope, params, actions)
|
6
18
|
@scope = scope
|
7
19
|
@actions = actions
|
data/search_object.gemspec
CHANGED
@@ -27,4 +27,6 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency 'coveralls'
|
28
28
|
spec.add_development_dependency 'will_paginate'
|
29
29
|
spec.add_development_dependency 'kaminari'
|
30
|
+
spec.add_development_dependency 'rubocop'
|
31
|
+
spec.add_development_dependency 'rubocop-rspec'
|
30
32
|
end
|