mongoid-mapreduce 0.1.2 → 0.1.3
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 +1 -3
- data/lib/mongoid/mapreduce.rb +1 -0
- data/lib/mongoid/mapreduce/base.rb +2 -12
- data/lib/mongoid/mapreduce/formula/aggregate_fields.rb +66 -0
- data/lib/mongoid/mapreduce/formula/array_values.rb +65 -0
- data/lib/mongoid/mapreduce/reducer.rb +31 -115
- data/lib/mongoid/mapreduce/serialization.rb +20 -0
- data/lib/mongoid/mapreduce/version.rb +1 -1
- metadata +17 -14
data/README.md
CHANGED
@@ -59,9 +59,7 @@ divs.has_key?('Sales') # => true
|
|
59
59
|
divs.to_hash # => { "Software" => ..., "Hardware" => ..., "Sales" => ... }
|
60
60
|
|
61
61
|
# Array Value formula: produces 6 records, one for each room.
|
62
|
-
rooms = Employee.map_reduce
|
63
|
-
field :rooms, :formula => :array_values
|
64
|
-
end
|
62
|
+
rooms = Employee.map_reduce(:rooms, :formula => :array_values)
|
65
63
|
rooms.length # => 6
|
66
64
|
rooms.find(1)._count # => 3
|
67
65
|
rooms.counts["5"] # => 2
|
data/lib/mongoid/mapreduce.rb
CHANGED
@@ -11,18 +11,8 @@ module Mongoid
|
|
11
11
|
#
|
12
12
|
# Returns a Hash of results
|
13
13
|
def map_reduce(map_key=:_id, options={}, &block)
|
14
|
-
|
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
|
-
options[:fields].each do |f|
|
22
|
-
reducer.field f.to_sym
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
14
|
+
options[:map_key] = map_key
|
15
|
+
reducer = Reducer.new(self, criteria.selector, options)
|
26
16
|
reducer.instance_eval(&block) if block.present?
|
27
17
|
reducer.run
|
28
18
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'mongoid/mapreduce/serialization'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module MapReduce
|
5
|
+
module Formula
|
6
|
+
|
7
|
+
class AggregateFields
|
8
|
+
include Mongoid::MapReduce::Serialization
|
9
|
+
|
10
|
+
def initialize(fields, options={})
|
11
|
+
options[:map_key] ||= :_id
|
12
|
+
options[:count_field] ||= :_count
|
13
|
+
|
14
|
+
@map_key = options[:map_key]
|
15
|
+
@count_field = options[:count_field]
|
16
|
+
@fields = fields
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate a map function
|
20
|
+
# Emits the map key with an array of field values
|
21
|
+
#
|
22
|
+
# Returns String
|
23
|
+
def map
|
24
|
+
fn = "function() { "
|
25
|
+
fn << "emit (this.#{@map_key}, [#{[1, @fields.collect{|k,v| "this.#{k}"}].flatten.join(", ")}]); "
|
26
|
+
fn << "}"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Generates a reduce function
|
30
|
+
# Adds each value in the given array
|
31
|
+
#
|
32
|
+
# Returns String
|
33
|
+
def reduce
|
34
|
+
fn = "function(k, v) { "
|
35
|
+
fn << "var results = [#{(["0"] * (@fields.length + 1)).flatten.join(", ")}]; "
|
36
|
+
fn << "v.forEach(function(val) { "
|
37
|
+
fn << "for(var i=0; i<= #{@fields.length}; i++) { "
|
38
|
+
fn << "results[i] += (typeof val[i] == Boolean) ? (val[i] ? 1 : 0) : val[i] "
|
39
|
+
fn << "} "
|
40
|
+
fn << "}); "
|
41
|
+
fn << "return results.toString(); "
|
42
|
+
fn << "}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Process the results of a given collection
|
46
|
+
#
|
47
|
+
# collection - the MongoDB collection returned from the map/reduce op
|
48
|
+
#
|
49
|
+
# Returns Results
|
50
|
+
def process(collection)
|
51
|
+
return collection.inject(Results.new) do |h, k|
|
52
|
+
key = k.values[0]
|
53
|
+
vals = (k.values[1].is_a?(String) ? k.values[1].split(',') : k.values[1])
|
54
|
+
doc = Document.new :_key_name => @map_key.to_s, :_key_value => key, @map_key => key, @count_field => vals[0].to_i
|
55
|
+
@fields.each_with_index do |f, i|
|
56
|
+
doc[f[0].to_sym] = serialize(vals[i + 1], f[1][:type])
|
57
|
+
end
|
58
|
+
h << doc
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'mongoid/mapreduce/serialization'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module MapReduce
|
5
|
+
module Formula
|
6
|
+
|
7
|
+
class ArrayValues
|
8
|
+
include Mongoid::MapReduce::Serialization
|
9
|
+
|
10
|
+
def initialize(fields, options={})
|
11
|
+
options[:map_key] ||= :_id
|
12
|
+
options[:count_type] ||= Integer
|
13
|
+
options[:count_field] ||= :_count
|
14
|
+
|
15
|
+
if fields.length > 1
|
16
|
+
raise "Error: The Array Values formula can only take 1 field"
|
17
|
+
end
|
18
|
+
|
19
|
+
@field_name = options[:map_key]
|
20
|
+
@field_type = options[:count_type]
|
21
|
+
@count_field = options[:count_field]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generate a map function
|
25
|
+
# Emits the value 1 for each value of the given array field
|
26
|
+
#
|
27
|
+
# Returns String
|
28
|
+
def map
|
29
|
+
fn = "function() { "
|
30
|
+
fn << "this.#{@field_name.to_s}.forEach(function(value) { "
|
31
|
+
fn << "emit(value, 1); "
|
32
|
+
fn << "}); "
|
33
|
+
fn << "}"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generates a reduce function
|
37
|
+
# Adds the given values
|
38
|
+
#
|
39
|
+
# Returns String
|
40
|
+
def reduce
|
41
|
+
fn = "function(k, v) { "
|
42
|
+
fn << "var result = 0; "
|
43
|
+
fn << "v.forEach(function(val) { result += val; }); "
|
44
|
+
fn << "return result; "
|
45
|
+
fn << "}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Process the results of a given collection
|
49
|
+
#
|
50
|
+
# collection - the MongoDB collection returned from the map/reduce op
|
51
|
+
#
|
52
|
+
# Returns Results
|
53
|
+
def process(collection)
|
54
|
+
return collection.inject(Results.new) do |h, k|
|
55
|
+
key = k.values[0].to_s =~ /(^[-+]?[0-9]+$)|(\.0+)$/ ? Integer(k.values[0]) : Float(k.values[0])
|
56
|
+
val = serialize(k.values[1].is_a?(String) ? k.values[1].split(',') : k.values[1], @field_type)
|
57
|
+
h << Document.new(:_key_name => @field_name, :_key_value => key, key.to_s => val, @count_field => val)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'mongoid/mapreduce/formula/aggregate_fields.rb'
|
2
|
+
require 'mongoid/mapreduce/formula/array_values.rb'
|
3
|
+
|
1
4
|
module Mongoid
|
2
5
|
module MapReduce
|
3
6
|
|
@@ -9,100 +12,41 @@ module Mongoid
|
|
9
12
|
#
|
10
13
|
# klass - Mongoid model Class
|
11
14
|
# selector - Selector to use for search (often from criteria)
|
12
|
-
#
|
15
|
+
# options - Hash of options:
|
16
|
+
# count_field - Name of field used to store count in results
|
17
|
+
# formula - Name of formula to be used (underscore)
|
18
|
+
# map_key - Name of field used as key in map function
|
13
19
|
#
|
14
20
|
# Returns nothing
|
15
|
-
def initialize(klass, selector,
|
21
|
+
def initialize(klass, selector, options)
|
22
|
+
options[:klass] = klass
|
23
|
+
options[:selector] = selector
|
24
|
+
options[:formula] ||= :aggregate_fields
|
25
|
+
|
16
26
|
@klass = klass
|
17
27
|
@selector = selector
|
18
|
-
@
|
19
|
-
@
|
28
|
+
@formula_name = options[:formula]
|
29
|
+
@options = options
|
20
30
|
@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?
|
35
|
-
end
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
#
|
42
|
-
# Returns String
|
43
|
-
def map
|
44
|
-
fn = "function() { "
|
45
|
-
if map_from_array?
|
46
|
-
fn << map_array_values(map_array_field)
|
47
|
-
else
|
48
|
-
fn << map_aggregates
|
32
|
+
if options.key?(:fields)
|
33
|
+
options[:fields].each do |f|
|
34
|
+
field f.to_sym
|
35
|
+
end
|
49
36
|
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
37
|
end
|
61
38
|
|
62
|
-
#
|
63
|
-
#
|
39
|
+
# Expose the currently selected formula, from instance variable if possible.
|
64
40
|
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
fn = "function(k, v) { "
|
74
|
-
if map_from_array?
|
75
|
-
fn << reduce_array_values
|
76
|
-
else
|
77
|
-
fn << reduce_aggregates
|
41
|
+
# Returns Formula object.
|
42
|
+
def formula
|
43
|
+
# Find and initialize our formula
|
44
|
+
klass = "Mongoid::MapReduce::Formula::#{@formula_name.to_s.camelize}"
|
45
|
+
begin
|
46
|
+
@formula ||= klass.constantize.new(@fields, @options)
|
47
|
+
rescue NameError
|
48
|
+
raise "Could not load formula for #{klass}"
|
78
49
|
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] += (typeof val[i] == Boolean) ? (val[i] ? 1 : 0) : 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; "
|
106
50
|
end
|
107
51
|
|
108
52
|
# Adds a field to the map/reduce operation
|
@@ -112,49 +56,21 @@ module Mongoid
|
|
112
56
|
# Returns nothing.
|
113
57
|
def field(sym, options={})
|
114
58
|
options[:type] ||= Integer
|
115
|
-
options[:formula] ||= :aggregate
|
116
59
|
@fields[sym.to_sym] = options
|
117
60
|
end
|
118
61
|
|
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.is_a?(Boolean) ? (obj ? 1 : 0) : obj
|
128
|
-
obj = obj.to_s =~ /(^[-+]?[0-9]+$)|(\.0+)$/ ? Integer(obj) : Float(obj)
|
129
|
-
Mongoid::Fields::Mappings.for(klass).allocate.serialize(obj)
|
130
|
-
end
|
131
|
-
|
132
62
|
# Runs the map/reduce operation and returns the result
|
133
63
|
#
|
134
64
|
# Returns Mongoid::MapReduce::Results object (array)
|
135
65
|
# containing Mongoid::MapReduce::Document objects (hashes)
|
136
66
|
def run
|
137
67
|
begin
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
d = (k.values[1].is_a?(String) ? k.values[1].split(',') : k.values[1])
|
142
|
-
|
143
|
-
if d.is_a?(Array)
|
144
|
-
doc = Document.new :_key_name => @map_key.to_s, :_key_value => idx, @map_key => idx, @count_field => d[0].to_i
|
145
|
-
@fields.each_with_index do |h, i|
|
146
|
-
doc[h[0].to_sym] = serialize(d[i + 1], h[1][:type])
|
147
|
-
end
|
148
|
-
else
|
149
|
-
f = map_array_field[0]
|
150
|
-
k = serialize(idx, map_array_field[1][:type])
|
151
|
-
v = d.to_i
|
152
|
-
doc = Document.new :_key_name => f, :_key_value => k, k.to_s => v, @count_field => v
|
153
|
-
end
|
154
|
-
h << doc
|
155
|
-
end
|
68
|
+
coll = @klass.collection.map_reduce(formula.map, formula.reduce, { query: @selector, out: "#map_reduce" } ).find.to_a
|
69
|
+
rescue
|
70
|
+
raise "Error: could not execute map reduce function"
|
156
71
|
end
|
157
72
|
|
73
|
+
formula.process(coll)
|
158
74
|
end
|
159
75
|
|
160
76
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module MapReduce
|
3
|
+
module Serialization
|
4
|
+
|
5
|
+
# Serialize an object to the specified class
|
6
|
+
#
|
7
|
+
# obj - Object to serialize
|
8
|
+
# klass - Class to prefer
|
9
|
+
#
|
10
|
+
# Returns serialized object or nil
|
11
|
+
def serialize(obj, klass)
|
12
|
+
return nil if obj.blank?
|
13
|
+
obj = obj.is_a?(Boolean) ? (obj ? 1 : 0) : obj
|
14
|
+
obj = obj.to_s =~ /(^[-+]?[0-9]+$)|(\.0+)$/ ? Integer(obj) : Float(obj)
|
15
|
+
Mongoid::Fields::Mappings.for(klass).allocate.serialize(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
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.3
|
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-24 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mongoid
|
16
|
-
requirement: &
|
16
|
+
requirement: &70361782922640 !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: *70361782922640
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bson_ext
|
27
|
-
requirement: &
|
27
|
+
requirement: &70361782922040 !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: *70361782922040
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: growl
|
38
|
-
requirement: &
|
38
|
+
requirement: &70361782921580 !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: *70361782921580
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rake
|
49
|
-
requirement: &
|
49
|
+
requirement: &70361782920960 !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: *70361782920960
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rspec
|
60
|
-
requirement: &
|
60
|
+
requirement: &70361782920360 !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: *70361782920360
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: guard-rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &70361782919780 !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: *70361782919780
|
80
80
|
description: Mongoid MapReduce provides simple aggregation features for your Mongoid
|
81
81
|
models
|
82
82
|
email:
|
@@ -87,8 +87,11 @@ extra_rdoc_files: []
|
|
87
87
|
files:
|
88
88
|
- lib/mongoid/mapreduce/base.rb
|
89
89
|
- lib/mongoid/mapreduce/document.rb
|
90
|
+
- lib/mongoid/mapreduce/formula/aggregate_fields.rb
|
91
|
+
- lib/mongoid/mapreduce/formula/array_values.rb
|
90
92
|
- lib/mongoid/mapreduce/reducer.rb
|
91
93
|
- lib/mongoid/mapreduce/results.rb
|
94
|
+
- lib/mongoid/mapreduce/serialization.rb
|
92
95
|
- lib/mongoid/mapreduce/version.rb
|
93
96
|
- lib/mongoid/mapreduce.rb
|
94
97
|
- lib/mongoid-mapreduce.rb
|