jnunemaker-mongomapper 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
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