jnunemaker-mongomapper 0.3.3 → 0.3.4

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/History CHANGED
@@ -1,3 +1,9 @@
1
+ 0.3.4 8/28/2009
2
+ * BACKWORDS COMPATIBILITY BREAK: Timestamps are now optional. To use them add timestamps! to your model.
3
+ * BACKWORDS COMPATIBILITY BREAK: Associations keys are no longer created automatically when you use belongs_to and many. Too much was hidden from the developer. You now have to declare them like key :creator_id, String and such.
4
+ * to_json now includes dynamic keys and embedded stuff by default
5
+ * added polymorphic many with :as option (dcu)
6
+
1
7
  0.3.3 8/16/2009
2
8
  * BACKWORDS COMPATIBILITY BREAK: _id is now once again a string rather than an object id and will stay that way.
3
9
  * Custom id's can now be used because of the change to string id's
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.3.4
data/lib/mongomapper.rb CHANGED
@@ -25,6 +25,7 @@ require dir + 'associations/many_proxy'
25
25
  require dir + 'associations/many_polymorphic_proxy'
26
26
  require dir + 'associations/many_embedded_proxy'
27
27
  require dir + 'associations/many_embedded_polymorphic_proxy'
28
+ require dir + 'associations/many_documents_as_proxy'
28
29
 
29
30
  require dir + 'callbacks'
30
31
  require dir + 'finder_options'
@@ -43,14 +44,15 @@ require dir + 'embedded_document'
43
44
  require dir + 'document'
44
45
 
45
46
  module MongoMapper
46
- DocumentNotFound = Class.new(StandardError)
47
- DocumentNotValid = Class.new(StandardError) do
47
+ DocumentNotFound = Class.new(StandardError)
48
+
49
+ DocumentNotValid = Class.new(StandardError) do
48
50
  def initialize(document)
49
51
  @document = document
50
52
  super("Validation failed: #{@document.errors.full_messages.join(", ")}")
51
53
  end
52
54
  end
53
-
55
+
54
56
  def self.connection
55
57
  @@connection ||= XGen::Mongo::Driver::Mongo.new
56
58
  end
@@ -20,7 +20,6 @@ module MongoMapper
20
20
  association = Associations::Base.new(type, name, options)
21
21
  associations[association.name] = association
22
22
  define_association_methods(association)
23
- define_association_keys(association)
24
23
  define_dependent_callback(association)
25
24
  association
26
25
  end
@@ -36,17 +35,6 @@ module MongoMapper
36
35
  end
37
36
  end
38
37
 
39
- def define_association_keys(association)
40
- if association.belongs_to?
41
- key(association.belongs_to_key_name, String)
42
- key(association.type_key_name, String) if association.polymorphic?
43
- end
44
-
45
- if association.many? && association.polymorphic?
46
- association.klass.send(:key, association.type_key_name, String)
47
- end
48
- end
49
-
50
38
  def define_dependent_callback(association)
51
39
  if association.options[:dependent]
52
40
  if association.many?
@@ -4,11 +4,9 @@ module MongoMapper
4
4
  attr_reader :type, :name, :options
5
5
 
6
6
  def initialize(type, name, options = {})
7
- @options = options
8
- @type = type
9
- @name = name
7
+ @type, @name, @options = type, name, options
10
8
  end
11
-
9
+
12
10
  def class_name
13
11
  @class_name ||= begin
14
12
  if cn = options[:class_name]
@@ -20,35 +18,43 @@ module MongoMapper
20
18
  end
21
19
  end
22
20
  end
23
-
21
+
24
22
  def klass
25
23
  @klass ||= class_name.constantize
26
24
  end
27
-
25
+
28
26
  def many?
29
27
  @many_type ||= @type == :many
30
28
  end
31
-
29
+
32
30
  def belongs_to?
33
31
  @belongs_to_type ||= @type == :belongs_to
34
32
  end
35
-
33
+
36
34
  def polymorphic?
37
35
  !!@options[:polymorphic]
38
36
  end
39
-
37
+
38
+ def as?
39
+ !!@options[:as]
40
+ end
41
+
40
42
  def type_key_name
41
- @type_key_name ||= many? ? '_type' : "#{name}_type"
43
+ @type_key_name ||= many? ? '_type' : "#{as}_type"
44
+ end
45
+
46
+ def as
47
+ @options[:as] || self.name
42
48
  end
