easy_filters 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +31 -0
- data/db/easy_filters_test.sqlite3 +0 -0
- data/easy_filters.gemspec +27 -0
- data/lib/easy_filters.rb +2 -0
- data/lib/easy_filters/filter.rb +148 -0
- data/lib/easy_filters/model_filter.rb +75 -0
- data/lib/easy_filters/version.rb +3 -0
- data/spec/filter_spec.rb +143 -0
- data/spec/minitest_helper.rb +17 -0
- data/spec/model_filter_spec.rb +102 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9c4d6be28e25fe3be62cf50ab45d044a7ca7083d
|
4
|
+
data.tar.gz: e221ccf98fa4c6fcc69cb60786997af1d5fb3712
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c24d10fbf0d42b5079a7e843ae0b4d98cb13c64e7e28cf66d4c6ee549f058690adf1492a88a341517368e28a9f5e9e8689690f9a651db873831576daab4f0a6
|
7
|
+
data.tar.gz: 946460bf9331eebe0578d072636168003598afdb9db9358629e043c37a2dc76a98f0fa1df5ef9c73e120f7398aa23708f1587af72f20ce10e4cc7d2c13e6d7ad
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 German Olle
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# EasyFilters
|
2
|
+
|
3
|
+
This gem provides a simple API for defining dynamic persistent(in session) filters based on active record models.
|
4
|
+
This was extracted from el_dia_backend
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
|
12
|
+
gem 'easy_filters'
|
13
|
+
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
```
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
EasyFilters expects you to define the following methods:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class ArticlesFilter < ModelFilter
|
29
|
+
|
30
|
+
# Default values for this filter.
|
31
|
+
def self.defaults
|
32
|
+
{ body: nil, date_from: nil, date_to: nil }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Model class to be filtered.
|
36
|
+
def model
|
37
|
+
Article
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
```
|
42
|
+
|
43
|
+
* __defaults__: defines the fields that will be used for filtering and its defaults values
|
44
|
+
* __model__: returns the target class to be filtered
|
45
|
+
|
46
|
+
### Customizing filtering strategy
|
47
|
+
|
48
|
+
For defining the parts of the query that will filter each each field,
|
49
|
+
define a method named `filter_by_#{field_name}`.
|
50
|
+
The field __must__ be present in the defaults array.
|
51
|
+
|
52
|
+
Each `filter_by_*` method receives 2 params:
|
53
|
+
* __scope__: The actual query.
|
54
|
+
* __value__: The value for the current field
|
55
|
+
|
56
|
+
For example:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class ArticlesFilter < ModelFilter
|
60
|
+
|
61
|
+
#(...) previously defined methods
|
62
|
+
|
63
|
+
# Custom filter method for :body field.
|
64
|
+
def filter_by_body(scope, value)
|
65
|
+
matcher = "%#{value}%"
|
66
|
+
scope.where('body like ? OR body like ?', matcher, matcher)
|
67
|
+
end
|
68
|
+
|
69
|
+
# The following 2 filter_by builds an from/to/between date filter:
|
70
|
+
|
71
|
+
# Custom filter method for :date_from field.
|
72
|
+
def filter_by_date_from(scope, value)
|
73
|
+
scope.joins(:editions).where('editions.date >= ?', Date.parse(value))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Custom filter method for :date_to field.
|
77
|
+
def filter_by_date_to(scope, value)
|
78
|
+
scope.joins(:editions).where('editions.date <= ?', Date.parse(value))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'active_record'
|
3
|
+
require 'sqlite3'
|
4
|
+
|
5
|
+
desc 'Create table structure for testing'
|
6
|
+
task :migrate do
|
7
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'db/easy_filters_test.sqlite3')
|
8
|
+
ActiveRecord::Schema.define do
|
9
|
+
create_table :articles do |t|
|
10
|
+
t.string :slug, null: false, index: true
|
11
|
+
t.text :heading
|
12
|
+
t.text :title, null: false
|
13
|
+
t.boolean :show_time, null: false, default: false
|
14
|
+
t.date :date
|
15
|
+
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Run tests'
|
22
|
+
Rake::TestTask.new :test do |t|
|
23
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'db/easy_filters_test.sqlite3')
|
24
|
+
|
25
|
+
Rake::Task[:migrate].invoke unless ActiveRecord::Base.connection.table_exists? 'articles'
|
26
|
+
|
27
|
+
t.libs << 'spec'
|
28
|
+
t.pattern = 'spec/**/*_spec.rb'
|
29
|
+
end
|
30
|
+
|
31
|
+
task default: :test
|
Binary file
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'easy_filters/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "easy_filters"
|
9
|
+
spec.version = EasyFilters::VERSION
|
10
|
+
spec.authors = ["German Olle"]
|
11
|
+
spec.email = ["golle@cespi.unlp.edu.ar"]
|
12
|
+
spec.summary = "This gem implement a base model filter to declare your owns filters writing a single file"
|
13
|
+
spec.description = "Define a model class, allowing declare a subclass of this and implement simple methods to filter each field model"
|
14
|
+
spec.homepage = "http://rubygems.org/gems/easy_filters"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
23
|
+
spec.add_development_dependency "rake", "~> 0"
|
24
|
+
spec.add_development_dependency "sqlite3", "~> 0"
|
25
|
+
|
26
|
+
spec.add_dependency 'activerecord', "~> 4.0"
|
27
|
+
end
|
data/lib/easy_filters.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
module EasyFilters
|
2
|
+
# Base class for any object used for keeping filtering logic + criteria together.
|
3
|
+
#
|
4
|
+
# This class provides the following functionality out-of-the-box:
|
5
|
+
#
|
6
|
+
# - Automatic values sanitizing: The values passed to the #initialize method
|
7
|
+
# will be fed into #sanitize and the result of that process will be used for
|
8
|
+
# setting the filter values.
|
9
|
+
# - Hash-like accessors: Any value for the filters can be get/set via []/[]=,
|
10
|
+
# just like Hashes.
|
11
|
+
#
|
12
|
+
# For examples, please:
|
13
|
+
# @see ArticlesFilter
|
14
|
+
# @see SupplementFilter
|
15
|
+
class Filter
|
16
|
+
|
17
|
+
# Default values. This method should be overridden
|
18
|
+
# when in need to specify particular defaults for a
|
19
|
+
# subclass.
|
20
|
+
def self.defaults
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Global key that will contain the values for any filter class
|
25
|
+
# inside the store or the request values source.
|
26
|
+
# In extension, the store options should be structured
|
27
|
+
# like this:
|
28
|
+
# {
|
29
|
+
# :"#{Filter.global_key}" => {
|
30
|
+
# :"#{@filter.key}" => {
|
31
|
+
# :value => 'for a filter field'
|
32
|
+
# }
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
def self.global_key
|
36
|
+
:filter
|
37
|
+
end
|
38
|
+
|
39
|
+
# Particular key that will contain the values for this specific
|
40
|
+
# filter class in the store/request values source - under the
|
41
|
+
# Filter#global_key main key.
|
42
|
+
# In extension, the store options should be structured
|
43
|
+
# like this:
|
44
|
+
# {
|
45
|
+
# :"#{Filter.global_key}" => {
|
46
|
+
# :"#{@filter.key}" => {
|
47
|
+
# :value => 'for a filter field'
|
48
|
+
# }
|
49
|
+
# }
|
50
|
+
# }
|
51
|
+
def self.key
|
52
|
+
self.name.to_sym
|
53
|
+
end
|
54
|
+
|
55
|
+
# Initialize a new filter with the given options.
|
56
|
+
def initialize(options = {})
|
57
|
+
@options = { values: nil, store: nil, persist: false }.merge options
|
58
|
+
|
59
|
+
# This is the order of precedence:
|
60
|
+
# values => store => defaults
|
61
|
+
actual = options[:values]
|
62
|
+
store = options[:store]
|
63
|
+
|
64
|
+
source = self.class.defaults
|
65
|
+
|
66
|
+
source = store[self.class.global_key][key] if store && store[self.class.global_key] && store[self.class.global_key][key]
|
67
|
+
source = actual if actual
|
68
|
+
|
69
|
+
self.values = sanitize source
|
70
|
+
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
# Conditionally clean up some values after the initialization is finished
|
75
|
+
# Note:
|
76
|
+
# These method have sense when the subclasses uses it,
|
77
|
+
# Filter class will not allows to pass any value
|
78
|
+
# due that the defaults is an empty hash so the valid keys is an empty array
|
79
|
+
def sanitize(values)
|
80
|
+
valid_keys = self.class.defaults.keys
|
81
|
+
values.inject(self.class.defaults) do |carry, (k, v)|
|
82
|
+
carry[k] = v if valid_keys.include?(k) && !v.blank?
|
83
|
+
carry
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Instance method used for convenience. Just a wrapper for the
|
88
|
+
# class method #key.
|
89
|
+
def key
|
90
|
+
self.class.key
|
91
|
+
end
|
92
|
+
|
93
|
+
# Filter field value accessor. Provides a Hash-like API for
|
94
|
+
# getting the values set to the different fields of this filter.
|
95
|
+
# Will return the value set for :key or nil.
|
96
|
+
def [] key
|
97
|
+
values[key] if values.has_key? key
|
98
|
+
end
|
99
|
+
|
100
|
+
# Filter field value writer. Provides a Hash-like API for
|
101
|
+
# setting the values of the filter fields.
|
102
|
+
def []=(key, value)
|
103
|
+
values[key] = value
|
104
|
+
end
|
105
|
+
|
106
|
+
# Answer whether any filter values have been applied. Will return
|
107
|
+
# true if the current values differ from the default ones.
|
108
|
+
def any?
|
109
|
+
self.class.defaults != self.values
|
110
|
+
end
|
111
|
+
|
112
|
+
# Gets the values for this filter.
|
113
|
+
def values
|
114
|
+
@values
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sets the values for this filter to :new_values.
|
118
|
+
# Will persist if the :persist option was set to true when creating
|
119
|
+
# this object and a valid store has been provided.
|
120
|
+
def values=(new_values)
|
121
|
+
@values = new_values
|
122
|
+
|
123
|
+
# Define getters for each of the new_values
|
124
|
+
new_values.each do |key, value|
|
125
|
+
self.class.send(:define_method, key, proc { self[key] }) unless self.respond_to? key
|
126
|
+
end
|
127
|
+
|
128
|
+
self.persist! @options[:store] if @options[:store] && @options[:persist]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Persist the current values to the given :store.
|
132
|
+
def persist!(store)
|
133
|
+
store[self.class.global_key] ||= {}
|
134
|
+
store[self.class.global_key][self.class.key] = self.values
|
135
|
+
end
|
136
|
+
|
137
|
+
# Clear the values of the filters - set them to the defaults.
|
138
|
+
def clear!
|
139
|
+
self.values = self.class.defaults
|
140
|
+
end
|
141
|
+
|
142
|
+
# Get all the matching records to the current filter criteria.
|
143
|
+
# This method must be implemented in subclasses.
|
144
|
+
def all
|
145
|
+
throw NotImplementedError
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module EasyFilters
|
2
|
+
# Base class for ActiveRecord-bound filter classes.
|
3
|
+
# Subclasses must implement the #model method and optionally add
|
4
|
+
# custom filtering methods, as explained below in the #apply_to method.
|
5
|
+
class ModelFilter < Filter
|
6
|
+
# Get the model class to which the filters will be applied.
|
7
|
+
# This method must be overridden in subclasses.
|
8
|
+
def model
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Get all the matching records to the current filter criteria.
|
13
|
+
def all
|
14
|
+
matches = model.send(scope)
|
15
|
+
matches = apply_to matches
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the scope that will start the filter criteria as a symbol.
|
19
|
+
# The default scope is :all.
|
20
|
+
def scope
|
21
|
+
:all
|
22
|
+
end
|
23
|
+
|
24
|
+
# Applies the filter criteria specified for the different fields to :matches,
|
25
|
+
# an ActiveRecordRelation which should start by matching all the "filterable"
|
26
|
+
# records.
|
27
|
+
#
|
28
|
+
# Each field will be processed in the following way:
|
29
|
+
# 1. Check if self responds to a custom filter method of the form: "filter_by_<FIELD_NAME>". If it does,
|
30
|
+
# delegate the filtering of records by that field to the method. Such a method must return the updated
|
31
|
+
# ActiveRecordRelation.
|
32
|
+
# For instance, if the field name is "slug", its custom filter method will be named "filter_by_slug":
|
33
|
+
#
|
34
|
+
# def filter_by_slug(partial, value)
|
35
|
+
# partial.where(slug: value)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# 2. If no custom filter method is available for the field, check for a default filter method by the class
|
39
|
+
# of the value. Supported classes out-of-the-box are:
|
40
|
+
#
|
41
|
+
# - String: Will search with a 'like "%value%"'.
|
42
|
+
#
|
43
|
+
# 3. Otherwise, leave the logic up to ActiveRecord. Will add a where constraint like follows:
|
44
|
+
#
|
45
|
+
# partial.where(field.to_sym => value)
|
46
|
+
#
|
47
|
+
def apply_to(matches)
|
48
|
+
values.inject(matches) do |partial, pair|
|
49
|
+
field, value = pair
|
50
|
+
unless value.blank?
|
51
|
+
type = value.class.name.downcase
|
52
|
+
custom_filter_method = "filter_by_#{field}"
|
53
|
+
default_filter_method = "filter_#{type}_field"
|
54
|
+
if self.respond_to? custom_filter_method
|
55
|
+
# Custom filtering
|
56
|
+
partial = self.send custom_filter_method, partial, value
|
57
|
+
elsif self.respond_to? default_filter_method
|
58
|
+
# Default method filtering
|
59
|
+
partial = self.send default_filter_method, partial, field, value
|
60
|
+
else
|
61
|
+
# Leave it up to AR and its magic
|
62
|
+
partial = partial.where(field.to_sym => value)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
partial
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Filters String fields using LIKE.
|
71
|
+
def filter_string_field(partial, field, value)
|
72
|
+
partial.where("#{field} like ?", "%#{value}%")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/spec/filter_spec.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
module EasyFilters
|
4
|
+
describe Filter do
|
5
|
+
before do
|
6
|
+
@filter = Filter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
after do
|
10
|
+
@filter.clear!
|
11
|
+
end
|
12
|
+
|
13
|
+
it ":defaults should return a hash with default filter values" do
|
14
|
+
assert_instance_of Hash, Filter.defaults, ':defaults does not return a Hash'
|
15
|
+
assert Filter.defaults.empty?, ':defaults does not return an empty set of defaults values'
|
16
|
+
end
|
17
|
+
|
18
|
+
it ":key should return a representative symbol" do
|
19
|
+
assert_same 'EasyFilters::Filter'.to_sym, Filter.key, ':key does not return a representative symbol'
|
20
|
+
end
|
21
|
+
|
22
|
+
it ":[]= should set values under :key key" do
|
23
|
+
@filter[:name] = 'test value'
|
24
|
+
|
25
|
+
assert_equal 'test value', @filter[:name], ':[]= does not set the passed in :value at :key'
|
26
|
+
end
|
27
|
+
|
28
|
+
it ":[] should access the values under :key key" do
|
29
|
+
@filter[:surname] = 'some test value'
|
30
|
+
|
31
|
+
assert_equal 'some test value', @filter[:surname], ':[] does not access previously-set values'
|
32
|
+
end
|
33
|
+
|
34
|
+
it ":[] should return nil if :key is not set" do
|
35
|
+
assert @filter[:non_existing_key].nil?, ':[] does not return nil? for non-existant keys'
|
36
|
+
end
|
37
|
+
|
38
|
+
it ":any? should return false if there's no filter applied" do
|
39
|
+
assert !@filter.any?, ':any? returns true when no filters are applied'
|
40
|
+
end
|
41
|
+
|
42
|
+
it ":any? should return true if there's a filter applied" do
|
43
|
+
@filter[:some] = 'value'
|
44
|
+
assert @filter.any?, ':any? returns false when there are filters applied'
|
45
|
+
end
|
46
|
+
|
47
|
+
it ":values should return a hash representation of the values applied to the filter" do
|
48
|
+
@filter[:some] = 'value'
|
49
|
+
@filter[:test] = 'data'
|
50
|
+
|
51
|
+
expected = { some: 'value', test: 'data' }
|
52
|
+
|
53
|
+
assert_equal expected, @filter.values, ':values does not return the values applied to the filter'
|
54
|
+
end
|
55
|
+
|
56
|
+
it ":persist! should store filter values in the provided store" do
|
57
|
+
session_mock = {}
|
58
|
+
@filter[:name] = 'value'
|
59
|
+
@filter.persist! session_mock
|
60
|
+
|
61
|
+
expected = { name: 'value' }
|
62
|
+
|
63
|
+
assert session_mock.has_key?(Filter.global_key), ':persist! does not create a namespaced hash of values in the store'
|
64
|
+
assert session_mock[Filter.global_key].has_key?(Filter.key), ':persist! does not add specific values for the unique key of the filters class'
|
65
|
+
assert_equal @filter.values, session_mock[Filter.global_key][Filter.key]
|
66
|
+
end
|
67
|
+
|
68
|
+
it ":initialize should not take values from store if these are invalid" do
|
69
|
+
expected = {}
|
70
|
+
session_mock = { filter: { Filter: { invalid_key: 'no valid value' } } }
|
71
|
+
|
72
|
+
@filter = Filter.new store: session_mock
|
73
|
+
|
74
|
+
assert_equal expected, @filter.values, ':initialize taked the values from the store'
|
75
|
+
end
|
76
|
+
|
77
|
+
it ":initialize should take values from defaults if the store doesn't have any values" do
|
78
|
+
session_mock = nil
|
79
|
+
|
80
|
+
@filter = Filter.new store: session_mock
|
81
|
+
|
82
|
+
assert_equal Filter.defaults, @filter.values, ':initialize did not take the values from the defaults'
|
83
|
+
end
|
84
|
+
|
85
|
+
it ":initialize should not prioritize passed in values over any other value source if these are invalid" do
|
86
|
+
expected = {}
|
87
|
+
value = { invalid_key: 'invalid value' }
|
88
|
+
session_mock = { filter: { Filter: {} } }
|
89
|
+
|
90
|
+
@filter = Filter.new store: session_mock, values: value
|
91
|
+
|
92
|
+
assert_equal expected, @filter.values, ':initialize taked the passed invalid values'
|
93
|
+
end
|
94
|
+
|
95
|
+
it ":initialize should not persist values by default even when a :store option is provided" do
|
96
|
+
store_mock = {}
|
97
|
+
expected = {}
|
98
|
+
|
99
|
+
@filter = Filter.new values: { this_value: 'will not be persisted' }, store: store_mock
|
100
|
+
|
101
|
+
assert_equal expected, store_mock, ':initialize persists values by default'
|
102
|
+
end
|
103
|
+
|
104
|
+
it ":initialize should not persist values if a valid store is provided and instructed to but the passed value is invalid" do
|
105
|
+
store_mock = {}
|
106
|
+
expected = {}
|
107
|
+
value = { look_me: "I will fail" }
|
108
|
+
|
109
|
+
@filter = Filter.new values: value, store: store_mock, persist: true
|
110
|
+
|
111
|
+
assert store_mock.has_key?(Filter.global_key), 'The values were not persisted as expected in the store: missing global filter namespace'
|
112
|
+
assert store_mock[Filter.global_key].has_key?(Filter.key), 'The values were not persisted as expected in the store: missing specific filter key'
|
113
|
+
assert_equal expected, store_mock[Filter.global_key][Filter.key]
|
114
|
+
end
|
115
|
+
|
116
|
+
it ":clear! should reset the filter values to :defaults" do
|
117
|
+
@filter = Filter.new values: { other: 'values' }
|
118
|
+
@filter.clear!
|
119
|
+
|
120
|
+
assert_equal Filter.defaults, @filter.values, 'Filter values were not reset to the defaults'
|
121
|
+
end
|
122
|
+
|
123
|
+
it ":clear! should persist the reset filters if filter was created with persist option set to true" do
|
124
|
+
store_mock = { filter: { :'EasyFilters::Filter' => { look_ma: "I'm gonna disappear!" } } }
|
125
|
+
|
126
|
+
@filter = Filter.new store: store_mock, persist: true
|
127
|
+
@filter.clear!
|
128
|
+
|
129
|
+
assert_equal Filter.defaults, store_mock[:filter][:'EasyFilters::Filter'], 'Filter values reset was not persisted'
|
130
|
+
end
|
131
|
+
|
132
|
+
it ":clear! shouldn't persist the reset filters if filter was created with persist option set to false" do
|
133
|
+
expected = { look_ma: "I'm gonna disappear!" }
|
134
|
+
store_mock = { filter: { Filter: expected } }
|
135
|
+
|
136
|
+
@filter = Filter.new store: store_mock, persist: false
|
137
|
+
@filter.clear!
|
138
|
+
|
139
|
+
assert_equal Filter.defaults, @filter.values, 'Filter values were not reset'
|
140
|
+
assert_equal expected, store_mock[:filter][:Filter], 'Filter values reset was not persisted'
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.require :default, ENV['RACK_ENV'].to_sym
|
5
|
+
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'minitest/pride'
|
8
|
+
require 'active_record'
|
9
|
+
require 'sqlite3'
|
10
|
+
|
11
|
+
ActiveRecord::Base.establish_connection({
|
12
|
+
adapter: 'sqlite3',
|
13
|
+
database: 'db/easy_filters_test.sqlite3'
|
14
|
+
})
|
15
|
+
|
16
|
+
class Article < ActiveRecord::Base
|
17
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
module EasyFilters
|
4
|
+
describe ModelFilter do
|
5
|
+
def create_article(params = {})
|
6
|
+
defaults = { slug: 'title-one', heading: 'heading one', title: 'title one', show_time: false, date: '2013-01-04' }
|
7
|
+
Article.create! defaults.merge params
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
@filter = ModelFilter.new
|
12
|
+
# Overriding #model method in order to make the class testable
|
13
|
+
class << @filter
|
14
|
+
def model
|
15
|
+
Article
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
@filter.clear!
|
22
|
+
Article.delete_all
|
23
|
+
end
|
24
|
+
|
25
|
+
it ":model should fail because there is no default model attached to the filter" do
|
26
|
+
filter = ModelFilter.new
|
27
|
+
|
28
|
+
assert_raises NotImplementedError, 'The base class has a default model attached to it' do
|
29
|
+
filter.model
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it ":all should return all records by default" do
|
34
|
+
assert_equal Article.all, @filter.all, 'Some records were left out by a vanilla :all'
|
35
|
+
end
|
36
|
+
|
37
|
+
it ":all should apply the filters when it values for them provided" do
|
38
|
+
create_article
|
39
|
+
@filter[:slug] = 'one'
|
40
|
+
|
41
|
+
assert_equal 1, @filter.all.count, ':all did not apply the filters specified'
|
42
|
+
end
|
43
|
+
|
44
|
+
it ":apply_to should not modify the matches ARRelation if no non-nil values are set" do
|
45
|
+
@filter[:slug] = nil
|
46
|
+
@filter[:title] = nil
|
47
|
+
|
48
|
+
arr = Article.all
|
49
|
+
|
50
|
+
assert_same arr, @filter.apply_to(arr), 'The ActiveRecordRelation passed in to :apply_to is modified even with nil values'
|
51
|
+
end
|
52
|
+
|
53
|
+
it ":apply_to should use custom filter methods when they are available" do
|
54
|
+
@filter[:heading] = 'heading'
|
55
|
+
|
56
|
+
class << @filter
|
57
|
+
def filter_by_heading(partial, value)
|
58
|
+
partial.where(heading: "not #{value}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
arr = Article.all
|
63
|
+
|
64
|
+
assert_equal 0, @filter.apply_to(arr).count, 'The filter was not applied'
|
65
|
+
end
|
66
|
+
|
67
|
+
it ":apply_to should filter string values using a LIKE query by default" do
|
68
|
+
# This scenario should only match one title: 'title one'
|
69
|
+
create_article(title: 'title one')
|
70
|
+
create_article(title: 'title two')
|
71
|
+
@filter[:title] = 'one'
|
72
|
+
arr = Article.all
|
73
|
+
|
74
|
+
assert_equal 1, @filter.apply_to(arr).count, 'The filter was not applied in the expected way'
|
75
|
+
|
76
|
+
@filter.clear!
|
77
|
+
|
78
|
+
# This scenario should match both of the titles: 'title one' and 'title two'
|
79
|
+
@filter[:title] = 'title'
|
80
|
+
assert_equal 2, @filter.apply_to(arr).count, 'The filter was not applied in the expected way'
|
81
|
+
end
|
82
|
+
|
83
|
+
it ":apply_to sould use a default filtering strategy when using Date values" do
|
84
|
+
create_article(date: '2013-01-01')
|
85
|
+
create_article(date: '2013-02-01', show_time: true)
|
86
|
+
|
87
|
+
# DateTime
|
88
|
+
@filter[:date] = '2013-01-01'
|
89
|
+
assert_equal 1, @filter.apply_to(Article.all).count, 'A DateTime value is not handled correctly'
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
it ":apply_to sould use a default filtering strategy when using Boolean values" do
|
94
|
+
create_article(date: '2013-01-01')
|
95
|
+
create_article(date: '2013-02-01', show_time: true)
|
96
|
+
|
97
|
+
# Boolean
|
98
|
+
@filter[:show_time] = true
|
99
|
+
assert_equal 1, @filter.apply_to(Article.all).count, 'A Boolean value is not handled correctly'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easy_filters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- German Olle
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
69
|
+
description: Define a model class, allowing declare a subclass of this and implement
|
70
|
+
simple methods to filter each field model
|
71
|
+
email:
|
72
|
+
- golle@cespi.unlp.edu.ar
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- db/easy_filters_test.sqlite3
|
83
|
+
- easy_filters.gemspec
|
84
|
+
- lib/easy_filters.rb
|
85
|
+
- lib/easy_filters/filter.rb
|
86
|
+
- lib/easy_filters/model_filter.rb
|
87
|
+
- lib/easy_filters/version.rb
|
88
|
+
- spec/filter_spec.rb
|
89
|
+
- spec/minitest_helper.rb
|
90
|
+
- spec/model_filter_spec.rb
|
91
|
+
homepage: http://rubygems.org/gems/easy_filters
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.2.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: This gem implement a base model filter to declare your owns filters writing
|
115
|
+
a single file
|
116
|
+
test_files:
|
117
|
+
- spec/filter_spec.rb
|
118
|
+
- spec/minitest_helper.rb
|
119
|
+
- spec/model_filter_spec.rb
|