mongoid 0.4.5 → 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/.watchr CHANGED
@@ -8,4 +8,4 @@ def spec(file)
8
8
  end
9
9
 
10
10
  watch("spec/.*/*_spec\.rb") {|md| p md[0]; spec(md[0]) }
11
- watch('lib/.*/(.*)\.rb') {|md| p md[1]; spec("spec/unit/mongoid/#{md[1]}_spec.rb") }
11
+ watch('lib/(.*/.*)\.rb') {|md| p md[1]; spec("spec/unit/#{md[1]}_spec.rb") }
data/README.textile CHANGED
@@ -18,6 +18,11 @@
18
18
  Mongoid is developed against Ruby 1.8.6, 1.8.7, 1.9.1, 1.9.2
19
19
  </p>
20
20
 
21
+ <p>
22
+ Note API changes will be frequent until the 1.0 release, and do not expect
23
+ the gem to be of full production quality until that point.
24
+ </p>
25
+
21
26
  <h2>Mongoid in Action</h2>
22
27
 
23
28
  Initialize Mongoid:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.5
1
+ 0.4.7
data/lib/mongoid.rb CHANGED
@@ -25,10 +25,11 @@ gem "mongo", "0.15.1"
25
25
  gem "durran-validatable", "1.7.5"
26
26
  gem "will_paginate", "2.3.11"
27
27
 
28
+ require "delegate"
29
+ require "observer"
28
30
  require "validatable"
29
31
  require "active_support/callbacks"
30
32
  require "active_support/core_ext"
31
- require "delegate"
32
33
  require "will_paginate/collection"
33
34
  require "mongo"
34
35
  require "mongoid/associations"
@@ -37,6 +38,7 @@ require "mongoid/criteria"
37
38
  require "mongoid/extensions"
38
39
  require "mongoid/field"
39
40
  require "mongoid/document"
41
+ require "mongoid/timestamps"
40
42
 
41
43
  module Mongoid
42
44
 
@@ -2,4 +2,87 @@ require "mongoid/associations/decorator"
2
2
  require "mongoid/associations/factory"
3
3
  require "mongoid/associations/belongs_to_association"
4
4
  require "mongoid/associations/has_many_association"
5
- require "mongoid/associations/has_one_association"
5
+ require "mongoid/associations/has_one_association"
6
+
7
+ module Mongoid # :nodoc:
8
+ module Associations
9
+
10
+ # Adds the association back to the parent document. This macro is
11
+ # necessary to set the references from the child back to the parent
12
+ # document. If a child does not define this association calling
13
+ # persistence methods on the child object will cause a save to fail.
14
+ #
15
+ # Options:
16
+ #
17
+ # association_name: A +Symbol+ that matches the name of the parent class.
18
+ #
19
+ # Example:
20
+ #
21
+ # class Person < Mongoid::Document
22
+ # has_many :addresses
23
+ # end
24
+ #
25
+ # class Address < Mongoid::Document
26
+ # belongs_to :person
27
+ # end
28
+ def belongs_to(association_name)
29
+ @embedded = true
30
+ add_association(:belongs_to, association_name.to_s.classify, association_name)
31
+ end
32
+
33
+ # Adds the association from a parent document to its children. The name
34
+ # of the association needs to be a pluralized form of the child class
35
+ # name.
36
+ #
37
+ # Options:
38
+ #
39
+ # association_name: A +Symbol+ that is the plural child class name.
40
+ #
41
+ # Example:
42
+ #
43
+ # class Person < Mongoid::Document
44
+ # has_many :addresses
45
+ # end
46
+ #
47
+ # class Address < Mongoid::Document
48
+ # belongs_to :person
49
+ # end
50
+ def has_many(association_name)
51
+ add_association(:has_many, association_name.to_s.classify, association_name)
52
+ end
53
+
54
+ # Adds the association from a parent document to its child. The name
55
+ # of the association needs to be a singular form of the child class
56
+ # name.
57
+ #
58
+ # Options:
59
+ #
60
+ # association_name: A +Symbol+ that is the plural child class name.
61
+ #
62
+ # Example:
63
+ #
64
+ # class Person < Mongoid::Document
65
+ # has_many :addresses
66
+ # end
67
+ #
68
+ # class Address < Mongoid::Document
69
+ # belongs_to :person
70
+ # end
71
+ def has_one(association_name)
72
+ add_association(:has_one, association_name.to_s.titleize, association_name)
73
+ end
74
+
75
+ private
76
+ # Adds the association to the associations hash with the type as the key,
77
+ # then adds the accessors for the association.
78
+ def add_association(type, class_name, name)
79
+ define_method(name) do
80
+ Associations::Factory.create(type, name, self)
81
+ end
82
+ define_method("#{name}=") do |object|
83
+ object.parentize(self, name)
84
+ @attributes[name] = object.mongoidize
85
+ end
86
+ end
87
+ end
88
+ end
@@ -2,10 +2,12 @@ module Mongoid #:nodoc:
2
2
  module Associations #:nodoc:
