filter_matcher 0.1.0 → 0.2.0
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/README.md +21 -6
- data/lib/filter_matcher/filter_matcher.rb +72 -13
- data/lib/filter_matcher/version.rb +1 -1
- data/test/first_from_top_matcher_test.rb +101 -0
- data/test/{people_matcher_test.rb → single_matcher_test.rb} +44 -24
- data/test/test_helper.rb +15 -0
- metadata +8 -6
- data/test/people_matcher.rb +0 -49
data/README.md
CHANGED
@@ -9,11 +9,13 @@ How to install?
|
|
9
9
|
What it does?
|
10
10
|
==
|
11
11
|
|
12
|
-
It filters a collection with defined filters. Launches the filters one by one on the collection until it finds a
|
12
|
+
It filters a collection with defined filters. Launches the filters one by one on the collection until it finds a result. When the result is found a final action is invoked.
|
13
13
|
|
14
14
|
Example
|
15
15
|
==
|
16
16
|
|
17
|
+
This example describes SingleMatcher.
|
18
|
+
|
17
19
|
Lets say we have following data about people:
|
18
20
|
|
19
21
|
db = [
|
@@ -57,7 +59,7 @@ Now, in order to run them all on the collection, we run the matcher method:
|
|
57
59
|
@input.each do |input|
|
58
60
|
collection = @db
|
59
61
|
|
60
|
-
matcher(collection) do |m|
|
62
|
+
matcher(collection, :single) do |m|
|
61
63
|
m.filter do |col|
|
62
64
|
col.select { |element| element[:name] == input[:name] }
|
63
65
|
end
|
@@ -94,7 +96,7 @@ The expected result is the db matched like:
|
|
94
96
|
{:id => 6, :name => "Dan", :age => 40, :homepage => "www.fakedanny.com", :matched => false}
|
95
97
|
]
|
96
98
|
|
97
|
-
Full PeopleMatcher class example, that is responsible only for the matching
|
99
|
+
Full PeopleMatcher class example, that is responsible only for the matching could look like that:
|
98
100
|
|
99
101
|
class PeopleMatcher
|
100
102
|
include FilterMatcher
|
@@ -133,9 +135,9 @@ Full PeopleMatcher class example, that is responsible only for the matching is s
|
|
133
135
|
end
|
134
136
|
end
|
135
137
|
|
136
|
-
Visual
|
138
|
+
Visual examples
|
137
139
|
==
|
138
|
-
Filtering a collection
|
140
|
+
Filtering a collection with SingleMatcher
|
139
141
|
|
140
142
|
[1,2,3] --first_filter--> [2,3] ---second_filter--> [2] -> match
|
141
143
|
|
@@ -147,6 +149,19 @@ Filtering a collection
|
|
147
149
|
|
148
150
|
[1,2,3] --first_filter--> [1,2,3] --second_filter--> [2,3] --third_filter--> [2,3] -> no match
|
149
151
|
|
152
|
+
Filtering a collection with FirstFromTopMatcher
|
153
|
+
|
154
|
+
[1,2,3] --first_filter--> [2,3] ---second_filter--> [2] -> matches 2
|
155
|
+
|
156
|
+
[1,2,3] --first_filter--> [] --> [1,2,3] --second_filter--> [3] -> matches 3
|
157
|
+
|
158
|
+
[1,2,3] --first_filter--> [1,2] --second_filter--> [1,2] --third_filter--> [1, 2] -> matches 1
|
159
|
+
|
160
|
+
[1,2,3] --first_filter--> [] --> [1,2,3] --second_filter--> [] --> [1,2,3] --third_filter--> [] -> no match
|
161
|
+
|
162
|
+
[1,2,3] --first_filter--> [1,2,3] --second_filter--> [2,3] --third_filter--> [2,3] -> matches 2
|
163
|
+
|
164
|
+
|
150
165
|
Filtering real data by SQL or JS
|
151
166
|
==
|
152
167
|
|
@@ -160,5 +175,5 @@ Did you find it usefull any how? Please let me know, and if you can want to impr
|
|
160
175
|
Contributors
|
161
176
|
==
|
162
177
|
|
163
|
-
[Albert Llop](https://github.com/mrsimo)
|
178
|
+
[Albert Llop](https://github.com/mrsimo),
|
164
179
|
[Sebastian Röbke](https://github.com/boosty)
|
@@ -1,35 +1,94 @@
|
|
1
1
|
module FilterMatcher
|
2
|
-
|
3
|
-
|
2
|
+
|
3
|
+
class Matcher
|
4
|
+
class UnknownMatcherError < StandardError; end
|
5
|
+
|
6
|
+
MATCHERS = [:single, :first_from_top]
|
7
|
+
|
8
|
+
def initialize(collection, matcher_type)
|
9
|
+
unless MATCHERS.include?(matcher_type)
|
10
|
+
raise UnknownMatcherError,
|
11
|
+
"No matcher #{matcher_type} found, select one of these: #{MATCHERS.join(', ')}"
|
12
|
+
end
|
13
|
+
|
4
14
|
@collection = collection
|
5
|
-
@
|
15
|
+
@matcher = get_matcher(matcher_type)
|
6
16
|
end
|
7
17
|
|
8
18
|
def filter(&block)
|
9
|
-
@filters << block
|
19
|
+
@matcher.filters << block
|
10
20
|
end
|
11
21
|
|
12
22
|
def match(&block)
|
13
23
|
@match = block
|
14
24
|
end
|
15
25
|
|
16
|
-
def
|
26
|
+
def run
|
27
|
+
@matcher.run(@collection, @match)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def get_matcher(symbol)
|
33
|
+
klass_name = symbol.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
34
|
+
klass_name << "Matcher"
|
35
|
+
klass = Object.const_get(klass_name)
|
36
|
+
klass.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# matches only when filters can narrow collection to one element
|
41
|
+
class SingleMatcher
|
42
|
+
attr_accessor :filters
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@filters = []
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(collection, match)
|
17
49
|
@filters.each do |filter|
|
18
|
-
filtered = filter.call(
|
19
|
-
|
50
|
+
filtered = filter.call(collection)
|
51
|
+
collection = filtered.empty? ? collection : filtered
|
52
|
+
|
53
|
+
if collection.size == 1
|
54
|
+
match.call(collection.first)
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
20
60
|
|
21
|
-
|
22
|
-
|
61
|
+
# matches the result from the top of the filtered collection
|
62
|
+
# does not match only when filtered didn't filtered anything
|
63
|
+
class FirstFromTopMatcher
|
64
|
+
attr_accessor :filters
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@filters = []
|
68
|
+
end
|
69
|
+
|
70
|
+
def run(collection, match)
|
71
|
+
last_filter_index = @filters.size - 1
|
72
|
+
collection_init_size = collection.size
|
73
|
+
@filters.each_with_index do |filter, index|
|
74
|
+
filtered = filter.call(collection)
|
75
|
+
collection = filtered.empty? ? collection : filtered
|
76
|
+
|
77
|
+
if (collection.size == 1) ||
|
78
|
+
((last_filter_index == index) && (collection_init_size != collection.size))
|
79
|
+
|
80
|
+
match.call(collection.first)
|
23
81
|
break
|
24
82
|
end
|
25
83
|
end
|
26
84
|
end
|
27
85
|
end
|
28
86
|
|
29
|
-
def matcher(collection)
|
30
|
-
filterer = Filterer.new(collection)
|
31
|
-
yield filterer
|
32
87
|
|
33
|
-
|
88
|
+
def matcher(collection, matcher_type)
|
89
|
+
matcher = Matcher.new(collection, matcher_type)
|
90
|
+
yield matcher
|
91
|
+
|
92
|
+
matcher.run
|
34
93
|
end
|
35
94
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class FirstFromTopMatcherTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def run_matcher(input)
|
6
|
+
input.each do |input|
|
7
|
+
collection = @db
|
8
|
+
|
9
|
+
matcher(collection, :first_from_top) do |m|
|
10
|
+
m.filter do |col|
|
11
|
+
col.select { |element| element[:name] == input[:name] }
|
12
|
+
end
|
13
|
+
|
14
|
+
m.filter do |col|
|
15
|
+
col.select { |element| element[:age] == input[:age]}
|
16
|
+
end
|
17
|
+
|
18
|
+
m.filter do |col|
|
19
|
+
col.select { |element| element[:homepage] == input[:homepage] }
|
20
|
+
end
|
21
|
+
|
22
|
+
m.match do |element|
|
23
|
+
element[:matched] = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup
|
30
|
+
@db = [
|
31
|
+
{ :id => 1, :name => "John", :age => 33, :homepage => "www.johny.com", :matched => false },
|
32
|
+
{ :id => 2, :name => "Mike", :age => 30, :homepage => "www.mikes.com", :matched => false },
|
33
|
+
{ :id => 3, :name => "Johny", :age => 25, :homepage => "www.johny.com", :matched => false },
|
34
|
+
{ :id => 4, :name => "Mike", :age => 30, :homepage => "www.realmike.com", :matched => false },
|
35
|
+
{ :id => 5, :name => "Dan", :age => 25, :homepage => "www.danny.com", :matched => false },
|
36
|
+
{ :id => 6, :name => "Dan", :age => 40, :homepage => "www.fakedanny.com", :matched => false }
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_filters_by_name
|
41
|
+
input = [
|
42
|
+
{ :name => "John" },
|
43
|
+
{ :name => "Johny" }
|
44
|
+
]
|
45
|
+
|
46
|
+
run_matcher(input)
|
47
|
+
|
48
|
+
assert_matched(@db, input, [1, 3])
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_filters_by_name_and_then_by_age
|
52
|
+
input = [
|
53
|
+
{ :name => "Dan", :age => 40 }
|
54
|
+
]
|
55
|
+
|
56
|
+
run_matcher(input)
|
57
|
+
|
58
|
+
assert_matched(@db, input, [6])
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_filters_by_name_and_then_by_age_and_then_by_homepage
|
62
|
+
input = [
|
63
|
+
{ :name => "Mike", :age => 30, :homepage => "www.realmike.com" },
|
64
|
+
{ :name => "Dan", :homepage => "www.danny.com" }
|
65
|
+
]
|
66
|
+
|
67
|
+
run_matcher(input)
|
68
|
+
|
69
|
+
assert_matched(@db, input, [4, 5])
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_filters_by_age_and_then_by_homepage_name_not_given
|
73
|
+
input = [
|
74
|
+
{ :age => 25, :homepage => "www.johny.com" }
|
75
|
+
]
|
76
|
+
|
77
|
+
run_matcher(input)
|
78
|
+
|
79
|
+
assert_matched(@db, input, [3])
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_doesnt_match_if_nothing_matched
|
83
|
+
input = [
|
84
|
+
{ :name => "George" }
|
85
|
+
]
|
86
|
+
|
87
|
+
run_matcher(input)
|
88
|
+
|
89
|
+
assert_matched(@db, input, [])
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_doesnt_match_if_many_matched
|
93
|
+
input = [
|
94
|
+
{ :name => "Mike", :age => 30 }
|
95
|
+
]
|
96
|
+
|
97
|
+
run_matcher(input)
|
98
|
+
|
99
|
+
assert_matched(@db, input, [2])
|
100
|
+
end
|
101
|
+
end
|
@@ -1,8 +1,31 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class FirstFromTopMatcherTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def run_matcher(input)
|
6
|
+
input.each do |input|
|
7
|
+
collection = @db
|
8
|
+
|
9
|
+
matcher(collection, :single) do |m|
|
10
|
+
m.filter do |col|
|
11
|
+
col.select { |element| element[:name] == input[:name] }
|
12
|
+
end
|
13
|
+
|
14
|
+
m.filter do |col|
|
15
|
+
col.select { |element| element[:age] == input[:age]}
|
16
|
+
end
|
17
|
+
|
18
|
+
m.filter do |col|
|
19
|
+
col.select { |element| element[:homepage] == input[:homepage] }
|
20
|
+
end
|
21
|
+
|
22
|
+
m.match do |element|
|
23
|
+
element[:matched] = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
4
28
|
|
5
|
-
class PeopleMatcherTest < Test::Unit::TestCase
|
6
29
|
def setup
|
7
30
|
@db = [
|
8
31
|
{ :id => 1, :name => "John", :age => 33, :homepage => "www.johny.com", :matched => false },
|
@@ -20,7 +43,9 @@ class PeopleMatcherTest < Test::Unit::TestCase
|
|
20
43
|
{ :name => "Johny" }
|
21
44
|
]
|
22
45
|
|
23
|
-
|
46
|
+
run_matcher(input)
|
47
|
+
|
48
|
+
assert_matched(@db, input, [1, 3])
|
24
49
|
end
|
25
50
|
|
26
51
|
def test_filters_by_name_and_then_by_age
|
@@ -28,7 +53,9 @@ class PeopleMatcherTest < Test::Unit::TestCase
|
|
28
53
|
{ :name => "Dan", :age => 40 }
|
29
54
|
]
|
30
55
|
|
31
|
-
|
56
|
+
run_matcher(input)
|
57
|
+
|
58
|
+
assert_matched(@db, input, [6])
|
32
59
|
end
|
33
60
|
|
34
61
|
def test_filters_by_name_and_then_by_age_and_then_by_homepage
|
@@ -37,7 +64,9 @@ class PeopleMatcherTest < Test::Unit::TestCase
|
|
37
64
|
{ :name => "Dan", :homepage => "www.danny.com" }
|
38
65
|
]
|
39
66
|
|
40
|
-
|
67
|
+
run_matcher(input)
|
68
|
+
|
69
|
+
assert_matched(@db, input, [4, 5])
|
41
70
|
end
|
42
71
|
|
43
72
|
def test_filters_by_age_and_then_by_homepage_name_not_given
|
@@ -45,7 +74,9 @@ class PeopleMatcherTest < Test::Unit::TestCase
|
|
45
74
|
{ :age => 25, :homepage => "www.johny.com" }
|
46
75
|
]
|
47
76
|
|
48
|
-
|
77
|
+
run_matcher(input)
|
78
|
+
|
79
|
+
assert_matched(@db, input, [3])
|
49
80
|
end
|
50
81
|
|
51
82
|
def test_doesnt_match_if_nothing_matched
|
@@ -53,7 +84,9 @@ class PeopleMatcherTest < Test::Unit::TestCase
|
|
53
84
|
{ :name => "George" }
|
54
85
|
]
|
55
86
|
|
56
|
-
|
87
|
+
run_matcher(input)
|
88
|
+
|
89
|
+
assert_matched(@db, input, [])
|
57
90
|
end
|
58
91
|
|
59
92
|
def test_doesnt_match_if_many_matched
|
@@ -61,21 +94,8 @@ class PeopleMatcherTest < Test::Unit::TestCase
|
|
61
94
|
{ :name => "Mike", :age => 30 }
|
62
95
|
]
|
63
96
|
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
97
|
+
run_matcher(input)
|
68
98
|
|
69
|
-
|
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
|
99
|
+
assert_matched(@db, input, [])
|
80
100
|
end
|
81
101
|
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'filter_matcher/filter_matcher'
|
4
|
+
|
5
|
+
include FilterMatcher
|
6
|
+
|
7
|
+
def assert_matched(db, input, matched)
|
8
|
+
db.select {|x| matched.include?(x[:id]) }.each do |entry|
|
9
|
+
assert(entry[:matched], "Matched expected: #{entry.inspect}")
|
10
|
+
end
|
11
|
+
|
12
|
+
db.reject {|x| matched.include?(x[:id]) }.each do |entry|
|
13
|
+
assert(!entry[:matched], "Not matched expected: #{entry.inspect}")
|
14
|
+
end
|
15
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: filter_matcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.2.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jakub Godawa
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-07-
|
13
|
+
date: 2011-07-21 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -36,8 +36,9 @@ files:
|
|
36
36
|
- lib/filter_matcher.rb
|
37
37
|
- lib/filter_matcher/filter_matcher.rb
|
38
38
|
- lib/filter_matcher/version.rb
|
39
|
-
- test/
|
40
|
-
- test/
|
39
|
+
- test/first_from_top_matcher_test.rb
|
40
|
+
- test/single_matcher_test.rb
|
41
|
+
- test/test_helper.rb
|
41
42
|
has_rdoc: true
|
42
43
|
homepage: http://github.com/vysogot/filter_matcher
|
43
44
|
licenses: []
|
@@ -67,5 +68,6 @@ signing_key:
|
|
67
68
|
specification_version: 3
|
68
69
|
summary: FilterMatcher makes it easy to find a match in a collection
|
69
70
|
test_files:
|
70
|
-
- test/
|
71
|
-
- test/
|
71
|
+
- test/first_from_top_matcher_test.rb
|
72
|
+
- test/single_matcher_test.rb
|
73
|
+
- test/test_helper.rb
|
data/test/people_matcher.rb
DELETED
@@ -1,49 +0,0 @@
|
|
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
|
-
matcher(collection) do |m|
|
31
|
-
m.filter do |col|
|
32
|
-
col.select { |element| element[:name] == input[:name] }
|
33
|
-
end
|
34
|
-
|
35
|
-
m.filter do |col|
|
36
|
-
col.select { |element| element[:age] == input[:age]}
|
37
|
-
end
|
38
|
-
|
39
|
-
m.filter do |col|
|
40
|
-
col.select { |element| element[:homepage] == input[:homepage] }
|
41
|
-
end
|
42
|
-
|
43
|
-
m.match do |element|
|
44
|
-
element[:matched] = true
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|