mongoid_ruby_or 0.1.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/MIT-LICENSE +20 -0
- data/README.md +93 -0
- data/Rakefile +13 -0
- data/lib/mongoid_ruby_or.rb +37 -0
- data/mongoid_ruby_or.gemspec +14 -0
- data/test/mongoid.yml +6 -0
- data/test/mongoid_ruby_or_test.rb +37 -0
- data/test/test_helper.rb +18 -0
- metadata +87 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Ary Djmal
|
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,93 @@
|
|
1
|
+
# mongoid_ruby_or
|
2
|
+
|
3
|
+
This gem hijacks the $or operator, doing one query per $or clause, merging the results with plain ruby.
|
4
|
+
|
5
|
+
## Background
|
6
|
+
|
7
|
+
**WARNING:** understand what this code does before using (it's only ~20 LOC!)
|
8
|
+
|
9
|
+
MongoDB is not the smartest when using indexes for the $or operator.
|
10
|
+
|
11
|
+
This is my case:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class Brand
|
15
|
+
include Mongoid::Document
|
16
|
+
|
17
|
+
field :name, type: String
|
18
|
+
|
19
|
+
has_many :resources
|
20
|
+
end
|
21
|
+
|
22
|
+
class Resource
|
23
|
+
include Mongoid::Document
|
24
|
+
|
25
|
+
field :name, type: String
|
26
|
+
field :content_id, type: String
|
27
|
+
|
28
|
+
index({ brand_id: 1, name: 1 })
|
29
|
+
index({ brand_id: 1, content_id: 1 })
|
30
|
+
|
31
|
+
belongs_to :brand
|
32
|
+
end
|
33
|
+
|
34
|
+
```
|
35
|
+
|
36
|
+
Given the above indexes, I do queries like:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
brand.resources # will use index
|
40
|
+
brand.resources.where(name: 'test') # will use index
|
41
|
+
brand.resources.where(content_id: 'test') # will use index
|
42
|
+
|
43
|
+
```
|
44
|
+
|
45
|
+
The problem comes when using the $or operator:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
brand.resources.or({name: 'test'}, {content_id: 'test'}) # will do a full scan
|
49
|
+
```
|
50
|
+
|
51
|
+
Unfortunately, it will not use an index unless it starts with the field from the $or's. In order to have indexes that satisfies that query and the ones from above we will need something like:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
index({ brand_id: 1 })
|
55
|
+
index({ name: 1, brand_id: 1 })
|
56
|
+
index({ content_id: 1, brand_id: 1 })
|
57
|
+
|
58
|
+
```
|
59
|
+
|
60
|
+
So in this simpliefied case, we will need to add an extra index.
|
61
|
+
|
62
|
+
Sometimes is not worth it, maybe that query is not use very often to justify the necessity to maintain the index, or you are trying to cut back on indexes for that collection.
|
63
|
+
|
64
|
+
This gem will add a #ruby_or method to the Mongoid::Criteria class, and do a query for each $or clause and then merge in ruby.
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
|
68
|
+
Add to your Gemfile:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
gem :mongoid_ruby_or
|
72
|
+
```
|
73
|
+
|
74
|
+
## Usage
|
75
|
+
|
76
|
+
Using the same example from above:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
# problematic query, will be slow as it will do a full scan
|
80
|
+
brand.resources.or({name: /^test/}, {content_id: /^test/}).limit(10)
|
81
|
+
```
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# this will do two fast queries and patch them together
|
85
|
+
brand.resources.or({name: /^test/}, {content_id: /^test/}).limit(10).ruby_or
|
86
|
+
```
|
87
|
+
|
88
|
+
## Some observations
|
89
|
+
|
90
|
+
* **built for a system running ruby 1.9.3, rails 3.2, mongo 2.6.3, and mongoid 3.1.6**, may not be an issue in the future
|
91
|
+
* tested for simple $or clauses
|
92
|
+
* can't do count for obvious reasons
|
93
|
+
* ruby_or will cut the criteria chain and get results as soon as invoked, so use at the end
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
desc 'Default: run unit tests.'
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
desc 'Test the mongoid_ruby_or gem.'
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << 'lib'
|
10
|
+
t.libs << 'test'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = false
|
13
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Mongoid
|
2
|
+
class Criteria
|
3
|
+
def ruby_or
|
4
|
+
selector = self.selector
|
5
|
+
or_clauses = selector.delete('$or')
|
6
|
+
limit = options[:limit]
|
7
|
+
sort = options[:sort]
|
8
|
+
criteria = klass.where(selector).limit(limit)
|
9
|
+
results = []
|
10
|
+
|
11
|
+
or_clauses.each do |or_clause|
|
12
|
+
results += criteria.where(or_clause).sort(sort).to_a
|
13
|
+
end
|
14
|
+
|
15
|
+
results.uniq!
|
16
|
+
|
17
|
+
if sort
|
18
|
+
field, direction = sort.first
|
19
|
+
if direction == 1
|
20
|
+
results.sort! { |x,y| x[field] <=> y[field] }
|
21
|
+
else
|
22
|
+
results.sort! { |x,y| y[field] <=> x[field] }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
limit ? results[0...limit] : results
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Contextual
|
31
|
+
class Mongo
|
32
|
+
def ruby_or
|
33
|
+
criteria.ruby_or
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'mongoid_ruby_or'
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.authors = ['Ary Djmal']
|
5
|
+
s.email = ['arydjmal@gmail.com']
|
6
|
+
s.summary = 'This gem hijacks the $or operator, doing one query per $or clause, merging the results with plain ruby.'
|
7
|
+
s.homepage = 'http://github.com/arydjmal/mongoid_ruby_or'
|
8
|
+
|
9
|
+
s.add_dependency 'mongoid', '~> 3.1.6'
|
10
|
+
|
11
|
+
s.add_development_dependency 'rake'
|
12
|
+
|
13
|
+
s.files = Dir["#{File.dirname(__FILE__)}/**/*"]
|
14
|
+
end
|
data/test/mongoid.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MongoidRubyOrTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
Mongoid.purge!
|
6
|
+
User.create!(name: 'ary', email: 'arydjmal@gmail.com', account_id: '1')
|
7
|
+
User.create!(name: 'aron', email: 'xaron@gmail.com', account_id: '1')
|
8
|
+
User.create!(name: 'xariel', email: 'arielx@gmail.com', account_id: '1')
|
9
|
+
User.create!(name: 'djmal', email: 'djmal@gmail.com', account_id: '1')
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_ruby_or
|
13
|
+
assert_criteria User.or({name: /^ar/}, {email: /^ar/})
|
14
|
+
assert_criteria User.or({name: /^ary/}, {email: /^ary/})
|
15
|
+
assert_criteria User.or({name: /^xxx/}, {email: /^xxx/})
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_limit
|
19
|
+
assert_criteria User.or({name: /^ar/}, {email: /^ar/}).limit(2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_sort
|
23
|
+
assert_criteria User.or({name: /^ar/}, {email: /^ar/}).asc(:name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_limit_and_sort
|
27
|
+
assert_criteria User.or({name: /^ar/}, {email: /^ar/}).limit(2).asc(:name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_scoped_selector
|
31
|
+
assert_criteria User.where(account_id: '1').or({name: /^ar/}, {email: /^ar/}).limit(2).asc(:name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_when_on_contextual
|
35
|
+
assert_criteria User.or({name: /^ar/}, {email: /^ar/}).sort(name: 1)
|
36
|
+
end
|
37
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'minitest'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'mongoid'
|
4
|
+
require 'mongoid_ruby_or'
|
5
|
+
|
6
|
+
Mongoid.load!('test/mongoid.yml', :test)
|
7
|
+
|
8
|
+
class User
|
9
|
+
include Mongoid::Document
|
10
|
+
|
11
|
+
field :name, type: String
|
12
|
+
field :email, type: String
|
13
|
+
field :account_id, type: String
|
14
|
+
end
|
15
|
+
|
16
|
+
def assert_criteria(criteria)
|
17
|
+
assert_equal criteria.to_a, criteria.ruby_or
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid_ruby_or
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ary Djmal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-12-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongoid
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.1.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.1.6
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description:
|
47
|
+
email:
|
48
|
+
- arydjmal@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- ./lib/mongoid_ruby_or.rb
|
54
|
+
- ./MIT-LICENSE
|
55
|
+
- ./mongoid_ruby_or.gemspec
|
56
|
+
- ./Rakefile
|
57
|
+
- ./README.md
|
58
|
+
- ./test/mongoid.yml
|
59
|
+
- ./test/mongoid_ruby_or_test.rb
|
60
|
+
- ./test/test_helper.rb
|
61
|
+
homepage: http://github.com/arydjmal/mongoid_ruby_or
|
62
|
+
licenses: []
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.8.23
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: This gem hijacks the $or operator, doing one query per $or clause, merging
|
85
|
+
the results with plain ruby.
|
86
|
+
test_files: []
|
87
|
+
has_rdoc:
|