light_mongo 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.md +134 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/integration/basic_storage_cycle_spec.rb +21 -0
- data/integration/cross_collection_relationships_spec.rb +29 -0
- data/integration/dynamic_finders_spec.rb +33 -0
- data/integration/meta_spec.rb +20 -0
- data/integration/object_embedding_spec.rb +42 -0
- data/integration/support/integration_helper.rb +9 -0
- data/lib/document/persistence.rb +84 -0
- data/lib/document/serialization/hash_serializer.rb +74 -0
- data/lib/document/serialization/serializer.rb +76 -0
- data/lib/document/serialization.rb +45 -0
- data/lib/document.rb +20 -0
- data/lib/light_mongo.rb +3 -0
- data/lib/util.rb +20 -0
- data/spec/document/persistence_spec.rb +219 -0
- data/spec/document/serialization/hash_serializer_spec.rb +153 -0
- data/spec/document/serialization/serializer_spec.rb +237 -0
- data/spec/document/serialization_spec.rb +75 -0
- data/spec/document_spec.rb +19 -0
- metadata +91 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Elliot Crosby-McCullough
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
LightMongo
|
2
|
+
==========
|
3
|
+
LightMongo is a lightweight Mongo object persistence layer for Ruby which makes use of Mongo's features rather than trying to emulate ActiveRecord.
|
4
|
+
|
5
|
+
Status
|
6
|
+
-----------------
|
7
|
+
LightMongo is only a few days old, so while most of the features demo'd below work, there's no strong integration testing yet, so I wouldn't use it for data you particularly care about until I release a gem.
|
8
|
+
|
9
|
+
Check out the development roadmap for an idea of my current priorities.
|
10
|
+
|
11
|
+
The problem
|
12
|
+
-----------
|
13
|
+
Developers occasionally encounter a domain which defies simple modelling in an ActiveRecord relational style, and look to some of the nosql databases for a solution. They find Mongo, a document database, and feel it might provide the flexibility they need. After a bit of research they pick out a persistence library which seems popular and well maintained. It even emulates most of ActiveRecord's behaviour, style and relational philosophy. Great!
|
14
|
+
|
15
|
+
Hang on a minute, wasn't it ActiveRecord's behaviour, style and relational philosophy they moved to Mongo to get away from?
|
16
|
+
|
17
|
+
The solution
|
18
|
+
------------
|
19
|
+
+ Ruby instances store their state in instance variables. Why do we need to hide this in the persistence layer?
|
20
|
+
+ Ruby has quite the heap of array management operators. Why do we need explicit relationships and relationship proxies?
|
21
|
+
+ Objects of the same class can perform a number of different roles or be related to other classes in lots of ways. Why do we need to jump through complicated and restrictive hoops to do something we do in pure Ruby domains all the time?
|
22
|
+
|
23
|
+
Mongo is a flexible database. We can make use of that flexibility to allow our persistence layer to make decisions on how to best serialise and deserialise our objects. It's our responsibility to make sure our domain is correct. It's the library's responsibility to store those domain objects.
|
24
|
+
|
25
|
+
We're Ruby developers. Let's act like it.
|
26
|
+
|
27
|
+
An example
|
28
|
+
----------
|
29
|
+
class Article
|
30
|
+
include LightMongo::Document
|
31
|
+
end
|
32
|
+
|
33
|
+
geology_article = Article.new(:title => 'Fluid Physics in Geology', :abstract => 'Lorem ipsum dolor..')
|
34
|
+
geology_article.save
|
35
|
+
|
36
|
+
Article.find.first
|
37
|
+
=> #<Article:0x101647448 @_id="4b93c1e97bc7697187000001" @title="Fluid Physics in Geology" @abstract="Lorem upsum dolor...">
|
38
|
+
|
39
|
+
No tables. No database. Save your migrations for when you actually have some data to shift around.
|
40
|
+
|
41
|
+
Slightly more complex
|
42
|
+
---------------------
|
43
|
+
Plain Ruby objects stored in your Documents will be serialised along with the Document and embedded in the Mongo document.
|
44
|
+
|
45
|
+
class Article
|
46
|
+
include LightMongo::Document
|
47
|
+
|
48
|
+
attr_accessor :title, :comments
|
49
|
+
|
50
|
+
def initialize(*args)
|
51
|
+
@comments = []
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Comment
|
57
|
+
attr_accessor :author_name, :text
|
58
|
+
end
|
59
|
+
|
60
|
+
geology_article = Article.create(:title => 'Fluid Physics in Geology')
|
61
|
+
comment = Comment.new
|
62
|
+
comment.author_name = 'Dave'
|
63
|
+
comment.text = "Cool article!"
|
64
|
+
|
65
|
+
geology_article.comments << comment
|
66
|
+
geology_article.save
|
67
|
+
|
68
|
+
first_article = Article.find.first
|
69
|
+
|
70
|
+
first_article.title
|
71
|
+
=> "Fluid Physics in Geology"
|
72
|
+
|
73
|
+
first_article.comments
|
74
|
+
=> [#<Comment:0x101664138 @author_name="Dave" @text="Cool article!">]
|
75
|
+
|
76
|
+
Dynamic finders
|
77
|
+
---------------
|
78
|
+
It's not generally a good idea to do much searching on keys that haven't been indexed (as in most databases), so LightMongo will only set up dynamic finders for attributes you've asked to have indexed. If you really want an unindexed finder, they're not difficult to write.
|
79
|
+
|
80
|
+
class Article
|
81
|
+
include LightMongo::Document
|
82
|
+
attr_reader :page_length
|
83
|
+
index :title
|
84
|
+
index :abstract, :as => :precis
|
85
|
+
end
|
86
|
+
|
87
|
+
geology_article = Article.create(:title => 'Fluid Physics in Geology',
|
88
|
+
:abstract => 'A study in geological fluid physics',
|
89
|
+
:page_length => 367)
|
90
|
+
|
91
|
+
Article.find_by_title('Fluid Physics in Geology').first == geology_article
|
92
|
+
=> true
|
93
|
+
|
94
|
+
Article.find_by_precis('A study in geological fluid physics').first == geology_article
|
95
|
+
=> true
|
96
|
+
|
97
|
+
The aliasing option is not required, but is recommended if you want dynamic finders for indexed keys that can't be represented in a standard Ruby method name (for example, a finder will not be created for a complex multi-level Mongo key index. See the Mongo manual for more information).
|
98
|
+
|
99
|
+
Cross-collection relationships
|
100
|
+
------------------------------
|
101
|
+
LightMongo uses its Document mixin to signify a collection, so if you embed a LightMongo::Document inside another LightMongo::Document, the serialisation engine will consider this a cross-collection relationship and behave accordingly.
|
102
|
+
|
103
|
+
class Article
|
104
|
+
include LightMongo::Document
|
105
|
+
attr_reader :author
|
106
|
+
end
|
107
|
+
|
108
|
+
class Person
|
109
|
+
include LightMongo::Document
|
110
|
+
end
|
111
|
+
|
112
|
+
dave = Person.new(:name => 'Dave')
|
113
|
+
fluid_physics = Article.create(:title => 'Fluid Physics in Geology', :author => dave)
|
114
|
+
|
115
|
+
Person.find.first
|
116
|
+
=> #<Person:0x101664138 @_id="4b93cf9397bc7697187000001" @name="Dave">
|
117
|
+
|
118
|
+
Article.find.first.author == Person.find.first
|
119
|
+
=> true
|
120
|
+
|
121
|
+
Roadmap
|
122
|
+
-------
|
123
|
+
1. Improved testbed to allow stronger integration testing.
|
124
|
+
2. More intelligent and efficient object serialisation.
|
125
|
+
3. Proper deserialisation of cross-collection objects (currently they go in, don't come back out).
|
126
|
+
4. Nested hash serialisation.
|
127
|
+
4. Migrations (e.g. when you rename classes or modify their collection style).
|
128
|
+
5. Some kind of validations, perhaps.
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "light_mongo"
|
8
|
+
gem.summary = %Q{A lightweight Ruby object persistence library for Mongo DB}
|
9
|
+
gem.description = %Q{LightMongo is a lightweight Mongo object persistence layer for Ruby which makes use of Mongo's features rather than trying to emulate ActiveRecord.}
|
10
|
+
gem.email = "elliot.cm@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/elliotcm/light_mongo"
|
12
|
+
gem.authors = ["Elliot Crosby-McCullough"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.3.0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
|
18
|
+
# check_dependencies is defined by jeweler
|
19
|
+
task :spec => :check_dependencies
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'spec/rake/spectask'
|
25
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
spec.spec_opts = ['--color']
|
29
|
+
end
|
30
|
+
|
31
|
+
Spec::Rake::SpecTask.new(:integration) do |spec|
|
32
|
+
spec.libs << 'lib' << 'integration'
|
33
|
+
spec.spec_files = FileList['integration/**/*_spec.rb']
|
34
|
+
spec.spec_opts = ['--color']
|
35
|
+
end
|
36
|
+
|
37
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
38
|
+
spec.libs << 'lib' << 'spec'
|
39
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
40
|
+
spec.rcov = true
|
41
|
+
spec.spec_opts = ['--color']
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
task :default => [:spec, :integration]
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/support/integration_helper')
|
2
|
+
|
3
|
+
class Article
|
4
|
+
include LightMongo::Document
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'The basic storage cycle' do
|
8
|
+
before(:each) do
|
9
|
+
@geology_article = Article.new(:title => (@title = 'Fluid Physics in Geology'),
|
10
|
+
:abstract => (@abstract = 'Lorem ipsum dolor..'))
|
11
|
+
end
|
12
|
+
|
13
|
+
it "allows the storage and retrieval of documents." do
|
14
|
+
@geology_article.save
|
15
|
+
stored_article = Article.find.first
|
16
|
+
stored_article.instance_variable_get('@title').should == @title
|
17
|
+
stored_article.instance_variable_get('@abstract').should == @abstract
|
18
|
+
end
|
19
|
+
|
20
|
+
db_teardown
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/support/integration_helper')
|
2
|
+
|
3
|
+
LightMongo.slow_serialization = true
|
4
|
+
|
5
|
+
class Article
|
6
|
+
include LightMongo::Document
|
7
|
+
attr_reader :author
|
8
|
+
end
|
9
|
+
|
10
|
+
class Person
|
11
|
+
include LightMongo::Document
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'Embedding a LightMongo::Document within another LightMongo::Document' do
|
15
|
+
before(:each) do
|
16
|
+
@dave = Person.new(:name => 'Dave')
|
17
|
+
@geology_article = Article.create(:title => 'Fluid Physics in Geology', :author => @dave)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "allows independent access to the embedded document via its collection." do
|
21
|
+
Person.find.first.should == @dave
|
22
|
+
end
|
23
|
+
|
24
|
+
it "retains a reference to the embedded document in its container document." do
|
25
|
+
Article.find.first.author.should == @dave
|
26
|
+
end
|
27
|
+
|
28
|
+
db_teardown
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/support/integration_helper')
|
2
|
+
|
3
|
+
LightMongo.slow_serialization = true
|
4
|
+
|
5
|
+
class Article
|
6
|
+
include LightMongo::Document
|
7
|
+
|
8
|
+
attr_reader :page_length
|
9
|
+
index :title
|
10
|
+
index :abstract, :as => :precis
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'Indexing an attribute' do
|
14
|
+
before(:each) do
|
15
|
+
@geology_article = Article.create(:title => 'Fluid Physics in Geology',
|
16
|
+
:abstract => 'A study in geological fluid physics',
|
17
|
+
:page_length => 367)
|
18
|
+
end
|
19
|
+
|
20
|
+
context "where an alias is not given" do
|
21
|
+
it "creates a finder using the key name" do
|
22
|
+
Article.find_by_title('Fluid Physics in Geology').first.should == @geology_article
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "where an alias has been given" do
|
27
|
+
it "creates a finder using the alias name" do
|
28
|
+
Article.find_by_precis('A study in geological fluid physics').first.should == @geology_article
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
db_teardown
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/support/integration_helper')
|
2
|
+
|
3
|
+
describe 'Integration tests' do
|
4
|
+
db_teardown
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
class Integration
|
8
|
+
include LightMongo::Document
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:all) do
|
13
|
+
Integration.find.should be_empty
|
14
|
+
end
|
15
|
+
|
16
|
+
it "tears down the database after each run" do
|
17
|
+
Integration.create :name => 'Database teardown example'
|
18
|
+
Integration.find.first.should be_a(Integration)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/support/integration_helper')
|
2
|
+
|
3
|
+
LightMongo.slow_serialization = true
|
4
|
+
|
5
|
+
class Article
|
6
|
+
include LightMongo::Document
|
7
|
+
|
8
|
+
attr_accessor :title, :comments
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
@comments = []
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Comment
|
17
|
+
attr_accessor :author_name, :text
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'Embedding a Ruby object within your LightMongo::Document' do
|
21
|
+
before(:each) do
|
22
|
+
@geology_article = Article.create(:title => (@title = 'Fluid Physics in Geology'))
|
23
|
+
|
24
|
+
@comment = Comment.new
|
25
|
+
@comment.author_name = 'Dave'
|
26
|
+
@comment.text = "Cool article!"
|
27
|
+
|
28
|
+
@geology_article.comments << @comment
|
29
|
+
@geology_article.save
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is represented as embedded MongoDB documents and eager restored." do
|
33
|
+
stored_article = Article.find.first
|
34
|
+
stored_article.title.should == @title
|
35
|
+
|
36
|
+
stored_comment = stored_article.comments.first
|
37
|
+
stored_comment.author_name.should == @comment.author_name
|
38
|
+
stored_comment.text.should == @comment.text
|
39
|
+
end
|
40
|
+
|
41
|
+
db_teardown
|
42
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module LightMongo
|
2
|
+
# Connection and database getters/setters hoofed from jnunemaker's MongoMapper
|
3
|
+
def self.connection
|
4
|
+
@@connection ||= Mongo::Connection.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.connection=(new_connection)
|
8
|
+
@@connection = new_connection
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.database=(name)
|
12
|
+
@@database = nil
|
13
|
+
@@database_name = name
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.database
|
17
|
+
unless defined?(@@database_name) and !Util.blank?(@@database_name)
|
18
|
+
raise 'You forgot to set the default database name: LightMongo.database = "foobar"'
|
19
|
+
end
|
20
|
+
|
21
|
+
@@database ||= LightMongo.connection.db(@@database_name)
|
22
|
+
end
|
23
|
+
|
24
|
+
module Document
|
25
|
+
module Persistence
|
26
|
+
def self.included(document_class)
|
27
|
+
document_class.extend ClassMethods
|
28
|
+
document_class.collection = Mongo::Collection.new(LightMongo.database, document_class.name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def collection
|
32
|
+
self.class.collection
|
33
|
+
end
|
34
|
+
|
35
|
+
def save
|
36
|
+
@_id = collection.save(self.to_hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
def id
|
40
|
+
@_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
self.id == other.id
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
attr_accessor :collection
|
50
|
+
|
51
|
+
def create(params)
|
52
|
+
new_object = new(params)
|
53
|
+
new_object.save
|
54
|
+
return new_object
|
55
|
+
end
|
56
|
+
|
57
|
+
def index(key_name, options={})
|
58
|
+
return if Util.blank?(key_name)
|
59
|
+
|
60
|
+
method_name = 'find_by_'+(options[:as] or key_name).to_s
|
61
|
+
if viable_method_name(method_name)
|
62
|
+
(class << self; self; end).class_eval do
|
63
|
+
define_method method_name.to_sym do |value|
|
64
|
+
collection.find(key_name.to_sym => value).map{|bson_hash| new(bson_hash)}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
collection.create_index(key_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def find(query=nil)
|
73
|
+
query = {'_id' => query} unless query.nil? or query.is_a?(Hash)
|
74
|
+
collection.find(query).map{|bson_hash| new(bson_hash)}
|
75
|
+
end
|
76
|
+
|
77
|
+
def viable_method_name(method_name)
|
78
|
+
method_name =~ /^\w+[!?]?$/
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
|
3
|
+
module LightMongo
|
4
|
+
module Document
|
5
|
+
module Serialization
|
6
|
+
|
7
|
+
class HashSerializer
|
8
|
+
class <<self
|
9
|
+
def dump(object_to_serialize, current_depth=0)
|
10
|
+
case object_to_serialize
|
11
|
+
when Array
|
12
|
+
return serialize_array(object_to_serialize, current_depth)
|
13
|
+
when Hash
|
14
|
+
return serialize_hash(object_to_serialize, current_depth)
|
15
|
+
else
|
16
|
+
return serialize_object(object_to_serialize, current_depth)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize_array(object_to_serialize, current_depth)
|
21
|
+
object_to_serialize.map do |entry|
|
22
|
+
Serializer.serialize(entry, current_depth + 1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize_hash(object_to_serialize, current_depth)
|
27
|
+
outbound_hash = {}
|
28
|
+
object_to_serialize.each_pair do |key, entry|
|
29
|
+
outbound_hash[key] = Serializer.serialize(entry, current_depth + 1)
|
30
|
+
end
|
31
|
+
outbound_hash
|
32
|
+
end
|
33
|
+
|
34
|
+
def serialize_object(object_to_serialize, current_depth)
|
35
|
+
return object_to_serialize if natively_embeddable?(object_to_serialize)
|
36
|
+
|
37
|
+
return object_to_serialize.export if object_to_serialize.is_a? LightMongo::Document and current_depth > 0
|
38
|
+
|
39
|
+
return hashify(object_to_serialize, current_depth)
|
40
|
+
end
|
41
|
+
|
42
|
+
def hashify(object_to_serialize, current_depth)
|
43
|
+
hashed_object = {}
|
44
|
+
hashed_object['_class_name'] = object_to_serialize.class.name if current_depth > 0
|
45
|
+
|
46
|
+
object_to_serialize.instance_variables.each do |attribute_name|
|
47
|
+
new_hash_key = attribute_name.sub(/^@/, '')
|
48
|
+
nested_object = object_to_serialize.instance_variable_get(attribute_name)
|
49
|
+
hashed_object[new_hash_key] = Serializer.serialize(nested_object, current_depth + 1)
|
50
|
+
end
|
51
|
+
|
52
|
+
return hashed_object
|
53
|
+
end
|
54
|
+
|
55
|
+
def natively_embeddable?(object)
|
56
|
+
begin
|
57
|
+
raise_unless_natively_embeddable(object)
|
58
|
+
rescue Mongo::InvalidDocument => e
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
|
65
|
+
def raise_unless_natively_embeddable(object)
|
66
|
+
BSON_RUBY.new.bson_type(object)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/hash_serializer'
|
2
|
+
|
3
|
+
module LightMongo
|
4
|
+
module Document
|
5
|
+
module Serialization
|
6
|
+
class Serializer
|
7
|
+
attr_accessor :depth
|
8
|
+
|
9
|
+
class <<self
|
10
|
+
def deserialize(object_to_deserialize)
|
11
|
+
return array_deserialize(object_to_deserialize) if object_to_deserialize.is_a?(Array)
|
12
|
+
if object_to_deserialize.is_a?(Hash)
|
13
|
+
if object_to_deserialize.has_key?('_data')
|
14
|
+
return Marshal.load(object_to_deserialize['_data'])
|
15
|
+
end
|
16
|
+
|
17
|
+
if object_to_deserialize.has_key?('_class_name')
|
18
|
+
class_name = object_to_deserialize.delete('_class_name')
|
19
|
+
|
20
|
+
if !object_to_deserialize.has_key?('_id')
|
21
|
+
object = Object.const_get(class_name).new
|
22
|
+
object_to_deserialize.each_pair do |attr_name, attr_value|
|
23
|
+
object.instance_variable_set '@'+attr_name, attr_value
|
24
|
+
end
|
25
|
+
|
26
|
+
return object
|
27
|
+
end
|
28
|
+
|
29
|
+
if object_to_deserialize.has_key?('_embed') and object_to_deserialize['_embed'] == true
|
30
|
+
return Object.const_get(class_name).find(object_to_deserialize['_id']).first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
return hash_deserialize(object_to_deserialize)
|
35
|
+
end
|
36
|
+
|
37
|
+
return object_to_deserialize
|
38
|
+
end
|
39
|
+
|
40
|
+
def serialize(object_to_serialize, depth=0)
|
41
|
+
serializer = Serializer.new(object_to_serialize, depth)
|
42
|
+
return serializer.hash_serialize if LightMongo.slow_serialization or depth < LightMongo.marshal_depth
|
43
|
+
serializer.marshal
|
44
|
+
end
|
45
|
+
|
46
|
+
def array_deserialize(array)
|
47
|
+
array.map do |entry|
|
48
|
+
deserialize(entry)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def hash_deserialize(hash)
|
53
|
+
deserialized_hash = {}
|
54
|
+
hash.each_pair do |key, value|
|
55
|
+
deserialized_hash[key] = deserialize(value)
|
56
|
+
end
|
57
|
+
return deserialized_hash
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(object_to_serialize, depth=0)
|
62
|
+
@object_to_serialize = object_to_serialize
|
63
|
+
@depth = depth
|
64
|
+
end
|
65
|
+
|
66
|
+
def marshal
|
67
|
+
{'_data' => Marshal.dump(@object_to_serialize)}
|
68
|
+
end
|
69
|
+
|
70
|
+
def hash_serialize
|
71
|
+
HashSerializer.dump(@object_to_serialize, @depth)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/serialization/serializer'
|
2
|
+
|
3
|
+
module LightMongo
|
4
|
+
def self.slow_serialization=(boolean)
|
5
|
+
@@slow_serialization = boolean
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.slow_serialization
|
9
|
+
@@slow_serialization = nil unless defined?(@@slow_serialization)
|
10
|
+
@@slow_serialization ||= false
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.marshal_depth=(depth)
|
14
|
+
@@marshal_depth = depth
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.marshal_depth
|
18
|
+
@@marshal_depth = nil unless defined?(@@marshal_depth)
|
19
|
+
@@marshal_depth ||= 3
|
20
|
+
end
|
21
|
+
|
22
|
+
module Document
|
23
|
+
module Serialization
|
24
|
+
def initialize(params={})
|
25
|
+
self.from_hash(params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash(current_depth=0)
|
29
|
+
Serializer.serialize(self, current_depth)
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_hash(hash)
|
33
|
+
Serializer.deserialize(hash).each_pair do |attr_name, attr_value|
|
34
|
+
self.instance_variable_set '@'+attr_name.to_s, attr_value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def export
|
39
|
+
return self unless self.class.include?(LightMongo::Document::Persistence)
|
40
|
+
self.save
|
41
|
+
{'_class_name' => self.class.name, '_id' => self.id, '_embed' => true}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/document.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mongo'
|
3
|
+
|
4
|
+
def require_document(lib)
|
5
|
+
require File.dirname(__FILE__) + "/document/" + lib
|
6
|
+
end
|
7
|
+
|
8
|
+
require_document 'serialization'
|
9
|
+
require_document 'persistence'
|
10
|
+
|
11
|
+
module LightMongo
|
12
|
+
module Document
|
13
|
+
def self.included(document_class)
|
14
|
+
document_class.class_eval %{
|
15
|
+
include Serialization
|
16
|
+
include Persistence
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/light_mongo.rb
ADDED