43
-
44
- def belongs_to_key_name
45
- "#{name}_id"
49
+
50
+ def foreign_key
51
+ @options[:foreign_key] || "#{name}_id"
46
52
  end
47
-
53
+
48
54
  def ivar
49
55
  @ivar ||= "@_#{name}"
50
56
  end
51
-
57
+
52
58
  def embeddable?
53
59
  many? && klass.embeddable?
54
60
  end
@@ -59,7 +65,13 @@ module MongoMapper
59
65
  if self.klass.embeddable?
60
66
  polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
61
67
  else
62
- polymorphic? ? ManyPolymorphicProxy : ManyProxy
68
+ if polymorphic?
69
+ ManyPolymorphicProxy
70
+ elsif as?
71
+ ManyDocumentsAsProxy
72
+ else
73
+ ManyProxy
74
+ end
63
75
  end
64
76
  else
65
77
  polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
@@ -7,7 +7,7 @@ module MongoMapper
7
7
  id, type = doc.id, doc.class.name
8
8
  end
9
9
 
10
- @owner.send("#{@association.belongs_to_key_name}=", id)
10
+ @owner.send("#{@association.foreign_key}=", id)
11
11
  @owner.send("#{@association.type_key_name}=", type)
12
12
  reset
13
13
  end
@@ -20,7 +20,7 @@ module MongoMapper
20
20
  end
21
21
 
22
22
  def proxy_id
23
- @proxy_id ||= @owner.send(@association.belongs_to_key_name)
23
+ @proxy_id ||= @owner.send(@association.foreign_key)
24
24
  end
25
25
 
26
26
  def proxy_class
@@ -7,13 +7,13 @@ module MongoMapper
7
7
  id = doc.id
8
8
  end
9
9
 
10
- @owner.send("#{@association.belongs_to_key_name}=", id)
10
+ @owner.send("#{@association.foreign_key}=", id)
11
11
  reset
12
12
  end
13
13
 
14
14
  protected
15
15
  def find_target
16
- if association_id = @owner.send(@association.belongs_to_key_name)
16
+ if association_id = @owner.send(@association.foreign_key)
17
17
  @association.klass.find_by_id(association_id)
18
18
  end
19
19
  end
@@ -0,0 +1,27 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsAsProxy < ManyDocumentsProxy
4
+ protected
5
+ def scoped_conditions
6
+ {as_type_name => @owner.class.name, as_id_name => @owner.id}
7
+ end
8
+
9
+ def apply_scope(doc)
10
+ ensure_owner_saved
11
+
12
+ doc.send("#{as_type_name}=", @owner.class.name)
13
+ doc.send("#{as_id_name}=", @owner.id)
14
+
15
+ doc
16
+ end
17
+
18
+ def as_type_name
19
+ @as_type_name ||= @association.options[:as].to_s + "_type"
20
+ end
21
+
22
+ def as_id_name
23
+ @as_id_name ||= @association.options[:as].to_s + "_id"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -11,9 +11,6 @@ module MongoMapper
11
11
  include SaveWithValidation
12
12
  include RailsCompatibility::Document
13
13
  extend ClassMethods
14
-
15
- key :created_at, Time
16
- key :updated_at, Time
17
14
  end
18
15
 
19
16
  descendants << model
@@ -140,7 +137,14 @@ module MongoMapper
140
137
  end
141
138
  @collection
142
139
  end
143
-
140
+
141
+ def timestamps!
142
+ key :created_at, Time
143
+ key :updated_at, Time
144
+
145
+ class_eval { before_save :update_timestamps }
146
+ end
147
+
144
148
  def validates_uniqueness_of(*args)
145
149
  add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
146
150
  end
@@ -181,7 +185,22 @@ module MongoMapper
181
185
 
182
186
  def find_last(options)
183
187
  options.merge!(:limit => 1)
184
- find_every({:order => '$natural desc'}.merge(options))[0]
188
+ options[:order] = invert_order_clause(options)
189
+ find_every(options)[0]
190
+ #find_every({:order => '$natural desc'}.merge(invert_order_clause(options)))[0]
191
+ end
192
+
193
+ def invert_order_clause(options)
194
+ return '$natural desc' unless options[:order]
195
+ options[:order].split(',').map do |order_segment|
196
+ if order_segment =~ /\sasc/i
197
+ order_segment.sub /\sasc/i, ' desc'
198
+ elsif order_segment =~ /\sdesc/i
199
+ order_segment.sub /\sdesc/i, ' asc'
200
+ else
201
+ "#{order_segment.strip} desc"
202
+ end
203
+ end.join(',')
185
204
  end