3
3
  class HasManyAssociation < DelegateClass(Array) #:nodoc:
4
4
 
5
+ attr_accessor :association_name
6
+
5
7
  # Appends the object to the +Array+, setting its parent in
6
8
  # the process.
7
9
  def <<(object)
8
- object.parentize(@parent)
10
+ object.parentize(@parent, @association_name)
9
11
  @documents << object
10
12
  end
11
13
 
@@ -28,6 +30,7 @@ module Mongoid #:nodoc:
28
30
  # Returns the newly created object.
29
31
  def build(attributes)
30
32
  object = @klass.new(attributes)
33
+ object.parentize(@parent, @association_name)
31
34
  push(object)
32
35
  object
33
36
  end
@@ -49,11 +52,12 @@ module Mongoid #:nodoc:
49
52
  # essentially a proxy to an array itself.
50
53
  def initialize(association_name, document)
51
54
  @parent = document
52
- @klass = association_name.to_s.classify.constantize
53
- attributes = document.attributes[association_name]
55
+ @association_name = association_name
56
+ @klass = @association_name.to_s.classify.constantize
57
+ attributes = document.attributes[@association_name]
54
58
  @documents = attributes ? attributes.collect do |attribute|
55
59
  child = @klass.new(attribute)
56
- child.parent = document
60
+ child.parentize(@parent, @association_name)
57
61
  child
58
62
  end : []
59
63
  super(@documents)
@@ -57,15 +57,15 @@ module Mongoid #:nodoc:
57
57
 
58
58
  # Update the attributes of the +Document+. Will call save after the
59
59
  # attributes have been updated.
60
- def update_attributes(attributes = {})
61
- self.attributes = attributes; save
60
+ def update_attributes(attrs = {})
61
+ write_attributes(attrs); save
62
62
  end
63
63
 
64
64
  # Update the attributes of the +Document+. Will call save! after the
65
65
  # attributes have been updated, causing a +ValidationError+ if the
66
66
  # +Document+ failed validation.
67
- def update_attributes!(attribtues = {})
68
- self.attributes = attributes; save!
67
+ def update_attributes!(attrs = {})
68
+ write_attributes(attrs); save!
69
69
  end
70
70
 
71
71
  end
@@ -14,8 +14,23 @@ module Mongoid #:nodoc:
14
14
  #
15
15
  # <tt>criteria.execute</tt>
16
16
  class Criteria
17
+ attr_accessor :klass
17
18
  attr_reader :selector, :options, :type
18
19
 
20
+ AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
21
+ # Aggregate the criteria. This will take the internally built selector and options
22
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
23
+ # collection itself will be retrieved from the class provided, and once the
24
+ # query has returned it will provided a grouping of keys with counts.
25
+ #
26
+ # Example:
27
+ #
28
+ # <tt>criteria.select(:field1).where(:field1 => "Title").aggregate(Person)</tt>
29
+ def aggregate(klass = nil)
30
+ @klass = klass if klass
31
+ @klass.collection.group(@options[:fields], @selector, { :count => 0 }, AGGREGATE_REDUCE)
32
+ end
33
+
19
34
  # Adds a criterion to the +Criteria+ that specifies values that must all
20
35
  # be matched in order to return results. Similar to an "in" clause but the
21
36
  # underlying conditional logic is an "AND" and not an "OR". The MongoDB
@@ -67,9 +82,10 @@ module Mongoid #:nodoc:
67
82
  #
68
83
  # If this is a +Criteria+ to find multiple results, will return an +Array+ of
69
84
  # objects of the type of class provided.
70
- def execute(klass)
71
- return klass.new(klass.collection.find_one(@selector, @options)) if type == :first
72
- return klass.collection.find(@selector, @options).collect { |doc| klass.new(doc) }
85
+ def execute(klass = nil)
86
+ @klass = klass if klass
87
+ return @klass.new(klass.collection.find_one(@selector, @options)) if type == :first
88
+ return @klass.collection.find(@selector, @options).collect { |doc| @klass.new(doc) }
73
89
  end
