search_object 1.2.1 → 1.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +18 -3
- data/.travis.yml +4 -2
- data/CHANGELOG.md +19 -2
- data/Gemfile +2 -0
- data/README.md +5 -5
- data/Rakefile +2 -0
- data/example/Gemfile +3 -1
- data/example/README.md +3 -3
- data/example/Rakefile +3 -1
- data/example/app/controllers/application_controller.rb +2 -0
- data/example/app/controllers/posts_controller.rb +2 -0
- data/example/app/models/post.rb +2 -0
- data/example/app/models/post_search.rb +6 -4
- data/example/app/models/user.rb +2 -0
- data/example/app/views/posts/index.html.slim +1 -1
- data/example/bin/bundle +3 -1
- data/example/bin/rails +3 -1
- data/example/bin/rake +2 -0
- data/example/config.ru +2 -0
- data/example/db/migrate/20131102130117_create_users.rb +1 -1
- data/example/db/migrate/20131102130413_create_posts.rb +1 -2
- data/example/db/schema.rb +19 -23
- data/example/spec/models/post_search_spec.rb +19 -19
- data/example/spec/spec_helper.rb +4 -2
- data/lib/search_object/base.rb +27 -7
- data/lib/search_object/errors.rb +2 -0
- data/lib/search_object/helper.rb +2 -4
- data/lib/search_object/plugin/enum.rb +6 -10
- data/lib/search_object/plugin/kaminari.rb +2 -0
- data/lib/search_object/plugin/model.rb +2 -0
- data/lib/search_object/plugin/paging.rb +8 -3
- data/lib/search_object/plugin/sorting.rb +2 -0
- data/lib/search_object/plugin/will_paginate.rb +2 -0
- data/lib/search_object/search.rb +14 -6
- data/lib/search_object/version.rb +3 -1
- data/lib/search_object.rb +2 -0
- data/search_object.gemspec +3 -3
- data/spec/search_object/base_spec.rb +58 -0
- data/spec/search_object/helper_spec.rb +2 -18
- data/spec/search_object/plugin/enum_spec.rb +57 -31
- data/spec/search_object/plugin/kaminari_spec.rb +2 -0
- data/spec/search_object/plugin/model_spec.rb +2 -0
- data/spec/search_object/plugin/paging_spec.rb +2 -0
- data/spec/search_object/plugin/sorting_spec.rb +2 -0
- data/spec/search_object/plugin/will_paginate_spec.rb +2 -0
- data/spec/search_object/search_spec.rb +40 -21
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_helper_active_record.rb +2 -0
- data/spec/support/paging_shared_example.rb +2 -0
- metadata +14 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c01187c2852ea65c1c340bc37e62527c9d4133691ded6a9db4ac5dea78441b1d
|
4
|
+
data.tar.gz: 177bf4f66eccf02f9195a9aa2b8563f8d1de4465be812157f9eeec1ac90121a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cd78261ad83342fec752d1e54714afe7971841ae5afa90270bd90a8a1713cbb2aa06fdbad3489be78127195666015918f9f356ec1f4c9809b201f5631fc0795
|
7
|
+
data.tar.gz: 575474f4d79ff650bccb1cb9e9d33cb540d4b85dfc18a23487e1239e6a6833bf4da7dc52193829d2c116a51c9089736720eb6ac5db5868b814558fe15675fc34
|
data/.rubocop.yml
CHANGED
@@ -7,15 +7,15 @@ AllCops:
|
|
7
7
|
- search_object.gemspec
|
8
8
|
|
9
9
|
# Disables "Line is too long"
|
10
|
-
LineLength:
|
10
|
+
Layout/LineLength:
|
11
11
|
Enabled: false
|
12
12
|
|
13
13
|
# Disables Module has too many lines
|
14
|
-
ModuleLength:
|
14
|
+
Metrics/ModuleLength:
|
15
15
|
Enabled: false
|
16
16
|
|
17
17
|
# Disables "Missing top-level class documentation comment"
|
18
|
-
Documentation:
|
18
|
+
Style/Documentation:
|
19
19
|
Enabled: false
|
20
20
|
|
21
21
|
# Disables "Use each_with_object instead of inject"
|
@@ -37,3 +37,18 @@ RSpec/ExampleLength:
|
|
37
37
|
# Disables "Too many expectations."
|
38
38
|
RSpec/MultipleExpectations:
|
39
39
|
Enabled: false
|
40
|
+
|
41
|
+
Lint/RaiseException:
|
42
|
+
Enabled: true
|
43
|
+
|
44
|
+
Lint/StructNewOverride:
|
45
|
+
Enabled: true
|
46
|
+
|
47
|
+
Style/HashEachMethods:
|
48
|
+
Enabled: true
|
49
|
+
|
50
|
+
Style/HashTransformKeys:
|
51
|
+
Enabled: true
|
52
|
+
|
53
|
+
Style/HashTransformValues:
|
54
|
+
Enabled: true
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 1.2.5
|
4
|
+
|
5
|
+
* __[feature]__ Added `param?` method to `Search::Base` (@rstankov)
|
6
|
+
|
7
|
+
## Version 1.2.4
|
8
|
+
|
9
|
+
* __[feature]__ Added `:with` and block support to enum (@hschne)
|
10
|
+
|
11
|
+
## Version 1.2.3
|
12
|
+
|
13
|
+
* __[fix]__ convert enum values to strings (@Postmodum37)
|
14
|
+
|
15
|
+
## Version 1.2.2
|
16
|
+
|
17
|
+
* __[feature]__ Added `SearchObject::Base#params=` method, to reset search results (@rstankov)
|
18
|
+
* __[change]__ `option :orderBy, enum: %(price date)`, now expects a method `apply_order_by_x`, instead of `apply_orderBy_with_` (__backward incompatible__) (@rstankov)
|
19
|
+
|
3
20
|
## Version 1.2.1
|
4
21
|
|
5
|
-
* __[feature]__
|
22
|
+
* __[feature]__ Added `default:` option to `sort_by` plugin (@rstankov)
|
6
23
|
|
7
24
|
```ruby
|
8
25
|
class ProductSearch
|
@@ -16,7 +33,7 @@ end
|
|
16
33
|
|
17
34
|
## Version 1.2.0
|
18
35
|
|
19
|
-
* __[feature]__ `enum` plugin
|
36
|
+
* __[feature]__ Added `enum` plugin (@rstankov)
|
20
37
|
|
21
38
|
```ruby
|
22
39
|
class ProductSearch
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -76,7 +76,7 @@ end
|
|
76
76
|
Then you can just search the given scope:
|
77
77
|
|
78
78
|
```ruby
|
79
|
-
search = PostSearch.new
|
79
|
+
search = PostSearch.new(filters: params[:filters])
|
80
80
|
|
81
81
|
# accessing search options
|
82
82
|
search.name # => name option
|
@@ -115,7 +115,7 @@ class ProductSearch
|
|
115
115
|
option :name
|
116
116
|
option :category_name
|
117
117
|
|
118
|
-
# per page defaults to
|
118
|
+
# per page defaults to 10
|
119
119
|
per_page 10
|
120
120
|
|
121
121
|
# range of values is also possible
|
@@ -123,7 +123,7 @@ class ProductSearch
|
|
123
123
|
max_per_page 100
|
124
124
|
end
|
125
125
|
|
126
|
-
search = ProductSearch.new
|
126
|
+
search = ProductSearch.new(filters: params[:filters], page: params[:page], per_page: params[:per_page])
|
127
127
|
|
128
128
|
search.page # => page number
|
129
129
|
search.per_page # => per page (10)
|
@@ -228,7 +228,7 @@ class ProductSearch
|
|
228
228
|
sort_by :name, :price
|
229
229
|
end
|
230
230
|
|
231
|
-
search = ProductSearch.new
|
231
|
+
search = ProductSearch.new(filters: {sort: 'price desc'})
|
232
232
|
|
233
233
|
search.results # => Product sorted my price DESC
|
234
234
|
search.sort_attribute # => 'price'
|
@@ -267,7 +267,7 @@ class ProductSearch
|
|
267
267
|
end
|
268
268
|
|
269
269
|
# first arguments is treated as scope (if no scope option is provided)
|
270
|
-
search = ProductSearch.new
|
270
|
+
search = ProductSearch.new(scope: Product.visible, filters: params[:f])
|
271
271
|
search.results # => includes only visible products
|
272
272
|
```
|
273
273
|
|
data/Rakefile
CHANGED
data/example/Gemfile
CHANGED
data/example/README.md
CHANGED
@@ -19,9 +19,9 @@ This is example application showing, one of the possible usages of ```SearchObje
|
|
19
19
|
```
|
20
20
|
gem install bundler
|
21
21
|
bundle install
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
rails db:create
|
23
|
+
rails db:migrate
|
24
|
+
rails db:seed
|
25
25
|
|
26
26
|
rails server
|
27
27
|
```
|
data/example/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
4
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('config/application', __dir__)
|
5
7
|
|
6
8
|
Example::Application.load_tasks
|
data/example/app/models/post.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class PostSearch
|
2
4
|
include SearchObject.module(:model, :sorting, :will_paginate, :enum)
|
3
5
|
|
4
|
-
scope { Post.all }
|
6
|
+
scope { Post.includes(:user).all }
|
5
7
|
|
6
8
|
sort_by :id, :created_at, :views_count, :likes_count, :comments_count
|
7
9
|
|
@@ -18,11 +20,11 @@ class PostSearch
|
|
18
20
|
option :rating, enum: %i[low high]
|
19
21
|
|
20
22
|
option :title do |scope, value|
|
21
|
-
scope.where 'title LIKE ?', escape_search_term(value)
|
23
|
+
scope.where 'title LIKE ?', escape_search_term(value) if value.present?
|
22
24
|
end
|
23
25
|
|
24
26
|
option :published do |scope, value|
|
25
|
-
scope.where published: true if value.present?
|
27
|
+
scope.where published: true if value.present? && value != '0'
|
26
28
|
end
|
27
29
|
|
28
30
|
option :created_after do |scope, value|
|
@@ -38,7 +40,7 @@ class PostSearch
|
|
38
40
|
private
|
39
41
|
|
40
42
|
def apply_term(scope, value)
|
41
|
-
scope.where 'title LIKE :term OR body LIKE :term', term: escape_search_term(value)
|
43
|
+
scope.where 'title LIKE :term OR body LIKE :term', term: escape_search_term(value) if value.present?
|
42
44
|
end
|
43
45
|
|
44
46
|
def apply_rating_with_low(scope)
|
data/example/app/models/user.rb
CHANGED
@@ -32,7 +32,7 @@
|
|
32
32
|
th
|
33
33
|
th = form.text_field :title
|
34
34
|
th = form.select :user_id, User.all.map { |u| [u.name, u.id] }, include_blank: true
|
35
|
-
th = form.select :category_name, Post.pluck('DISTINCT category_name'), include_blank: true
|
35
|
+
th = form.select :category_name, Post.pluck(Arel.sql('DISTINCT category_name')), include_blank: true
|
36
36
|
th
|
37
37
|
th
|
38
38
|
th
|
data/example/bin/bundle
CHANGED
data/example/bin/rails
CHANGED
data/example/bin/rake
CHANGED
data/example/config.ru
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class CreatePosts < ActiveRecord::Migration
|
1
|
+
class CreatePosts < ActiveRecord::Migration[4.2]
|
2
2
|
def change
|
3
3
|
create_table :posts do |t|
|
4
4
|
t.integer :user_id, null: false
|
@@ -9,7 +9,6 @@ class CreatePosts < ActiveRecord::Migration
|
|
9
9
|
t.integer :likes_count, null: false, default: 0
|
10
10
|
t.integer :comments_count, null: false, default: 0
|
11
11
|
t.boolean :published, null: false, default: false
|
12
|
-
t.datetime :published_at
|
13
12
|
t.timestamps
|
14
13
|
end
|
15
14
|
|
data/example/db/schema.rb
CHANGED
@@ -1,40 +1,36 @@
|
|
1
|
-
# encoding: UTF-8
|
2
1
|
# This file is auto-generated from the current state of the database. Instead
|
3
2
|
# of editing this file, please use the migrations feature of Active Record to
|
4
3
|
# incrementally modify your database, and then regenerate this schema definition.
|
5
4
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# from scratch.
|
10
|
-
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `rails
|
6
|
+
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
11
10
|
#
|
12
11
|
# It's strongly recommended that you check this file into your version control system.
|
13
12
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
13
|
+
ActiveRecord::Schema.define(version: 2013_11_02_130413) do
|
15
14
|
|
16
|
-
create_table "posts", force:
|
17
|
-
t.integer
|
18
|
-
t.string
|
19
|
-
t.string
|
20
|
-
t.string
|
21
|
-
t.integer
|
22
|
-
t.integer
|
23
|
-
t.integer
|
24
|
-
t.boolean
|
25
|
-
t.datetime "published_at"
|
15
|
+
create_table "posts", force: :cascade do |t|
|
16
|
+
t.integer "user_id", null: false
|
17
|
+
t.string "title", null: false
|
18
|
+
t.string "body", null: false
|
19
|
+
t.string "category_name", null: false
|
20
|
+
t.integer "views_count", default: 0, null: false
|
21
|
+
t.integer "likes_count", default: 0, null: false
|
22
|
+
t.integer "comments_count", default: 0, null: false
|
23
|
+
t.boolean "published", default: false, null: false
|
26
24
|
t.datetime "created_at"
|
27
25
|
t.datetime "updated_at"
|
26
|
+
t.index ["user_id"], name: "index_posts_on_user_id"
|
28
27
|
end
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
create_table "users", force: true do |t|
|
33
|
-
t.string "name", null: false
|
29
|
+
create_table "users", force: :cascade do |t|
|
30
|
+
t.string "name", limit: 255, null: false
|
34
31
|
t.datetime "created_at"
|
35
32
|
t.datetime "updated_at"
|
33
|
+
t.index ["name"], name: "index_users_on_name", unique: true
|
36
34
|
end
|
37
35
|
|
38
|
-
add_index "users", ["name"], name: "index_users_on_name", unique: true
|
39
|
-
|
40
36
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
@@ -11,11 +11,11 @@ describe PostSearch do
|
|
11
11
|
|
12
12
|
def create(attributes = {})
|
13
13
|
Post.create! attributes.reverse_merge(
|
14
|
-
user:
|
15
|
-
title:
|
16
|
-
body:
|
17
|
-
category_name:
|
18
|
-
published:
|
14
|
+
user: user,
|
15
|
+
title: 'Title',
|
16
|
+
body: 'Body',
|
17
|
+
category_name: 'Tech',
|
18
|
+
published: true
|
19
19
|
)
|
20
20
|
end
|
21
21
|
|
@@ -24,29 +24,29 @@ describe PostSearch do
|
|
24
24
|
end
|
25
25
|
|
26
26
|
it 'can search by category name' do
|
27
|
-
post
|
28
|
-
|
27
|
+
post = create category_name: 'Personal'
|
28
|
+
_other = create category_name: 'Other'
|
29
29
|
|
30
30
|
expect_search(category_name: 'Personal').to eq [post]
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'can search by user_id' do
|
34
|
-
post
|
35
|
-
|
34
|
+
post = create user: create_user
|
35
|
+
_other = create user: create_user
|
36
36
|
|
37
37
|
expect_search(user_id: post.user_id).to eq [post]
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'can search by title' do
|
41
|
-
post
|
42
|
-
|
41
|
+
post = create title: 'Title'
|
42
|
+
_other = create title: 'Other'
|
43
43
|
|
44
44
|
expect_search(title: 'itl').to eq [post]
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'can search by published' do
|
48
|
-
post
|
49
|
-
|
48
|
+
post = create published: true
|
49
|
+
_other = create published: false
|
50
50
|
|
51
51
|
expect_search(published: true).to eq [post]
|
52
52
|
end
|
@@ -54,21 +54,21 @@ describe PostSearch do
|
|
54
54
|
it 'can search by term' do
|
55
55
|
post_with_body = create body: 'pattern'
|
56
56
|
post_with_title = create title: 'pattern'
|
57
|
-
|
57
|
+
_other = create
|
58
58
|
|
59
59
|
expect_search(term: 'pattern').to eq [post_with_title, post_with_body]
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'can search by created after' do
|
63
|
-
post
|
64
|
-
|
63
|
+
post = create created_at: 1.month.ago
|
64
|
+
_other = create created_at: 3.month.ago
|
65
65
|
|
66
66
|
expect_search(created_after: 2.month.ago.strftime('%Y-%m-%d')).to eq [post]
|
67
67
|
end
|
68
68
|
|
69
69
|
it 'can search by created before' do
|
70
|
-
post
|
71
|
-
|
70
|
+
post = create created_at: 3.month.ago
|
71
|
+
_other = create created_at: 1.month.ago
|
72
72
|
|
73
73
|
expect_search(created_before: 2.month.ago.strftime('%Y-%m-%d')).to eq [post]
|
74
74
|
end
|
data/example/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
2
4
|
ENV['RAILS_ENV'] ||= 'test'
|
3
5
|
|
4
|
-
require File.expand_path('
|
6
|
+
require File.expand_path('../config/environment', __dir__)
|
5
7
|
require 'rspec/rails'
|
6
8
|
|
7
9
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
8
10
|
# in spec/support/ and its subdirectories.
|
9
|
-
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
|
11
|
+
Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
|
10
12
|
|
11
13
|
# Checks for pending migrations before tests are run.
|
12
14
|
# If you are not using ActiveRecord, you can remove this line.
|
data/lib/search_object/base.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
module Base
|
3
5
|
def self.included(base)
|
4
6
|
base.extend ClassMethods
|
5
7
|
base.instance_eval do
|
6
8
|
@config = {
|
7
|
-
defaults:
|
8
|
-
|
9
|
-
scope: nil
|
9
|
+
defaults: {},
|
10
|
+
options: {}
|
10
11
|
}
|
11
12
|
end
|
12
13
|
end
|
@@ -14,12 +15,15 @@ module SearchObject
|
|
14
15
|
def initialize(options = {})
|
15
16
|
config = self.class.config
|
16
17
|
scope = options[:scope] || (config[:scope] && instance_eval(&config[:scope]))
|
17
|
-
actions = config[:actions] || {}
|
18
|
-
params = Helper.normalize_params(config[:defaults], options[:filters], actions.keys)
|
19
18
|
|
20
19
|
raise MissingScopeError unless scope
|
21
20
|
|
22
|
-
@search = Search.new(
|
21
|
+
@search = Search.new(
|
22
|
+
scope: scope,
|
23
|
+
options: config[:options],
|
24
|
+
defaults: config[:defaults],
|
25
|
+
params: options[:filters]
|
26
|
+
)
|
23
27
|
end
|
24
28
|
|
25
29
|
def results
|
@@ -34,6 +38,12 @@ module SearchObject
|
|
34
38
|
@count ||= @search.count self
|
35
39
|
end
|
36
40
|
|
41
|
+
def params=(params)
|
42
|
+
@count = nil
|
43
|
+
@results = nil
|
44
|
+
@search.params = params
|
45
|
+
end
|
46
|
+
|
37
47
|
def params(additions = {})
|
38
48
|
if additions.empty?
|
39
49
|
@search.params
|
@@ -42,6 +52,16 @@ module SearchObject
|
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
55
|
+
def param?(*args)
|
56
|
+
if args.size == 1
|
57
|
+
params.key?(args[0].to_s)
|
58
|
+
elsif args.size == 2
|
59
|
+
params[args[0].to_s] == args[1]
|
60
|
+
else
|
61
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1 or 2)"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
45
65
|
private
|
46
66
|
|
47
67
|
def fetch_results
|
@@ -67,7 +87,7 @@ module SearchObject
|
|
67
87
|
handler = options[:with] || block
|
68
88
|
|
69
89
|
config[:defaults][name] = default unless default.nil?
|
70
|
-
config[:
|
90
|
+
config[:options][name] = Helper.normalize_search_handler(handler, name)
|
71
91
|
|
72
92
|
define_method(name) { @search.param name }
|
73
93
|
end
|
data/lib/search_object/errors.rb
CHANGED
data/lib/search_object/helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
# :api: private
|
3
5
|
module Helper
|
@@ -54,10 +56,6 @@ module SearchObject
|
|
54
56
|
end
|
55
57
|
end
|
56
58
|
|
57
|
-
def normalize_params(defaults, filters, keys)
|
58
|
-
(defaults || {}).merge(slice_keys(stringify_keys(filters || {}), keys || []))
|
59
|
-
end
|
60
|
-
|
61
59
|
def deep_copy(object) # rubocop:disable Metrics/MethodLength
|
62
60
|
case object
|
63
61
|
when Array
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
module Plugin
|
3
5
|
module Enum
|
@@ -9,10 +11,7 @@ module SearchObject
|
|
9
11
|
def option(name, options = nil, &block)
|
10
12
|
return super unless options.is_a?(Hash) && options[:enum]
|
11
13
|
|
12
|
-
|
13
|
-
raise WithIgnoredError if options[:with]
|
14
|
-
|
15
|
-
handler = Handler.build(name, options[:enum])
|
14
|
+
handler = options[:with] || block || Handler.build(name, options[:enum])
|
16
15
|
|
17
16
|
super(name, options, &handler)
|
18
17
|
end
|
@@ -30,19 +29,16 @@ module SearchObject
|
|
30
29
|
def apply_filter(object:, option:, enums:, scope:, value:)
|
31
30
|
return if value.nil? || value == ''
|
32
31
|
|
33
|
-
unless enums.include? value
|
34
|
-
return handle_invalid_value(object: object, option: option, enums: enums, scope: scope, value: value)
|
35
|
-
end
|
32
|
+
return handle_invalid_value(object: object, option: option, enums: enums, scope: scope, value: value) unless enums.include? value.to_s
|
36
33
|
|
37
|
-
object.send("apply_#{option}_with_#{Helper.underscore(value)}", scope)
|
34
|
+
object.send("apply_#{Helper.underscore(option)}_with_#{Helper.underscore(value)}", scope)
|
38
35
|
end
|
39
36
|
|
40
37
|
def handle_invalid_value(object:, option:, enums:, scope:, value:)
|
41
38
|
specific = "handle_invalid_#{option}"
|
42
39
|
return object.send(specific, scope, value) if object.respond_to? specific, true
|
43
40
|
|
44
|
-
|
45
|
-
return object.send(catch_all, option, scope, value) if object.respond_to? catch_all, true
|
41
|
+
return object.handle_invalid_enum(option, scope, value) if object.respond_to? :handle_invalid_enum, true
|
46
42
|
|
47
43
|
raise InvalidEnumValueError.new(option, enums, value)
|
48
44
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
module Plugin
|
3
5
|
module Paging
|
@@ -26,17 +28,20 @@ module SearchObject
|
|
26
28
|
|
27
29
|
module ClassMethods
|
28
30
|
def per_page(number)
|
29
|
-
raise InvalidNumberError.new('Per page', number) unless number
|
31
|
+
raise InvalidNumberError.new('Per page', number) unless number.positive?
|
32
|
+
|
30
33
|
config[:per_page] = number
|
31
34
|
end
|
32
35
|
|
33
36
|
def min_per_page(number)
|
34
|
-
raise InvalidNumberError.new('Min per page', number) unless number
|
37
|
+
raise InvalidNumberError.new('Min per page', number) unless number.positive?
|
38
|
+
|
35
39
|
config[:min_per_page] = number
|
36
40
|
end
|
37
41
|
|
38
42
|
def max_per_page(number)
|
39
|
-
raise InvalidNumberError.new('Max per page', number) unless number
|
43
|
+
raise InvalidNumberError.new('Max per page', number) unless number.positive?
|
44
|
+
|
40
45
|
config[:max_per_page] = number
|
41
46
|
end
|
42
47
|
|
data/lib/search_object/search.rb
CHANGED
@@ -1,21 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SearchObject
|
2
4
|
# :api: private
|
3
5
|
class Search
|
4
6
|
attr_reader :params
|
5
7
|
|
6
|
-
def initialize(scope,
|
7
|
-
@scope
|
8
|
-
@
|
9
|
-
@
|
8
|
+
def initialize(scope:, options: nil, defaults: nil, params: nil)
|
9
|
+
@scope = scope
|
10
|
+
@options = options || {}
|
11
|
+
@defaults = defaults || {}
|
12
|
+
|
13
|
+
self.params = params
|
14
|
+
end
|
15
|
+
|
16
|
+
def params=(params)
|
17
|
+
@params = @defaults.merge(Helper.slice_keys(Helper.stringify_keys(params || {}), @options.keys))
|
10
18
|
end
|
11
19
|
|
12
20
|
def param(name)
|
13
|
-
@params[name]
|
21
|
+
@params[name.to_s]
|
14
22
|
end
|
15
23
|
|
16
24
|
def query(context)
|
17
25
|
@params.inject(@scope) do |scope, (name, value)|
|
18
|
-
new_scope = context.instance_exec scope, value, &@
|
26
|
+
new_scope = context.instance_exec scope, value, &@options[name]
|
19
27
|
new_scope || scope
|
20
28
|
end
|
21
29
|
end
|
data/lib/search_object.rb
CHANGED
data/search_object.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler"
|
21
|
+
spec.add_development_dependency "bundler"
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency 'rspec', '~> 3.5'
|
24
24
|
spec.add_development_dependency 'rspec-mocks', '~> 3.5'
|
@@ -29,6 +29,6 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency 'will_paginate'
|
30
30
|
spec.add_development_dependency 'kaminari'
|
31
31
|
spec.add_development_dependency 'kaminari-activerecord'
|
32
|
-
spec.add_development_dependency 'rubocop', '0.
|
33
|
-
spec.add_development_dependency 'rubocop-rspec', '1.
|
32
|
+
spec.add_development_dependency 'rubocop', '0.81.0'
|
33
|
+
spec.add_development_dependency 'rubocop-rspec', '1.38.1'
|
34
34
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'active_support/core_ext/object/blank'
|
3
5
|
|
@@ -282,6 +284,23 @@ module SearchObject
|
|
282
284
|
end
|
283
285
|
end
|
284
286
|
|
287
|
+
describe '#params=' do
|
288
|
+
it 'resets search' do
|
289
|
+
search = new_search [1, 2, 3], value: 1 do
|
290
|
+
option :value do |scope, value|
|
291
|
+
scope.select { |v| v > value }
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
expect(search.results).to eq [2, 3]
|
296
|
+
expect(search.count).to eq 2
|
297
|
+
|
298
|
+
search.params = { value: 2 }
|
299
|
+
expect(search.results).to eq [3]
|
300
|
+
expect(search.count).to eq 1
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
285
304
|
describe '#params' do
|
286
305
|
it 'exports options as params' do
|
287
306
|
search = new_search [], value: 1
|
@@ -310,5 +329,44 @@ module SearchObject
|
|
310
329
|
expect(search.params).to eq 'value' => 1
|
311
330
|
end
|
312
331
|
end
|
332
|
+
|
333
|
+
describe '#param?' do
|
334
|
+
context 'one argument' do
|
335
|
+
it 'returns true when params is set' do
|
336
|
+
search = new_search([], value: 2) do
|
337
|
+
option :value
|
338
|
+
end
|
339
|
+
|
340
|
+
expect(search.param?(:value)).to eq true
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'returns false when params isnt set' do
|
344
|
+
search = new_search do
|
345
|
+
option :value
|
346
|
+
end
|
347
|
+
|
348
|
+
expect(search.param?(:value)).to eq false
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'two arguments' do
|
353
|
+
it 'returns true when param matches value' do
|
354
|
+
search = new_search([], value: 2) do
|
355
|
+
option :value
|
356
|
+
end
|
357
|
+
|
358
|
+
expect(search.param?(:value, 2)).to eq true
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'returns false when param matches value' do
|
362
|
+
search = new_search([], value: 2) do
|
363
|
+
option :value
|
364
|
+
end
|
365
|
+
|
366
|
+
expect(search.param?(:value, 3)).to eq false
|
367
|
+
expect(search.param?(:other, 3)).to eq false
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
313
371
|
end
|
314
372
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'action_controller'
|
3
5
|
|
@@ -41,24 +43,6 @@ module SearchObject
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
describe '.normalize_filters' do
|
45
|
-
it 'combines defaults and filters' do
|
46
|
-
expect(described_class.normalize_params({ 'a' => 1, 'b' => 2 }, { a: 2 }, %w[a b])).to eq 'a' => 2, 'b' => 2
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'excludes non specified keys' do
|
50
|
-
expect(described_class.normalize_params({ 'a' => 1 }, { b: 2 }, %w[a])).to eq 'a' => 1
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'handles missing defaults' do
|
54
|
-
expect(described_class.normalize_params(nil, { a: 1 }, %w[a])).to eq 'a' => 1
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'handles missing filters' do
|
58
|
-
expect(described_class.normalize_params(nil, nil, ['a'])).to eq({})
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
46
|
describe 'deep_copy' do
|
63
47
|
it 'returns a deep copy on the given object' do
|
64
48
|
original = {
|
@@ -1,63 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
4
6
|
module SearchObject
|
5
7
|
module Plugin
|
6
8
|
describe Enum do
|
7
|
-
|
8
|
-
|
9
|
+
let(:test_search) do
|
10
|
+
Class.new do
|
11
|
+
include SearchObject.module(:enum)
|
9
12
|
|
10
|
-
|
13
|
+
scope { [1, 2, 3, 4, 5] }
|
11
14
|
|
12
|
-
|
15
|
+
option :filter, enum: %w[odd even]
|
16
|
+
option :camelCase, enum: %w[someValue]
|
13
17
|
|
14
|
-
|
18
|
+
private
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
20
|
+
def apply_filter_with_odd(scope)
|
21
|
+
scope.select(&:odd?)
|
22
|
+
end
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
24
|
+
def apply_filter_with_even(scope)
|
25
|
+
scope.select(&:even?)
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
|
28
|
+
def apply_camel_case_with_some_value(_scope)
|
29
|
+
[1]
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle_invalid_filter(_scope, value)
|
33
|
+
"invalid filter - #{value}"
|
34
|
+
end
|
26
35
|
end
|
27
36
|
end
|
28
37
|
|
29
38
|
it 'can filter by enum values' do
|
30
|
-
expect(
|
31
|
-
expect(
|
39
|
+
expect(test_search.results(filters: { filter: 'odd' })).to eq [1, 3, 5]
|
40
|
+
expect(test_search.results(filters: { filter: 'even' })).to eq [2, 4]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'converts input to string' do
|
44
|
+
expect(test_search.results(filters: { filter: :odd })).to eq [1, 3, 5]
|
45
|
+
expect(test_search.results(filters: { filter: :even })).to eq [2, 4]
|
32
46
|
end
|
33
47
|
|
34
48
|
it 'ignores blank values' do
|
35
|
-
expect(
|
36
|
-
expect(
|
49
|
+
expect(test_search.results(filters: { filter: nil })).to eq [1, 2, 3, 4, 5]
|
50
|
+
expect(test_search.results(filters: { filter: '' })).to eq [1, 2, 3, 4, 5]
|
37
51
|
end
|
38
52
|
|
39
53
|
it 'handles wrong enum values' do
|
40
|
-
expect(
|
54
|
+
expect(test_search.results(filters: { filter: 'foo' })).to eq 'invalid filter - foo'
|
41
55
|
end
|
42
56
|
|
43
|
-
it '
|
44
|
-
expect
|
45
|
-
|
46
|
-
include SearchObject.module(:enum)
|
57
|
+
it 'underscores method and enum values' do
|
58
|
+
expect(test_search.results(filters: { camelCase: 'someValue' })).to eq [1]
|
59
|
+
end
|
47
60
|
|
48
|
-
|
49
|
-
|
50
|
-
|
61
|
+
it 'can filter by passed block' do
|
62
|
+
block_search = Class.new do
|
63
|
+
include SearchObject.module(:enum)
|
64
|
+
|
65
|
+
scope { [1, 2, 3, 4, 5] }
|
66
|
+
|
67
|
+
option(:filter, enum: %w[odd even]) { |scope, value| scope.select(&:"#{value}?".to_sym) }
|
68
|
+
end
|
69
|
+
expect(block_search.results(filters: { filter: :odd })).to eq [1, 3, 5]
|
70
|
+
expect(block_search.results(filters: { filter: :even })).to eq [2, 4]
|
51
71
|
end
|
52
72
|
|
53
|
-
it '
|
54
|
-
|
55
|
-
|
56
|
-
|
73
|
+
it 'can filter by with option' do
|
74
|
+
with_search = Class.new do
|
75
|
+
include SearchObject.module(:enum)
|
76
|
+
|
77
|
+
scope { [1, 2, 3, 4, 5] }
|
57
78
|
|
58
|
-
|
79
|
+
option(:filter, enum: %w[odd even], with: :filter)
|
80
|
+
|
81
|
+
def filter(scope, value)
|
82
|
+
scope.select(&:"#{value}?".to_sym)
|
59
83
|
end
|
60
|
-
end
|
84
|
+
end
|
85
|
+
expect(with_search.results(filters: { filter: :odd })).to eq [1, 3, 5]
|
86
|
+
expect(with_search.results(filters: { filter: :even })).to eq [2, 4]
|
61
87
|
end
|
62
88
|
end
|
63
89
|
|
@@ -1,70 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'ostruct'
|
3
5
|
|
4
6
|
module SearchObject
|
5
7
|
describe Search do
|
6
8
|
describe '.params' do
|
7
|
-
it '
|
8
|
-
search =
|
9
|
-
expect(search.params).to eq '
|
9
|
+
it 'stringify param keys' do
|
10
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' }, options: { 'name' => nil })
|
11
|
+
expect(search.params).to eq 'name' => 'value'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'filters invalid params' do
|
15
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' })
|
16
|
+
expect(search.params).to eq({})
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'supports default values' do
|
20
|
+
search = described_class.new(scope: 'scope', params: {}, defaults: { 'name' => 'value' })
|
21
|
+
expect(search.params).to eq 'name' => 'value'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can be updated' do
|
25
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' }, options: { 'name' => nil })
|
26
|
+
search.params = { name: 'updated', fake: 'value' }
|
27
|
+
|
28
|
+
expect(search.params).to eq 'name' => 'updated'
|
10
29
|
end
|
11
30
|
end
|
12
31
|
|
13
32
|
describe '.param' do
|
14
33
|
it 'returns the param value' do
|
15
|
-
search =
|
34
|
+
search = described_class.new(scope: 'scope', params: { name: 'value' }, options: { 'name' => nil })
|
16
35
|
expect(search.param(:name)).to eq 'value'
|
17
36
|
end
|
18
37
|
end
|
19
38
|
|
20
39
|
describe '.query' do
|
21
40
|
it 'returns filtered result' do
|
22
|
-
|
23
|
-
min
|
41
|
+
options = {
|
42
|
+
'min' => ->(scope, min) { scope.select { |v| v > min } }
|
24
43
|
}
|
25
44
|
|
26
|
-
search =
|
45
|
+
search = described_class.new(scope: [1, 2, 3], params: { min: 2 }, options: options)
|
27
46
|
expect(search.query(Object.new)).to eq [3]
|
28
47
|
end
|
29
48
|
|
30
|
-
it 'applies
|
31
|
-
|
32
|
-
min
|
33
|
-
max
|
49
|
+
it 'applies options to params' do
|
50
|
+
options = {
|
51
|
+
'min' => ->(scope, min) { scope.select { |v| v > min } },
|
52
|
+
'max' => ->(scope, max) { scope.select { |v| v < max } }
|
34
53
|
}
|
35
54
|
|
36
|
-
search =
|
55
|
+
search = described_class.new(scope: [1, 2, 3, 4, 5], params: { min: 2, max: 5 }, options: options)
|
37
56
|
expect(search.query(Object.new)).to eq [3, 4]
|
38
57
|
end
|
39
58
|
|
40
59
|
it 'handles nil returned from action' do
|
41
|
-
|
42
|
-
odd
|
60
|
+
options = {
|
61
|
+
'odd' => ->(scope, odd) { scope.select(&:odd?) if odd }
|
43
62
|
}
|
44
63
|
|
45
|
-
search =
|
64
|
+
search = described_class.new(scope: [1, 2, 3, 4, 5], params: { odd: false }, options: options)
|
46
65
|
expect(search.query(Object.new)).to eq [1, 2, 3, 4, 5]
|
47
66
|
end
|
48
67
|
|
49
68
|
it 'executes action in the passed context' do
|
50
|
-
|
51
|
-
search
|
69
|
+
options = {
|
70
|
+
'search' => ->(scope, _) { scope.select { |v| v == target_value } }
|
52
71
|
}
|
53
72
|
|
54
73
|
context = OpenStruct.new target_value: 2
|
55
74
|
|
56
|
-
search =
|
75
|
+
search = described_class.new(scope: [1, 2, 3, 4, 5], params: { search: true }, options: options)
|
57
76
|
expect(search.query(context)).to eq [2]
|
58
77
|
end
|
59
78
|
end
|
60
79
|
|
61
80
|
describe '.count' do
|
62
81
|
it 'counts the results of the query' do
|
63
|
-
|
64
|
-
value
|
82
|
+
options = {
|
83
|
+
'value' => ->(scope, value) { scope.select { |v| v == value } }
|
65
84
|
}
|
66
85
|
|
67
|
-
search =
|
86
|
+
search = described_class.new(scope: [1, 2, 3], params: { value: 2 }, options: options)
|
68
87
|
expect(search.count(Object.new)).to eq 1
|
69
88
|
end
|
70
89
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: search_object
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Radoslav Stankov
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,28 +170,28 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - '='
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.
|
173
|
+
version: 0.81.0
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - '='
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: 0.
|
180
|
+
version: 0.81.0
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: rubocop-rspec
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
184
184
|
requirements:
|
185
185
|
- - '='
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: 1.
|
187
|
+
version: 1.38.1
|
188
188
|
type: :development
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - '='
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: 1.
|
194
|
+
version: 1.38.1
|
195
195
|
description: Search object DSL
|
196
196
|
email:
|
197
197
|
- rstankov@gmail.com
|
@@ -278,7 +278,7 @@ homepage: https://github.com/RStankov/SearchObject
|
|
278
278
|
licenses:
|
279
279
|
- MIT
|
280
280
|
metadata: {}
|
281
|
-
post_install_message:
|
281
|
+
post_install_message:
|
282
282
|
rdoc_options: []
|
283
283
|
require_paths:
|
284
284
|
- lib
|
@@ -293,9 +293,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
293
293
|
- !ruby/object:Gem::Version
|
294
294
|
version: '0'
|
295
295
|
requirements: []
|
296
|
-
|
297
|
-
|
298
|
-
signing_key:
|
296
|
+
rubygems_version: 3.1.4
|
297
|
+
signing_key:
|
299
298
|
specification_version: 4
|
300
299
|
summary: Provides DSL for creating search objects
|
301
300
|
test_files:
|