mm-embeddable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +66 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/lib/mm-embeddable.rb +81 -0
- data/spec/blueprints.rb +22 -0
- data/spec/mm_embeddable_spec.rb +47 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +41 -0
- metadata +150 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Michael Bleigh
|
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.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= MongoMapper Embeddable
|
2
|
+
|
3
|
+
Because MongoDB doesn't have joins but does have the ability to embed documents in other documents, it is often the case that for query minimization purposes it is useful to provide a few key properties of a document when it's embedded in another one. MongoMapper Embeddable is a plugin for MongoMapper to do just that.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
gem install mm-embeddable
|
8
|
+
|
9
|
+
== Usage
|
10
|
+
|
11
|
+
To declare an embeddable version of a MongoMapper document, simply call <tt>embeds</tt> after all of your <tt>key</tt> declarations and specify the keys that should be embedded in the "compacted" version of the model. This will automatically generate a <tt>YourDocumentClass::Embeddable</tt> class that can be embedded in other documents.
|
12
|
+
|
13
|
+
=== Example
|
14
|
+
|
15
|
+
require 'mongo_mapper'
|
16
|
+
require 'mm-embeddable'
|
17
|
+
|
18
|
+
class User
|
19
|
+
include MongoMapper::Document
|
20
|
+
|
21
|
+
key :name, String
|
22
|
+
key :profile_photo, String
|
23
|
+
key :bio, String
|
24
|
+
key :interests, Array
|
25
|
+
|
26
|
+
embeds :name, :profile_photo
|
27
|
+
end
|
28
|
+
|
29
|
+
class Post
|
30
|
+
include MongoMapper::Document
|
31
|
+
|
32
|
+
key :author, User::Embeddable
|
33
|
+
key :title, String
|
34
|
+
key :body, String
|
35
|
+
end
|
36
|
+
|
37
|
+
This creates an embeddable version of the <tt>User</tt> model that only contains the name and profile picture. If that's all I access, then no additional queries are called. Example:
|
38
|
+
|
39
|
+
<h1><%= @post.title %></h1>
|
40
|
+
<span class='author'>
|
41
|
+
<img src='<%= @post.author.profile_photo %>'/> <%= @post.author.name %>
|
42
|
+
</span>
|
43
|
+
<div class='body'><%= @post.body %></div>
|
44
|
+
|
45
|
+
But if we need other attributes that haven't been stored in the embeddable model, we can do so seamlessly:
|
46
|
+
|
47
|
+
# no additional query is performed
|
48
|
+
@post.author.name
|
49
|
+
# the full document is seamlessly fetched in the background
|
50
|
+
@post.author.bio
|
51
|
+
|
52
|
+
In this way we take full advantage of Mongo's embedded documents while still being able to easily fall back to the full document when necessary.
|
53
|
+
|
54
|
+
== Note on Patches/Pull Requests
|
55
|
+
|
56
|
+
* Fork the project.
|
57
|
+
* Make your feature addition or bug fix.
|
58
|
+
* Add tests for it. This is important so I don't break it in a
|
59
|
+
future version unintentionally.
|
60
|
+
* Commit, do not mess with rakefile, version, or history.
|
61
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
62
|
+
* Send me a pull request. Bonus points for topic branches.
|
63
|
+
|
64
|
+
== Copyright
|
65
|
+
|
66
|
+
Copyright (c) 2010 Michael Bleigh. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "mm-embeddable"
|
8
|
+
gem.summary = %Q{Create compact, embeddable versions of your MongoMapper documents.}
|
9
|
+
gem.description = %Q{For query minimization purposes in MongoDB it is useful to provide a few key properties of a document when it's embedded in another one. MongoMapper Embeddable is a plugin for MongoMapper to do just that.}
|
10
|
+
gem.email = "michael@intridea.com"
|
11
|
+
gem.homepage = "http://github.com/intridea/mm-embeddable"
|
12
|
+
gem.add_dependency 'mongo_mapper', '>= 0.7.0'
|
13
|
+
gem.authors = ["Michael Bleigh"]
|
14
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
15
|
+
gem.add_development_dependency 'mocha'
|
16
|
+
gem.add_development_dependency 'machinist'
|
17
|
+
gem.add_development_dependency 'machinist_mongo'
|
18
|
+
gem.add_development_dependency 'faker'
|
19
|
+
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
29
|
+
spec.libs << 'lib' << 'spec'
|
30
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
31
|
+
end
|
32
|
+
|
33
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
34
|
+
spec.libs << 'lib' << 'spec'
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :spec => :check_dependencies
|
40
|
+
|
41
|
+
task :default => :spec
|
42
|
+
|
43
|
+
require 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "mm_compactable #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'mongo_mapper'
|
2
|
+
|
3
|
+
module MongoMapper
|
4
|
+
module Plugins
|
5
|
+
module Embeddable
|
6
|
+
class EmbeddableDocument #:nodoc:
|
7
|
+
include ::MongoMapper::EmbeddedDocument
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_writer :full_class
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.from_full(full_document)
|
14
|
+
d = self.new
|
15
|
+
keys.keys.each do |k|
|
16
|
+
d.send("#{k}=".to_sym, full_document.send(k.to_sym))
|
17
|
+
end
|
18
|
+
d
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.full_class
|
22
|
+
@full_class or raise NotImplementedError, 'This embed has no full class.'
|
23
|
+
end
|
24
|
+
|
25
|
+
def expand!
|
26
|
+
@expanded = true
|
27
|
+
@expansion = self.class.full_class.find(self.id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def method_missing(*args)
|
31
|
+
if !@expanded
|
32
|
+
expand!
|
33
|
+
@expansion.send(*args)
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.to_mongo(instance)
|
40
|
+
case instance
|
41
|
+
when full_class
|
42
|
+
from_full(instance).to_mongo
|
43
|
+
else
|
44
|
+
instance.to_mongo
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
# Tells MongoMapper that this document can be embedded. Pass
|
51
|
+
# in an array of the keys that should be persisted to the
|
52
|
+
# embedded document (<tt>_id</tt> is automatically persisted
|
53
|
+
# as a reference to the full document).
|
54
|
+
def embeds(*ckeys)
|
55
|
+
@embeddable_keys ||= []
|
56
|
+
@embeddable_keys.push(*ckeys)
|
57
|
+
|
58
|
+
self.const_set(:Embeddable, Class.new(::MongoMapper::Plugins::Embeddable::EmbeddableDocument))
|
59
|
+
|
60
|
+
self.const_get(:Embeddable).full_class = self
|
61
|
+
self.const_get(:Embeddable).key :_id, ObjectId
|
62
|
+
|
63
|
+
ckeys.each do |k|
|
64
|
+
self.const_get(:Embeddable).key k
|
65
|
+
self.const_get(:Embeddable).keys[k.to_s] = self.keys[k.to_s].dup
|
66
|
+
end
|
67
|
+
|
68
|
+
include MongoMapper::Plugins::Embeddable::EmbeddableMethods
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module EmbeddableMethods
|
73
|
+
def to_embeddable
|
74
|
+
self.class.const_get(:Embeddable).from_full(self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
MongoMapper::Document.append_extensions(MongoMapper::Plugins::Embeddable::ClassMethods)
|
data/spec/blueprints.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'machinist/mongo_mapper'
|
2
|
+
require 'sham'
|
3
|
+
require 'faker'
|
4
|
+
|
5
|
+
Sham.name { Faker::Name.name }
|
6
|
+
Sham.login { Faker::Internet.user_name }
|
7
|
+
Sham.bio { Faker::Lorem.sentence(30) }
|
8
|
+
Sham.subject { Faker::Lorem.sentence(10) }
|
9
|
+
Sham.body { Faker::Lorem.paragraphs(4).join("\n\n") }
|
10
|
+
|
11
|
+
TestUser.blueprint do
|
12
|
+
name
|
13
|
+
login
|
14
|
+
bio
|
15
|
+
end
|
16
|
+
|
17
|
+
TestMessage.blueprint do
|
18
|
+
sender { TestUser.make }
|
19
|
+
receivers { [TestUser.make, TestUser.make] }
|
20
|
+
subject
|
21
|
+
body
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "MongoMapper::Plugins::Embeddable" do
|
4
|
+
it { TestUser.should be_respond_to(:embeds) }
|
5
|
+
|
6
|
+
it 'should define the Embedded subclass' do
|
7
|
+
defined?(TestUser::Embeddable).should be_true
|
8
|
+
TestUser::Embeddable.should < MongoMapper::Plugins::Embeddable::EmbeddableDocument
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should be able to convert to embedded' do
|
12
|
+
TestUser.new.to_embeddable.should be_kind_of(TestUser::Embeddable)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '::EmbeddedDocument' do
|
16
|
+
it 'should set attributes from a document' do
|
17
|
+
mid = Mongo::ObjectID.new
|
18
|
+
c = TestUser::Embeddable.from_full(stub(:_id => mid, :login => 'abc', :name => 'Bob Bobson', :bio => "A great dude."))
|
19
|
+
c.login.should == 'abc'
|
20
|
+
c.name.should == 'Bob Bobson'
|
21
|
+
c._id.should == mid
|
22
|
+
lambda{c.bio}.should raise_error(NoMethodError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should expand when an attribute is missing' do
|
26
|
+
mid = Mongo::ObjectID.new
|
27
|
+
u = TestUser.new(:_id => mid, :name => "Frank", :bio => 'Once upon a time.')
|
28
|
+
TestUser.expects(:find).with(mid).returns(u)
|
29
|
+
TestUser::Embeddable.from_full(u).bio.should == 'Once upon a time.'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ' in another model' do
|
34
|
+
it 'should coerce as a compact document' do
|
35
|
+
u1 = TestUser.make
|
36
|
+
u2 = TestUser.make
|
37
|
+
u3 = TestUser.make
|
38
|
+
|
39
|
+
t = TestMessage.make(:sender => u1, :receivers => [u2, u3])
|
40
|
+
|
41
|
+
t.reload
|
42
|
+
t.sender.should be_kind_of(TestUser::Embeddable)
|
43
|
+
t.sender._id.should == u1._id
|
44
|
+
t.receivers.first.should be_kind_of(TestUser::Embeddable)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'rspec', '1.3.0'
|
6
|
+
|
7
|
+
require 'mm-embeddable'
|
8
|
+
require 'spec'
|
9
|
+
require 'spec/autorun'
|
10
|
+
require 'spec/mocks'
|
11
|
+
require 'mocha'
|
12
|
+
|
13
|
+
class TestUser
|
14
|
+
include MongoMapper::Document
|
15
|
+
|
16
|
+
key :login, String
|
17
|
+
key :name, String
|
18
|
+
key :bio, String
|
19
|
+
|
20
|
+
embeds :login, :name
|
21
|
+
end
|
22
|
+
|
23
|
+
class TestMessage
|
24
|
+
include MongoMapper::Document
|
25
|
+
|
26
|
+
key :sender, TestUser::Embeddable
|
27
|
+
many :receivers, :class_name => 'TestUser::Embeddable'
|
28
|
+
|
29
|
+
key :subject, String
|
30
|
+
key :body, String
|
31
|
+
end
|
32
|
+
|
33
|
+
require File.expand_path(File.dirname(__FILE__) + "/blueprints")
|
34
|
+
|
35
|
+
MongoMapper.database = 'mm_plugin_test'
|
36
|
+
|
37
|
+
Spec::Runner.configure do |config|
|
38
|
+
config.mock_with :mocha
|
39
|
+
config.before(:all) { Sham.reset(:before_all) }
|
40
|
+
config.before(:each) { Sham.reset(:before_each); }#MongoMapper.connection.drop_database('mm_plugin_test') }
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mm-embeddable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Michael Bleigh
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-29 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: mongo_mapper
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 7
|
30
|
+
- 0
|
31
|
+
version: 0.7.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 2
|
44
|
+
- 9
|
45
|
+
version: 1.2.9
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: mocha
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
type: :development
|
59
|
+
version_requirements: *id003
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: machinist
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: machinist_mongo
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :development
|
83
|
+
version_requirements: *id005
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: faker
|
86
|
+
prerelease: false
|
87
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
type: :development
|
95
|
+
version_requirements: *id006
|
96
|
+
description: For query minimization purposes in MongoDB it is useful to provide a few key properties of a document when it's embedded in another one. MongoMapper Embeddable is a plugin for MongoMapper to do just that.
|
97
|
+
email: michael@intridea.com
|
98
|
+
executables: []
|
99
|
+
|
100
|
+
extensions: []
|
101
|
+
|
102
|
+
extra_rdoc_files:
|
103
|
+
- LICENSE
|
104
|
+
- README.rdoc
|
105
|
+
files:
|
106
|
+
- .document
|
107
|
+
- .gitignore
|
108
|
+
- LICENSE
|
109
|
+
- README.rdoc
|
110
|
+
- Rakefile
|
111
|
+
- VERSION
|
112
|
+
- lib/mm-embeddable.rb
|
113
|
+
- spec/blueprints.rb
|
114
|
+
- spec/mm_embeddable_spec.rb
|
115
|
+
- spec/spec.opts
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
has_rdoc: true
|
118
|
+
homepage: http://github.com/intridea/mm-embeddable
|
119
|
+
licenses: []
|
120
|
+
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options:
|
123
|
+
- --charset=UTF-8
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
segments:
|
138
|
+
- 0
|
139
|
+
version: "0"
|
140
|
+
requirements: []
|
141
|
+
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 1.3.6
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: Create compact, embeddable versions of your MongoMapper documents.
|
147
|
+
test_files:
|
148
|
+
- spec/blueprints.rb
|
149
|
+
- spec/mm_embeddable_spec.rb
|
150
|
+
- spec/spec_helper.rb
|