mongoid-mapreduce 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +2 -0
- data/MIT-LICENSE +20 -0
- data/README.md +95 -0
- data/Rakefile +9 -0
- data/lib/mongoid-mapreduce.rb +1 -0
- data/lib/mongoid/mapreduce.rb +6 -0
- data/lib/mongoid/mapreduce/base.rb +31 -0
- data/lib/mongoid/mapreduce/document.rb +48 -0
- data/lib/mongoid/mapreduce/reducer.rb +69 -0
- data/lib/mongoid/mapreduce/results.rb +54 -0
- data/lib/mongoid/mapreduce/version.rb +7 -0
- metadata +123 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 Jason Coene
|
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,95 @@
|
|
1
|
+
# Mongoid MapReduce
|
2
|
+
|
3
|
+
Mongoid MapReduce provides simple aggregation functions to your models using MongoDB map/reduce.
|
4
|
+
|
5
|
+
[![travis](https://secure.travis-ci.org/jcoene/mongoid-mapreduce.png)](http://travis-ci.org/jcoene/mongoid-mapreduce)
|
6
|
+
|
7
|
+
## How simple is simple?
|
8
|
+
|
9
|
+
Very. You provide a Mongoid model, criteria, map key and a list of fields to be aggregated. It returns a list of results (one per unique map key value).
|
10
|
+
|
11
|
+
## Getting Started
|
12
|
+
|
13
|
+
First, add mongoid-mapreduce to your Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'mongoid-mapreduce'
|
17
|
+
```
|
18
|
+
|
19
|
+
Next, include the module in any models for collections you'll be wanting to map/reduce on:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Employee
|
23
|
+
include Mongoid::Document
|
24
|
+
include Mongoid::MapReduce
|
25
|
+
|
26
|
+
field :name
|
27
|
+
field :division
|
28
|
+
field :awards, :type => Integer
|
29
|
+
field :age, :type => Integer
|
30
|
+
field :male, :type => Integer
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
You can now use the *map_reduce* method on your model to aggregate data:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Create a few example employees
|
38
|
+
Employee.create :name => 'Alan', :division => 'Software', :age => 20, :awards => 5, :male => 1
|
39
|
+
Employee.create :name => 'Bob', :division => 'Software', :age => 25, :awards => 4, :male => 1
|
40
|
+
Employee.create :name => 'Chris', :division => 'Hardware', :age => 30, :awards => 3, :male => 1
|
41
|
+
Employee.create :name => 'Darcy', :division => 'Sales', :age => 35, :awards => 3, :male => 0
|
42
|
+
|
43
|
+
# Produces 3 records, one for each division.
|
44
|
+
divs = Employee.map_reduce(:division, :fields => [:age, :awards])
|
45
|
+
divs.length # => 3
|
46
|
+
divs.find('Software').age # => 45
|
47
|
+
divs['Hardware'].awards # => 3
|
48
|
+
divs.first.awards # => 9
|
49
|
+
divs.last.age # => 35
|
50
|
+
divs.keys # => ['Hardware', 'Software', 'Sales']
|
51
|
+
divs.has_key?('Sales') # => true
|
52
|
+
divs.to_hash # => { "Software" => ..., "Hardware" => ..., "Sales" => ... }
|
53
|
+
```
|
54
|
+
|
55
|
+
You can also add Mongoid criteria before the operation:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# Produces 2 records, one for each matching division (men only!)
|
59
|
+
divs = Employee.where(:male => 1).map_reduce(:division, :fields => [:age, :awards])
|
60
|
+
divs.length # => 2
|
61
|
+
divs.has_key?('Sales') # => false
|
62
|
+
```
|
63
|
+
|
64
|
+
Additional meta fields are included in the results:
|
65
|
+
|
66
|
+
NOTE: _key_name and _key_value are discarded when converting to Hash.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# Produces 2 records, one for each matching division (men only!)
|
70
|
+
divs = Employee.map_reduce(:division, :fields => [:age, :awards])
|
71
|
+
divs.find('Software')._key_name # => :division
|
72
|
+
divs.find('Software')._key_value # => "Software"
|
73
|
+
divs.find('Software').division # => "Software" (_key_name => _key_value)
|
74
|
+
divs.find('Software')._count # => 2
|
75
|
+
|
76
|
+
# You can choose another name for the count field
|
77
|
+
Employee.map_reduce(:division, :count_field => :num).find('Software').num #=> 2
|
78
|
+
```
|
79
|
+
|
80
|
+
You can also choose to supply fields in a block:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
Employee.where(:age.gt => 20).map_reduce(:division) do
|
84
|
+
field :age
|
85
|
+
field :awards
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
## Enhancements and Pull Requests
|
90
|
+
|
91
|
+
If you find the project useful but it doesn't meet all of your needs, feel free to fork it and send a pull request.
|
92
|
+
|
93
|
+
## License
|
94
|
+
|
95
|
+
MIT license, go wild.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'mongoid/mapreduce'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module MapReduce
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
|
8
|
+
# Run a map/reduce operation on the current model
|
9
|
+
#
|
10
|
+
# map_key: Symbol or String, the field used in the map function
|
11
|
+
#
|
12
|
+
# Returns a Hash of results
|
13
|
+
def map_reduce(map_key=:_id, options={}, &block)
|
14
|
+
reducer = Reducer.new(self, criteria.selector, map_key)
|
15
|
+
|
16
|
+
if options.key?(:count_field)
|
17
|
+
reducer.count_field = options[:count_field].to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
if options.key?(:fields)
|
21
|
+
reducer.fields = options[:fields].collect {|f| f.to_sym }
|
22
|
+
end
|
23
|
+
|
24
|
+
reducer.instance_eval(&block) if block.present?
|
25
|
+
reducer.run
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module MapReduce
|
3
|
+
|
4
|
+
class Document < Hash
|
5
|
+
|
6
|
+
# Accept a hash of attributes dring initialization
|
7
|
+
#
|
8
|
+
# attrs - Hash of attributes to initialize with
|
9
|
+
#
|
10
|
+
# Returns value of super
|
11
|
+
def initialize(attrs)
|
12
|
+
attrs.each do |k, v|
|
13
|
+
self[k.to_sym] = v
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Allow dot notation on our Document
|
19
|
+
#
|
20
|
+
# sym - Symbol/String of the missing value
|
21
|
+
# args - Arguments supplied
|
22
|
+
# block - Block supplied
|
23
|
+
#
|
24
|
+
# Returns value of supplied symbol/string if exists
|
25
|
+
def method_missing(sym, *args, &block)
|
26
|
+
if self.has_key?(sym.to_sym)
|
27
|
+
return self[sym.to_sym]
|
28
|
+
elsif self.has_key?(sym.to_s)
|
29
|
+
return self[sym.to_s]
|
30
|
+
end
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
# Converts the Results to a Hash
|
35
|
+
#
|
36
|
+
# Returns Hash
|
37
|
+
def to_hash
|
38
|
+
h = Hash.new
|
39
|
+
self.each do |k, v|
|
40
|
+
h[k.to_s] = v unless [:_key_name, :_key_value].include?(k)
|
41
|
+
end
|
42
|
+
h
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module MapReduce
|
3
|
+
|
4
|
+
class Reducer
|
5
|
+
|
6
|
+
attr_accessor :fields, :count_field
|
7
|
+
|
8
|
+
# Initialize the reducer with given values
|
9
|
+
#
|
10
|
+
# klass - Mongoid model Class
|
11
|
+
# selector - Selector to use for search (often from criteria)
|
12
|
+
# map_key - Key to use in map function
|
13
|
+
#
|
14
|
+
# Returns nothing
|
15
|
+
def initialize(klass, selector, map_key)
|
16
|
+
@klass = klass
|
17
|
+
@selector = selector
|
18
|
+
@map_key = map_key
|
19
|
+
@count_field = :_count
|
20
|
+
@fields = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates the JavaScript map function
|
24
|
+
#
|
25
|
+
# Returns String
|
26
|
+
def map
|
27
|
+
"function() { emit(this.#{@map_key}, [1, #{@fields.collect{|k| "this.#{k}"}.join(", ")}]); }"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Generates the JavaScript reduce function
|
31
|
+
#
|
32
|
+
# Returns String
|
33
|
+
def reduce
|
34
|
+
"function(k, v) { var results = [0#{",0" * @fields.length}]; v.forEach(function(v){ [0,#{@fields.collect.with_index{|k,i| i+1}.join(",")}].forEach(function(k){ results[k] += v[k] }) }); return results.toString(); }"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Adds a field to the map/reduce operation
|
38
|
+
#
|
39
|
+
# sym - String or Symbol, name of field to add
|
40
|
+
#
|
41
|
+
# Returns nothing.
|
42
|
+
def field(sym)
|
43
|
+
@fields << sym.to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
# Runs the map/reduce operation and returns the result
|
47
|
+
#
|
48
|
+
# Returns Mongoid::MapReduce::Results object (array)
|
49
|
+
# containing Mongoid::MapReduce::Document objects (hashes)
|
50
|
+
def run
|
51
|
+
begin
|
52
|
+
res = @klass.collection.map_reduce(map, reduce, { query: @selector, out: "#map_reduce" } ).find.to_a
|
53
|
+
return res.inject(Results.new) do |h, k|
|
54
|
+
idx = k.values[0]
|
55
|
+
d = (k.values[1].is_a?(String) ? k.values[1].split(',') : k.values[1]).collect {|i| i.is_a?(Boolean) ? (i ? 1 : 0) : i.to_i }
|
56
|
+
doc = Document.new :_key_name => @map_key.to_sym, :_key_value => idx, @map_key.to_sym => idx, @count_field.to_sym => d[0]
|
57
|
+
@fields.flatten.each_with_index do |k, i|
|
58
|
+
doc[k.to_sym] = d[i + 1]
|
59
|
+
end
|
60
|
+
h << doc
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module MapReduce
|
3
|
+
|
4
|
+
class Results < Array
|
5
|
+
|
6
|
+
# Allow [] to be used to find records by key
|
7
|
+
#
|
8
|
+
# value - Value of the index or value to search by
|
9
|
+
#
|
10
|
+
# Return record at index if Fixnum, or result of find(value)
|
11
|
+
def [](value)
|
12
|
+
unless value.is_a? Fixnum
|
13
|
+
return find(value)
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Search for contained Documents by key
|
19
|
+
#
|
20
|
+
# value - Value of the map key
|
21
|
+
#
|
22
|
+
# Return Document or nil
|
23
|
+
def find(value)
|
24
|
+
self.each {|doc| return doc if doc._key_value == value }
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a list of keys from contained documents
|
29
|
+
#
|
30
|
+
# Returns Array
|
31
|
+
def keys
|
32
|
+
self.collect {|d| d._key_value }
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determines whether or not a key exists
|
36
|
+
#
|
37
|
+
# value - Value of the map key
|
38
|
+
#
|
39
|
+
# Returns true or false
|
40
|
+
def has_key?(value)
|
41
|
+
find(value) ? true : false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts the Results to a Hash
|
45
|
+
#
|
46
|
+
# Returns Hash
|
47
|
+
def to_hash
|
48
|
+
self.each.inject({}){|h, doc| h[doc._key_value] = doc.to_hash; h }
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-mapreduce
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jason Coene
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-22 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mongoid
|
16
|
+
requirement: &70365613354940 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70365613354940
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bson_ext
|
27
|
+
requirement: &70365613354440 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.3'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70365613354440
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: growl
|
38
|
+
requirement: &70365613354060 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70365613354060
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70365613353520 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.9.2
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70365613353520
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70365613353020 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '2.6'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70365613353020
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: &70365613352560 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.4.3
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70365613352560
|
80
|
+
description: Mongoid MapReduce provides simple aggregation features for your Mongoid
|
81
|
+
models
|
82
|
+
email:
|
83
|
+
- jcoene@gmail.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- lib/mongoid/mapreduce/base.rb
|
89
|
+
- lib/mongoid/mapreduce/document.rb
|
90
|
+
- lib/mongoid/mapreduce/reducer.rb
|
91
|
+
- lib/mongoid/mapreduce/results.rb
|
92
|
+
- lib/mongoid/mapreduce/version.rb
|
93
|
+
- lib/mongoid/mapreduce.rb
|
94
|
+
- lib/mongoid-mapreduce.rb
|
95
|
+
- MIT-LICENSE
|
96
|
+
- Rakefile
|
97
|
+
- Gemfile
|
98
|
+
- README.md
|
99
|
+
homepage: http://github.com/jcoene/mongoid-mapreduce
|
100
|
+
licenses: []
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.8.6
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: Simple map-reduce functionality for your Mongoid models
|
123
|
+
test_files: []
|