mongoid 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ .DS_STORE
2
+ coverage/*
3
+ pkg/*
4
+ scratch_directory/*
5
+ tmp/*
@@ -0,0 +1,2 @@
1
+ 0.2.5:
2
+ - Switching pagination over to will_paginate
@@ -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.
@@ -0,0 +1,135 @@
1
+ <h2>Overview</h2>
2
+
3
+ <h3>About Mongoid</h3>
4
+
5
+ <p>
6
+ Mongoid is an ODM (Object-Document-Mapper) framework for MongoDB in Ruby. Mongoid differs from other
7
+ mapping frameworks in that it constrains the models into behaving in a manner appropriate for a
8
+ document database. That is to say there are no relationships between documents in the underlying datastore.
9
+ If a relationship is set up in the Model the child models are automatically embedded within the parent document
10
+ in the database. The concept of a foreign key relationship to another Document does not exist, as that is
11
+ design and thinking for a relational database, not a document database. Mongoloid does however provide
12
+ all the ActiveRecord style functionality you need, with the difference that it stores all associations
13
+ within the parent document.
14
+ </p>
15
+
16
+ <h3>Project Tracking</h3>
17
+
18
+ <a href="http://www.pivotaltracker.com/projects/27482">Mongoid on Pivotal Tracker</a>
19
+ <a href="http://groups.google.com/group/mongoid">Mongoid Google Group</a>
20
+
21
+ <h3>Compatibility</h3>
22
+
23
+ <p>
24
+ Mongoid is developed against Ruby 1.8.6, 1.8.7, 1.9.1
25
+ </p>
26
+
27
+ <h2>Mongoid in Action</h2>
28
+
29
+ Initialize Mongoid:
30
+
31
+ <pre>
32
+ Mongoid.connect_to("myapp_database_name")
33
+ </pre>
34
+
35
+ Example of a simple domain model:
36
+
37
+ <pre>
38
+ class Person < Mongoid::Document
39
+ fields :title
40
+ has_many :addresses
41
+ has_one :name
42
+ end
43
+
44
+ class Address < Mongoid::Document
45
+ fields \
46
+ :street,
47
+ :city,
48
+ :state,
49
+ :post_code
50
+ belongs_to :person
51
+ end
52
+
53
+ class Name < Mongoid::Document
54
+ fields \
55
+ :first_name,
56
+ :last_name
57
+ end
58
+ </pre>
59
+
60
+ Create a new Document:
61
+
62
+ <pre>
63
+ Person.create(:title => "Esquire")
64
+ </pre>
65
+
66
+ Save a Document:
67
+
68
+ <pre>
69
+ person.save
70
+ </pre>
71
+
72
+ Delete a Document:
73
+
74
+ <pre>
75
+ person.destroy
76
+ </pre>
77
+
78
+ Update a Document:
79
+
80
+ <pre>
81
+ person.update_attributes(:title => "Sir")
82
+ </pre>
83
+
84
+ Search for a Document in the database:
85
+
86
+ <pre>
87
+ Person.find(:all, :title => "Esquire")
88
+
89
+ Person.find(:first, :title => "Esquire")
90
+ </pre>
91
+
92
+ Paginate Document search results:
93
+
94
+ <pre>
95
+ Person.paginate(:title => "Esquire", :page => 1, :per_page => 20)
96
+ </pre>
97
+
98
+ Validations:
99
+
100
+ Mongoid supports all validations provided by Jay Fields' <tt>Validatable</tt> gem.
101
+ For more information please see the <tt>Validatable</tt> documentation.
102
+
103
+ <a href="http://validatable.rubyforge.org/">Validatable on RubyForge</a>
104
+
105
+ <h2>License</h2>
106
+
107
+ <p>
108
+ Copyright (c) 2009 Durran Jordan
109
+ </p>
110
+ <p>
111
+ Permission is hereby granted, free of charge, to any person obtaining
112
+ a copy of this software and associated documentation files (the
113
+ "Software"), to deal in the Software without restriction, including
114
+ without limitation the rights to use, copy, modify, merge, publish,
115
+ distribute, sublicense, and/or sell copies of the Software, and to
116
+ permit persons to whom the Software is furnished to do so, subject to
117
+ the following conditions:
118
+ </p>
119
+ <p>
120
+ The above copyright notice and this permission notice shall be
121
+ included in all copies or substantial portions of the Software.
122
+ </p>
123
+ <p>
124
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
125
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
126
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
127
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
128
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
129
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
130
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
131
+ </p>
132
+
133
+ <h2>Credits</h2>
134
+
135
+ Durran Jordan: durran at gmail dot com
@@ -0,0 +1,49 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "rake/rdoctask"
4
+ require "spec/rake/spectask"
5
+ require "metric_fu"
6
+
7
+ begin
8
+ require "jeweler"
9
+ Jeweler::Tasks.new do |gem|
10
+ gem.name = "mongoid"
11
+ gem.summary = %Q{Mongoid}
12
+ gem.email = "durran@gmail.com"
13
+ gem.homepage = "http://github.com/durran/mongoid"
14
+ gem.authors = ["Durran Jordan"]
15
+ gem.add_dependency "activesupport"
16
+ gem.add_dependency "mongodb-mongo"
17
+ end
18
+
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << "lib" << "spec"
25
+ spec.pattern = "spec/**/*_spec.rb"
26
+ spec.spec_opts = ['--options', "spec/spec.opts"]
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
30
+ spec.libs << "lib" << "spec"
31
+ spec.pattern = "spec/**/*_spec.rb"
32
+ spec.spec_opts = ['--options', "spec/spec.opts"]
33
+ spec.rcov = true
34
+ end
35
+
36
+ Rake::RDocTask.new do |rdoc|
37
+ if File.exist?("VERSION.yml")
38
+ config = YAML.load(File.read("VERSION.yml"))
39
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
40
+ else
41
+ version = ""
42
+ end
43
+ rdoc.rdoc_dir = "rdoc"
44
+ rdoc.title = "my_emma #{version}"
45
+ rdoc.rdoc_files.include("README*")
46
+ rdoc.rdoc_files.include("lib/**/*.rb")
47
+ end
48
+
49
+ task :default => ["rcov", "metrics:all"]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.5
@@ -0,0 +1,79 @@
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.
21
+ require "rubygems"
22
+
23
+ gem "activesupport", "2.3.4"
24
+ gem "mongodb-mongo", "0.14.1"
25
+ gem "durran-validatable", "1.7.5"
26
+ gem "mislav-will_paginate", "2.3.11"
27
+
28
+ require "validatable"
29
+ require "active_support/callbacks"
30
+ require "active_support/core_ext"
31
+ require "delegate"
32
+ require "will_paginate/collection"
33
+ require "mongo"
34
+ require "mongoid/associations/association_factory"
35
+ require "mongoid/associations/belongs_to_association"
36
+ require "mongoid/associations/has_many_association"
37
+ require "mongoid/associations/has_one_association"
38
+ require "mongoid/extensions/array/conversions"
39
+ require "mongoid/extensions/object/conversions"
40
+ require "mongoid/extensions"
41
+ require "mongoid/document"
42
+ require "mongoid/paginator"
43
+
44
+ module Mongoid
45
+
46
+ # Thrown when the database connection has not been set up.
47
+ class NoConnectionError < RuntimeError
48
+ end
49
+
50
+ # Thrown when :document_class is not provided in the attributes
51
+ # hash when creating a new Document
52
+ class ClassNotProvidedError < RuntimeError
53
+ end
54
+
55
+ # Thrown when an association is defined on the class, but the
56
+ # attribute in the hash is not an Array or Hash.
57
+ class TypeMismatchError < RuntimeError
58
+ end
59
+
60
+ # Thrown when an association is defined that is not valid. Must
61
+ # be belongs_to, has_many, has_one
62
+ class InvalidAssociationError < RuntimeError
63
+ end
64
+
65
+ # Connect to the database name supplied. This should be run
66
+ # for initial setup, potentially in a rails initializer.
67
+ def self.connect_to(name)
68
+ @@connection ||= Mongo::Connection.new
69
+ @@database ||= @@connection.db(name)
70
+ end
71
+
72
+ # Get the MongoDB database. If initialization via Mongoid.connect_to()
73
+ # has not happened, an exception will occur.
74
+ def self.database
75
+ raise NoConnectionError unless @@database
76
+ @@database
77
+ end
78
+
79
+ end
@@ -0,0 +1,21 @@
1
+ module Mongoid #:nodoc:
2
+ module Associations #:nodoc:
3
+ class AssociationFactory #:nodoc:
4
+
5
+ # Creates a new association, based on the type provided and
6
+ # passes the name and document into the newly instantiated
7
+ # association.
8
+ #
9
+ # If the type is invalid a InvalidAssociationError will be thrown.
10
+ def self.create(association_type, association_name, document)
11
+ case association_type
12
+ when :belongs_to then BelongsToAssociation.new(document)
13
+ when :has_many then HasManyAssociation.new(association_name, document)
14
+ when :has_one then HasOneAssociation.new(association_name, document)
15
+ else raise InvalidAssociationError
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module Mongoid #:nodoc:
2
+ module Associations #:nodoc:
3
+ class BelongsToAssociation #:nodoc:
4
+
5
+ # Creates the new association by setting the internal
6
+ # document as the passed in Document. This should be the
7
+ # parent.
8
+ #
9
+ # All method calls on this object will then be delegated
10
+ # to the internal document itself.
11
+ def initialize(document)
12
+ @document = document.parent
13
+ end
14
+
15
+ # All calls to this association will be delegated straight
16
+ # to the encapsulated document.
17
+ def method_missing(method, *args)
18
+ @document.send(method, *args)
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ module Mongoid #:nodoc:
2
+ module Associations #:nodoc:
3
+ class HasManyAssociation < DelegateClass(Array) #:nodoc:
4
+
5
+ # Creates the new association by finding the attributes in
6
+ # the parent document with its name, and instantiating a
7
+ # new document for each one found. These will then be put in an
8
+ # internal array.
9
+ #
10
+ # This then delegated all methods to the array class since this is
11
+ # essentially a proxy to an array itself.
12
+ def initialize(association_name, document)
13
+ @klass = association_name.to_s.classify.constantize
14
+ attributes = document.attributes[association_name]
15
+ @documents = attributes ? attributes.collect do |attribute|
16
+ child = @klass.new(attribute)
17
+ child.parent = document
18
+ child
19
+ end : []
20
+ super(@documents)
21
+ end
22
+
23
+ # Builds a new Document and adds it to the association collection. The
24
+ # document created will be of the same class as the others in the
25
+ # association, and the attributes will be passed into the constructor.
26
+ #
27
+ # Returns the newly created object.
28
+ def build(attributes)
29
+ object = @klass.new(attributes)
30
+ push(object)
31
+ object
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module Mongoid #:nodoc:
2
+ module Associations #:nodoc:
3
+ class HasOneAssociation #:nodoc:
4
+
5
+ attr_reader :document
6
+ delegate :valid?, :to => :document
7
+
8
+ # Creates the new association by finding the attributes in
9
+ # the parent document with its name, and instantiating a
10
+ # new document for it.
11
+ #
12
+ # All method calls on this object will then be delegated
13
+ # to the internal document itself.
14
+ def initialize(association_name, document)
15
+ klass = association_name.to_s.titleize.constantize
16
+ attributes = document.attributes[association_name]
17
+ @document = klass.new(attributes)
18
+ @document.parent = document
19
+ decorate!
20
+ end
21
+
22
+ private
23
+ def decorate!
24
+ @document.public_methods(false).each do |method|
25
+ (class << self; self; end).class_eval do
26
+ define_method method do |*args|
27
+ @document.send method, *args
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,208 @@
1
+ module Mongoid #:nodoc:
2
+ class Document #:nodoc:
3
+ include ActiveSupport::Callbacks
4
+ include Validatable
5
+
6
+ AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
7
+ GROUP_BY_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
8
+
9
+ attr_reader :attributes, :parent
10
+
11
+ define_callbacks \
12
+ :after_create,
13
+ :after_save,
14
+ :before_create,
15
+ :before_save
16
+
17
+ class << self
18
+
19
+ # Get an aggregate count for the supplied group of fields and the
20
+ # selector that is provided.
21
+ def aggregate(fields, selector)
22
+ collection.group(fields, selector, { :count => 0 }, AGGREGATE_REDUCE)
23
+ end
24
+
25
+ # Create an association to a parent Document.
26
+ def belongs_to(association_name)
27
+ add_association(:belongs_to, association_name.to_s.classify, association_name)
28
+ end
29
+
30
+ # Get the Mongo::Collection associated with this Document.
31
+ def collection
32
+ @collection_name = self.to_s.demodulize.tableize
33
+ @collection ||= Mongoid.database.collection(@collection_name)
34
+ end
35
+
36
+ # Create a new Document with the supplied attribtues, and insert it into the database.
37
+ def create(attributes = {})
38
+ new(attributes).save
39
+ end
40
+
41
+ # Defines all the fields that are accessable on the Document
42
+ # For each field that is defined, a getter and setter will be
43
+ # added as an instance method to the Document.
44
+ def fields(*names)
45
+ @fields = []
46
+ names.flatten.each do |name|
47
+ @fields << name
48
+ define_method(name) { read_attribute(name) }
49
+ define_method("#{name}=") { |value| write_attribute(name, value) }
50
+ end
51
+ end
52
+
53
+ # Find all Documents in several ways.
54
+ # Model.find(:first, :attribute => "value")
55
+ # Model.find(:all, :attribute => "value")
56
+ def find(*args)
57
+ type, selector = args[0], args[1]
58
+ case type
59
+ when :all then find_all(selector[:conditions])
60
+ when :first then find_first(selector[:conditions])
61
+ else find_first(Mongo::ObjectID.from_string(type.to_s))
62
+ end
63
+ end
64
+
65
+ # Find a single Document given the passed selector, which is a Hash of attributes that
66
+ # must match the Document in the database exactly.
67
+ def find_first(selector = nil)
68
+ new(collection.find_one(selector))
69
+ end
70
+
71
+ # Find all Documents given the passed selector, which is a Hash of attributes that
72
+ # must match the Document in the database exactly.
73
+ def find_all(selector = nil)
74
+ collection.find(selector).collect { |doc| new(doc) }
75
+ end
76
+
77
+ # Find all Documents given the supplied criteria, grouped by the fields
78
+ # provided.
79
+ def group_by(fields, selector)
80
+ collection.group(fields, selector, { :group => [] }, GROUP_BY_REDUCE).collect do |docs|
81
+ group!(docs)
82
+ end
83
+ end
84
+
85
+ # Create a one-to-many association between Documents.
86
+ def has_many(association_name)
87
+ add_association(:has_many, association_name.to_s.classify, association_name)
88
+ end
89
+
90
+ # Create a one-to-many association between Documents.
91
+ def has_one(association_name)
92
+ add_association(:has_one, association_name.to_s.titleize, association_name)
93
+ end
94
+
95
+ # Adds an index on the field specified. Options can be :unique => true or
96
+ # :unique => false. It will default to the latter.
97
+ def index(name, options = { :unique => false })
98
+ collection.create_index(name, options)
99
+ end
100
+
101
+ # Find all documents in paginated fashion given the supplied arguments.
102
+ # If no parameters are passed just default to offset 0 and limit 20.
103
+ def paginate(selector = {}, params = {})
104
+ WillPaginate::Collection.create(
105
+ params[:page] || 1,
106
+ params[:per_page] || 20,
107
+ 0) do |pager|
108
+ results = collection.find(selector[:conditions], { :limit => pager.per_page, :offset => pager.offset })
109
+ pager.total_entries = results.count
110
+ pager.replace(results.collect { |doc| new(doc) })
111
+ end
112
+ end
113
+
114
+ end
115
+
116
+ # Get the Mongo::Collection associated with this Document.
117
+ def collection
118
+ self.class.collection
119
+ end
120
+
121
+ # Delete this Document from the database.
122
+ def destroy
123
+ collection.remove(:_id => id)
124
+ end
125
+
126
+ # Get the Mongo::ObjectID associated with this object.
127
+ # This is in essence the primary key.
128
+ def id
129
+ @attributes[:_id]
130
+ end
131
+
132
+ # Instantiate a new Document, setting the Document's attirbutes if given.
133
+ # If no attributes are provided, they will be initialized with an empty Hash.
134
+ def initialize(attributes = {})
135
+ @attributes = attributes.symbolize_keys if attributes
136
+ @attributes = {} unless attributes
137
+ end
138
+
139
+ # Returns true is the Document has not been persisted to the database, false if it has.
140
+ def new_record?
141
+ @attributes[:_id].nil?
142
+ end
143
+
144
+ # Set the parent to this document.
145
+ def parent=(document)
146
+ @parent = document
147
+ end
148
+
149
+ # Save this document to the database. If this document is the root document
150
+ # in the object graph, it will save itself, and return self. If the
151
+ # document is embedded within another document, or is multiple levels down
152
+ # the tree, the root object will get saved, and return itself.
153
+ def save
154
+ if @parent
155
+ @parent.save
156
+ else
157
+ run_callbacks(:before_save)
158
+ collection.save(@attributes)
159
+ run_callbacks(:after_save)
160
+ return self
161
+ end
162
+ end
163
+
164
+ # Returns the id of the Document
165
+ def to_param
166
+ id.to_s
167
+ end
168
+
169
+ # Update the attributes of this Document and return true
170
+ def update_attributes(attributes)
171
+ @attributes = attributes.symbolize_keys!; save; true
172
+ end
173
+
174
+ private
175
+
176
+ class << self
177
+
178
+ # Adds the association to the associations hash with the type as the key,
179
+ # then adds the accessors for the association.
180
+ def add_association(type, class_name, name)
181
+ define_method(name) do
182
+ Mongoid::Associations::AssociationFactory.create(type, name, self)
183
+ end
184
+ define_method("#{name}=") do |object|
185
+ @attributes[name] = object.mongoidize
186
+ end
187
+ end
188
+
189
+ # Takes the supplied raw grouping of documents and alters it to a
190
+ # grouping of actual document objects.
191
+ def group!(docs)
192
+ docs["group"] = docs["group"].collect { |attrs| new(attrs) }; docs
193
+ end
194
+
195
+ end
196
+
197
+ # Read from the attributes hash.
198
+ def read_attribute(name)
199
+ @attributes[name.to_sym]
200
+ end
201
+
202
+ # Write to the attributes hash.
203
+ def write_attribute(name, value)
204
+ @attributes[name.to_sym] = value
205
+ end
206
+
207
+ end
208
+ end