filter_matcher 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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +122 -0
- data/Rakefile +2 -0
- data/filter_matcher.gemspec +21 -0
- data/lib/filter_matcher/filter_matcher.rb +23 -0
- data/lib/filter_matcher/people_matcher.rb +71 -0
- data/lib/filter_matcher/version.rb +3 -0
- data/lib/filter_matcher.rb +1 -0
- data/test/people_matcher_test.rb +81 -0
- metadata +70 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jakub Godawa
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
filter_matcher - filter your collections
|
2
|
+
===
|
3
|
+
|
4
|
+
How to install?
|
5
|
+
==
|
6
|
+
|
7
|
+
gem install filter_matcher
|
8
|
+
|
9
|
+
What it does?
|
10
|
+
==
|
11
|
+
|
12
|
+
It filters a collection with defined filters. Launches the filters one by one on the collection until it finds a single result. When the result is found a final action is invoked.
|
13
|
+
|
14
|
+
Example
|
15
|
+
==
|
16
|
+
|
17
|
+
Lets say we have following data about people:
|
18
|
+
|
19
|
+
db = [
|
20
|
+
{ :id => 1, :name => "John", :age => 33, :homepage => "www.johny.com", :matched => false },
|
21
|
+
{ :id => 2, :name => "Mike", :age => 30, :homepage => "www.mikes.com", :matched => false },
|
22
|
+
{ :id => 3, :name => "Johny", :age => 25, :homepage => "www.johny.com", :matched => false },
|
23
|
+
{ :id => 4, :name => "Mike", :age => 30, :homepage => "www.realmike.com", :matched => false },
|
24
|
+
{ :id => 5, :name => "Dan", :age => 25, :homepage => "www.danny.com", :matched => false },
|
25
|
+
{ :id => 6, :name => "Dan", :age => 40, :homepage => "www.fakedanny.com", :matched => false }
|
26
|
+
]
|
27
|
+
|
28
|
+
And we want to set the matched flag to true (or run any other more complicated action), only for the entries that match the input data. Lets say that input is:
|
29
|
+
|
30
|
+
input = [
|
31
|
+
{ :name => "Mike", :age => 30, :homepage => "www.realmike.com" },
|
32
|
+
{ :name => "Dan", :homepage => "www.danny.com" }
|
33
|
+
]
|
34
|
+
|
35
|
+
First we need to specify how we want to filter our data to find the best match. As the filters run sequentially the order matters.
|
36
|
+
|
37
|
+
Lets say the most important is the name. If name filter finds a single result then this is a match. We can define the filter like:
|
38
|
+
|
39
|
+
def name_filter(collection, name)
|
40
|
+
collection.select { |element| element[:name] == name }
|
41
|
+
end
|
42
|
+
|
43
|
+
The filter has to be named in the convention: <what_it_filters>_filter
|
44
|
+
|
45
|
+
If this is not sufficient we run another filter on the result of the previous one. Lets define another filter, saying that the second most important thing to find a match is age.
|
46
|
+
|
47
|
+
def age_filter(collection, age)
|
48
|
+
collection.select { |element| element[:age] == age}
|
49
|
+
end
|
50
|
+
|
51
|
+
Simple and very similar to the previous one. Filters can have any logic you need. Lets define the last one - homepage filter.
|
52
|
+
|
53
|
+
def homepage_filter(collection, homepage)
|
54
|
+
collection.select { |element| element[:homepage] == homepage }
|
55
|
+
end
|
56
|
+
|
57
|
+
Now, in order to run them all on the collection, we run the filter_and_match method:
|
58
|
+
|
59
|
+
@input.each do |input|
|
60
|
+
collection = @db
|
61
|
+
|
62
|
+
filter_and_match collection, {
|
63
|
+
:by_name => input[:name],
|
64
|
+
:by_age => input[:age],
|
65
|
+
:by_homepage => input[:homepage]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
If any filter returns an empty result then next filter is given the collection of the last not empty result or the original data itself.
|
70
|
+
|
71
|
+
If a single result will be found the elemnt_matched method will be triggered. In this example we just change the entry's flag:
|
72
|
+
|
73
|
+
def element_matched(element)
|
74
|
+
element[:matched] = true
|
75
|
+
end
|
76
|
+
|
77
|
+
Full PeopleMatcher class example, that is responsible only for the matching is shown below:
|
78
|
+
|
79
|
+
class PeopleMatcher
|
80
|
+
include FilterMatcher
|
81
|
+
|
82
|
+
attr_reader :db
|
83
|
+
|
84
|
+
def initialize(db, input)
|
85
|
+
@db, @input = db, input
|
86
|
+
end
|
87
|
+
|
88
|
+
def match
|
89
|
+
@input.each do |input|
|
90
|
+
collection = @db
|
91
|
+
|
92
|
+
filter_and_match collection, {
|
93
|
+
:by_name => input[:name],
|
94
|
+
:by_age => input[:age],
|
95
|
+
:by_homepage => input[:homepage]
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def name_filter(collection, name)
|
103
|
+
collection.select { |element| element[:name] == name }
|
104
|
+
end
|
105
|
+
|
106
|
+
def age_filter(collection, age)
|
107
|
+
collection.select { |element| element[:age] == age}
|
108
|
+
end
|
109
|
+
|
110
|
+
def homepage_filter(collection, homepage)
|
111
|
+
collection.select { |element| element[:homepage] == homepage }
|
112
|
+
end
|
113
|
+
|
114
|
+
def element_matched(element)
|
115
|
+
element[:matched] = true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
Feel free to contribute or give any feedback
|
120
|
+
==
|
121
|
+
|
122
|
+
Did you find it usefull any how? Please let me know, and if you can want to improve it just send me a pull request.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "filter_matcher/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "filter_matcher"
|
7
|
+
s.version = FilterMatcher::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Jakub Godawa"]
|
10
|
+
s.email = ["jakub.godawa@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/vysogot/filter_matcher"
|
12
|
+
s.summary = %q{FilterMatcher makes it easy to find a match in a collection}
|
13
|
+
s.description = %q{FilterMatcher is helpful when it comes to find a single match in the collection
|
14
|
+
by a group of filters. Every filter is can be easly defined and chained with others. Filters narrow the
|
15
|
+
collection sequantially.}
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module FilterMatcher
|
2
|
+
|
3
|
+
#
|
4
|
+
# define filter in a class that uses this module
|
5
|
+
# named like:
|
6
|
+
# - name_filter
|
7
|
+
# - age_filter
|
8
|
+
#
|
9
|
+
# they should a filtered array
|
10
|
+
#
|
11
|
+
|
12
|
+
def filter_and_match(collection, filters)
|
13
|
+
filters.each do |key, param|
|
14
|
+
method_name = key.to_s.tr('by_', '') + "_filter"
|
15
|
+
filtered = send(method_name.to_sym, collection, param)
|
16
|
+
collection = filtered.empty? ? collection : filtered
|
17
|
+
if collection.size == 1
|
18
|
+
element_matched(collection.first)
|
19
|
+
break
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#
|
2
|
+
# example class that uses filter matcher module
|
3
|
+
#
|
4
|
+
# @db:
|
5
|
+
# represents data from which machter tries to filter
|
6
|
+
# a single result
|
7
|
+
#
|
8
|
+
# @input:
|
9
|
+
# criteria for maching
|
10
|
+
#
|
11
|
+
|
12
|
+
require 'filter_matcher'
|
13
|
+
|
14
|
+
class PeopleMatcher
|
15
|
+
include FilterMatcher
|
16
|
+
|
17
|
+
attr_reader :db
|
18
|
+
|
19
|
+
def initialize(db, input)
|
20
|
+
@db, @input = db, input
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# run filters on all data for every input element
|
25
|
+
#
|
26
|
+
def match
|
27
|
+
@input.each do |input|
|
28
|
+
collection = @db
|
29
|
+
|
30
|
+
#
|
31
|
+
# filters are run sequentially until they find
|
32
|
+
# a single result
|
33
|
+
#
|
34
|
+
# they are called by the following convention:
|
35
|
+
# :by_<what_it_filters => paramters of <what_it_filters>_filter method
|
36
|
+
#
|
37
|
+
# when one of the filters reveal a single result
|
38
|
+
# next filters are not run for current input
|
39
|
+
#
|
40
|
+
filter_and_match collection, {
|
41
|
+
:by_name => input[:name],
|
42
|
+
:by_age => input[:age],
|
43
|
+
:by_homepage => input[:homepage]
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
#
|
51
|
+
# filters are named by the following convention:
|
52
|
+
# <what_it_filters>_filter
|
53
|
+
#
|
54
|
+
# every filter should return a filtered result
|
55
|
+
#
|
56
|
+
def name_filter(collection, name)
|
57
|
+
collection.select { |element| element[:name] == name }
|
58
|
+
end
|
59
|
+
|
60
|
+
def age_filter(collection, age)
|
61
|
+
collection.select { |element| element[:age] == age}
|
62
|
+
end
|
63
|
+
|
64
|
+
def homepage_filter(collection, homepage)
|
65
|
+
collection.select { |element| element[:homepage] == homepage }
|
66
|
+
end
|
67
|
+
|
68
|
+
def element_matched(element)
|
69
|
+
element[:matched] = true
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'filter_matcher/filter_matcher'
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'filter_matcher/people_matcher'
|
4
|
+
|
5
|
+
class PeopleMatcherTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@db = [
|
8
|
+
{ :id => 1, :name => "John", :age => 33, :homepage => "www.johny.com", :matched => false },
|
9
|
+
{ :id => 2, :name => "Mike", :age => 30, :homepage => "www.mikes.com", :matched => false },
|
10
|
+
{ :id => 3, :name => "Johny", :age => 25, :homepage => "www.johny.com", :matched => false },
|
11
|
+
{ :id => 4, :name => "Mike", :age => 30, :homepage => "www.realmike.com", :matched => false },
|
12
|
+
{ :id => 5, :name => "Dan", :age => 25, :homepage => "www.danny.com", :matched => false },
|
13
|
+
{ :id => 6, :name => "Dan", :age => 40, :homepage => "www.fakedanny.com", :matched => false }
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_filters_by_name
|
18
|
+
input = [
|
19
|
+
{ :name => "John" },
|
20
|
+
{ :name => "Johny" }
|
21
|
+
]
|
22
|
+
|
23
|
+
assert_matched(input, [1, 3])
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_filters_by_name_and_then_by_age
|
27
|
+
input = [
|
28
|
+
{ :name => "Dan", :age => 40 }
|
29
|
+
]
|
30
|
+
|
31
|
+
assert_matched(input, [6])
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_filters_by_name_and_then_by_age_and_then_by_homepage
|
35
|
+
input = [
|
36
|
+
{ :name => "Mike", :age => 30, :homepage => "www.realmike.com" },
|
37
|
+
{ :name => "Dan", :homepage => "www.danny.com" }
|
38
|
+
]
|
39
|
+
|
40
|
+
assert_matched(input, [4, 5])
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_filters_by_age_and_then_by_homepage_name_not_given
|
44
|
+
input = [
|
45
|
+
{ :age => 25, :homepage => "www.johny.com" }
|
46
|
+
]
|
47
|
+
|
48
|
+
assert_matched(input, [3])
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_doesnt_match_if_nothing_matched
|
52
|
+
input = [
|
53
|
+
{ :name => "George" }
|
54
|
+
]
|
55
|
+
|
56
|
+
assert_matched(input, [])
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_doesnt_match_if_many_matched
|
60
|
+
input = [
|
61
|
+
{ :name => "Mike", :age => 30 }
|
62
|
+
]
|
63
|
+
|
64
|
+
assert_matched(input, [])
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def assert_matched(input, matched)
|
70
|
+
matcher = PeopleMatcher.new(@db, input)
|
71
|
+
matcher.match
|
72
|
+
|
73
|
+
matcher.db.select {|x| matched.include?(x[:id]) }.each do |entry|
|
74
|
+
assert(entry[:matched], "Matched expected: #{entry.inspect}")
|
75
|
+
end
|
76
|
+
|
77
|
+
matcher.db.reject {|x| matched.include?(x[:id]) }.each do |entry|
|
78
|
+
assert(!entry[:matched], "Not matched expected: #{entry.inspect}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: filter_matcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jakub Godawa
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-07-16 00:00:00 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: |-
|
18
|
+
FilterMatcher is helpful when it comes to find a single match in the collection
|
19
|
+
by a group of filters. Every filter is can be easly defined and chained with others. Filters narrow the
|
20
|
+
collection sequantially.
|
21
|
+
email:
|
22
|
+
- jakub.godawa@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- .gitignore
|
31
|
+
- Gemfile
|
32
|
+
- LICENSE.txt
|
33
|
+
- README.md
|
34
|
+
- Rakefile
|
35
|
+
- filter_matcher.gemspec
|
36
|
+
- lib/filter_matcher.rb
|
37
|
+
- lib/filter_matcher/filter_matcher.rb
|
38
|
+
- lib/filter_matcher/people_matcher.rb
|
39
|
+
- lib/filter_matcher/version.rb
|
40
|
+
- test/people_matcher_test.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/vysogot/filter_matcher
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.6.2
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: FilterMatcher makes it easy to find a match in a collection
|
69
|
+
test_files:
|
70
|
+
- test/people_matcher_test.rb
|