74
90
 
75
91
  # Adds a criterion to the +Criteria+ that specifies additional options
@@ -88,6 +104,27 @@ module Mongoid #:nodoc:
88
104
  @options = extras; self
89
105
  end
90
106
 
107
+ GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
108
+ # Groups the criteria. This will take the internally built selector and options
109
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
110
+ # collection itself will be retrieved from the class provided, and once the
111
+ # query has returned it will provided a grouping of keys with objects.
112
+ #
113
+ # Example:
114
+ #
115
+ # <tt>criteria.select(:field1).where(:field1 => "Title").group(Person)</tt>
116
+ def group(klass = nil)
117
+ @klass = klass if klass
118
+ @klass.collection.group(
119
+ @options[:fields],
120
+ @selector,
121
+ { :group => [] },
122
+ GROUP_REDUCE
123
+ ).collect do |docs|
124
+ docs["group"] = docs["group"].collect { |attrs| @klass.new(attrs) }; docs
125
+ end
126
+ end
127
+
91
128
  # Adds a criterion to the +Criteria+ that specifies values where any can
92
129
  # be matched in order to return results. This is similar to an SQL "IN"
93
130
  # clause. The MongoDB conditional operator that will be used is "$in".
@@ -129,8 +166,9 @@ module Mongoid #:nodoc:
129
166
  # Options:
130
167
  #
131
168
  # type: One of :all, :first:, or :last
132
- def initialize(type)
133
- @selector, @options, @type = {}, {}, type
169
+ # klass: The class to execute on.
170
+ def initialize(type, klass = nil)
171
+ @selector, @options, @type, @klass = {}, {}, type, klass
134
172
  end
135
173
 
136
174
  # Adds a criterion to the +Criteria+ that specifies the maximum number of
@@ -170,6 +208,14 @@ module Mongoid #:nodoc:
170
208
  exclusions.each { |key, value| @selector[key] = { "$nin" => value } }; self
171
209
  end
172
210
 
211
+ # Returns the offset option. If a per_page option is in the list then it
212
+ # will replace it with a skip parameter and return the same value. Defaults
213
+ # to 20 if nothing was provided.
214
+ def offset
215
+ offset = @options.delete(:per_page) || 20
216
+ @options[:skip] ||= offset
217
+ end
218
+
173
219
  # Adds a criterion to the +Criteria+ that specifies the sort order of
174
220
  # the returned documents in the database. Similar to a SQL "ORDER BY".
175
221
  #
@@ -186,6 +232,12 @@ module Mongoid #:nodoc:
186
232
  @options[:sort] = params; self
187
233
  end
188
234
 
235
+ # Either returns the page option and removes it from the options, or
236
+ # returns a default value of 1.
237
+ def page
238
+ @options.delete(:page) || 1
239
+ end
240
+
189
241
  # Adds a criterion to the +Criteria+ that specifies the fields that will
190
242
  # get returned from the Document. Used mainly for list views that do not
191
243
  # require all fields to be present. This is similar to SQL "SELECT" values.
@@ -1,13 +1,11 @@
1
1
  module Mongoid #:nodoc:
2
2
  class Document
3
3
  include ActiveSupport::Callbacks
4
- include Validatable
5
- include Commands
4
+ include Commands, Observable, Validatable
5
+ extend Associations
6
6
 
7
- AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
8
- GROUP_BY_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
9
-
10
- attr_accessor :attributes, :parent
7
+ attr_accessor :association_name, :parent
8
+ attr_reader :attributes
11
9
 
12
10
  define_callbacks \
13
11
  :after_create,
@@ -19,37 +17,22 @@ module Mongoid #:nodoc:
19
17
 
20
18
  class << self
21
19
 
22
- # Get an aggregate count for the supplied group of fields and the
23
- # selector that is provided.
24
- def aggregate(fields, params = {})
25
- selector = params[:conditions]
26
- collection.group(fields, selector, { :count => 0 }, AGGREGATE_REDUCE)
27
- end
28
-
29
- # Adds the association back to the parent document. This macro is
30
- # necessary to set the references from the child back to the parent
31
- # document. If a child does not define this association calling
32
- # persistence methods on the child object will cause a save to fail.
20
+ # Find +Documents+ given the conditions.
33
21
  #
34
22
  # Options:
35
23
  #