186
205
 
187
206
  def find_some(ids, options={})
@@ -295,7 +314,6 @@ module MongoMapper
295
314
  end
296
315
 
297
316
  def create
298
- update_timestamps
299
317
  assign_id
300
318
  save_to_collection
301
319
  end
@@ -307,7 +325,6 @@ module MongoMapper
307
325
  end
308
326
 
309
327
  def update
310
- update_timestamps
311
328
  save_to_collection
312
329
  end
313
330
 
@@ -77,28 +77,39 @@ module MongoMapper
77
77
  end
78
78
 
79
79
  def parent_model
80
- if parent = ancestors[1]
81
- parent if parent.ancestors.include?(EmbeddedDocument)
80
+ (ancestors - [self,EmbeddedDocument]).find do |parent_class|
81
+ parent_class.ancestors.include?(EmbeddedDocument)
82
82
  end
83
83
  end
84
84
 
85
85
  private
86
- def create_accessors_for(key)
87
- define_method(key.name) do
88
- read_attribute(key.name)
86
+ def accessors_module
87
+ if const_defined?('MongoMapperKeys') && constants.include?( 'MongoMapperKeys' )
88
+ const_get 'MongoMapperKeys'
89
+ else
90
+ const_set 'MongoMapperKeys', Module.new
89
91
  end
92
+ end
90
93
 
91
- define_method("#{key.name}_before_typecast") do
92
- read_attribute_before_typecast(key.name)
93
- end
94
+ def create_accessors_for(key)
95
+ accessors_module.module_eval <<-end_eval
96
+ def #{key.name}
97
+ read_attribute( :'#{key.name}' )
98
+ end
94
99
 
95
- define_method("#{key.name}=") do |value|
96
- write_attribute(key.name, value)
97
- end
100
+ def #{key.name}_before_typecast
101
+ read_attribute_before_typecast(:'#{key.name}')
102
+ end
98
103
 
99
- define_method("#{key.name}?") do
100
- read_attribute(key.name).present?
101
- end
104
+ def #{key.name}=(value)
105
+ write_attribute(:'#{key.name}', value)
106
+ end
107
+
108
+ def #{key.name}?
109
+ read_attribute(:#{key.name}).present?
110
+ end
111
+ end_eval
112
+ include accessors_module
102
113
  end
103
114
 
104
115
  def create_indexes_for(key)
@@ -10,7 +10,7 @@ module MongoMapper #:nodoc:
10
10
  end
11
11
 
12
12
  def serializable_key_names
13
- key_names = @record.class.keys.keys
13
+ key_names = @record.attributes.keys
14
14
 
15
15
  if options[:only]
16
16
  options.delete(:except)
data/mongomapper.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongomapper}
8
- s.version = "0.3.3"
8
+ s.version = "0.3.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Nunemaker"]
12
- s.date = %q{2009-08-16}
12
+ s.date = %q{2009-08-28}
13
13
  s.default_executable = %q{mmconsole}
14
14
  s.email = %q{nunemaker@gmail.com}
15
15
  s.executables = ["mmconsole"]
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
30
30
  "lib/mongomapper/associations/base.rb",
31
31
  "lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb",
32
32
  "lib/mongomapper/associations/belongs_to_proxy.rb",
33
+ "lib/mongomapper/associations/many_documents_as_proxy.rb",
33
34
  "lib/mongomapper/associations/many_documents_proxy.rb",
34
35
  "lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb",
35
36
  "lib/mongomapper/associations/many_embedded_proxy.rb",
@@ -55,6 +56,7 @@ Gem::Specification.new do |s|
55
56
  "test/NOTE_ON_TESTING",
56
57
  "test/functional/associations/test_belongs_to_polymorphic_proxy.rb",
57
58
  "test/functional/associations/test_belongs_to_proxy.rb",
59
+ "test/functional/associations/test_many_documents_as_proxy.rb",
58
60
  "test/functional/associations/test_many_embedded_polymorphic_proxy.rb",
59
61
  "test/functional/associations/test_many_embedded_proxy.rb",
60
62
  "test/functional/associations/test_many_polymorphic_proxy.rb",
