mongoid-mapreduce 0.1.0 → 0.1.1
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 +45 -15
- data/lib/mongoid/mapreduce/base.rb +3 -1
- data/lib/mongoid/mapreduce/document.rb +4 -2
- data/lib/mongoid/mapreduce/reducer.rb +103 -10
- data/lib/mongoid/mapreduce/results.rb +8 -1
- data/lib/mongoid/mapreduce/version.rb +1 -1
- metadata +14 -14
data/README.md
CHANGED
@@ -6,7 +6,13 @@ Mongoid MapReduce provides simple aggregation functions to your models using Mon
|
|
6
6
|
|
7
7
|
## How simple is simple?
|
8
8
|
|
9
|
-
|
9
|
+
Short answer: very!
|
10
|
+
|
11
|
+
There are two map/reduce formulae:
|
12
|
+
|
13
|
+
**Aggregates:** Provide a map key and a list of fields to be aggregated via addition.
|
14
|
+
|
15
|
+
**Array List:** Provide an array field, the values will be individually aggregated via addition.
|
10
16
|
|
11
17
|
## Getting Started
|
12
18
|
|
@@ -28,6 +34,7 @@ class Employee
|
|
28
34
|
field :awards, :type => Integer
|
29
35
|
field :age, :type => Integer
|
30
36
|
field :male, :type => Integer
|
37
|
+
field :rooms, :type => Array
|
31
38
|
end
|
32
39
|
```
|
33
40
|
|
@@ -35,12 +42,12 @@ You can now use the *map_reduce* method on your model to aggregate data:
|
|
35
42
|
|
36
43
|
```ruby
|
37
44
|
# 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
|
45
|
+
Employee.create :name => 'Alan', :division => 'Software', :age => 20, :awards => 5, :male => 1, :rooms => [1,2,3]
|
46
|
+
Employee.create :name => 'Bob', :division => 'Software', :age => 25, :awards => 4, :male => 1, :rooms => [1,2,3]
|
47
|
+
Employee.create :name => 'Chris', :division => 'Hardware', :age => 30, :awards => 3, :male => 1, :rooms => [4,5,6]
|
48
|
+
Employee.create :name => 'Darcy', :division => 'Sales', :age => 35, :awards => 3, :male => 0, :rooms => [1,2,3,4,5,6]
|
42
49
|
|
43
|
-
#
|
50
|
+
# Aggregate formula: produces 3 records, one for each division.
|
44
51
|
divs = Employee.map_reduce(:division, :fields => [:age, :awards])
|
45
52
|
divs.length # => 3
|
46
53
|
divs.find('Software').age # => 45
|
@@ -50,6 +57,15 @@ divs.last.age # => 35
|
|
50
57
|
divs.keys # => ['Hardware', 'Software', 'Sales']
|
51
58
|
divs.has_key?('Sales') # => true
|
52
59
|
divs.to_hash # => { "Software" => ..., "Hardware" => ..., "Sales" => ... }
|
60
|
+
|
61
|
+
# Array Value formula: produces 6 records, one for each room.
|
62
|
+
rooms = Employee.map_reduce do
|
63
|
+
field :rooms, :formula => :array_values
|
64
|
+
end
|
65
|
+
rooms.length # => 6
|
66
|
+
rooms.find(1)._count # => 3
|
67
|
+
rooms.counts["5"] # => 2
|
68
|
+
rooms.counts # => { "1" => 3, "2" => 3, "3" => 3, "4" => 2, "5" => 2, "6" => 2 }
|
53
69
|
```
|
54
70
|
|
55
71
|
You can also add Mongoid criteria before the operation:
|
@@ -61,6 +77,29 @@ divs.length # => 2
|
|
61
77
|
divs.has_key?('Sales') # => false
|
62
78
|
```
|
63
79
|
|
80
|
+
You choose to supply fields as arguments or in a block:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# These are the same:
|
84
|
+
Employee.where(:age.gt => 20).map_reduce(:division, :fields => [:age, :awards])
|
85
|
+
Employee.where(:age.gt => 20).map_reduce(:division) do
|
86
|
+
field :age
|
87
|
+
field :awards
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Fields can be of any type supported by Mongoid serialization, and field type is specified in block configuration:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
divs = Employee.map_reduce(:division) do
|
95
|
+
field :age, :type => Integer
|
96
|
+
field :awards, :type => Float
|
97
|
+
end
|
98
|
+
|
99
|
+
divs.find('Software').age # => 60
|
100
|
+
divs.find('Software').awards # => 9.0
|
101
|
+
```
|
102
|
+
|
64
103
|
Additional meta fields are included in the results:
|
65
104
|
|
66
105
|
NOTE: _key_name and _key_value are discarded when converting to Hash.
|
@@ -77,15 +116,6 @@ divs.find('Software')._count # => 2
|
|
77
116
|
Employee.map_reduce(:division, :count_field => :num).find('Software').num #=> 2
|
78
117
|
```
|
79
118
|
|
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
119
|
## Enhancements and Pull Requests
|
90
120
|
|
91
121
|
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.
|
@@ -10,7 +10,7 @@ module Mongoid
|
|
10
10
|
# Returns value of super
|
11
11
|
def initialize(attrs)
|
12
12
|
attrs.each do |k, v|
|
13
|
-
self[k
|
13
|
+
self[k] = v
|
14
14
|
end
|
15
15
|
super
|
16
16
|
end
|
@@ -23,7 +23,9 @@ module Mongoid
|
|
23
23
|
#
|
24
24
|
# Returns value of supplied symbol/string if exists
|
25
25
|
def method_missing(sym, *args, &block)
|
26
|
-
if self.has_key?(sym
|
26
|
+
if self.has_key?(sym)
|
27
|
+
return self[sym]
|
28
|
+
elsif self.has_key?(sym.to_sym)
|
27
29
|
return self[sym.to_sym]
|
28
30
|
elsif self.has_key?(sym.to_s)
|
29
31
|
return self[sym.to_s]
|
@@ -3,7 +3,7 @@ module Mongoid
|
|
3
3
|
|
4
4
|
class Reducer
|
5
5
|
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :count_field
|
7
7
|
|
8
8
|
# Initialize the reducer with given values
|
9
9
|
#
|
@@ -17,21 +17,92 @@ module Mongoid
|
|
17
17
|
@selector = selector
|
18
18
|
@map_key = map_key
|
19
19
|
@count_field = :_count
|
20
|
-
@fields =
|
20
|
+
@fields = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Obtain the field we are using for the map, if using an array values map.
|
24
|
+
#
|
25
|
+
# Returns true or false
|
26
|
+
def map_array_field
|
27
|
+
@fields.select { |k, v| v[:formula] == :array_values }.first
|
28
|
+
end
|
29
|
+
|
30
|
+
# Determines whether or not we are mapping from an array value
|
31
|
+
#
|
32
|
+
# Returns true or false
|
33
|
+
def map_from_array?
|
34
|
+
@fields.select { |k, v| v[:formula] == :array_values }.any?
|
21
35
|
end
|
22
36
|
|
23
37
|
# Generates the JavaScript map function
|
24
38
|
#
|
39
|
+
# If we have any fields defined with a map function of :array_values, use that to map
|
40
|
+
# otherwise, use our aggregate map function.
|
41
|
+
#
|
25
42
|
# Returns String
|
26
43
|
def map
|
27
|
-
"function() {
|
44
|
+
fn = "function() { "
|
45
|
+
if map_from_array?
|
46
|
+
fn << map_array_values(map_array_field)
|
47
|
+
else
|
48
|
+
fn << map_aggregates
|
49
|
+
end
|
50
|
+
fn << "}"
|
51
|
+
fn
|
52
|
+
end
|
53
|
+
|
54
|
+
# Generate a map function from one unique map key and a number of aggregate sources
|
55
|
+
#
|
56
|
+
#
|
57
|
+
def map_aggregates
|
58
|
+
fields = @fields.select { |k, v| v[:formula] == :aggregate }
|
59
|
+
"emit (this.#{@map_key}, [#{[1, fields.collect{|k,v| "this.#{k}"}].flatten.join(", ")}]); "
|
60
|
+
end
|
61
|
+
|
62
|
+
# Generate a map function from one unique map key and a number of aggregate sources
|
63
|
+
#
|
64
|
+
#
|
65
|
+
def map_array_values(field)
|
66
|
+
"this.#{field[0].to_s}.forEach(function(value) { emit(value, 1); }); "
|
28
67
|
end
|
29
68
|
|
30
69
|
# Generates the JavaScript reduce function
|
31
70
|
#
|
32
71
|
# Returns String
|
33
72
|
def reduce
|
34
|
-
|
73
|
+
fn = "function(k, v) { "
|
74
|
+
if map_from_array?
|
75
|
+
fn << reduce_array_values
|
76
|
+
else
|
77
|
+
fn << reduce_aggregates
|
78
|
+
end
|
79
|
+
fn << "}"
|
80
|
+
fn
|
81
|
+
end
|
82
|
+
|
83
|
+
# Generates a reduce function for aggregate map
|
84
|
+
#
|
85
|
+
# Returns String
|
86
|
+
def reduce_aggregates
|
87
|
+
fields = @fields.select { |k, v| v[:formula] == :aggregate }
|
88
|
+
fn = ""
|
89
|
+
fn << "var results = [#{(["0"] * (fields.length + 1)).flatten.join(", ")}]; "
|
90
|
+
fn << "v.forEach(function(val) { "
|
91
|
+
fn << "for(var i=0; i<= #{fields.length}; i++) { "
|
92
|
+
fn << "results[i] += val[i] "
|
93
|
+
fn << "} "
|
94
|
+
fn << "}); "
|
95
|
+
fn << "return results.toString(); "
|
96
|
+
end
|
97
|
+
|
98
|
+
# Generates a reduce function for array values
|
99
|
+
#
|
100
|
+
# Returns String
|
101
|
+
def reduce_array_values
|
102
|
+
fn = ""
|
103
|
+
fn << "var result = 0; "
|
104
|
+
fn << "v.forEach(function(val) { result += val; }); "
|
105
|
+
fn << "return result; "
|
35
106
|
end
|
36
107
|
|
37
108
|
# Adds a field to the map/reduce operation
|
@@ -39,8 +110,22 @@ module Mongoid
|
|
39
110
|
# sym - String or Symbol, name of field to add
|
40
111
|
#
|
41
112
|
# Returns nothing.
|
42
|
-
def field(sym)
|
43
|
-
|
113
|
+
def field(sym, options={})
|
114
|
+
options[:type] ||= Integer
|
115
|
+
options[:formula] ||= :aggregate
|
116
|
+
@fields[sym.to_sym] = options
|
117
|
+
end
|
118
|
+
|
119
|
+
# Serialize an object to the specified class
|
120
|
+
#
|
121
|
+
# obj - Object to serialize
|
122
|
+
# klass - Class to prefer
|
123
|
+
#
|
124
|
+
# Returns serialized object or nil
|
125
|
+
def serialize(obj, klass)
|
126
|
+
return nil if obj.blank?
|
127
|
+
obj = obj.to_s =~ /(^[-+]?[0-9]+$)|(\.0+)$/ ? Integer(obj) : Float(obj)
|
128
|
+
Mongoid::Fields::Mappings.for(klass).allocate.serialize(obj)
|
44
129
|
end
|
45
130
|
|
46
131
|
# Runs the map/reduce operation and returns the result
|
@@ -52,10 +137,18 @@ module Mongoid
|
|
52
137
|
res = @klass.collection.map_reduce(map, reduce, { query: @selector, out: "#map_reduce" } ).find.to_a
|
53
138
|
return res.inject(Results.new) do |h, k|
|
54
139
|
idx = k.values[0]
|
55
|
-
d = (k.values[1].is_a?(String) ? k.values[1].split(',') : k.values[1])
|
56
|
-
|
57
|
-
|
58
|
-
doc
|
140
|
+
d = (k.values[1].is_a?(String) ? k.values[1].split(',') : k.values[1])
|
141
|
+
|
142
|
+
if d.is_a?(Array)
|
143
|
+
doc = Document.new :_key_name => @map_key.to_s, :_key_value => idx, @map_key => idx, @count_field => d[0].to_i
|
144
|
+
@fields.each_with_index do |h, i|
|
145
|
+
doc[h[0].to_sym] = serialize(d[i + 1], h[1][:type])
|
146
|
+
end
|
147
|
+
else
|
148
|
+
f = map_array_field[0]
|
149
|
+
k = serialize(idx, map_array_field[1][:type])
|
150
|
+
v = d.to_i
|
151
|
+
doc = Document.new :_key_name => f, :_key_value => k, k.to_s => v, @count_field => v
|
59
152
|
end
|
60
153
|
h << doc
|
61
154
|
end
|
@@ -45,7 +45,14 @@ module Mongoid
|
|
45
45
|
#
|
46
46
|
# Returns Hash
|
47
47
|
def to_hash
|
48
|
-
self.each.inject({}){|h, doc| h[doc._key_value] = doc.to_hash; h }
|
48
|
+
self.each.inject({}){|h, doc| h[doc._key_value.to_s] = doc.to_hash; h }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Simplifies the Results to a Hash containing only a key and a single value (the count)
|
52
|
+
#
|
53
|
+
# Returns Hash
|
54
|
+
def counts
|
55
|
+
self.each.inject({}) {|h, doc| h[doc._key_value.to_s] = doc._count; h }
|
49
56
|
end
|
50
57
|
|
51
58
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-mapreduce
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-09-
|
12
|
+
date: 2011-09-23 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mongoid
|
16
|
-
requirement: &
|
16
|
+
requirement: &70109620996440 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '2.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70109620996440
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bson_ext
|
27
|
-
requirement: &
|
27
|
+
requirement: &70109620995920 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '1.3'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70109620995920
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: growl
|
38
|
-
requirement: &
|
38
|
+
requirement: &70109620995540 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70109620995540
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rake
|
49
|
-
requirement: &
|
49
|
+
requirement: &70109620994980 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 0.9.2
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70109620994980
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rspec
|
60
|
-
requirement: &
|
60
|
+
requirement: &70109620994340 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '2.6'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70109620994340
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: guard-rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &70109620993880 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: 0.4.3
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70109620993880
|
80
80
|
description: Mongoid MapReduce provides simple aggregation features for your Mongoid
|
81
81
|
models
|
82
82
|
email:
|