durran-mongoid 0.2.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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_STORE
2
+ coverage/*
3
+ pkg/*
4
+ scratch_directory/*
5
+ tmp/*
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/README.textile ADDED
@@ -0,0 +1,134 @@
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
+
20
+ <h3>Compatibility</h3>
21
+
22
+ <p>
23
+ Mongoid is developed against Ruby 1.8.6, 1.8.7, 1.9.1
24
+ </p>
25
+
26
+ <h2>Mongoid in Action</h2>
27
+
28
+ Initialize Mongoid:
29
+
30
+ <pre>
31
+ Mongoid.connect_to("myapp_database_name")
32
+ </pre>
33
+
34
+ Example of a simple domain model:
35
+
36
+ <pre>
37
+ class Person < Mongoid::Document
38
+ fields :title
39
+ has_many :addresses
40
+ has_one :name
41
+ end
42
+
43
+ class Address < Mongoid::Document
44
+ fields \
45
+ :street,
46
+ :city,
47
+ :state,
48
+ :post_code
49
+ belongs_to :person
50
+ end
51
+
52
+ class Name < Mongoid::Document
53
+ fields \
54
+ :first_name,
55
+ :last_name
56
+ end
57
+ </pre>
58
+
59
+ Create a new Document:
60
+
61
+ <pre>
62
+ Person.create(:title => "Esquire")
63
+ </pre>
64
+
65
+ Save a Document:
66
+
67
+ <pre>
68
+ person.save
69
+ </pre>
70
+
71
+ Delete a Document:
72
+
73
+ <pre>
74
+ person.destroy
75
+ </pre>
76
+
77
+ Update a Document:
78
+
79
+ <pre>
80
+ person.update_attributes(:title => "Sir")
81
+ </pre>
82
+
83
+ Search for a Document in the database:
84
+
85
+ <pre>
86
+ Person.find(:all, :title => "Esquire")
87
+
88
+ Person.find(:first, :title => "Esquire")
89
+ </pre>
90
+
91
+ Paginate Document search results:
92
+
93
+ <pre>
94
+ Person.paginate(:title => "Esquire", :page => 1, :per_page => 20)
95
+ </pre>
96
+
97
+ Validations:
98
+
99
+ Mongoid supports all validations provided by Jay Fields' <tt>Validatable</tt> gem.
100
+ For more information please see the <tt>Validatable</tt> documentation.
101
+
102
+ <a href="http://validatable.rubyforge.org/">Validatable on RubyForge</a>
103
+
104
+ <h2>License</h2>
105
+
106
+ <p>
107
+ Copyright (c) 2009 Durran Jordan
108
+ </p>
109
+ <p>
110
+ Permission is hereby granted, free of charge, to any person obtaining
111
+ a copy of this software and associated documentation files (the
112
+ "Software"), to deal in the Software without restriction, including
113
+ without limitation the rights to use, copy, modify, merge, publish,
114
+ distribute, sublicense, and/or sell copies of the Software, and to
115
+ permit persons to whom the Software is furnished to do so, subject to
116
+ the following conditions:
117
+ </p>
118
+ <p>
119
+ The above copyright notice and this permission notice shall be
120
+ included in all copies or substantial portions of the Software.
121
+ </p>
122
+ <p>
123
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
124
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
125
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
126
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
127
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
128
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
129
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
130
+ </p>
131
+
132
+ <h2>Credits</h2>
133
+
134
+ Durran Jordan: durran at gmail dot com
data/Rakefile ADDED
@@ -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 "active_support"
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.0
data/lib/mongoid.rb ADDED
@@ -0,0 +1,77 @@
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 "hashrocket-validatable", "1.7.4"
26
+
27
+ require "validatable"
28
+ require "active_support/callbacks"
29
+ require "active_support/core_ext"
30
+ require "delegate"
31
+ require "mongo"
32
+ require "mongoid/associations/association_factory"
33
+ require "mongoid/associations/belongs_to_association"
34
+ require "mongoid/associations/has_many_association"
35
+ require "mongoid/associations/has_one_association"
36
+ require "mongoid/extensions/array/conversions"
37
+ require "mongoid/extensions/object/conversions"
38
+ require "mongoid/extensions"
39
+ require "mongoid/document"
40
+ require "mongoid/paginator"
41
+
42
+ module Mongoid
43
+
44
+ # Thrown when the database connection has not been set up.
45
+ class NoConnectionError < RuntimeError
46
+ end
47
+
48
+ # Thrown when :document_class is not provided in the attributes
49
+ # hash when creating a new Document
50
+ class ClassNotProvidedError < RuntimeError
51
+ end
52
+
53
+ # Thrown when an association is defined on the class, but the
54
+ # attribute in the hash is not an Array or Hash.
55
+ class TypeMismatchError < RuntimeError
56
+ end
57
+
58
+ # Thrown when an association is defined that is not valid. Must
59
+ # be belongs_to, has_many, has_one
60
+ class InvalidAssociationError < RuntimeError
61
+ end
62
+
63
+ # Connect to the database name supplied. This should be run
64
+ # for initial setup, potentially in a rails initializer.
65
+ def self.connect_to(name)
66
+ @@connection ||= Mongo::Connection.new
67
+ @@database ||= @@connection.db(name)
68
+ end
69
+
70
+ # Get the MongoDB database. If initialization via Mongoid.connect_to()
71
+ # has not happened, an exception will occur.
72
+ def self.database
73
+ raise NoConnectionError unless @@database
74
+ @@database
75
+ end
76
+
77
+ 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,25 @@
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
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Mongoid #:nodoc:
2
+ module Associations #:nodoc:
3
+ class HasOneAssociation #: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 it.
8
+ #
9
+ # All method calls on this object will then be delegated
10
+ # to the internal document itself.
11
+ def initialize(association_name, document)
12
+ klass = association_name.to_s.titleize.constantize
13
+ attributes = document.attributes[association_name]
14
+ @document = klass.new(attributes)
15
+ @document.parent = document
16
+ end
17
+
18
+ # All calls to this association will be delegated straight
19
+ # to the encapsulated document.
20
+ def method_missing(method, *args)
21
+ @document.send(method, *args)
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,202 @@
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)
60
+ when :first then find_first(selector)
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 = nil, params = {})
104
+ collection.find(selector, Mongoid::Paginator.new(params).options).collect { |doc| new(doc) }
105
+ end
106
+
107
+ end
108
+
109
+ # Get the Mongo::Collection associated with this Document.
110
+ def collection
111
+ self.class.collection
112
+ end
113
+
114
+ # Delete this Document from the database.
115
+ def destroy
116
+ collection.remove(:_id => id)
117
+ end
118
+
119
+ # Get the Mongo::ObjectID associated with this object.
120
+ # This is in essence the primary key.
121
+ def id
122
+ @attributes[:_id]
123
+ end
124
+
125
+ # Instantiate a new Document, setting the Document's attirbutes if given.
126
+ # If no attributes are provided, they will be initialized with an empty Hash.
127
+ def initialize(attributes = {})
128
+ @attributes = attributes.symbolize_keys if attributes
129
+ @attributes = {} unless attributes
130
+ end
131
+
132
+ # Returns true is the Document has not been persisted to the database, false if it has.
133
+ def new_record?
134
+ @attributes[:_id].nil?
135
+ end
136
+
137
+ # Set the parent to this document.
138
+ def parent=(document)
139
+ @parent = document
140
+ end
141
+
142
+ # Save this document to the database. If this document is the root document
143
+ # in the object graph, it will save itself, and return self. If the
144
+ # document is embedded within another document, or is multiple levels down
145
+ # the tree, the root object will get saved, and return itself.
146
+ def save
147
+ if @parent
148
+ @parent.save
149
+ else
150
+ run_callbacks(:before_save)
151
+ collection.save(@attributes)
152
+ run_callbacks(:after_save)
153
+ return self
154
+ end
155
+ end
156
+
157
+ # Returns the id of the Document
158
+ def to_param
159
+ id.to_s
160
+ end
161
+
162
+ # Update the attributes of this Document and return true
163
+ def update_attributes(attributes)
164
+ @attributes = attributes.symbolize_keys!; save; true
165
+ end
166
+
167
+ private
168
+
169
+ class << self
170
+
171
+ # Adds the association to the associations hash with the type as the key,
172
+ # then adds the accessors for the association.
173
+ def add_association(type, class_name, name)
174
+ define_method(name) do
175
+ Mongoid::Associations::AssociationFactory.create(type, name, self)
176
+ end
177
+ define_method("#{name}=") do |object|
178
+ @attributes[name] = object.mongoidize
179
+ end
180
+ end
181
+
182
+ # Takes the supplied raw grouping of documents and alters it to a
183
+ # grouping of actual document objects.
184
+ # TODO: Me no likey this...
185
+ def group!(docs)
186
+ docs["group"] = docs["group"].collect { |attrs| new(attrs) }; docs
187
+ end
188
+
189
+ end
190
+
191
+ # Read from the attributes hash.
192
+ def read_attribute(name)
193
+ @attributes[name.to_sym]
194
+ end
195
+
196
+ # Write to the attributes hash.
197
+ def write_attribute(name, value)
198
+ @attributes[name.to_sym] = value
199
+ end
200
+
201
+ end
202
+ end