@@ -89,6 +91,7 @@ Gem::Specification.new do |s|
89
91
  s.test_files = [
90
92
  "test/functional/associations/test_belongs_to_polymorphic_proxy.rb",
91
93
  "test/functional/associations/test_belongs_to_proxy.rb",
94
+ "test/functional/associations/test_many_documents_as_proxy.rb",
92
95
  "test/functional/associations/test_many_embedded_polymorphic_proxy.rb",
93
96
  "test/functional/associations/test_many_embedded_proxy.rb",
94
97
  "test/functional/associations/test_many_polymorphic_proxy.rb",
@@ -0,0 +1,253 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ clear_all_collections
7
+ end
8
+
9
+ should "default reader to empty array" do
10
+ Post.new.comments.should == []
11
+ end
12
+
13
+ should "add type and id key to polymorphic class base" do
14
+ PostComment.keys.keys.should include('commentable_type')
15
+ PostComment.keys.keys.should include('commentable_id')
16
+ end
17
+
18
+ should "allow adding to association like it was an array" do
19
+ post = Post.new
20
+ post.comments << PostComment.new(:body => 'foo bar')
21
+ post.comments << PostComment.new(:body => 'baz')
22
+ post.comments.concat PostComment.new(:body => 'baz')
23
+
24
+ post.comments.size.should == 3
25
+ end
26
+
27
+ should "be able to replace the association" do
28
+ post = Post.new
29
+
30
+ lambda {
31
+ post.comments = [
32
+ PostComment.new(:body => 'foo'),
33
+ PostComment.new(:body => 'bar'),
34
+ PostComment.new(:body => 'baz')
35
+ ]
36
+ }.should change { PostComment.count }.by(3)
37
+
38
+ from_db = Post.find(post.id)
39
+ from_db.comments.size.should == 3
40
+ from_db.comments[0].body.should == 'foo'
41
+ from_db.comments[1].body.should == 'bar'
42
+ from_db.comments[2].body.should == 'baz'
43
+ end
44
+
45
+ context "build" do
46
+ should "assign foreign key" do
47
+ post = Post.new
48
+ comment = post.comments.build
49
+ comment.commentable_id.should == post.id
50
+ end
51
+
52
+ should "assign _type" do
53
+ post = Post.new
54
+ comment = post.comments.build
55
+ comment.commentable_type.should == "Post"
56
+ end
57
+
58
+ should "allow assigning attributes" do
59
+ post = Post.new
60
+ comment = post.comments.build(:body => 'foo bar')
61
+ comment.body.should == 'foo bar'
62
+ end
63
+ end
64
+
65
+ context "create" do
66
+ should "assign foreign key" do
67
+ post = Post.new
68
+ comment = post.comments.create
69
+ comment.commentable_id.should == post.id
70
+ end
71
+
72
+ should "assign _type" do
73
+ post = Post.new
74
+ comment = post.comments.create
75
+ comment.commentable_type.should == "Post"
76
+ end
77
+
78
+ should "save record" do
79
+ post = Post.new
80
+ lambda {
81
+ post.comments.create(:body => 'baz')
82
+ }.should change { PostComment.count }
83
+ end
84
+
85
+ should "allow passing attributes" do
86
+ post = Post.create
87
+ comment = post.comments.create(:body => 'foo bar')
88
+ comment.body.should == 'foo bar'
89
+ end
90
+ end
91
+
92
+ context "count" do
93
+ should "work scoped to association" do
94
+ post = Post.create
95
+ 3.times { post.comments.create(:body => 'foo bar') }
96
+
97
+ other_post = Post.create
98
+ 2.times { other_post.comments.create(:body => 'baz') }
99
+
100
+ post.comments.count.should == 3
101
+ other_post.comments.count.should == 2
102
+ end
103
+
104
+ should "work with conditions" do
105
+ post = Post.create
106
+ post.comments.create(:body => 'foo bar')
107
+ post.comments.create(:body => 'baz')
108
+ post.comments.create(:body => 'foo bar')
109
+
110
+ post.comments.count(:body => 'foo bar').should == 2
111
+ end
112
+ end
113
+
114
+ context "Finding scoped to association" do
115
+ setup do
116
+ @post = Post.new
117
+
118
+ @comment1 = PostComment.create(:body => 'comment1')
119
+ @comment2 = PostComment.create(:body => 'comment2')
120
+ @comment3 = PostComment.create(:body => 'comment3')
121
+ @post.comments = [@comment1, @comment2]
122
+ @post.save
123
+
124
+ @post2 = Post.create(:body => "post #2")
125
+ @comment4 = PostComment.create(:body => 'comment4')
126
+ @comment5 = PostComment.create(:body => 'comment5')
127
+ @comment6 = PostComment.create(:body => 'comment6')
128
+ @post2.comments = [@comment4, @comment5, @comment6]
129
+ @post2.save
130
+ end
131
+
132
+ context "with :all" do
133
+ should "work" do
134
+ @post.comments.find(:all).should include(@comment1)
135
+ @post.comments.find(:all).should include(@comment2)
136
+ end
137
+
138
+ should "work with conditions" do
139
+ comments = @post.comments.find(:all, :conditions => {:body => 'comment1'})
140
+ comments.should == [@comment1]
141
+ end
142
+
143
+ should "work with order" do
144
+ comments = @post.comments.find(:all, :order => '$natural desc')
145
+ comments.should == [@comment2, @comment1]
146
+ end
147
+ end
148
+
149
+ context "with #all" do
150
+ should "work" do
151
+ @post.comments.all.should == [@comment1, @comment2]
152
+ end
153
+
154
+ should "work with conditions" do
155
+ comments = @post.comments.all(:conditions => {:body => 'comment1'})
156
+ comments.should == [@comment1]
157
+ end
158
+
159
+ should "work with order" do
160
+ comments = @post.comments.all(:order => '$natural desc')
161
+ comments.should == [@comment2, @comment1]
162
+ end
163
+ end
164
+
165
+ context "with :first" do
166
+ should "work" do
167
+ lambda {@post.comments.find(:first)}.should_not raise_error
168
+ end
169
+
170
+ should "work with conditions" do
171
+ comment = @post.comments.find(:first, :conditions => {:body => 'comment2'})
172
+ comment.body.should == 'comment2'
173
+ end
174
+ end
175
+
176
+ context "with #first" do
177
+ should "work" do
178
+ @post.comments.first.should == @comment1
179
+ end
180
+
181
+ should "work with conditions" do
182
+ comment = @post.comments.first(:conditions => {:body => 'comment2'})
183
+ comment.should == @comment2
184
+ end
185
+ end
186
+
187
+ context "with :last" do
188
+ should "work" do
189
+ @post.comments.find(:last, :order => 'created_at asc').should == @comment2
190
+ end
191
+
192
+ should "work with conditions" do
193
+ post = @post.comments.find(:last, :conditions => {:body => 'comment1'})
194
+ post.body.should == 'comment1'
195
+ end
196
+ end
197
+
198
+ context "with #last" do
199
+ should "work" do
200
+ @post.comments.last.should == @comment2
201
+ end
202
+
203
+ should "work with conditions" do
204
+ comment = @post.comments.last(:conditions => {:body => 'comment1'})
205
+ comment.should == @comment1
206
+ end
207
+ end
208
+
209
+ context "with one id" do
210
+ should "work for id in association" do
211
+ @post.comments.find(@comment2.id).should == @comment2
212
+ end
213
+
214
+ should "not work for id not in association" do
215
+ lambda {
216
+ @post.comments.find(@comment5.id)
217
+ }.should raise_error(MongoMapper::DocumentNotFound)
218
+ end
219
+ end
220
+
221
+ context "with multiple ids" do
222
+ should "work for ids in association" do
223
+ posts = @post.comments.find(@comment1.id, @comment2.id)
224
+ posts.should == [@comment1, @comment2]
225
+ end
226
+
227
+ should "not work for ids not in association" do
228
+ lambda {
229
+ @post.comments.find(@comment1.id, @comment2.id, @comment4.id)
230
+ }.should raise_error(MongoMapper::DocumentNotFound)
231
+ end
232
+ end
233
+
234
+ context "with #paginate" do
235
+ setup do
236
+ @comments = @post2.comments.paginate(:per_page => 2, :page => 1, :order => 'created_at asc')
237
+ end
238
+
239
+ should "return total pages" do
240
+ @comments.total_pages.should == 2
241
+ end
242
+
243
+ should "return total entries" do
244
+ @comments.total_entries.should == 3
245
+ end
246
+
247
+ should "return the subject" do
248
+ @comments.should include(@comment4)
249
+ @comments.should include(@comment5)
250
+ end
251
+ end
252
+ end
253
+ end