mongoid-mapreduce 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
@@ -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.
@@ -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.
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc 'Run all specs in the spec directory'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
@@ -0,0 +1 @@
1
+ require 'mongoid/mapreduce'
@@ -0,0 +1,6 @@
1
+ require 'mongoid'
2
+ require 'mongoid/mapreduce/base'
3
+ require 'mongoid/mapreduce/document'
4
+ require 'mongoid/mapreduce/reducer'
5
+ require 'mongoid/mapreduce/results'
6
+ require 'mongoid/mapreduce/version'
@@ -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
@@ -0,0 +1,7 @@
1
+ module Mongoid
2
+ module MapReduce
3
+
4
+ VERSION = '0.1.0'
5
+
6
+ end
7
+ 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: []