document_hydrator 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/.autotest +9 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +184 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/document_hydrator.gemspec +81 -0
- data/lib/document_hydrator.rb +83 -0
- data/lib/document_hydrator/hydration_proc/mongo.rb +38 -0
- data/lib/document_hydrator/inflector.rb +37 -0
- data/lib/document_hydrator/inflector/inflections.rb +88 -0
- data/spec/document_hydrator_spec.rb +170 -0
- data/spec/hydration_proc/mongo_spec.rb +66 -0
- data/spec/spec_helper.rb +3 -0
- metadata +229 -0
data/.autotest
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
# Add dependencies to develop your gem here.
|
4
|
+
# Include everything needed to run rake, tests, features, etc.
|
5
|
+
group :development do
|
6
|
+
gem 'bundler', '~> 1.0.0'
|
7
|
+
gem 'jeweler', '~> 1.6.2'
|
8
|
+
gem 'rspec', '~> 2.6.0'
|
9
|
+
gem 'ZenTest', '~> 4.4.2'
|
10
|
+
gem 'autotest-growl'
|
11
|
+
gem 'autotest-fsevent'
|
12
|
+
gem 'bson_ext', :platforms => :ruby
|
13
|
+
gem 'bson', :platforms => :jruby
|
14
|
+
gem 'mongo'
|
15
|
+
gem 'SystemTimer', :platforms => :ruby_18
|
16
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Greg Spurrier
|
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.markdown
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# DocumentHydrator
|
2
|
+
|
3
|
+
DocumentHydrator takes a document, represented as a Ruby Hash, and
|
4
|
+
efficiently updates it so that embedded references to other documents
|
5
|
+
are replaced with their corresponding subdocuments.
|
6
|
+
|
7
|
+
Along with the document, DocumentHydrator takes a path (or array of
|
8
|
+
paths) specifying the location of the references to be expanded and a
|
9
|
+
Proc--known as the hydration proc--that is capable of providing
|
10
|
+
expanded subdocuments for those references. The hydration proc is
|
11
|
+
guaranteed to be called at most once during any given invocation of
|
12
|
+
DocumentHydrator, ensuring efficient hydration of multiple
|
13
|
+
subdocuments.
|
14
|
+
|
15
|
+
## Hydration Procs
|
16
|
+
Hydration procs are responsible for transforming an array of document
|
17
|
+
references into a hash that maps those references to their
|
18
|
+
corresponding subdocuments. The subdocuments must themselves be
|
19
|
+
hashes.
|
20
|
+
|
21
|
+
DocumentHydrator provides a hydration proc factory that makes it
|
22
|
+
simple to hydrate a document when pulling the subdocuments from a
|
23
|
+
MongoDB collection. Use of the factory is described in the "Hydrating
|
24
|
+
Documents with MongoDB Collections" section found later in the
|
25
|
+
document.
|
26
|
+
|
27
|
+
Most of the following examples illustrate DocumentHydrator
|
28
|
+
functionality that is independent of the choice of hydration proc. In
|
29
|
+
order to keep them stand-alone, a simple "identity" hydration proc
|
30
|
+
will be used:
|
31
|
+
|
32
|
+
identity_hydrator = Proc.new do |ids|
|
33
|
+
ids.inject({}) do |hash, id|
|
34
|
+
hash[id] = { 'id' => id }
|
35
|
+
hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
It simply maps IDs to hashes containing the ID under the key 'id'. For
|
40
|
+
example:
|
41
|
+
|
42
|
+
identity_hydrator.call([1, 2, 3])
|
43
|
+
# => {1=>{"id"=>1}, 2=>{"id"=>2}, 3=>{"id"=>3}}
|
44
|
+
|
45
|
+
## A Simple Example
|
46
|
+
Armed with `identity_hydrator`, the simplest example is:
|
47
|
+
|
48
|
+
doc = { 'thing' => 1, 'gizmo' => 3 }
|
49
|
+
DocumentHydrator.hydrate_document(doc, 'thing', identity_hydrator)
|
50
|
+
# => {"thing"=>{"id"=>1},"gizmo"=>3}
|
51
|
+
|
52
|
+
In this case DocumentHydrator is asked to hydrate a single document,
|
53
|
+
`doc`, by replacing the reference found at `doc['thing']` to its
|
54
|
+
corresponding subdocument, as provided by `identity_hydrator`.
|
55
|
+
|
56
|
+
## Paths
|
57
|
+
In the example above, the path was the name of a top level key in
|
58
|
+
the document to be hydrated. DocumentHydrator also supports paths
|
59
|
+
to arrays, nested paths, and nested paths that contain intermediate
|
60
|
+
arrays.
|
61
|
+
|
62
|
+
For example, consider the document:
|
63
|
+
|
64
|
+
status_update = {
|
65
|
+
'user' => 19,
|
66
|
+
'text' => 'I am loving MongoDB!',
|
67
|
+
'likers' => [37, 42, 99],
|
68
|
+
'comments' => [
|
69
|
+
{ 'user' => 88, 'text' => 'Me too!' },
|
70
|
+
{ 'user' => 99, 'text' => 'Drinking the Kool-Aid, eh?' },
|
71
|
+
{ 'user' => 88, 'text' => "Don't be a hater. :)" }
|
72
|
+
]
|
73
|
+
}
|
74
|
+
|
75
|
+
The following are all valid hydration paths referencing user IDs in
|
76
|
+
`status_update`:
|
77
|
+
|
78
|
+
* `'user'` -- single ID
|
79
|
+
* `'likers'` -- array of IDs
|
80
|
+
* `'comments.user'` -- single ID contained within an array of objects
|
81
|
+
|
82
|
+
## Multi-path Hydration
|
83
|
+
|
84
|
+
DocumentHydrator will accept an array of paths to all be hydrated
|
85
|
+
concurrently:
|
86
|
+
|
87
|
+
|
88
|
+
DocumentHydrator.hydrate_document(status_update,
|
89
|
+
['user', 'likers', 'comments.user'],
|
90
|
+
identity_hydrator)
|
91
|
+
pp status_update
|
92
|
+
# {"user"=>{"id"=>19},
|
93
|
+
# "text"=>"I am loving MongoDB!",
|
94
|
+
# "likers"=>[{"id"=>37}, {"id"=>42}, {"id"=>99}],
|
95
|
+
# "comments"=>
|
96
|
+
# [{"user"=>{"id"=>88}, "text"=>"Me too!"},
|
97
|
+
# {"user"=>{"id"=>99}, "text"=>"Drinking the KoolAid, eh?"},
|
98
|
+
# {"user"=>{"id"=>88}, "text"=>"Don't be a hater. :)"}]}
|
99
|
+
|
100
|
+
Regardless of the number of paths, the hydration is accomplished with a
|
101
|
+
single call to the hydration proc.
|
102
|
+
|
103
|
+
## Multi-document Hydration
|
104
|
+
Multiple documents may be hydrated at once using
|
105
|
+
`DocumentHydrator.hydrate_documents`:
|
106
|
+
|
107
|
+
doc1 = { 'thing' => 1, 'gizmo' => 3 }
|
108
|
+
doc2 = { 'thing' => 2, 'gizmo' => 3 }
|
109
|
+
DocumentHydrator.hydrate_documents([doc1, doc2], 'thing', identity_hydrator)
|
110
|
+
# => [{"thing"=>{"id"=>1}, "gizmo"=>3}, {"thing"=>{"id"=>2}, "gizmo"=>3}]
|
111
|
+
|
112
|
+
The only difference between `hydrate_document` and `hydrate_documents`
|
113
|
+
is that the latter takes an array of documents. The other parameters
|
114
|
+
are the same.
|
115
|
+
|
116
|
+
## _id Suffix Stripping
|
117
|
+
DocumentHydrator automatically strips any 'id' or 'ids' suffixes from
|
118
|
+
keys that are the last step in a hydration path:
|
119
|
+
|
120
|
+
doc = {
|
121
|
+
'user_id' => 33,
|
122
|
+
'follower_ids' => [11, 23]
|
123
|
+
}
|
124
|
+
DocumentHydrator.hydrate_document(doc,
|
125
|
+
['user_id', 'follower_ids'], identity_hydrator)
|
126
|
+
# => {"user"=>{"id"=>33}, "followers"=>[{"id"=>11}, {"id"=>23}]}
|
127
|
+
|
128
|
+
Notice that the document now has the keys 'user' and 'followers'.
|
129
|
+
|
130
|
+
## Hydrating Documents with MongoDB Collections
|
131
|
+
DocumentHydrator provides a hydration proc factory that makes it
|
132
|
+
simple to hydrate documents with subdocuments that are fecthed from a
|
133
|
+
MongoDB collection.
|
134
|
+
|
135
|
+
The following examples require a bit of setup:
|
136
|
+
|
137
|
+
require 'mongo'
|
138
|
+
db = Mongo::Connection.new.db('document_hydrator_example')
|
139
|
+
users_collection = db['users']
|
140
|
+
users_collection.remove
|
141
|
+
users_collection.insert('_id' => 1, 'name' => 'Fred', 'age' => 33)
|
142
|
+
users_collection.insert('_id' => 2, 'name' => 'Wilma', 'age' => 30)
|
143
|
+
users_collection.insert('_id' => 3, 'name' => 'Barney', 'age' => 29)
|
144
|
+
users_collection.insert('_id' => 4, 'name' => 'Betty', 'age' => 28)
|
145
|
+
|
146
|
+
Now create a hydration proc that fetches documents from the users
|
147
|
+
collection:
|
148
|
+
|
149
|
+
user_hydrator = DocumentHydrator::HydrationProc::Mongo.collection(users_collection)
|
150
|
+
|
151
|
+
Note that DocumentHydrator::HydrationProc::Mongo is automatically
|
152
|
+
loaded by `require 'document_dehydrator'` if the MongoDB Ruby Driver
|
153
|
+
has already been loaded.
|
154
|
+
|
155
|
+
Here is the hydration proc in action:
|
156
|
+
|
157
|
+
doc = { 'user_ids' => [1, 3] }
|
158
|
+
Documenthydrator.hydrate_document(doc, 'user_ids', user_hydrator)
|
159
|
+
# => {"users"=>[{"_id"=>1, "name"=>"Fred", "age"=>33}, {"_id"=>3, "name"=>"Barney", "age"=>29}]}
|
160
|
+
|
161
|
+
### Limiting Fields in Subdocuments
|
162
|
+
By default a Mongo collection hydrator will return all of the fields
|
163
|
+
that are present for the subdocument in the database. This can be
|
164
|
+
changed, however, by passing an optional `:fields` argument to the
|
165
|
+
factory. This option takes the same form as it does for
|
166
|
+
Mongo::Collection#find.
|
167
|
+
|
168
|
+
For example:
|
169
|
+
|
170
|
+
user_hydrator = DocumentHydrator::HydrationProc::Mongo.collection(users_collection,
|
171
|
+
:fields => { '_id' => 0, 'name' => 1 })
|
172
|
+
DocumentHydrator.hydrate_document(doc, 'user_ids', user_hydrator)
|
173
|
+
# => {"users"=>[{"name"=>"Fred"}, {"name"=>"Barney"}]}
|
174
|
+
|
175
|
+
## Supported Rubies
|
176
|
+
DocumentHydrator has been tested with:
|
177
|
+
|
178
|
+
* Ruby 1.8.7 (p334)
|
179
|
+
* Ruby 1.9.2 (p180)
|
180
|
+
* JRuby 1.6.2
|
181
|
+
|
182
|
+
## Copyright
|
183
|
+
Copyright (c) 2011 Greg Spurrier. See LICENSE.txt for
|
184
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "document_hydrator"
|
16
|
+
gem.homepage = "http://github.com/gregspurrier/document_hydrator"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{DocumentHydrator takes a document, represented as a Ruby Hash, and efficiently updates it so that embedded references to other documents are replaced with their corresponding subdocuments.}
|
19
|
+
gem.description = %Q{DocumentHydrator takes a document, represented as a Ruby Hash, and efficiently updates it so that embedded references to other documents are replaced with their corresponding subdocuments.}
|
20
|
+
gem.email = "greg.spurrier@gmail.com"
|
21
|
+
gem.authors = ["Greg Spurrier"]
|
22
|
+
# dependencies defined in Gemfile
|
23
|
+
end
|
24
|
+
Jeweler::RubygemsDotOrgTasks.new
|
25
|
+
|
26
|
+
require 'rspec/core/rake_task'
|
27
|
+
RSpec::Core::RakeTask.new
|
28
|
+
|
29
|
+
task :default => :spec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{document_hydrator}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Greg Spurrier"]
|
12
|
+
s.date = %q{2011-06-19}
|
13
|
+
s.description = %q{DocumentHydrator takes a document, represented as a Ruby Hash, and efficiently updates it so that embedded references to other documents are replaced with their corresponding subdocuments.}
|
14
|
+
s.email = %q{greg.spurrier@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".autotest",
|
21
|
+
".rspec",
|
22
|
+
"Gemfile",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.markdown",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"document_hydrator.gemspec",
|
28
|
+
"lib/document_hydrator.rb",
|
29
|
+
"lib/document_hydrator/hydration_proc/mongo.rb",
|
30
|
+
"lib/document_hydrator/inflector.rb",
|
31
|
+
"lib/document_hydrator/inflector/inflections.rb",
|
32
|
+
"spec/document_hydrator_spec.rb",
|
33
|
+
"spec/hydration_proc/mongo_spec.rb",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{http://github.com/gregspurrier/document_hydrator}
|
37
|
+
s.licenses = ["MIT"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.6.2}
|
40
|
+
s.summary = %q{DocumentHydrator takes a document, represented as a Ruby Hash, and efficiently updates it so that embedded references to other documents are replaced with their corresponding subdocuments.}
|
41
|
+
|
42
|
+
if s.respond_to? :specification_version then
|
43
|
+
s.specification_version = 3
|
44
|
+
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
47
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
48
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
|
49
|
+
s.add_development_dependency(%q<ZenTest>, ["~> 4.4.2"])
|
50
|
+
s.add_development_dependency(%q<autotest-growl>, [">= 0"])
|
51
|
+
s.add_development_dependency(%q<autotest-fsevent>, [">= 0"])
|
52
|
+
s.add_development_dependency(%q<bson_ext>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<bson>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<mongo>, [">= 0"])
|
55
|
+
s.add_development_dependency(%q<SystemTimer>, [">= 0"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
58
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
59
|
+
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
60
|
+
s.add_dependency(%q<ZenTest>, ["~> 4.4.2"])
|
61
|
+
s.add_dependency(%q<autotest-growl>, [">= 0"])
|
62
|
+
s.add_dependency(%q<autotest-fsevent>, [">= 0"])
|
63
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
64
|
+
s.add_dependency(%q<bson>, [">= 0"])
|
65
|
+
s.add_dependency(%q<mongo>, [">= 0"])
|
66
|
+
s.add_dependency(%q<SystemTimer>, [">= 0"])
|
67
|
+
end
|
68
|
+
else
|
69
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
70
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
71
|
+
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
72
|
+
s.add_dependency(%q<ZenTest>, ["~> 4.4.2"])
|
73
|
+
s.add_dependency(%q<autotest-growl>, [">= 0"])
|
74
|
+
s.add_dependency(%q<autotest-fsevent>, [">= 0"])
|
75
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
76
|
+
s.add_dependency(%q<bson>, [">= 0"])
|
77
|
+
s.add_dependency(%q<mongo>, [">= 0"])
|
78
|
+
s.add_dependency(%q<SystemTimer>, [">= 0"])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'document_hydrator/inflector'
|
2
|
+
if defined? Mongo
|
3
|
+
require 'document_hydrator/hydration_proc/mongo'
|
4
|
+
end
|
5
|
+
|
6
|
+
module DocumentHydrator
|
7
|
+
class <<self
|
8
|
+
# Given a +document+ hash, a path or array of paths describing locations of object IDs within
|
9
|
+
# the hash, and a function that will convert object IDs to a hash of subdocument hashes indexed
|
10
|
+
# by object ID, modifies the document hash so that all of the IDs referenced by the paths are
|
11
|
+
# replaced with the corresponding subdocuments.
|
12
|
+
#
|
13
|
+
# Path examples:
|
14
|
+
#
|
15
|
+
# document = {
|
16
|
+
# 'owner' => 99,
|
17
|
+
# 'clients' => [100, 101],
|
18
|
+
# 'comments' => [
|
19
|
+
# { 'user' => 10, text => 'hi' },
|
20
|
+
# { 'user' => 11, text => 'hello' }
|
21
|
+
# ]
|
22
|
+
# }
|
23
|
+
#
|
24
|
+
# Each of these are valid paths:
|
25
|
+
# - 'owner'
|
26
|
+
# - 'clients'
|
27
|
+
# - 'comments.user'
|
28
|
+
#
|
29
|
+
# Returns the document to allow for chaining.
|
30
|
+
def hydrate_document(document, path_or_paths, hydration_proc)
|
31
|
+
hydrate_documents([document],path_or_paths, hydration_proc)
|
32
|
+
document
|
33
|
+
end
|
34
|
+
|
35
|
+
def hydrate_documents(documents, path_or_paths, hydration_proc)
|
36
|
+
# Traverse the documents replacing each ID with a corresponding dehydrated document
|
37
|
+
dehydrated_subdocuments = Hash.new { |h, k| h[k] = Hash.new }
|
38
|
+
documents.each do |document|
|
39
|
+
paths = path_or_paths.kind_of?(Array) ? path_or_paths : [path_or_paths]
|
40
|
+
paths.each do |path|
|
41
|
+
replace_ids_with_dehydrated_documents(document, path.split('.'), dehydrated_subdocuments)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Rehydrate the documents that we discovered during traversal all in one go
|
46
|
+
ids = dehydrated_subdocuments.keys
|
47
|
+
hydrated_subdocuments = hydration_proc.call(ids)
|
48
|
+
ids.each {|id| dehydrated_subdocuments[id].replace(hydrated_subdocuments[id])}
|
49
|
+
|
50
|
+
documents
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def replace_ids_with_dehydrated_documents(document, path_steps, dehydrated_documents)
|
56
|
+
step = path_steps.first
|
57
|
+
next_steps = path_steps[1..-1]
|
58
|
+
if document.has_key?(step)
|
59
|
+
subdocument = document[step]
|
60
|
+
if next_steps.empty?
|
61
|
+
# End of the path, do the hydration, dropping any _id or _ids suffix
|
62
|
+
if step =~ /_ids?$/
|
63
|
+
document.delete(step)
|
64
|
+
step = step.sub(/_id(s?)$/, '')
|
65
|
+
step = Inflector.pluralize(step) if $1 == 's'
|
66
|
+
end
|
67
|
+
if subdocument.kind_of?(Array)
|
68
|
+
document[step] = subdocument.map {|id| dehydrated_documents[id] }
|
69
|
+
else
|
70
|
+
document[step] = dehydrated_documents[subdocument]
|
71
|
+
end
|
72
|
+
else
|
73
|
+
# Keep on stepping
|
74
|
+
if subdocument.kind_of?(Array)
|
75
|
+
subdocument.each { |item| replace_ids_with_dehydrated_documents(item, next_steps, dehydrated_documents) }
|
76
|
+
else
|
77
|
+
replace_ids_with_dehydrated_documents(subdocument, next_steps, dehydrated_documents)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DocumentHydrator
|
2
|
+
module HydrationProc
|
3
|
+
module Mongo
|
4
|
+
class <<self
|
5
|
+
# Create a hydration proc that fetches subdocuments by ID from
|
6
|
+
# the provided collection.
|
7
|
+
#
|
8
|
+
# coll - The Mongo::Collection containing the subdocuments
|
9
|
+
# options - (Optional) hash of options to pass to MongoDB::Collection#find.
|
10
|
+
# Defaults to {}.
|
11
|
+
#
|
12
|
+
# Returns a Proc that maps IDs to their corresponding subdocuments
|
13
|
+
# within the collection.
|
14
|
+
def collection(coll, options = {})
|
15
|
+
Proc.new do |ids|
|
16
|
+
if options[:fields]
|
17
|
+
# We need to _id key in order to assemble the results hash.
|
18
|
+
# If the caller has requested that it be omitted from the
|
19
|
+
# result, re-enable it and then strip later.
|
20
|
+
field_selectors = options[:fields]
|
21
|
+
id_key = field_selectors.keys.detect { |k| k.to_s == '_id' }
|
22
|
+
if id_key && field_selectors[id_key] == 0
|
23
|
+
field_selectors.delete(id_key)
|
24
|
+
strip_id = true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
subdocuments = coll.find({ '_id' => { '$in' => ids } }, options)
|
28
|
+
subdocuments.inject({}) do |hash, subdocument|
|
29
|
+
hash[subdocument['_id']] = subdocument
|
30
|
+
subdocument.delete('_id') if strip_id
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'document_hydrator/inflector/inflections'
|
2
|
+
|
3
|
+
module DocumentHydrator
|
4
|
+
# Inflection rules extracted from ActiveSupport 3.0.9.
|
5
|
+
Inflector.inflections do |inflect|
|
6
|
+
inflect.plural(/$/, 's')
|
7
|
+
inflect.plural(/s$/i, 's')
|
8
|
+
inflect.plural(/(ax|test)is$/i, '\1es')
|
9
|
+
inflect.plural(/(octop|vir)us$/i, '\1i')
|
10
|
+
inflect.plural(/(octop|vir)i$/i, '\1i')
|
11
|
+
inflect.plural(/(alias|status)$/i, '\1es')
|
12
|
+
inflect.plural(/(bu)s$/i, '\1ses')
|
13
|
+
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
|
14
|
+
inflect.plural(/([ti])um$/i, '\1a')
|
15
|
+
inflect.plural(/([ti])a$/i, '\1a')
|
16
|
+
inflect.plural(/sis$/i, 'ses')
|
17
|
+
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
|
18
|
+
inflect.plural(/(hive)$/i, '\1s')
|
19
|
+
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
|
20
|
+
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
|
21
|
+
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
|
22
|
+
inflect.plural(/([m|l])ouse$/i, '\1ice')
|
23
|
+
inflect.plural(/([m|l])ice$/i, '\1ice')
|
24
|
+
inflect.plural(/^(ox)$/i, '\1en')
|
25
|
+
inflect.plural(/^(oxen)$/i, '\1')
|
26
|
+
inflect.plural(/(quiz)$/i, '\1zes')
|
27
|
+
|
28
|
+
inflect.irregular('person', 'people')
|
29
|
+
inflect.irregular('man', 'men')
|
30
|
+
inflect.irregular('child', 'children')
|
31
|
+
inflect.irregular('sex', 'sexes')
|
32
|
+
inflect.irregular('move', 'moves')
|
33
|
+
inflect.irregular('cow', 'kine')
|
34
|
+
|
35
|
+
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# Extracted from ActiveSupport::Inflector in ActiveSupport 3.0.9.
|
2
|
+
module DocumentHydrator
|
3
|
+
module Inflector
|
4
|
+
class Inflections
|
5
|
+
def self.instance
|
6
|
+
@__instance__ ||= new
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :plurals, :uncountables
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@plurals, @uncountables, @humans = [], [], []
|
13
|
+
end
|
14
|
+
|
15
|
+
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
|
16
|
+
# The replacement should always be a string that may include references to the matched data from the rule.
|
17
|
+
def plural(rule, replacement)
|
18
|
+
@uncountables.delete(rule) if rule.is_a?(String)
|
19
|
+
@uncountables.delete(replacement)
|
20
|
+
@plurals.insert(0, [rule, replacement])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
|
24
|
+
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
|
25
|
+
#
|
26
|
+
# Examples:
|
27
|
+
# irregular 'octopus', 'octopi'
|
28
|
+
# irregular 'person', 'people'
|
29
|
+
def irregular(singular, plural)
|
30
|
+
@uncountables.delete(singular)
|
31
|
+
@uncountables.delete(plural)
|
32
|
+
if singular[0,1].upcase == plural[0,1].upcase
|
33
|
+
plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
|
34
|
+
plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
|
35
|
+
else
|
36
|
+
plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
|
37
|
+
plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
|
38
|
+
plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
|
39
|
+
plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add uncountable words that shouldn't be attempted inflected.
|
44
|
+
#
|
45
|
+
# Examples:
|
46
|
+
# uncountable "money"
|
47
|
+
# uncountable "money", "information"
|
48
|
+
# uncountable %w( money information rice )
|
49
|
+
def uncountable(*words)
|
50
|
+
(@uncountables << words).flatten!
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Yields a singleton instance of Inflector::Inflections so you can specify additional
|
55
|
+
# inflector rules.
|
56
|
+
#
|
57
|
+
# Example:
|
58
|
+
# ActiveSupport::Inflector.inflections do |inflect|
|
59
|
+
# inflect.uncountable "rails"
|
60
|
+
# end
|
61
|
+
def self.inflections
|
62
|
+
if block_given?
|
63
|
+
yield Inflections.instance
|
64
|
+
else
|
65
|
+
Inflections.instance
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the plural form of the word in the string.
|
70
|
+
#
|
71
|
+
# Examples:
|
72
|
+
# "post".pluralize # => "posts"
|
73
|
+
# "octopus".pluralize # => "octopi"
|
74
|
+
# "sheep".pluralize # => "sheep"
|
75
|
+
# "words".pluralize # => "words"
|
76
|
+
# "CamelOctopus".pluralize # => "CamelOctopi"
|
77
|
+
def self.pluralize(word)
|
78
|
+
result = word.to_s.dup
|
79
|
+
|
80
|
+
if word.empty? || inflections.uncountables.include?(result.downcase)
|
81
|
+
result
|
82
|
+
else
|
83
|
+
inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
|
84
|
+
result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DocumentHydrator do
|
4
|
+
class Dummy
|
5
|
+
class <<self
|
6
|
+
attr_reader :invocation_count
|
7
|
+
|
8
|
+
def reset_invocation_count
|
9
|
+
@invocation_count = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
def ids_to_document_hash(ids)
|
13
|
+
@invocation_count += 1
|
14
|
+
ids.inject({}) do |hash, id|
|
15
|
+
hash[id] = { 'id' => id }
|
16
|
+
hash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
Dummy.reset_invocation_count
|
24
|
+
@hydration_proc = lambda { |ids| Dummy.ids_to_document_hash(ids) }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '.hydrate_document' do
|
28
|
+
context 'with a simple path to an array of ids' do
|
29
|
+
it 'replaces the array with an array of hydrated documents' do
|
30
|
+
orig = { 'key1' => 37, 'users' => [27, 39] }
|
31
|
+
expected = { 'key1' => 37, 'users' => [{ 'id' => 27 }, { 'id' => 39 }] }
|
32
|
+
DocumentHydrator.hydrate_document(orig.dup, 'users', @hydration_proc).should == expected
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'leaves the array empty if it was empty' do
|
36
|
+
orig = { 'key1' => 37, 'users' => [] }
|
37
|
+
DocumentHydrator.hydrate_document(orig.dup, 'users', @hydration_proc).should == orig
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'makes no modification to the document when the path does not exist' do
|
41
|
+
orig = { 'key1' => 37, 'users' => [3, 5] }
|
42
|
+
DocumentHydrator.hydrate_document(orig.dup, 'losers', @hydration_proc).should == orig
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with a simple path to an individual id' do
|
47
|
+
it 'replaces the id with the corresponding hydrated document' do
|
48
|
+
orig = { 'key1' => 37, 'user' => 72}
|
49
|
+
expected = { 'key1' => 37, 'user' => { 'id' => 72 }}
|
50
|
+
DocumentHydrator.hydrate_document(orig.dup, 'user', @hydration_proc).should == expected
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'with a compound path to an array of ids' do
|
55
|
+
it 'replaces the array with an array of hydrated documents' do
|
56
|
+
orig = { 'key1' => 37, 'foo' => { 'users' => [27, 39] } }
|
57
|
+
expected = { 'key1' => 37, 'foo' => { 'users' => [{ 'id' => 27 }, { 'id' => 39 }] } }
|
58
|
+
DocumentHydrator.hydrate_document(orig.dup, 'foo.users', @hydration_proc).should == expected
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'makes no modification to the document when the path does not exist' do
|
62
|
+
orig = { 'key1' => 37, 'foo' => { 'users' => [27, 39] } }
|
63
|
+
DocumentHydrator.hydrate_document(orig.dup, 'bar.users', @hydration_proc).should == orig
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with a compound path that includes an array as an intermediate step' do
|
68
|
+
it 'hydrates all of the expanded paths' do
|
69
|
+
orig = {
|
70
|
+
'key1' => 37,
|
71
|
+
'foos' => [ { 'users' => [27, 39] }, { 'users' => [27, 88] } ]
|
72
|
+
}
|
73
|
+
expected = {
|
74
|
+
'key1' => 37,
|
75
|
+
'foos' => [ { 'users' => [{ 'id' => 27 }, { 'id' => 39 }] },
|
76
|
+
{ 'users' => [{ 'id' => 27 }, { 'id' => 88 }] }]
|
77
|
+
}
|
78
|
+
DocumentHydrator.hydrate_document(orig.dup, 'foos.users', @hydration_proc).should == expected
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'with an array of paths' do
|
83
|
+
before(:each) do
|
84
|
+
@orig = {
|
85
|
+
'key1' => 77,
|
86
|
+
'users' => [1, 2, 3, 4],
|
87
|
+
'stuff' => {
|
88
|
+
'monkeys' => [99, 1]
|
89
|
+
},
|
90
|
+
'blah' => {
|
91
|
+
'nested_stuff' => [
|
92
|
+
{ 'user' => 99 },
|
93
|
+
{ 'user' => 101 }
|
94
|
+
]
|
95
|
+
}
|
96
|
+
}
|
97
|
+
@paths = ['users', 'stuff.monkeys', 'blah.nested_stuff.user']
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'achieves the same result as hydrating each path individually' do
|
101
|
+
expected = @orig.dup.tap do |document|
|
102
|
+
@paths.each { |path| DocumentHydrator.hydrate_document(document, path, @hydration_proc) }
|
103
|
+
end
|
104
|
+
|
105
|
+
DocumentHydrator.hydrate_document(@orig.dup, @paths, @hydration_proc).should == expected
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'invokes the hydration proc only once' do
|
109
|
+
DocumentHydrator.hydrate_document(@orig.dup, @paths, @hydration_proc)
|
110
|
+
Dummy.invocation_count.should == 1
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with a path whose terminal key ends with '_id'" do
|
115
|
+
it "removes the '_id' suffix during hydration" do
|
116
|
+
orig = {
|
117
|
+
'key1' => 37,
|
118
|
+
'user_id' => 99
|
119
|
+
}
|
120
|
+
expected = {
|
121
|
+
'key1' => 37,
|
122
|
+
'user' => { 'id' => 99 }
|
123
|
+
}
|
124
|
+
DocumentHydrator.hydrate_document(orig.dup, 'user_id', @hydration_proc).should == expected
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context "with a path whose terminal key ends with '_ids'" do
|
129
|
+
it "removes the '_ids' suffix and pluralizes the root during hydration" do
|
130
|
+
orig = {
|
131
|
+
'key1' => 37,
|
132
|
+
'foos' => [ { 'user_ids' => [27, 39] }, { 'user_ids' => [27, 88] } ]
|
133
|
+
}
|
134
|
+
expected = {
|
135
|
+
'key1' => 37,
|
136
|
+
'foos' => [ { 'users' => [{ 'id' => 27 }, { 'id' => 39 }] },
|
137
|
+
{ 'users' => [{ 'id' => 27 }, { 'id' => 88 }] }]
|
138
|
+
}
|
139
|
+
DocumentHydrator.hydrate_document(orig.dup, 'foos.user_ids', @hydration_proc).should == expected
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '.hydrate_documents' do
|
145
|
+
before(:each) do
|
146
|
+
@documents = [
|
147
|
+
{
|
148
|
+
'random_key' => 37,
|
149
|
+
'creator' => 99,
|
150
|
+
'users' => [37, 42]
|
151
|
+
},
|
152
|
+
{
|
153
|
+
'random_key' => 37,
|
154
|
+
'users' => [88, 42]
|
155
|
+
}
|
156
|
+
]
|
157
|
+
@paths = ['creator', 'users']
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'gives the same result as hydrating each document individually' do
|
161
|
+
individual_results = @documents.map { |doc| DocumentHydrator.hydrate_document(doc.dup, @paths, @hydration_proc) }
|
162
|
+
DocumentHydrator.hydrate_documents(@documents, @paths, @hydration_proc).should == individual_results
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'invokes the hydration proc only once' do
|
166
|
+
DocumentHydrator.hydrate_documents(@documents, @paths, @hydration_proc)
|
167
|
+
Dummy.invocation_count.should == 1
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DocumentHydrator::HydrationProc::Mongo, '.collection' do
|
4
|
+
before(:each) do
|
5
|
+
db = Mongo::Connection.new.db('document_hydrator_test')
|
6
|
+
@users_collection = db['users']
|
7
|
+
@users_collection.remove
|
8
|
+
@users_collection.insert('_id' => 1, 'name' => 'Fred')
|
9
|
+
@users_collection.insert('_id' => 2, 'name' => 'Wilma')
|
10
|
+
@users_collection.insert('_id' => 3, 'name' => 'Barney')
|
11
|
+
@users_collection.insert('_id' => 4, 'name' => 'Betty')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'returns a hydration proc that fetches subdocuments from the provided Mongo::Collection' do
|
15
|
+
hydrator = DocumentHydrator::HydrationProc::Mongo.collection(@users_collection)
|
16
|
+
expected = {
|
17
|
+
1 => @users_collection.find_one('_id' => 1),
|
18
|
+
3 => @users_collection.find_one('_id' => 3),
|
19
|
+
}
|
20
|
+
hydrator.call([1,3]).should == expected
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'integrates with DocumentHydrator' do
|
24
|
+
document = { 'users' => [2, 4] }
|
25
|
+
expected = { 'users' => [
|
26
|
+
{ '_id' => 2, 'name' => 'Wilma' },
|
27
|
+
{ '_id' => 4, 'name' => 'Betty' }
|
28
|
+
]
|
29
|
+
}
|
30
|
+
|
31
|
+
hydrator = DocumentHydrator::HydrationProc::Mongo.collection(@users_collection)
|
32
|
+
DocumentHydrator.hydrate_document(document, ['users'], hydrator).should == expected
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with optional finder options' do
|
36
|
+
it 'passes them to Mongo::Collection#find' do
|
37
|
+
options = { :fields => { 'name' => 1 } }
|
38
|
+
@users_collection.should_receive(:find).with(anything, options).and_return([])
|
39
|
+
|
40
|
+
hydrator = DocumentHydrator::HydrationProc::Mongo.collection(@users_collection, options)
|
41
|
+
hydrator.call([1, 3])
|
42
|
+
end
|
43
|
+
|
44
|
+
it "handles the case of '_id' being explicitly removed from result set" do
|
45
|
+
options = { :fields => { 'name' => 1, '_id' => 0 } }
|
46
|
+
expected = {
|
47
|
+
1 => { 'name' => 'Fred' },
|
48
|
+
3 => { 'name' => 'Barney' }
|
49
|
+
}
|
50
|
+
|
51
|
+
hydrator = DocumentHydrator::HydrationProc::Mongo.collection(@users_collection, options)
|
52
|
+
hydrator.call([1, 3]).should == expected
|
53
|
+
end
|
54
|
+
|
55
|
+
it "handles the case of :_id being explicitly removed from result set" do
|
56
|
+
options = { :fields => { :name => 1, :_id => 0 } }
|
57
|
+
expected = {
|
58
|
+
1 => { 'name' => 'Fred' },
|
59
|
+
3 => { 'name' => 'Barney' }
|
60
|
+
}
|
61
|
+
|
62
|
+
hydrator = DocumentHydrator::HydrationProc::Mongo.collection(@users_collection, options)
|
63
|
+
hydrator.call([1, 3]).should == expected
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: document_hydrator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Greg Spurrier
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-19 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :development
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 1.0.0
|
35
|
+
name: bundler
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :development
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 11
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 6
|
49
|
+
- 2
|
50
|
+
version: 1.6.2
|
51
|
+
name: jeweler
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
prerelease: false
|
55
|
+
type: :development
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 23
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 6
|
65
|
+
- 0
|
66
|
+
version: 2.6.0
|
67
|
+
name: rspec
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
prerelease: false
|
71
|
+
type: :development
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 43
|
78
|
+
segments:
|
79
|
+
- 4
|
80
|
+
- 4
|
81
|
+
- 2
|
82
|
+
version: 4.4.2
|
83
|
+
name: ZenTest
|
84
|
+
version_requirements: *id004
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
prerelease: false
|
87
|
+
type: :development
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
hash: 3
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
name: autotest-growl
|
98
|
+
version_requirements: *id005
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
prerelease: false
|
101
|
+
type: :development
|
102
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
name: autotest-fsevent
|
112
|
+
version_requirements: *id006
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
prerelease: false
|
115
|
+
type: :development
|
116
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
name: bson_ext
|
126
|
+
version_requirements: *id007
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
prerelease: false
|
129
|
+
type: :development
|
130
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
hash: 3
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
version: "0"
|
139
|
+
name: bson
|
140
|
+
version_requirements: *id008
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
prerelease: false
|
143
|
+
type: :development
|
144
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
hash: 3
|
150
|
+
segments:
|
151
|
+
- 0
|
152
|
+
version: "0"
|
153
|
+
name: mongo
|
154
|
+
version_requirements: *id009
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
prerelease: false
|
157
|
+
type: :development
|
158
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
159
|
+
none: false
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
hash: 3
|
164
|
+
segments:
|
165
|
+
- 0
|
166
|
+
version: "0"
|
167
|
+
name: SystemTimer
|
168
|
+
version_requirements: *id010
|
169
|
+
description: DocumentHydrator takes a document, represented as a Ruby Hash, and efficiently updates it so that embedded references to other documents are replaced with their corresponding subdocuments.
|
170
|
+
email: greg.spurrier@gmail.com
|
171
|
+
executables: []
|
172
|
+
|
173
|
+
extensions: []
|
174
|
+
|
175
|
+
extra_rdoc_files:
|
176
|
+
- LICENSE.txt
|
177
|
+
- README.markdown
|
178
|
+
files:
|
179
|
+
- .autotest
|
180
|
+
- .rspec
|
181
|
+
- Gemfile
|
182
|
+
- LICENSE.txt
|
183
|
+
- README.markdown
|
184
|
+
- Rakefile
|
185
|
+
- VERSION
|
186
|
+
- document_hydrator.gemspec
|
187
|
+
- lib/document_hydrator.rb
|
188
|
+
- lib/document_hydrator/hydration_proc/mongo.rb
|
189
|
+
- lib/document_hydrator/inflector.rb
|
190
|
+
- lib/document_hydrator/inflector/inflections.rb
|
191
|
+
- spec/document_hydrator_spec.rb
|
192
|
+
- spec/hydration_proc/mongo_spec.rb
|
193
|
+
- spec/spec_helper.rb
|
194
|
+
has_rdoc: true
|
195
|
+
homepage: http://github.com/gregspurrier/document_hydrator
|
196
|
+
licenses:
|
197
|
+
- MIT
|
198
|
+
post_install_message:
|
199
|
+
rdoc_options: []
|
200
|
+
|
201
|
+
require_paths:
|
202
|
+
- lib
|
203
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
hash: 3
|
209
|
+
segments:
|
210
|
+
- 0
|
211
|
+
version: "0"
|
212
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
213
|
+
none: false
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
hash: 3
|
218
|
+
segments:
|
219
|
+
- 0
|
220
|
+
version: "0"
|
221
|
+
requirements: []
|
222
|
+
|
223
|
+
rubyforge_project:
|
224
|
+
rubygems_version: 1.6.2
|
225
|
+
signing_key:
|
226
|
+
specification_version: 3
|
227
|
+
summary: DocumentHydrator takes a document, represented as a Ruby Hash, and efficiently updates it so that embedded references to other documents are replaced with their corresponding subdocuments.
|
228
|
+
test_files: []
|
229
|
+
|