mongo-parser-rb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mongo-parser-rb.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ben McRedmond
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,56 @@
1
+ # MongoParserRB
2
+
3
+ Parse and evaluate MongoDB queries in Ruby. MongoParserRB is useful for checking if already loaded documents match a query, without the overhead of making requests to the database.
4
+
5
+ Parse a query:
6
+
7
+ ```ruby
8
+ q = MongoParserRB::Query.parse({:comment_count => 5})
9
+ ```
10
+
11
+ Once a query has been parsed you can check if individual documents match a query:
12
+
13
+ ```ruby
14
+ q.matches_document?({:comment_count => 4}) => false
15
+ q.matches_document?({:comment_count => 5}) => true
16
+ ```
17
+
18
+ You can use MongoDB's conditional operators, specify them as symbols:
19
+
20
+ ```ruby
21
+ q = MongoParserRB::Query.parse({:comment_count => {:$gt => 5, :$lt => 10}})
22
+ q.matches_document?({:comment_count => 11}) => false
23
+ q.matches_document?({:comment_count => 6}) => true
24
+ ```
25
+
26
+ The following operators are currently supported: `$and`, `$or`, `$in`, `$nin`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`.
27
+
28
+ Regexps are also supported:
29
+
30
+ ```ruby
31
+ q = MongoParserRB::Query.parse({:"email" => /gmail/})
32
+ q.matches_document?({:email => "ben@intercom.io"}) => false
33
+ q.matches_document?({:email => "ciaran@gmail.com"}) => true
34
+ ```
35
+
36
+ MongoDB's dot field syntax can be used:
37
+
38
+ ```ruby
39
+ q = MongoParserRB::Query.parse({:"author.name" => "Ben"})
40
+ q.matches_document?({:author => {:name => "Ciaran"}}) => false
41
+ q.matches_document?({:author => {:name => "Ben"}}) => true
42
+ ```
43
+
44
+ ## Installation
45
+
46
+ Add this line to your application's Gemfile:
47
+
48
+ gem 'mongo-parser-rb'
49
+
50
+ And then execute:
51
+
52
+ $ bundle
53
+
54
+ Or install it yourself as:
55
+
56
+ $ gem install mongo-parser-rb
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new("test") do |test|
5
+ test.libs << 'lib'
6
+ test.libs << 'test'
7
+
8
+ test.test_files = FileList['test/**/*_test.rb']
9
+ test.warning = false
10
+ test.verbose = true
11
+ end
@@ -0,0 +1,5 @@
1
+ module MongoParserRB
2
+
3
+ class NotParsedError < StandardError; end
4
+
5
+ end
@@ -0,0 +1,39 @@
1
+ module MongoParserRB
2
+ class Field
3
+
4
+ def initialize(field)
5
+ @field = field
6
+ @field_parts = field.to_s.split('.')
7
+ end
8
+
9
+ def value_in_document(document)
10
+ document = stringify_keys(document)
11
+ @field_parts.reduce(document) do |value, field|
12
+ value[field]
13
+ end
14
+ rescue NoMethodError
15
+ nil
16
+ end
17
+
18
+ def in_document?(document)
19
+ document = stringify_keys(document)
20
+
21
+ @field_parts.reduce(document) do |value, field|
22
+ return false unless value.has_key?(field)
23
+ value[field]
24
+ end
25
+
26
+ true
27
+ end
28
+
29
+ private
30
+
31
+ def stringify_keys(document)
32
+ document.reduce({}) do |new_document, (k,v)|
33
+ new_document[k.to_s] = v.kind_of?(Hash) ? stringify_keys(v) : v
34
+ new_document
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,126 @@
1
+ module MongoParserRB
2
+ class Query
3
+ class Expression
4
+
5
+ class << self
6
+
7
+ def conjunction_operators
8
+ @conjunction_operators ||= [
9
+ :$and,
10
+ :$or
11
+ ]
12
+ end
13
+
14
+ def conjunction_operator?(operator)
15
+ conjunction_operators.include?(operator)
16
+ end
17
+
18
+ def negative_equality_operators
19
+ @negative_equality_operators ||= [
20
+ :$nin,
21
+ :$ne
22
+ ]
23
+ end
24
+
25
+ def equality_operators
26
+ @equality_operators ||= [
27
+ :$eq,
28
+ :$gt,
29
+ :$lt,
30
+ :$gte,
31
+ :$lte,
32
+ :$in
33
+ ] | negative_equality_operators
34
+ end
35
+
36
+ def equality_operator?(operator)
37
+ equality_operators.include?(operator)
38
+ end
39
+
40
+ def operator?(operator)
41
+ equality_operator?(operator) || conjunction_operator?(operator)
42
+ end
43
+
44
+ end
45
+
46
+ def initialize(operator, *args)
47
+ @operator = operator
48
+
49
+ if Expression.conjunction_operator? @operator
50
+ @arguments = args[0]
51
+ else
52
+ @field = Field.new(args[0])
53
+ @arguments = args[1]
54
+ end
55
+ end
56
+
57
+ def evaluate(document)
58
+ case @operator
59
+ when *Expression.conjunction_operators
60
+ evaluate_conjunction(document)
61
+ when *Expression.negative_equality_operators
62
+ evaluate_negative_equality(document)
63
+ when *Expression.equality_operators
64
+ evaluate_equality(document)
65
+ end
66
+ rescue NoMethodError
67
+ false
68
+ end
69
+
70
+ private
71
+
72
+ def evaluate_conjunction(document)
73
+ case @operator
74
+ when :$and
75
+ @arguments.all? do |arg|
76
+ arg.evaluate(document)
77
+ end
78
+ when :$or
79
+ @arguments.any? do |arg|
80
+ arg.evaluate(document)
81
+ end
82
+ end
83
+ end
84
+
85
+ def evaluate_negative_equality(document)
86
+ value_for_field = @field.value_in_document(document)
87
+
88
+ # Mongo negative equality operators return true when
89
+ # the specified field does not exist on a document.
90
+ return true if !value_for_field && !@field.in_document?(document)
91
+
92
+ case @operator
93
+ when :$ne
94
+ value_for_field != @arguments
95
+ when :$nin
96
+ (value_for_field & @arguments).length.zero?
97
+ end
98
+ end
99
+
100
+ def evaluate_equality(document)
101
+ value_for_field = @field.value_in_document(document)
102
+ return false if !value_for_field && !@field.in_document?(document)
103
+
104
+ case @operator
105
+ when :$eq
106
+ if @arguments.kind_of?(Regexp)
107
+ !!(value_for_field =~ @arguments)
108
+ else
109
+ value_for_field == @arguments
110
+ end
111
+ when :$gt
112
+ value_for_field > @arguments
113
+ when :$gte
114
+ value_for_field >= @arguments
115
+ when :$lt
116
+ value_for_field < @arguments
117
+ when :$lte
118
+ value_for_field <= @arguments
119
+ when :$in
120
+ (value_for_field & @arguments).length > 0
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,46 @@
1
+ module MongoParserRB
2
+ class Query
3
+
4
+ def self.parse(raw_query)
5
+ new(raw_query).parse!
6
+ end
7
+
8
+ def initialize(raw_query)
9
+ @raw_query = raw_query
10
+ end
11
+
12
+ def parse!
13
+ @expression_tree = parse_root_expression(@raw_query)
14
+ self
15
+ end
16
+
17
+ def matches_document?(document)
18
+ raise NotParsedError, "Query not parsed (run parse!)" if @expression_tree.nil?
19
+ @expression_tree.evaluate(document)
20
+ end
21
+
22
+ private
23
+
24
+ def parse_root_expression(query, field = nil)
25
+ Expression.new(:$and, query.to_a.map do |(key, value)|
26
+ parse_sub_expression(key, value, field)
27
+ end)
28
+ end
29
+
30
+ def parse_sub_expression(key, value, field = nil)
31
+ if Expression.operator?(key)
32
+ case key
33
+ when *Expression.conjunction_operators
34
+ Expression.new(key, value.map { |v| parse_root_expression(v) })
35
+ else
36
+ Expression.new(key, field, value)
37
+ end
38
+ elsif value.kind_of?(Hash)
39
+ parse_root_expression(value, key)
40
+ else
41
+ Expression.new(:$eq, key, value)
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module MongoParserRB
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "mongo-parser-rb/version"
2
+ require "mongo-parser-rb/exceptions"
3
+ require "mongo-parser-rb/field"
4
+ require "mongo-parser-rb/query/expression"
5
+ require "mongo-parser-rb/query"
6
+
7
+ module MongoParserRB
8
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mongo-parser-rb/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "mongo-parser-rb"
8
+ gem.version = MongoParserRB::VERSION
9
+ gem.authors = ["Ben McRedmond"]
10
+ gem.email = ["ben@intercom.io"]
11
+ gem.description = %q{Parse and evaluate mongo queries in Ruby}
12
+ gem.summary = gem.description
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,26 @@
1
+ require 'test_helper'
2
+
3
+ class FieldTest < MiniTest::Unit::TestCase
4
+
5
+ def test_returns_value_when_key_present
6
+ field = MongoParserRB::Field.new(:"custom_data.tracked_users")
7
+ assert_equal 10, field.value_in_document(:custom_data => {:tracked_users => 10})
8
+ end
9
+
10
+ def test_returns_nil_when_key_not_present
11
+ field = MongoParserRB::Field.new(:"custom_data.tracked_users")
12
+ assert_nil field.value_in_document(:custom_data => {:something_else => 10})
13
+ assert_nil field.value_in_document(:name => "Ben")
14
+ end
15
+
16
+ def test_document_has_a_field
17
+ field = MongoParserRB::Field.new(:"custom_data.tracked_users")
18
+ assert field.in_document?(:custom_data => {:tracked_users => 10})
19
+ refute field.in_document?(:not_custom_data => {:tracked_users => 10})
20
+
21
+ field = MongoParserRB::Field.new(:"session_count")
22
+ assert field.in_document?(:custom_data => {:tracked_users => 10}, :session_count => 5)
23
+ refute field.in_document?(:custom_data => {:tracked_users => 10})
24
+ end
25
+
26
+ end
@@ -0,0 +1,152 @@
1
+ require 'test_helper'
2
+
3
+ class QueryTest < MiniTest::Unit::TestCase
4
+
5
+ def test_raises_if_not_parsed
6
+ assert_raises(MongoParserRB::NotParsedError) do
7
+ query = MongoParserRB::Query.new(:integer_key => 10)
8
+ query.matches_document?({})
9
+ end
10
+ end
11
+
12
+ def test_integer_eq
13
+ query = MongoParserRB::Query.parse(:integer_key => 10)
14
+ assert query.matches_document?(:integer_key => 10)
15
+ refute query.matches_document?(:integer_key => 9)
16
+ end
17
+
18
+ def test_integer_ne
19
+ query = MongoParserRB::Query.parse(:integer_key => {:$ne => 10})
20
+ assert query.matches_document?(:integer_key => 9)
21
+ refute query.matches_document?(:integer_key => 10)
22
+ end
23
+
24
+ def test_integer_gt
25
+ query = MongoParserRB::Query.parse(:integer_key => {:$gt => 10})
26
+ assert query.matches_document?(:integer_key => 11)
27
+ refute query.matches_document?(:integer_key => 10)
28
+ refute query.matches_document?(:integer_key => 9)
29
+ end
30
+
31
+ def test_integer_lt
32
+ query = MongoParserRB::Query.parse(:integer_key => {:$lt => 10})
33
+ query.matches_document?(:integer_key => 9)
34
+ query.matches_document?(:integer_key => 10)
35
+ query.matches_document?(:integer_key => 11)
36
+ end
37
+
38
+ def test_integer_lte
39
+ query = MongoParserRB::Query.parse(:integer_key => {:$lte => 10})
40
+ assert query.matches_document?(:integer_key => 9)
41
+ assert query.matches_document?(:integer_key => 10)
42
+ refute query.matches_document?(:integer_key => 11)
43
+ end
44
+
45
+ def test_integer_gte
46
+ query = MongoParserRB::Query.parse(:integer_key => {:$lte => 10})
47
+ assert query.matches_document?(:integer_key => 9)
48
+ assert query.matches_document?(:integer_key => 10)
49
+ refute query.matches_document?(:integer_key => 11)
50
+ end
51
+
52
+ def test_integer_lte_and_integer_gt
53
+ query = MongoParserRB::Query.parse(:integer_key => {:$lte => 10}, :integer_key_2 => {:$gt => 5})
54
+ assert query.matches_document?(:integer_key => 9, :integer_key_2 => 6)
55
+ refute query.matches_document?(:integer_key => 9, :integer_key_2 => 4)
56
+ refute query.matches_document?(:integer_key => 11, :integer_key_2 => 4)
57
+ end
58
+
59
+ def test_string_eq
60
+ query = MongoParserRB::Query.parse(:string_key => "hello world")
61
+ assert query.matches_document?(:string_key => "hello world")
62
+ refute query.matches_document?(:string_key => 1)
63
+ refute query.matches_document?(:string_key => "bye world")
64
+ end
65
+
66
+ def test_string_gt
67
+ query = MongoParserRB::Query.parse(:string_key => {:$gt => 'abc'})
68
+ assert query.matches_document?(:string_key => "abcd")
69
+ assert query.matches_document?(:string_key => "e")
70
+ refute query.matches_document?(:string_key => "abc")
71
+ end
72
+
73
+ def test_string_lt
74
+ query = MongoParserRB::Query.parse(:string_key => {:$lt => 'm'})
75
+ assert query.matches_document?(:string_key => "abc")
76
+ refute query.matches_document?(:string_key => "xyz")
77
+ end
78
+
79
+ def test_string_and_integer_equality
80
+ query = MongoParserRB::Query.parse(:string_key => {:$lt => 'm'}, :integer_key => {:$gt => 4})
81
+ assert query.matches_document?(:string_key => "abc", :integer_key => 5)
82
+ refute query.matches_document?(:string_key => "xyz", :integer_key => 5)
83
+ refute query.matches_document?(:string_key => "abc", :integer_key => 4)
84
+ end
85
+
86
+
87
+ def test_or
88
+ query = MongoParserRB::Query.parse(:$or => [
89
+ {:string_key => "abc"},
90
+ {:integer_key => {:$gt => 5}}
91
+ ])
92
+
93
+ assert query.matches_document?(:string_key => "abc")
94
+ refute query.matches_document?(:string_key => "cde")
95
+ assert query.matches_document?(:string_key => "cde", :integer_key => 6)
96
+ assert query.matches_document?(:string_key => "abc", :integer_key => 6)
97
+ assert query.matches_document?(:integer_key => 6)
98
+ refute query.matches_document?(:integer_key => 5)
99
+ end
100
+
101
+ def test_boolean_eq
102
+ query = MongoParserRB::Query.parse(:boolean_key => false)
103
+ assert query.matches_document?(:boolean_key => false)
104
+ refute query.matches_document?(:boolean_key => true)
105
+ end
106
+
107
+ def test_query_field_integration
108
+ query = MongoParserRB::Query.parse(:"custom_data.tracked_users" => {:$gt => 3})
109
+ assert query.matches_document?(:custom_data => {:tracked_users => 10})
110
+ refute query.matches_document?(:custom_data => {:tracked_users => 1})
111
+ end
112
+
113
+ def test_array_in
114
+ query = MongoParserRB::Query.parse(:array_key => {:$in => [1]})
115
+ assert query.matches_document?(:array_key => [1,2])
116
+ refute query.matches_document?(:array_key => [2,3])
117
+ end
118
+
119
+ def test_array_nin
120
+ query = MongoParserRB::Query.parse(:array_key => {:$nin => [1,2]})
121
+ assert query.matches_document?(:array_key => [3,4,5])
122
+ refute query.matches_document?(:array_key => [1,4,5])
123
+ end
124
+
125
+ def test_eq_nil
126
+ query = MongoParserRB::Query.parse(:string_key => nil)
127
+ assert query.matches_document?(:string_key => nil)
128
+ refute query.matches_document?(:string_key => 'hey')
129
+ end
130
+
131
+ def test_regex_eq
132
+ query = MongoParserRB::Query.parse(:string_key => /hello/)
133
+ assert query.matches_document?(:string_key => 'hello world')
134
+ refute query.matches_document?(:string_key => 'world')
135
+ end
136
+
137
+ def test_operator_data_type_mismatch
138
+ query = MongoParserRB::Query.parse(:array_key => {:$in => [1]})
139
+ refute query.matches_document?(:array_key => "hey")
140
+ end
141
+
142
+ def test_date_range
143
+ query = MongoParserRB::Query.parse(:date_key => {:$gt => Time.new(1993,2,13,0,0,0)})
144
+ assert query.matches_document?(:date_key => Time.new(1994,2,13,0,0,0))
145
+ refute query.matches_document?(:date_key => Time.new(1992,2,13,0,0,0))
146
+
147
+ query = MongoParserRB::Query.parse(:date_key => {:$gt => Time.new(1993,2,13,0,0,0), :$lt => Time.new(1995,2,13,0,0,0)})
148
+ assert query.matches_document?(:date_key => Time.new(1994,2,13,0,0,0))
149
+ refute query.matches_document?(:date_key => Time.new(1996,2,13,0,0,0))
150
+ end
151
+
152
+ end
@@ -0,0 +1,2 @@
1
+ require 'mongo-parser-rb'
2
+ require 'minitest/autorun'
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo-parser-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ben McRedmond
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Parse and evaluate mongo queries in Ruby
15
+ email:
16
+ - ben@intercom.io
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - lib/mongo-parser-rb.rb
27
+ - lib/mongo-parser-rb/exceptions.rb
28
+ - lib/mongo-parser-rb/field.rb
29
+ - lib/mongo-parser-rb/query.rb
30
+ - lib/mongo-parser-rb/query/expression.rb
31
+ - lib/mongo-parser-rb/version.rb
32
+ - mongo-parser-rb.gemspec
33
+ - test/mongo-parser-rb/field_test.rb
34
+ - test/mongo-parser-rb/query_test.rb
35
+ - test/test_helper.rb
36
+ homepage: ''
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.25
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: Parse and evaluate mongo queries in Ruby
60
+ test_files:
61
+ - test/mongo-parser-rb/field_test.rb
62
+ - test/mongo-parser-rb/query_test.rb
63
+ - test/test_helper.rb
64
+ has_rdoc: