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 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: []