mongo_thing 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +3 -0
- data/.gitignore +2 -0
- data/Gemfile +15 -0
- data/MIT_LICENSE +20 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/lib/mongo_thing/cursor.rb +82 -0
- data/lib/mongo_thing/document.rb +176 -0
- data/lib/mongo_thing/named_scope.rb +37 -0
- data/lib/mongo_thing/nested_ostruct.rb +49 -0
- data/lib/mongo_thing/railtie.rb +53 -0
- data/lib/mongo_thing/railties/database.rake +37 -0
- data/lib/mongo_thing.rb +64 -0
- data/mongo_thing.gemspec +64 -0
- data/spec/config/mongoid.yml +19 -0
- data/spec/integration/mongo_thing/document_spec.rb +143 -0
- data/spec/integration/mongo_thing/nested_ostruct_spec.rb +49 -0
- data/spec/integration/mongo_thing_spec.rb +15 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +34 -0
- metadata +111 -0
data/.bundle/config
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Use `bundle install` in order to install these gems
|
2
|
+
# Use `bundle exec rake` in order to run the specs using the bundle
|
3
|
+
source "http://rubygems.org"
|
4
|
+
|
5
|
+
gem "mongo", "~> 1.0"
|
6
|
+
gem "bson", "~> 1.0"
|
7
|
+
gem "bson_ext", "~> 1.0"
|
8
|
+
gem "activesupport", "3.0.0.beta3"
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem "rake"
|
12
|
+
gem "ruby-debug"
|
13
|
+
gem "rspec", "1.3.0"
|
14
|
+
gem "jeweler"
|
15
|
+
end
|
data/MIT_LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Durran Jordan
|
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/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
|
4
|
+
begin
|
5
|
+
Bundler.require(:development)
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "mongo_thing"
|
8
|
+
gem.summary = "ODM framework for MongoDB"
|
9
|
+
gem.email = "daniel@flyingmachinestudios.com"
|
10
|
+
gem.homepage = ""
|
11
|
+
gem.authors = ["Daniel Higginbotham"]
|
12
|
+
|
13
|
+
gem.add_dependency("mongo", "~> 1.0.1")
|
14
|
+
gem.add_dependency("bson", "~> 1.0.1")
|
15
|
+
end
|
16
|
+
|
17
|
+
require "spec/rake/spectask"
|
18
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
19
|
+
spec.pattern = "spec/**/*_spec.rb"
|
20
|
+
spec.spec_opts = ["--options", "spec/spec.opts"]
|
21
|
+
end
|
22
|
+
|
23
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
24
|
+
spec.libs << "lib" << "spec"
|
25
|
+
spec.pattern = "spec/**/*_spec.rb"
|
26
|
+
spec.spec_opts = ["--options", "spec/spec.opts"]
|
27
|
+
spec.rcov = true
|
28
|
+
end
|
29
|
+
|
30
|
+
task :default => :spec do
|
31
|
+
end
|
32
|
+
rescue LoadError => e
|
33
|
+
puts "Error loading dependcy: #{e}."
|
34
|
+
end
|
35
|
+
|
36
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MongoThing #:nodoc
|
3
|
+
class Cursor
|
4
|
+
include Enumerable
|
5
|
+
# Operations on the Mongo::Cursor object that will not get overriden by the
|
6
|
+
# MongoThing::Cursor are defined here.
|
7
|
+
OPERATIONS = [
|
8
|
+
:admin,
|
9
|
+
:close,
|
10
|
+
:closed?,
|
11
|
+
:count,
|
12
|
+
:explain,
|
13
|
+
:fields,
|
14
|
+
:full_collection_name,
|
15
|
+
:hint,
|
16
|
+
:limit,
|
17
|
+
:order,
|
18
|
+
:query_options_hash,
|
19
|
+
:query_opts,
|
20
|
+
:selector,
|
21
|
+
:skip,
|
22
|
+
:snapshot,
|
23
|
+
:sort,
|
24
|
+
:timeout
|
25
|
+
]
|
26
|
+
|
27
|
+
attr_reader :collection
|
28
|
+
|
29
|
+
# The operations above will all delegate to the proxied Mongo::Cursor.
|
30
|
+
#
|
31
|
+
# Example:
|
32
|
+
#
|
33
|
+
# <tt>cursor.close</tt>
|
34
|
+
OPERATIONS.each do |name|
|
35
|
+
define_method(name) { |*args| @cursor.send(name, *args) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Iterate over each document in the cursor and yield to it.
|
39
|
+
#
|
40
|
+
# Example:
|
41
|
+
#
|
42
|
+
# <tt>cursor.each { |doc| p doc.title }</tt>
|
43
|
+
def each
|
44
|
+
@cursor.each do |document|
|
45
|
+
yield @klass.new(document)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create the new +MongoThing::Cursor+.
|
50
|
+
#
|
51
|
+
# Options:
|
52
|
+
#
|
53
|
+
# collection: The MongoThing::Collection instance.
|
54
|
+
# cursor: The Mongo::Cursor to be proxied.
|
55
|
+
#
|
56
|
+
# Example:
|
57
|
+
#
|
58
|
+
# <tt>MongoThing::Cursor.new(Person, cursor)</tt>
|
59
|
+
def initialize(klass, cursor)
|
60
|
+
@klass, @cursor = klass, cursor
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return the next document in the cursor. Will instantiate a new MongoThing
|
64
|
+
# document with the attributes.
|
65
|
+
#
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# <tt>cursor.next_document</tt>
|
69
|
+
def next_document
|
70
|
+
@klass.new(@cursor.next_document)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns an array of all the documents in the cursor.
|
74
|
+
#
|
75
|
+
# Example:
|
76
|
+
#
|
77
|
+
# <tt>cursor.to_a</tt>
|
78
|
+
def to_a
|
79
|
+
@cursor.to_a.collect { |attrs| @klass.new(attrs) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module MongoThing #:nodoc:
|
3
|
+
module Document
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class << self
|
8
|
+
attr_accessor :properties
|
9
|
+
def properties(*set)
|
10
|
+
if !set.empty?
|
11
|
+
@properties = set
|
12
|
+
else
|
13
|
+
@properties | [:_id]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
self.properties = []
|
18
|
+
|
19
|
+
delegate :db, :collection, :to => "self.class"
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# Return the database associated with this class.
|
24
|
+
def db
|
25
|
+
collection.db
|
26
|
+
end
|
27
|
+
|
28
|
+
def collection
|
29
|
+
MongoThing.db.collection(self.name)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Finding
|
33
|
+
# Returns an array of Document objects
|
34
|
+
# Wonder if it would be desirable to expose the cursor?
|
35
|
+
def find(selector={}, opts={})
|
36
|
+
mongo_cursor = collection.find(selector, opts)
|
37
|
+
Cursor.new(self, mongo_cursor).to_a
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](doc_id)
|
41
|
+
find_one(doc_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_one(spec_or_object_id=nil, opts={})
|
45
|
+
self.new(collection.find_one(spec_or_object_id, opts))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Creating
|
49
|
+
def create(document)
|
50
|
+
self.new(document).save
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deleting
|
54
|
+
def remove(selector={}, opts={})
|
55
|
+
collection.remove(selector, opts)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module InstanceMethods
|
60
|
+
# OpenStruct like behavior
|
61
|
+
def method_missing(mid, *args) # :nodoc:
|
62
|
+
mname = mid.id2name
|
63
|
+
chomped = mname.chomp('=')
|
64
|
+
len = args.length
|
65
|
+
if self.class.properties.include?(chomped.to_sym)
|
66
|
+
if mname == chomped && len > 0 || len > 1
|
67
|
+
raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
|
68
|
+
end
|
69
|
+
new_attribute(mname.chomp('='))
|
70
|
+
self.send(mid, *args)
|
71
|
+
else
|
72
|
+
raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def new_attribute(name)
|
77
|
+
name = name.to_sym
|
78
|
+
unless self.respond_to?(name)
|
79
|
+
class << self; self; end.class_eval do
|
80
|
+
define_method(name) { @attributes.send(name) }
|
81
|
+
define_method("#{name}=") { |x| @attributes.send("#{name}=".to_sym, x) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
name
|
85
|
+
end
|
86
|
+
|
87
|
+
# Performs equality checking on the document ids. For more robust
|
88
|
+
# equality checking please override this method.
|
89
|
+
def ==(other)
|
90
|
+
return false unless other.is_a?(Document)
|
91
|
+
id == other.id
|
92
|
+
end
|
93
|
+
|
94
|
+
# Delegates to ==
|
95
|
+
def eql?(comparison_object)
|
96
|
+
self == (comparison_object)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_hash
|
100
|
+
@attributes.to_hash
|
101
|
+
end
|
102
|
+
|
103
|
+
# Clone the current +Document+. This will return all attributes with the
|
104
|
+
# exception of the document's id and versions.
|
105
|
+
def clone
|
106
|
+
self.class.instantiate(@attributes.except("_id").except("versions").dup, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
# Instantiate a new +Document+, setting the Document's attributes if
|
111
|
+
# given. If no attributes are provided, they will be initialized with
|
112
|
+
# an empty +Hash+.
|
113
|
+
#
|
114
|
+
# If a primary key is defined, the document's id will be set to that key,
|
115
|
+
# otherwise it will be set to a fresh +BSON::ObjectID+ string.
|
116
|
+
#
|
117
|
+
# Options:
|
118
|
+
#
|
119
|
+
# attrs: The attributes +Hash+ to set up the document with.
|
120
|
+
def initialize(attrs = {})
|
121
|
+
@attributes = NestedOpenStruct.new
|
122
|
+
self.attributes = attrs
|
123
|
+
@new_record = true if id.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
def attributes=(attrs={})
|
127
|
+
attrs.each do |k,v|
|
128
|
+
self.send("#{k}=", v)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the class name plus its attributes.
|
133
|
+
def inspect
|
134
|
+
attrs = fields.map { |name, field| "#{name}: #{@attributes[name].inspect}" }
|
135
|
+
if Mongoid.allow_dynamic_fields
|
136
|
+
dynamic_keys = @attributes.keys - fields.keys - ["_id", "_type"]
|
137
|
+
attrs += dynamic_keys.map { |name| "#{name}: #{@attributes[name].inspect}" }
|
138
|
+
end
|
139
|
+
"#<#{self.class.name} _id: #{id}, #{attrs * ', '}>"
|
140
|
+
end
|
141
|
+
|
142
|
+
# Reloads the +Document+ attributes from the database.
|
143
|
+
def reload
|
144
|
+
if self.id
|
145
|
+
self.attributes = self.class[self.id].to_hash
|
146
|
+
else
|
147
|
+
raise ArgumentError, "this document has not been saved"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Return an array with this +Document+ only in it.
|
152
|
+
def to_a
|
153
|
+
[ self ]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the id of the Document, used in Rails compatibility.
|
157
|
+
def to_param
|
158
|
+
id
|
159
|
+
end
|
160
|
+
|
161
|
+
def id
|
162
|
+
@attributes._id
|
163
|
+
end
|
164
|
+
|
165
|
+
def id=(new_id)
|
166
|
+
@attributes._id = new_id
|
167
|
+
end
|
168
|
+
|
169
|
+
def save
|
170
|
+
@attributes._id = BSON::ObjectID.new if @attributes._id.nil?
|
171
|
+
collection.save(self.to_hash)
|
172
|
+
self
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module NamedScope
|
4
|
+
# Creates a named_scope for the +Document+, similar to ActiveRecord's
|
5
|
+
# named_scopes. +NamedScopes+ are proxied +Criteria+ objects that can be
|
6
|
+
# chained.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class Person
|
11
|
+
# include Mongoid::Document
|
12
|
+
# field :active, :type => Boolean
|
13
|
+
# field :count, :type => Integer
|
14
|
+
#
|
15
|
+
# named_scope :active, :where => { :active => true }
|
16
|
+
# named_scope :count_gt_one, :where => { :count.gt => 1 }
|
17
|
+
# named_scope :at_least_count, lambda { |count| { :where => { :count.gt => count } } }
|
18
|
+
# end
|
19
|
+
def named_scope(name, options = {}, &block)
|
20
|
+
name = name.to_sym
|
21
|
+
scopes[name] = lambda do |parent, *args|
|
22
|
+
Scope.new(parent, options.scoped(*args), &block)
|
23
|
+
end
|
24
|
+
(class << self; self; end).class_eval <<-EOT
|
25
|
+
def #{name}(*args)
|
26
|
+
scopes[:#{name}].call(self, *args)
|
27
|
+
end
|
28
|
+
EOT
|
29
|
+
end
|
30
|
+
alias :scope :named_scope
|
31
|
+
|
32
|
+
# Return the scopes or default to an empty +Hash+.
|
33
|
+
def scopes
|
34
|
+
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# NestedOpenStruct works like OpenStruct except that all hash values are converted
|
2
|
+
# to NestedOpenStruct
|
3
|
+
|
4
|
+
module MongoThing
|
5
|
+
class NestedOpenStruct < OpenStruct
|
6
|
+
def initialize(hash=nil)
|
7
|
+
@table = {}
|
8
|
+
@nested_open_structs = []
|
9
|
+
if hash
|
10
|
+
for k,v in hash
|
11
|
+
v = NestedOpenStruct.new(v) if v.is_a? Hash
|
12
|
+
v = v.collect{|nv| nv.is_a?(Hash) ? NestedOpenStruct.new(nv) : nv} if v.is_a?(Array)
|
13
|
+
@table[k.to_sym] = v
|
14
|
+
new_ostruct_member(k)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(mid, *args) # :nodoc:
|
20
|
+
mname = mid.id2name
|
21
|
+
if mname[-1..-1] == "=" && args[0] && args[0].is_a?(Hash)
|
22
|
+
args[0] = NestedOpenStruct.new(args[0])
|
23
|
+
end
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
marshal_dump
|
29
|
+
end
|
30
|
+
|
31
|
+
def marshal_dump
|
32
|
+
@table.collect do |k,v|
|
33
|
+
n = if v.is_a?(NestedOpenStruct)
|
34
|
+
v.marshal_dump
|
35
|
+
elsif v.is_a?(Array)
|
36
|
+
v.collect{|a| a.is_a?(NestedOpenStruct) ? a.marshal_dump : a}
|
37
|
+
else
|
38
|
+
v
|
39
|
+
end
|
40
|
+
[k,n]
|
41
|
+
end.inject({}){|h,v|h[v[0]] = v[1]; h}
|
42
|
+
end
|
43
|
+
|
44
|
+
def marshal_load(x)
|
45
|
+
@table = x
|
46
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rails #:nodoc:
|
2
|
+
module MongoThing #:nodoc:
|
3
|
+
class Railtie < Rails::Railtie #:nodoc:
|
4
|
+
|
5
|
+
# do we want a custom log subscriber for mongoid?
|
6
|
+
# log_subscriber :mongoid, ::Mongoid::Railties::LogSubscriber.new
|
7
|
+
|
8
|
+
rake_tasks do
|
9
|
+
load "mongo-thing/railties/database.rake"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Initialize Mongoid. This will look for a mongoid.yml in the config
|
13
|
+
# directory and configure mongoid appropriately.
|
14
|
+
#
|
15
|
+
# Example mongoid.yml:
|
16
|
+
#
|
17
|
+
# defaults: &defaults
|
18
|
+
# host: localhost
|
19
|
+
# slaves:
|
20
|
+
# # - host: localhost
|
21
|
+
# # port: 27018
|
22
|
+
# # - host: localhost
|
23
|
+
# # port: 27019
|
24
|
+
# allow_dynamic_fields: false
|
25
|
+
# parameterize_keys: false
|
26
|
+
# persist_in_safe_mode: false
|
27
|
+
#
|
28
|
+
# development:
|
29
|
+
# <<: *defaults
|
30
|
+
# database: mongoid
|
31
|
+
initializer "setup database" do
|
32
|
+
config_file = Rails.root.join("config", "mongoid.yml")
|
33
|
+
if config_file.file?
|
34
|
+
settings = YAML.load(ERB.new(config_file.read).result)[Rails.env]
|
35
|
+
::MongoThing.connection = settings if settings.present?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
initializer "verify that mongoid is configured" do
|
40
|
+
config.after_initialize do
|
41
|
+
begin
|
42
|
+
::Mongoid.master
|
43
|
+
rescue ::Mongoid::Errors::InvalidDatabase => e
|
44
|
+
unless Rails.root.join("config", "mongoid.yml").file?
|
45
|
+
puts "\nMongoid config not found. Create a config file at: config/mongoid.yml"
|
46
|
+
puts "to generate one run: script/rails generate mongoid:config\n\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
namespace :db do
|
2
|
+
|
3
|
+
desc 'Drops all the collections for the database for the current Rails.env'
|
4
|
+
task :drop => :environment do
|
5
|
+
Mongoid.master.collections.each{|col| col.drop unless col.name == 'system.users' }
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Load the seed data from db/seeds.rb'
|
9
|
+
task :seed => :environment do
|
10
|
+
seed_file = File.join(Rails.root, 'db', 'seeds.rb')
|
11
|
+
load(seed_file) if File.exist?(seed_file)
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Create the database, and initialize with the seed data'
|
15
|
+
task :setup => [ 'db:create', 'db:seed' ]
|
16
|
+
|
17
|
+
desc 'Delete data and seed'
|
18
|
+
task :reseed => [ 'db:drop', 'db:seed' ]
|
19
|
+
|
20
|
+
task :create => :environment do
|
21
|
+
# noop
|
22
|
+
end
|
23
|
+
|
24
|
+
task :migrate => :environment do
|
25
|
+
# noop
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :schema do
|
29
|
+
task :load do
|
30
|
+
# noop
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
########
|
35
|
+
# TODO: lots more useful db tasks can be added here. stuff like copyDatabase, etc
|
36
|
+
########
|
37
|
+
end
|
data/lib/mongo_thing.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright (c) 2010 Daniel Higginbotham
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'rubygems'
|
24
|
+
require 'bundler'
|
25
|
+
Bundler.setup(:default)
|
26
|
+
require 'active_support/core_ext'
|
27
|
+
require 'bson'
|
28
|
+
require 'ostruct'
|
29
|
+
|
30
|
+
$:.unshift(File.expand_path(File.dirname(__FILE__)))
|
31
|
+
require 'mongo_thing/cursor'
|
32
|
+
require 'mongo_thing/document'
|
33
|
+
require 'mongo_thing/nested_ostruct'
|
34
|
+
|
35
|
+
|
36
|
+
module MongoThing #:nodoc
|
37
|
+
|
38
|
+
class << self
|
39
|
+
attr_accessor :connection, :db
|
40
|
+
def connection=(conn)
|
41
|
+
case conn
|
42
|
+
when Hash
|
43
|
+
@connection = Mongo::Connection.new(conn.delete(:host), conn.delete(:port), conn)
|
44
|
+
when Mongo::Connection
|
45
|
+
@connection = conn
|
46
|
+
end
|
47
|
+
self.db = conn if conn[:name]
|
48
|
+
end
|
49
|
+
|
50
|
+
def db=(new_db)
|
51
|
+
case new_db
|
52
|
+
when String, Symbol
|
53
|
+
@db = connection.db(new_db.to_s)
|
54
|
+
when Hash
|
55
|
+
@db = connection.db(new_db[:name], new_db)
|
56
|
+
when Mongo::DB
|
57
|
+
@db = new_db
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
data/mongo_thing.gemspec
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mongo_thing}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Daniel Higginbotham"]
|
12
|
+
s.date = %q{2010-05-09}
|
13
|
+
s.email = %q{daniel@flyingmachinestudios.com}
|
14
|
+
s.files = [
|
15
|
+
".bundle/config",
|
16
|
+
".gitignore",
|
17
|
+
"Gemfile",
|
18
|
+
"MIT_LICENSE",
|
19
|
+
"Rakefile",
|
20
|
+
"VERSION",
|
21
|
+
"lib/mongo_thing.rb",
|
22
|
+
"lib/mongo_thing/cursor.rb",
|
23
|
+
"lib/mongo_thing/document.rb",
|
24
|
+
"lib/mongo_thing/named_scope.rb",
|
25
|
+
"lib/mongo_thing/nested_ostruct.rb",
|
26
|
+
"lib/mongo_thing/railtie.rb",
|
27
|
+
"lib/mongo_thing/railties/database.rake",
|
28
|
+
"mongo_thing.gemspec",
|
29
|
+
"spec/config/mongoid.yml",
|
30
|
+
"spec/integration/mongo_thing/document_spec.rb",
|
31
|
+
"spec/integration/mongo_thing/nested_ostruct_spec.rb",
|
32
|
+
"spec/integration/mongo_thing_spec.rb",
|
33
|
+
"spec/spec.opts",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.homepage = %q{}
|
37
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
38
|
+
s.require_paths = ["lib"]
|
39
|
+
s.rubygems_version = %q{1.3.6}
|
40
|
+
s.summary = %q{ODM framework for MongoDB}
|
41
|
+
s.test_files = [
|
42
|
+
"spec/integration/mongo_thing/document_spec.rb",
|
43
|
+
"spec/integration/mongo_thing/nested_ostruct_spec.rb",
|
44
|
+
"spec/integration/mongo_thing_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<mongo>, ["~> 1.0.1"])
|
54
|
+
s.add_runtime_dependency(%q<bson>, ["~> 1.0.1"])
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<mongo>, ["~> 1.0.1"])
|
57
|
+
s.add_dependency(%q<bson>, ["~> 1.0.1"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<mongo>, ["~> 1.0.1"])
|
61
|
+
s.add_dependency(%q<bson>, ["~> 1.0.1"])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
:defaults: &defaults
|
2
|
+
:host: localhost
|
3
|
+
:slaves:
|
4
|
+
# - host: localhost
|
5
|
+
# port: 27018
|
6
|
+
# - host: localhost
|
7
|
+
# port: 27019
|
8
|
+
:allow_dynamic_fields: false
|
9
|
+
:parameterize_keys: false
|
10
|
+
:persist_in_safe_mode: false
|
11
|
+
:raise_not_found_error: false
|
12
|
+
:reconnect_time: 5
|
13
|
+
:use_object_ids: true
|
14
|
+
:persist_types: false
|
15
|
+
:option_no_exist: false
|
16
|
+
|
17
|
+
:test:
|
18
|
+
<<: *defaults
|
19
|
+
:name: mongo_thing_test
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Person
|
4
|
+
include MongoThing::Document
|
5
|
+
self.properties = [:name, :address, :friends]
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
describe MongoThing::Document do
|
10
|
+
describe "attributes" do
|
11
|
+
it "should only allow properties to be treated as attributes" do
|
12
|
+
p = Person.new
|
13
|
+
lambda{p.blarg = "Tom"}.should raise_error(NoMethodError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow properties to be treated as attributes" do
|
17
|
+
p = Person.new
|
18
|
+
lambda{p.name = "Tom"}.should_not raise_error(NoMethodError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should assign attributes in the initializer" do
|
22
|
+
p = Person.new({
|
23
|
+
:name => "Tom"
|
24
|
+
})
|
25
|
+
p.name.should == "Tom"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise an error when trying to assign a non-attribute in the initializer" do
|
29
|
+
lambda {
|
30
|
+
p = Person.new({
|
31
|
+
:name => "Tom",
|
32
|
+
:foo => "bar"
|
33
|
+
})
|
34
|
+
}.should raise_error(NoMethodError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return a hash of attributes" do
|
38
|
+
person_hash = {
|
39
|
+
:name => "Tom",
|
40
|
+
:address => {:street => "Tomahawk", :city => "Placeville"},
|
41
|
+
:friends => [
|
42
|
+
{:name => "John", :address => {:street => "Pleasant", :city => "Cityville"}},
|
43
|
+
{:name => "Bill", :address => {:street => "Prospect", :city => "Townville"}}
|
44
|
+
]
|
45
|
+
}
|
46
|
+
|
47
|
+
p = Person.new(person_hash)
|
48
|
+
p.to_hash.should == person_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should set properties using properties method" do
|
52
|
+
class SetProperties
|
53
|
+
include MongoThing::Document
|
54
|
+
properties :one, :two, :three
|
55
|
+
end
|
56
|
+
|
57
|
+
lambda{SetProperties.new(:one => 1, :two => 2, :three => 3)}.should_not raise_error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "class modifications" do
|
62
|
+
it "should add the 'properties' attribute to class" do
|
63
|
+
Person.respond_to?(:properties).should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should default 'properties' to [:_id]" do
|
67
|
+
class Bear
|
68
|
+
include MongoThing::Document
|
69
|
+
end
|
70
|
+
|
71
|
+
Bear.properties.should == [:_id]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "actual db interaction" do
|
76
|
+
before(:all) do
|
77
|
+
connect_to_test_database
|
78
|
+
end
|
79
|
+
describe "saving" do
|
80
|
+
it "should save a basic document" do
|
81
|
+
p = Person.new
|
82
|
+
p.name = "Tom"
|
83
|
+
p.save
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not have an ID before being saved" do
|
87
|
+
p = Person.new
|
88
|
+
p.id.should be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should have an ID after being saved" do
|
92
|
+
p = Person.new
|
93
|
+
p.name = "Tom"
|
94
|
+
p.save
|
95
|
+
p.id.should_not be_nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should save a document with 'create'" do
|
99
|
+
Person.create(:name => "Tom").id.should_not be_nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "finding" do
|
104
|
+
it "should find a person given an ID" do
|
105
|
+
p = Person.new
|
106
|
+
p.name = "Tom"
|
107
|
+
p.save
|
108
|
+
|
109
|
+
h = Person[p.id]
|
110
|
+
h.name.should == "Tom"
|
111
|
+
h.id.should == p.id
|
112
|
+
|
113
|
+
i = Person.find_one(p.id)
|
114
|
+
i.name.should == "Tom"
|
115
|
+
i.id.should == p.id
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should return all matching documents" do
|
119
|
+
Person.create(:name => "Tom")
|
120
|
+
Person.create(:name => "Voldemort")
|
121
|
+
|
122
|
+
r = Person.find.to_a
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should initialize documents from find" do
|
126
|
+
Person.create(:name => "Tom")
|
127
|
+
Person.create(:name => "Voldemort")
|
128
|
+
|
129
|
+
r = Person.find.to_a
|
130
|
+
r[0].name.should == "Tom"
|
131
|
+
r[1].name.should == "Voldemort"
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should reload a document" do
|
135
|
+
p = Person.create(:name => "Tom")
|
136
|
+
|
137
|
+
p.name = "Voldemort"
|
138
|
+
p.reload
|
139
|
+
p.name.should == "Tom"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MongoThing::NestedOpenStruct do
|
4
|
+
before(:each) do
|
5
|
+
@person_hash = {
|
6
|
+
:name => "Tom",
|
7
|
+
:address => {:street => "Tomahawk", :city => "Placeville"},
|
8
|
+
:friends => [
|
9
|
+
{:name => "John", :address => {:street => "Pleasant", :city => "Cityville"}},
|
10
|
+
{:name => "Bill", :address => {:street => "Prospect", :city => "Townville"}}
|
11
|
+
]
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not modify original hash" do
|
16
|
+
MongoThing::NestedOpenStruct.new(@person_hash)
|
17
|
+
|
18
|
+
@person_hash.should == {
|
19
|
+
:name => "Tom",
|
20
|
+
:address => {:street => "Tomahawk", :city => "Placeville"},
|
21
|
+
:friends => [
|
22
|
+
{:name => "John", :address => {:street => "Pleasant", :city => "Cityville"}},
|
23
|
+
{:name => "Bill", :address => {:street => "Prospect", :city => "Townville"}}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should convert a hash value to a nested open struct" do
|
29
|
+
p = MongoThing::NestedOpenStruct.new(@person_hash)
|
30
|
+
|
31
|
+
p.name.should == "Tom"
|
32
|
+
p.address.street.should == "Tomahawk"
|
33
|
+
p.address.city.should == "Placeville"
|
34
|
+
p.friends.size.should == 2
|
35
|
+
p.friends[0].name.should == "John"
|
36
|
+
p.friends[0].address.street.should == "Pleasant"
|
37
|
+
p.friends[0].address.city.should == "Cityville"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should convert NestedOpenStruct values to hashes when dumping" do
|
41
|
+
p = MongoThing::NestedOpenStruct.new(@person_hash)
|
42
|
+
p.marshal_dump.should == @person_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should respond identically to marshal_dump and to_hash" do
|
46
|
+
p = MongoThing::NestedOpenStruct.new(@person_hash)
|
47
|
+
p.marshal_dump.should == p.to_hash
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Connection is established in spec_helper
|
4
|
+
describe MongoThing do
|
5
|
+
describe "connection" do
|
6
|
+
it "should set the connection given a hash" do
|
7
|
+
MongoThing.connection.host.should == "localhost"
|
8
|
+
MongoThing.connection.size.should == 1
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should set the database when a name is given in connection settings" do
|
12
|
+
MongoThing.db.should be_a Mongo::DB
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require(:default, :development)
|
4
|
+
require "mongo_thing"
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
8
|
+
|
9
|
+
MODELS = File.join(File.dirname(__FILE__), "models")
|
10
|
+
$LOAD_PATH.unshift(MODELS)
|
11
|
+
|
12
|
+
|
13
|
+
Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) }
|
14
|
+
|
15
|
+
Spec::Runner.configure do |config|
|
16
|
+
config.before :suite do
|
17
|
+
connect_to_test_database
|
18
|
+
end
|
19
|
+
|
20
|
+
config.after(:each) do
|
21
|
+
MongoThing.db.collections.each(&:remove)
|
22
|
+
end
|
23
|
+
|
24
|
+
config.after :suite do
|
25
|
+
MongoThing.db.collections.each(&:drop)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# This is probably a very naive way to do thsi
|
30
|
+
def connect_to_test_database
|
31
|
+
config = File.read(File.join(File.dirname(__FILE__), 'config', 'mongoid.yml'))
|
32
|
+
settings = YAML.load(ERB.new(config).result)
|
33
|
+
MongoThing.connection = settings[:test] unless MongoThing.connection
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongo_thing
|
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
|
+
- Daniel Higginbotham
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-09 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
type: :runtime
|
22
|
+
name: mongo
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
- 1
|
31
|
+
version: 1.0.1
|
32
|
+
requirement: *id001
|
33
|
+
prerelease: false
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
type: :runtime
|
36
|
+
name: bson
|
37
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 0
|
44
|
+
- 1
|
45
|
+
version: 1.0.1
|
46
|
+
requirement: *id002
|
47
|
+
prerelease: false
|
48
|
+
description:
|
49
|
+
email: daniel@flyingmachinestudios.com
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- .bundle/config
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- MIT_LICENSE
|
61
|
+
- Rakefile
|
62
|
+
- VERSION
|
63
|
+
- lib/mongo_thing.rb
|
64
|
+
- lib/mongo_thing/cursor.rb
|
65
|
+
- lib/mongo_thing/document.rb
|
66
|
+
- lib/mongo_thing/named_scope.rb
|
67
|
+
- lib/mongo_thing/nested_ostruct.rb
|
68
|
+
- lib/mongo_thing/railtie.rb
|
69
|
+
- lib/mongo_thing/railties/database.rake
|
70
|
+
- mongo_thing.gemspec
|
71
|
+
- spec/config/mongoid.yml
|
72
|
+
- spec/integration/mongo_thing/document_spec.rb
|
73
|
+
- spec/integration/mongo_thing/nested_ostruct_spec.rb
|
74
|
+
- spec/integration/mongo_thing_spec.rb
|
75
|
+
- spec/spec.opts
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
has_rdoc: true
|
78
|
+
homepage: ""
|
79
|
+
licenses: []
|
80
|
+
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options:
|
83
|
+
- --charset=UTF-8
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
version: "0"
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.6
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: ODM framework for MongoDB
|
107
|
+
test_files:
|
108
|
+
- spec/integration/mongo_thing/document_spec.rb
|
109
|
+
- spec/integration/mongo_thing/nested_ostruct_spec.rb
|
110
|
+
- spec/integration/mongo_thing_spec.rb
|
111
|
+
- spec/spec_helper.rb
|