mongoid-mapreduce 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/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
|
+
[](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: []
|