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 ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode # JRuby in 1.8 mode
7
+ - jruby-19mode # JRuby in 1.9 mode
8
+ - rbx-18mode
9
+ - rbx-19mode
10
+ script: bundle exec rake spec
data/Gemfile CHANGED
@@ -9,10 +9,17 @@ end
9
9
 
10
10
  group :development, :test do
11
11
  # ORM
12
- gem "mongoid", "~> 2.2"
13
- gem "bson_ext"
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
@@ -9,3 +9,8 @@ rescue LoadError
9
9
  abort "Jasmine is not available. In order to run jasmine, you must: (sudo) gem install jasmine"
10
10
  end
11
11
  end
12
+
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new do |t|
15
+ t.rspec_opts = ["--color", '--format doc']
16
+ end
@@ -1,44 +1,47 @@
1
1
  require 'digest/md5'
2
2
 
3
3
  module MongoScript
4
- # Execute raw code on the Mongo server
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 < Errno::ENOENT; end
11
+ class ScriptNotFound < StandardError; end
19
12
  class NoScriptDirectory < ArgumentError; end
20
13
  class ExecutionFailure < RuntimeError; end
21
14
 
22
- def self.included(base)
23
- base.class_eval do
24
- class << self
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: resolve_arguments(args)}.merge(options))
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
@@ -1,3 +1,4 @@
1
+ # @private
1
2
  # TBD: do we need this?
2
3
  module MongoidDocumentMethods
3
4
  module ClassMethods
@@ -1,3 +1,3 @@
1
1
  module Mongoscript
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
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
- There's also no guarantee that using Javascript to perform semi-complex queries is actually worth it -- Javascript can be significantly slower. I'll post some performance statistics soon.
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
- ###The cool stuff###
33
+ When run locally, it doesn't make a significant difference:
18
34
 
19
- You understand all that, you just want to hack some server-side code for the fun of it? Let's get to it!
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
- ####More readme coming soon!####
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 < Errno::ENOENT" do
22
- MongoScript::Execution::ScriptNotFound.superclass.should == Errno::ENOENT
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(:exist?).returns(false)
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: 1, _type: 1}
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
- MongoScript.stubs(:const_defined?).with("Mongoid").returns(false)
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
@@ -1,5 +1,5 @@
1
1
  mongoscript_settings: &mongoscript_settings
2
- host: localhost
2
+ host: 127.0.0.1
3
3
  database: mongoscript_dev
4
4
 
5
5
  development:
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.8
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-02-27 00:00:00.000000000 Z
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: &70212414216240 !ruby/object:Gem::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: *70212414216240
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: []