mongoscript 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +10 -0
- data/Gemfile +9 -2
- data/Rakefile +5 -0
- data/lib/mongoscript/execution.rb +57 -27
- data/lib/mongoscript/multiquery.rb +4 -8
- data/lib/mongoscript/orm/mongoid_adapter.rb +14 -6
- data/lib/mongoscript/orm/mongoid_document_methods.rb +1 -0
- data/lib/mongoscript/version.rb +1 -1
- data/lib/mongoscript.rb +1 -1
- data/mongoscript.gemspec +1 -1
- data/readme.md +41 -4
- data/spec/cases/execution_spec.rb +3 -3
- data/spec/cases/mongoid_adapter_spec.rb +1 -1
- data/spec/cases/mongoscript_spec.rb +1 -1
- data/spec/fixtures/mongoid.yml +1 -1
- metadata +6 -5
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -9,10 +9,17 @@ end
|
|
9
9
|
|
10
10
|
group :development, :test do
|
11
11
|
# ORM
|
12
|
-
gem "mongoid", "~> 2.
|
13
|
-
|
12
|
+
gem "mongoid", "~> 2.3.0"
|
13
|
+
# mongo & bson 1.6 generate invalid gemspecs in rbx
|
14
|
+
gem "mongo", "~> 1.5.0"
|
15
|
+
if defined? JRUBY_VERSION
|
16
|
+
gem "bson"
|
17
|
+
else
|
18
|
+
gem "bson_ext", "~> 1.5.0"
|
19
|
+
end
|
14
20
|
|
15
21
|
# Testing infrastructure
|
22
|
+
gem 'rake'
|
16
23
|
gem 'rspec'
|
17
24
|
gem 'mocha'
|
18
25
|
gem 'guard'
|
data/Rakefile
CHANGED
@@ -1,44 +1,47 @@
|
|
1
1
|
require 'digest/md5'
|
2
2
|
|
3
3
|
module MongoScript
|
4
|
-
# Execute
|
5
|
-
# code_for can
|
6
|
-
# longer term, we could store functions on the server
|
7
|
-
# and have deploy tasks that delete all and reinstall stored method on deploys
|
8
|
-
# see http://neovintage.blogspot.com/2010/07/mongodb-stored-javascript-functions.html
|
9
|
-
# 10gen recommends not using stored functions, but that's mainly b/c
|
10
|
-
# they should be stored and versioned with other code
|
11
|
-
# but a cool 6W gem for automatically clearing and installing such code would meet that objection
|
12
|
-
# see http://www.mongodb.org/display/DOCS/Server-side+Code+Execution#Server-sideCodeExecution-Storingfunctionsserverside
|
4
|
+
# Execute code on the Mongo server.
|
13
5
|
|
14
6
|
module Execution
|
7
|
+
extend ActiveSupport::Concern
|
15
8
|
|
16
9
|
LOADED_SCRIPTS = {}
|
17
10
|
|
18
|
-
class ScriptNotFound <
|
11
|
+
class ScriptNotFound < StandardError; end
|
19
12
|
class NoScriptDirectory < ArgumentError; end
|
20
13
|
class ExecutionFailure < RuntimeError; end
|
21
14
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
attr_accessor :script_dirs
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.gem_path
|
29
|
-
mongoscript = Bundler.load.specs.find {|s| s.name == "mongoscript"}
|
30
|
-
File.join(mongoscript.full_gem_path, "lib", "mongoscript", "javascripts")
|
31
|
-
end
|
32
|
-
|
33
|
-
# start out with the scripts provided by the gem
|
34
|
-
@script_dirs = [gem_path]
|
35
|
-
|
36
|
-
extend MongoScript::Execution::ClassMethods
|
15
|
+
included do
|
16
|
+
class << self
|
17
|
+
attr_accessor :script_dirs
|
37
18
|
end
|
19
|
+
|
20
|
+
# start out with the scripts provided by the gem
|
21
|
+
@script_dirs = [gem_path]
|
38
22
|
end
|
39
23
|
|
40
24
|
module ClassMethods
|
41
|
-
# code from stored files
|
25
|
+
# Get code from stored files.
|
26
|
+
# This looks through each of the script directories, returning the first match it finds.
|
27
|
+
#
|
28
|
+
# Longer term, we could store functions on the server
|
29
|
+
# keyed by both name and a hash of the contents (to ensure uniqueness).
|
30
|
+
#
|
31
|
+
# @see http://neovintage.blogspot.com/2010/07/mongodb-stored-javascript-functions.html.
|
32
|
+
# @see http://www.mongodb.org/display/DOCS/Server-side+Code+Execution#Server-sideCodeExecution-Storingfunctionsserverside
|
33
|
+
#
|
34
|
+
# @note 10gen recommends not using stored functions, but that's mainly because
|
35
|
+
# they should be stored and versioned with other code.
|
36
|
+
# However, automatic installation and versioning via MD5 hashes should meet that objection.
|
37
|
+
#
|
38
|
+
# @param script_name the name of the script file (without .js)
|
39
|
+
#
|
40
|
+
# @note this is currently cached in LOADED_SCRIPTS without hashing. (To do!)
|
41
|
+
#
|
42
|
+
# @raises ScriptNotFound if a file with the provided basename doesn't exist in any provided directory.
|
43
|
+
#
|
44
|
+
# @returns the script contents
|
42
45
|
def code_for(script_name)
|
43
46
|
script_name = script_name.to_s
|
44
47
|
dir = @script_dirs.find {|d| File.exists?(File.join(d, "#{script_name}.js"))}
|
@@ -56,10 +59,30 @@ module MongoScript
|
|
56
59
|
# code
|
57
60
|
end
|
58
61
|
|
62
|
+
# Execute read-only code stored in a file.
|
63
|
+
# The code will be run non-blockingly;
|
64
|
+
# it should not contain any statements that write to the database.
|
65
|
+
#
|
66
|
+
# @note The name of this function may change before 1.0.
|
67
|
+
#
|
68
|
+
# @param script_name the name of the script file (see #code_for)
|
69
|
+
# @param args any arguments to the function
|
70
|
+
#
|
71
|
+
# @returns the result of the Mongo call
|
59
72
|
def execute_readonly_routine(script_name, *args)
|
60
73
|
execute_readonly_code(code_for(script_name), *args)
|
61
74
|
end
|
62
75
|
|
76
|
+
# Execute code stored in a file that can write to the database.
|
77
|
+
#
|
78
|
+
# @note This call will block MongoDB, so use this method sparingly.
|
79
|
+
#
|
80
|
+
# @note (see #execute_readonly_routine)
|
81
|
+
#
|
82
|
+
# @param script_name the name of the script file (see #code_for)
|
83
|
+
# @param args any arguments to the function
|
84
|
+
#
|
85
|
+
# @returns (see #execute_readonly_routine)
|
63
86
|
def execute_readwrite_routine(script_name, *args)
|
64
87
|
execute_readwrite_code(code_for(script_name), *args)
|
65
88
|
end
|
@@ -79,12 +102,19 @@ module MongoScript
|
|
79
102
|
|
80
103
|
def execute(code, args = [], options = {})
|
81
104
|
# see http://mrdanadams.com/2011/mongodb-eval-ruby-driver/
|
82
|
-
result = MongoScript.database.command({:$eval => code, args
|
105
|
+
result = MongoScript.database.command({:$eval => code, :args => resolve_arguments(args)}.merge(options))
|
83
106
|
unless result["ok"] == 1.0
|
84
107
|
raise ExecutionFailure, "MongoScript.execute JS didn't return {ok: 1.0}! Result: #{result.inspect}"
|
85
108
|
end
|
86
109
|
result["retval"]
|
87
110
|
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
def gem_path
|
115
|
+
mongoscript = Bundler.load.specs.find {|s| s.name == "mongoscript"}
|
116
|
+
File.join(mongoscript.full_gem_path, "lib", "mongoscript", "javascripts")
|
117
|
+
end
|
88
118
|
end
|
89
119
|
end
|
90
120
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module MongoScript
|
2
2
|
module Multiquery
|
3
|
+
extend ActiveSupport::Concern
|
3
4
|
|
5
|
+
# An error class representing Mongo queries that for some reason failed in the Javascript.
|
6
|
+
# This error will never be raised; instead it will be returned as an object in the results array.
|
4
7
|
class QueryFailedError < RuntimeError
|
5
8
|
# The original query whose execution failed.
|
6
9
|
attr_accessor :query_parameters
|
@@ -9,6 +12,7 @@ module MongoScript
|
|
9
12
|
# The response from the multiquery Javascript.
|
10
13
|
attr_accessor :db_response
|
11
14
|
|
15
|
+
# Initialize the error, and set its backtrace.
|
12
16
|
def initialize(name, query, response)
|
13
17
|
@query_name = name
|
14
18
|
@query_parameters = query
|
@@ -19,15 +23,7 @@ module MongoScript
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
|
-
# @private
|
23
|
-
def self.included(base)
|
24
|
-
base.class_eval do
|
25
|
-
extend MongoScript::Multiquery::ClassMethods
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
26
|
module ClassMethods
|
30
|
-
|
31
27
|
# Runs multiple find queries at once,
|
32
28
|
# returning the results keyed to the original query names.
|
33
29
|
# If a query produces an error, its result will be a QueryFailedError object
|
@@ -1,18 +1,21 @@
|
|
1
1
|
module MongoScript
|
2
2
|
module ORM
|
3
3
|
module MongoidAdapter
|
4
|
-
|
5
|
-
def self.included(base)
|
6
|
-
base.class_eval do
|
7
|
-
extend MongoScript::ORM::MongoidAdapter::ClassMethods
|
8
|
-
end
|
9
|
-
end
|
4
|
+
extend ActiveSupport::Concern
|
10
5
|
|
11
6
|
module ClassMethods
|
7
|
+
# The MongoDB database.
|
12
8
|
def database
|
13
9
|
Mongoid::Config.database
|
14
10
|
end
|
15
11
|
|
12
|
+
# Turn a raw hash returned by the MongoDB driver into a regular Ruby object.
|
13
|
+
# For this adapter, it a Mongoid document.
|
14
|
+
#
|
15
|
+
# @param klass an ORM class that can instantiate objects from data
|
16
|
+
# @param data raw data returned from the database
|
17
|
+
#
|
18
|
+
# @returns a Ruby object
|
16
19
|
def rehydrate(klass, data)
|
17
20
|
Mongoid::Factory.from_db(klass, data)
|
18
21
|
end
|
@@ -73,6 +76,11 @@ module MongoScript
|
|
73
76
|
end
|
74
77
|
end
|
75
78
|
|
79
|
+
# Answers whether a given non-hash object can be turned into a hash
|
80
|
+
# suitable for database queries.
|
81
|
+
# Currently, it checks if the object is a Mongoid::Criteria.
|
82
|
+
#
|
83
|
+
# @param object the object to test
|
76
84
|
def processable_into_parameters?(object)
|
77
85
|
object.is_a?(Mongoid::Criteria)
|
78
86
|
end
|
data/lib/mongoscript/version.rb
CHANGED
data/lib/mongoscript.rb
CHANGED
@@ -15,7 +15,7 @@ module MongoScript
|
|
15
15
|
#
|
16
16
|
# @returns MongoScript::ORM::Mongoid if Mongoid is detected
|
17
17
|
def self.orm_adapter
|
18
|
-
if const_defined? "Mongoid"
|
18
|
+
if Object.const_defined? "Mongoid"
|
19
19
|
MongoScript::ORM::MongoidAdapter
|
20
20
|
else
|
21
21
|
raise NoORMError, "Unable to locate Mongoid!"
|
data/mongoscript.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
|
|
6
6
|
gem.email = ["alex+git@alexkoppel.com"]
|
7
7
|
gem.description = %q{An experimental Ruby library for running serverside Javascript in MongoDB.}
|
8
8
|
gem.summary = %q{An experimental Ruby library for running serverside Javascript in MongoDB.}
|
9
|
-
gem.homepage = ""
|
9
|
+
gem.homepage = "http://github.com/arsduo/mongoscript"
|
10
10
|
|
11
11
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
12
|
gem.files = `git ls-files`.split("\n")
|
data/readme.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[![Build Status](https://secure.travis-ci.org/arsduo/mongoscript.png)](http://travis-ci.org/arsduo/mongoscript)
|
2
|
+
|
1
3
|
#MongoScript#
|
2
4
|
|
3
5
|
An experimental library designed to make server-side Javascript execution in MongoDB easy, fun, and profitable.
|
@@ -10,13 +12,48 @@ This library isn't "experimental" only in the sense that I'm trying out new code
|
|
10
12
|
|
11
13
|
If you're building a small-to-medium-sized system that you know will never grow huge, you may be able to use MongoScript to very cool effect. I wouldn't recommend it for any kind of "we're gonna scale it to the moon!" kind of product, though. Disentangling Javascript functions and reimplementing them in Ruby so you can shard your growing database doesn't sound like my kind of fun.
|
12
14
|
|
15
|
+
###The cool stuff###
|
16
|
+
|
17
|
+
You understand all that, you just want to hack some server-side code for the fun of it? Let's get to it!
|
18
|
+
|
19
|
+
First, what are a few things you could do with Mongo server-side Javascript?
|
20
|
+
|
21
|
+
* Multiquery: execute multiple find queries in one call. (See Performance below.)
|
22
|
+
* Join: Mongo's (in)famous for not offering native joins, but a Javascript method to fetch from collection A and then get any referenced records from collection B won't be hard.
|
23
|
+
* Easy maintenance: why transfer records and updates back forth between your web server and your database, when you can do everything at localhost speed?
|
24
|
+
|
25
|
+
You'll notice a theme: making one connection to the database and executing multiple operations. That's where server-side Javascript shines: it's a language you already know that can do whatever you need it to, all within the context of your server.
|
26
|
+
|
27
|
+
(more ideas/explanation coming soon)
|
28
|
+
|
13
29
|
###Performance###
|
14
30
|
|
15
|
-
|
31
|
+
Just to show what a Javascript-based approach to Mongo can do, let's take a look at multiquery, one of the first tools built into MongoScript. This lets you execute multiple find queries easily using a single database connection.
|
16
32
|
|
17
|
-
|
33
|
+
When run locally, it doesn't make a significant difference:
|
18
34
|
|
19
|
-
|
35
|
+
```
|
36
|
+
# Macbook Pro running Ruby and MongoDB locally
|
37
|
+
# 100 run average
|
38
|
+
Traditional execution took 0.38300072999999996
|
39
|
+
Multiquery execution took 0.37415497999999997
|
40
|
+
```
|
41
|
+
|
42
|
+
But check out what happens if the web server and the database aren't on the same machine:
|
43
|
+
|
44
|
+
```
|
45
|
+
# Test web and MongoDB servers in the same EC2 location
|
46
|
+
# 100 run average
|
47
|
+
Traditional execution took 0.02256884963
|
48
|
+
Multiquery execution took 0.01187204267
|
49
|
+
```
|
50
|
+
|
51
|
+
The results hold up over multiple runs, and I suspect would be even stronger with writes.
|
52
|
+
|
53
|
+
###Coming Soon###
|
54
|
+
|
55
|
+
A more detailed readme, with usage instructions and so on.
|
20
56
|
|
57
|
+
###Note on Dependencies###
|
21
58
|
|
22
|
-
|
59
|
+
Since this is currently experimental/for fun, I'm relying on ActiveSupport for HashWithIndifferentAccess and Concern. I acknowledge this restricts the gem to projects where ActiveSupport is an option, but should be easy enough to rip out if someone wants to use this seriously.
|
@@ -18,8 +18,8 @@ describe MongoScript::Execution do
|
|
18
18
|
MongoScript::Execution::LOADED_SCRIPTS.should be_a(Hash)
|
19
19
|
end
|
20
20
|
|
21
|
-
it "defines ScriptNotFound error
|
22
|
-
MongoScript::Execution::ScriptNotFound.superclass.should ==
|
21
|
+
it "defines ScriptNotFound error" do
|
22
|
+
MongoScript::Execution::ScriptNotFound.superclass.should == StandardError
|
23
23
|
end
|
24
24
|
|
25
25
|
it "defines ExecutionFailure error < RuntimeError" do
|
@@ -62,7 +62,7 @@ describe MongoScript::Execution do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
it "raises a ScriptNotFound error if the file doesn't exist" do
|
65
|
-
File.stubs(:
|
65
|
+
File.stubs(:exists?).returns(false)
|
66
66
|
expect { ObjectWithExecution.code_for("i don't exist") }.to raise_exception(ObjectWithExecution::ScriptNotFound)
|
67
67
|
end
|
68
68
|
|
@@ -105,7 +105,7 @@ describe MongoScript::ORM::MongoidAdapter do
|
|
105
105
|
end
|
106
106
|
|
107
107
|
it "returns the fields to get as :fields" do
|
108
|
-
ObjectWithMongoidAdapter.build_multiquery_parameters(criteria)[:fields].should == {_id
|
108
|
+
ObjectWithMongoidAdapter.build_multiquery_parameters(criteria)[:fields].should == {:_id => 1, :_type => 1}
|
109
109
|
end
|
110
110
|
|
111
111
|
it "returns all other options as :modifiers" do
|
@@ -18,7 +18,7 @@ describe MongoScript do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
it "raises a NoORMError if no Mongo ORM is available" do
|
21
|
-
|
21
|
+
Object.stubs(:const_defined?).with("Mongoid").returns(false)
|
22
22
|
expect { MongoScript.orm_adapter }.to raise_exception(MongoScript::NoORMError)
|
23
23
|
end
|
24
24
|
end
|
data/spec/fixtures/mongoid.yml
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoscript
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
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: 2012-
|
12
|
+
date: 2012-03-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &70280991269600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70280991269600
|
25
25
|
description: An experimental Ruby library for running serverside Javascript in MongoDB.
|
26
26
|
email:
|
27
27
|
- alex+git@alexkoppel.com
|
@@ -30,6 +30,7 @@ extensions: []
|
|
30
30
|
extra_rdoc_files: []
|
31
31
|
files:
|
32
32
|
- .gitignore
|
33
|
+
- .travis.yml
|
33
34
|
- Gemfile
|
34
35
|
- Guardfile
|
35
36
|
- Rakefile
|
@@ -56,7 +57,7 @@ files:
|
|
56
57
|
- spec/spec_helper.rb
|
57
58
|
- spec/support/mongoid.yml
|
58
59
|
- spec/support/mongoid_classes.rb
|
59
|
-
homepage:
|
60
|
+
homepage: http://github.com/arsduo/mongoscript
|
60
61
|
licenses: []
|
61
62
|
post_install_message:
|
62
63
|
rdoc_options: []
|