36
- # association_name: A +Symbol+ that matches the name of the parent class.
37
- #
38
- # Example:
39
- #
40
- # class Person < Mongoid::Document
41
- # has_many :addresses
42
- # end
24
+ # args: A +Hash+ with a conditions key and other options
43
25
  #
44
- # class Address < Mongoid::Document
45
- # belongs_to :person
46
- # end
47
- def belongs_to(association_name)
48
- @embedded = true
49
- add_association(:belongs_to, association_name.to_s.classify, association_name)
26
+ # <tt>Person.all(:conditions => { :attribute => "value" })</tt>
27
+ def all(*args)
28
+ find(:all, *args)
50
29
  end
51
30
 
52
- # Get the Mongo::Collection associated with this Document.
31
+ # Returns the collection associated with this +Document+. If the
32
+ # document is embedded, there will be no collection associated
33
+ # with it.
34
+ #
35
+ # Returns: <tt>Mongo::Collection</tt>
53
36
  def collection
54
37
  return nil if @embedded
55
38
  @collection_name = self.to_s.demodulize.tableize
@@ -97,85 +80,57 @@ module Mongoid #:nodoc:
97
80
  Criteria.translate(*args).execute(self)
98
81
  end
99
82
 
100
- # Find a single Document given the passed selector, which is a Hash of attributes that
101
- # must match the Document in the database exactly.
83
+ # Find the first +Document+ given the conditions.
84
+ #
85
+ # Options:
86
+ #
87
+ # args: A +Hash+ with a conditions key and other options
88
+ #
89
+ # <tt>Person.first(:conditions => { :attribute => "value" })</tt>
102
90
  def first(*args)
103
91
  find(:first, *args)
104
92
  end
105
93
 
106
- # Find all Documents given the passed selector, which is a Hash of attributes that
107
- # must match the Document in the database exactly.
108
- def all(*args)
109
- find(:all, *args)
94
+ # Adds an index on the field specified. Options can be :unique => true or
95
+ # :unique => false. It will default to the latter.
96
+ def index(name, options = { :unique => false })
97
+ collection.create_index(name, options)
110
98
  end
111
99
 
112
- # Find all Documents given the supplied criteria, grouped by the fields
113
- # provided.
114
- def group_by(fields, params = {})
115
- selector = params[:condition]
116
- collection.group(fields, selector, { :group => [] }, GROUP_BY_REDUCE).collect do |docs|
117
- docs["group"] = docs["group"].collect { |attrs| new(attrs) }; docs
118
- end
100
+ # Defines the field that will be used for the id of this +Document+. This
101
+ # set the id of this +Document+ before save to a parameterized version of
102
+ # the field that was supplied. This is good for use for readable URLS in
103
+ # web applications and *MUST* be defined on documents that are embedded
104
+ # in order for proper updates in has_may associations.
105
+ def key(*fields)
106
+ @primary_key = fields
107
+ before_save :generate_key
119
108
  end
120
109
 
121
- # Adds the association from a parent document to its children. The name
122
- # of the association needs to be a pluralized form of the child class
123
- # name.
110
+ # Returns the primary key field of the +Document+
111
+ def primary_key
112
+ @primary_key
113
+ end
114
+
115
+ # Find all documents in paginated fashion given the supplied arguments.
116
+ # If no parameters are passed just default to offset 0 and limit 20.
124
117
  #
125
118
  # Options:
126
119
  #
127
- # association_name: A +Symbol+ that is the plural child class name.
120
+ # params: A +Hash+ of params to pass to the Criteria API.
128
121
  #
129
122
  # Example:
130
123
  #
131
- # class Person < Mongoid::Document
132
- # has_many :addresses
133
- # end
124
+ # <tt>Person.paginate(:conditions => { :field => "Test" }, :page => 1,
125
+ # :per_page => 20)</tt>
134
126
  #
135
- # class Address < Mongoid::Document
136
- # belongs_to :person
137
- # end
138
- def has_many(association_name)
139
- add_association(:has_many, association_name.to_s.classify, association_name)
140
- end
141
-
142
- # Create a one-to-many association between Documents.
143
- def has_one(association_name)
144
- add_association(:has_one, association_name.to_s.titleize, association_name)
145
- end
146
-
147
- # Adds timestamps on the Document in the form of the fields 'created_on'
148
- # and 'last_modified'
149
- def has_timestamps
150
- field :created_at
151
- field :last_modified
152
- class_eval do
153
- before_create \
154
- :update_created_at,
155
- :update_last_modified
156
- before_save :update_last_modified
157
- end
158
- end
159
-
160
- # Adds an index on the field specified. Options can be :unique => true or
161
- # :unique => false. It will default to the latter.
162
- def index(name, options = { :unique => false })
163
- collection.create_index(name, options)
164
- end
165
-
166
- # Find all documents in paginated fashion given the supplied arguments.
167
- # If no parameters are passed just default to offset 0 and limit 20.
127
+ # Returns paginated array of docs.
168
128
  def paginate(params = {})
