mongo_thing 0.0.1
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/.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
|