169
- selector = params[:conditions]
170
- WillPaginate::Collection.create(
171
- params[:page] || 1,
172
- params[:per_page] || 20,
173
- 0) do |pager|
174
- results = collection.find(selector, { :sort => params[:sort],
175
- :limit => pager.per_page,
176
- :skip => pager.offset })
177
- pager.total_entries = results.count
178
- pager.replace(results.collect { |doc| new(doc) })
129
+ criteria = Criteria.translate(:all, params)
130
+ WillPaginate::Collection.create(criteria.page, criteria.offset, 0) do |pager|
131
+ results = criteria.execute(self)
132
+ pager.total_entries = results.size
133
+ pager.replace(results)
179
134
  end
180
135
  end
181
136
 
@@ -193,7 +148,7 @@ module Mongoid #:nodoc:
193
148
  #
194
149
  # Returns: <tt>Criteria</tt>
195
150
  def select(*args)
196
- Criteria.new(:all).select(*args)
151
+ Criteria.new(:all, self).select(*args)
197
152
  end
198
153
 
199
154
  end
@@ -219,6 +174,12 @@ module Mongoid #:nodoc:
219
174
  def initialize(attributes = {})
220
175
  @attributes = attributes.symbolize_keys if attributes
221
176
  @attributes = {} unless attributes
177
+ generate_key
178
+ end
179
+
180
+ # Return the +Document+ primary key.
181
+ def primary_key
182
+ self.class.primary_key
222
183
  end
223
184
 
224
185
  # Returns true is the Document has not been persisted to the database, false if it has.
@@ -226,27 +187,10 @@ module Mongoid #:nodoc:
226
187
  @attributes[:_id].nil?
227
188
  end
228
189
 
229
- # Returns the id of the Document
230
- def to_param
231
- id.to_s
232
- end
233
-
234
- private
235
-
236
- class << self
237
-
238
- # Adds the association to the associations hash with the type as the key,
239
- # then adds the accessors for the association.
240
- def add_association(type, class_name, name)
241
- define_method(name) do
242
- Mongoid::Associations::Factory.create(type, name, self)
243
- end
244
- define_method("#{name}=") do |object|
245
- object.parentize(self)
246
- @attributes[name] = object.mongoidize
247
- end
248
- end
249
-
190
+ # Notify observers that this Document has changed.
191
+ def notify
192
+ changed(true)
193
+ notify_observers(self)
250
194
  end
251
195
 
252
196
  # Read from the attributes hash.
@@ -255,23 +199,37 @@ module Mongoid #:nodoc:
255
199
  fields[symbol].value(@attributes[symbol])
256
200
  end
257
201
 
258
- # Update the created_at field on the Document to the current time. This is
259
- # only called on create.
260
- def update_created_at
261
- self.created_at = Time.now
202
+ # Returns the id of the Document
203
+ def to_param
204
+ id.to_s
262
205
  end
263
206
 
264
- # Update the last_modified field on the Document to the current time.
265
- # This is only called on create and on save.
266
- def update_last_modified
267
- self.last_modified = Time.now
207
+ # Update the document based on notify from child
208
+ def update(child)
209
+ @attributes.insert(child.association_name, child.attributes)
210
+ notify
268
211
  end
269
212
 
270
213
  # Write to the attributes hash.
271
214
  def write_attribute(name, value)
272
215
  symbol = name.to_sym
273
- @attributes[name.to_sym] = fields[symbol].value(value)
216
+ @attributes[name.to_sym] = value
217
+ notify
218
+ end
219
+
220
+ # Writes all the attributes of this Document, and delegate up to
221
+ # the parent.
222
+ def write_attributes(attrs)
223
+ @attributes = attrs
224
+ notify
274
225
  end
275
226
 
227
+ private
228
+ def generate_key
229
+ if primary_key
230
+ values = primary_key.collect { |key| @attributes[key] }
231
+ @attributes[:_id] = values.join(" ").parameterize.to_s
232
+ end
233
+ end
276
234
  end
277
235
  end