jnunemaker-mongomapper 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/History +11 -0
  2. data/VERSION +1 -1
  3. data/lib/mongomapper/associations/base.rb +5 -2
  4. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +1 -2
  5. data/lib/mongomapper/associations/belongs_to_proxy.rb +3 -3
  6. data/lib/mongomapper/associations/many_documents_proxy.rb +85 -0
  7. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +2 -3
  8. data/lib/mongomapper/associations/many_embedded_proxy.rb +2 -4
  9. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  10. data/lib/mongomapper/associations/many_proxy.rb +1 -50
  11. data/lib/mongomapper/associations/proxy.rb +1 -1
  12. data/lib/mongomapper/document.rb +5 -12
  13. data/lib/mongomapper/embedded_document.rb +21 -2
  14. data/lib/mongomapper/key.rb +2 -1
  15. data/lib/mongomapper/serializers/json_serializer.rb +15 -0
  16. data/lib/mongomapper.rb +17 -10
  17. data/mongomapper.gemspec +17 -4
  18. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +39 -0
  19. data/test/functional/associations/test_belongs_to_proxy.rb +35 -0
  20. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  21. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  22. data/test/functional/associations/test_many_polymorphic_proxy.rb +259 -0
  23. data/test/functional/associations/test_many_proxy.rb +236 -0
  24. data/test/functional/test_associations.rb +22 -467
  25. data/test/functional/test_document.rb +76 -19
  26. data/test/functional/test_pagination.rb +2 -3
  27. data/test/models.rb +16 -0
  28. data/test/test_helper.rb +1 -1
  29. data/test/unit/serializers/test_json_serializer.rb +69 -16
  30. data/test/unit/test_association_base.rb +5 -0
  31. data/test/unit/test_document.rb +14 -4
  32. data/test/unit/test_embedded_document.rb +46 -21
  33. data/test/unit/test_key.rb +5 -0
  34. data/test/unit/test_mongo_id.rb +2 -2
  35. data/test/unit/test_rails_compatibility.rb +3 -3
  36. metadata +16 -3
  37. data/lib/mongomapper/associations/array_proxy.rb +0 -6
data/History CHANGED
@@ -1,3 +1,14 @@
1
+ 0.3.2 8/6/2009
2
+ * Added many polymorphic documents association
3
+ * Implemented build and create for many and many polymorphic documents
4
+ * <<, push and concat now work correctly for many and many polymorphic documents
5
+ * find(:first) now accepts order option
6
+ * id is now included by default with to_json
7
+ * _id is now always excluded from to_json
8
+ * Times are now always returned as UTC
9
+ * Default values are now a bit more intelligent for Array and Hash keys (djsun)
10
+ * Embedded documents now have _id as well so they can be identified more easily
11
+
1
12
  0.3.1 7/28/2009
2
13
  * 1 minor tweak
3
14
  * Removed deep_merge gem as dependency as ActiveSupport has deep_merge that works good enough
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.3.2
@@ -56,8 +56,11 @@ module MongoMapper
56
56
  def proxy_class
57
57
  @proxy_class ||= begin
58
58
  if many?
59
- return ManyProxy unless self.klass.embeddable?
60
- polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
59
+ if self.klass.embeddable?
60
+ polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
61
+ else
62
+ polymorphic? ? ManyPolymorphicProxy : ManyProxy
63
+ end
61
64
  else
62
65
  polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
63
66
  end
@@ -9,8 +9,7 @@ module MongoMapper
9
9
 
10
10
  @owner.send("#{@association.belongs_to_key_name}=", id)
11
11
  @owner.send("#{@association.type_key_name}=", type)
12
-
13
- reload_target
12
+ reset
14
13
  end
15
14
 
16
15
  protected
@@ -8,13 +8,13 @@ module MongoMapper
8
8
  end
9
9
 
10
10
  @owner.send("#{@association.belongs_to_key_name}=", id)
11
- reload_target
11
+ reset
12
12
  end
13
13
 
14
14
  protected
15
15
  def find_target
16
- if ref = @owner.send(@association.belongs_to_key_name)
17
- @association.klass.find(ref)
16
+ if association_id = @owner.send(@association.belongs_to_key_name)
17
+ @association.klass.find(association_id)
18
18
  end
19
19
  end
20
20
  end
@@ -0,0 +1,85 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsProxy < Proxy
4
+ delegate :klass, :to => :@association
5
+
6
+ def find(*args)
7
+ options = args.extract_options!
8
+ klass.find(*args << scoped_options(options))
9
+ end
10
+
11
+ def paginate(options)
12
+ klass.paginate(scoped_options(options))
13
+ end
14
+
15
+ def all(options={})
16
+ find(:all, scoped_options(options))
17
+ end
18
+
19
+ def first(options={})
20
+ find(:first, scoped_options(options))
21
+ end
22
+
23
+ def last(options={})
24
+ find(:last, scoped_options(options))
25
+ end
26
+
27
+ def count(conditions={})
28
+ klass.count(conditions.deep_merge(scoped_conditions))
29
+ end
30
+
31
+ def replace(docs)
32
+ @target.map(&:destroy) if load_target
33
+ docs.each { |doc| apply_scope(doc).save }
34
+ reset
35
+ end
36
+
37
+ def <<(*docs)
38
+ ensure_owner_saved
39
+ flatten_deeper(docs).each { |doc| apply_scope(doc).save }
40
+ reset
41
+ end
42
+ alias_method :push, :<<
43
+ alias_method :concat, :<<
44
+
45
+ def build(attrs={})
46
+ doc = klass.new(attrs)
47
+ apply_scope(doc)
48
+ doc
49
+ end
50
+
51
+ def create(attrs={})
52
+ doc = klass.new(attrs)
53
+ apply_scope(doc).save
54
+ doc
55
+ end
56
+
57
+ protected
58
+ def scoped_conditions
59
+ {self.foreign_key => @owner.id}
60
+ end
61
+
62
+ def scoped_options(options)
63
+ options.deep_merge({:conditions => scoped_conditions})
64
+ end
65
+
66
+ def find_target
67
+ find(:all)
68
+ end
69
+
70
+ def ensure_owner_saved
71
+ @owner.save if @owner.new?
72
+ end
73
+
74
+ def apply_scope(doc)
75
+ ensure_owner_saved
76
+ doc.send("#{self.foreign_key}=", @owner.id)
77
+ doc
78
+ end
79
+
80
+ def foreign_key
81
+ @association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,6 +1,6 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class ManyEmbeddedPolymorphicProxy < ArrayProxy
3
+ class ManyEmbeddedPolymorphicProxy < Proxy
4
4
  def replace(v)
5
5
  @_values = v.map do |doc_or_hash|
6
6
  if doc_or_hash.kind_of?(EmbeddedDocument)
@@ -11,8 +11,7 @@ module MongoMapper
11
11
  end
12
12
  end
13
13
 
14
- @target = nil
15
- reload_target
14
+ reset
16
15
  end
17
16
 
18
17
  protected
@@ -1,11 +1,9 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class ManyEmbeddedProxy < ArrayProxy
3
+ class ManyEmbeddedProxy < Proxy
4
4
  def replace(v)
5
5
  @_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
6
- @target = nil
7
-
8
- reload_target
6
+ reset
9
7
  end
10
8
 
11
9
  protected
@@ -0,0 +1,11 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyPolymorphicProxy < ManyDocumentsProxy
4
+ private
5
+ def apply_scope(doc)
6
+ doc.send("#{@association.type_key_name}=", doc.class.name)
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,55 +1,6 @@
1
1
  module MongoMapper
2
2
  module Associations
3
- class ManyProxy < ArrayProxy
4
- delegate :klass, :to => :@association
5
-
6
- def find(*args)
7
- options = args.extract_options!
8
- klass.find(*args << scoped_options(options))
9
- end
10
-
11
- def paginate(options)
12
- klass.paginate(scoped_options(options))
13
- end
14
-
15
- def all(options={})
16
- find(:all, scoped_options(options))
17
- end
18
-
19
- def first(options={})
20
- find(:first, scoped_options(options))
21
- end
22
-
23
- def last(options={})
24
- find(:last, scoped_options(options))
25
- end
26
-
27
- def replace(docs)
28
- if load_target
29
- @target.map(&:destroy)
30
- end
31
-
32
- docs.each do |doc|
33
- @owner.save if @owner.new?
34
- doc.send(:write_attribute, self.foreign_key, @owner.id)
35
- doc.save
36
- end
37
-
38
- reload_target
39
- end
40
-
41
- protected
42
- def scoped_options(options)
43
- options.dup.deep_merge({:conditions => {self.foreign_key => @owner.id}})
44
- end
45
-
46
- def find_target
47
- find(:all)
48
- end
49
-
50
- def foreign_key
51
- @association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
52
- end
3
+ class ManyProxy < ManyDocumentsProxy
53
4
  end
54
5
  end
55
6
  end
@@ -8,7 +8,7 @@ module MongoMapper
8
8
  end
9
9
 
10
10
  def initialize(owner, association)
11
- @owner= owner
11
+ @owner = owner
12
12
  @association = association
13
13
  reset
14
14
  end
@@ -12,7 +12,6 @@ module MongoMapper
12
12
  include RailsCompatibility::Document
13
13
  extend ClassMethods
14
14
 
15
- key :_id, MongoID
16
15
  key :created_at, Time
17
16
  key :updated_at, Time
18
17
  end
@@ -162,7 +161,8 @@ module MongoMapper
162
161
  end
163
162
 
164
163
  def find_first(options)
165
- find_every(options.merge(:limit => 1, :order => '$natural asc'))[0]
164
+ options.merge!(:limit => 1)
165
+ find_every({:order => '$natural asc'}.merge(options))[0]
166
166
  end
167
167
 
168
168
  def find_last(options)
@@ -247,14 +247,6 @@ module MongoMapper
247
247
  freeze
248
248
  end
249
249
 
250
- def ==(other)
251
- other.is_a?(self.class) && id == other.id
252
- end
253
-
254
- def id
255
- read_attribute('_id').to_s
256
- end
257
-
258
250
  private
259
251
  def create_or_update
260
252
  result = new? ? create : update
@@ -277,8 +269,9 @@ module MongoMapper
277
269
  end
278
270
 
279
271
  def update_timestamps
280
- write_attribute('created_at', Time.now.utc) if new?
281
- write_attribute('updated_at', Time.now.utc)
272
+ now = Time.now.utc
273
+ write_attribute('created_at', now) if new?
274
+ write_attribute('updated_at', now)
282
275
  end
283
276
  end
284
277
  end # Document
@@ -13,11 +13,17 @@ module MongoMapper
13
13
  include RailsCompatibility::EmbeddedDocument
14
14
  include Validatable
15
15
  include Serialization
16
+
17
+ key :_id, MongoID
16
18
  end
17
19
  end
18
20
 
19
21
  module ClassMethods
20
22
  def inherited(subclass)
23
+ unless subclass.embeddable?
24
+ subclass.collection(self.collection.name)
25
+ end
26
+
21
27
  (@subclasses ||= []) << subclass
22
28
  end
23
29
 
@@ -141,6 +147,10 @@ module MongoMapper
141
147
 
142
148
  self.attributes = attrs
143
149
  end
150
+
151
+ if self.class.embeddable? && read_attribute(:_id).blank?
152
+ write_attribute :_id, XGen::Mongo::Driver::ObjectID.new
153
+ end
144
154
  end
145
155
 
146
156
  def attributes=(attrs)
@@ -170,7 +180,15 @@ module MongoMapper
170
180
  end
171
181
 
172
182
  def ==(other)
173
- other.is_a?(self.class) && attributes == other.attributes
183
+ other.is_a?(self.class) && id == other.id
184
+ end
185
+
186
+ def id
187
+ self._id.to_s
188
+ end
189
+
190
+ def id=(value)
191
+ self._id = value
174
192
  end
175
193
 
176
194
  def inspect
@@ -191,7 +209,8 @@ module MongoMapper
191
209
  end
192
210
 
193
211
  def read_attribute(name)
194
- self.class.keys[name].get(instance_variable_get("@#{name}"))
212
+ value = self.class.keys[name].get(instance_variable_get("@#{name}"))
213
+ instance_variable_set "@#{name}", value
195
214
  end
196
215
 
197
216
  def read_attribute_before_typecast(name)
@@ -41,12 +41,13 @@ module MongoMapper
41
41
  private
42
42
  def typecast(value)
43
43
  return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
44
+ return value.utc if type == Time && value.kind_of?(type)
44
45
  return value if value.kind_of?(type) || value.nil?
45
46
  begin
46
47
  if type == String then value.to_s
47
48
  elsif type == Float then value.to_f
48
49
  elsif type == Array then value.to_a
49
- elsif type == Time then Time.parse(value.to_s)
50
+ elsif type == Time then Time.parse(value.to_s).utc
50
51
  elsif type == MongoID then MongoID.mm_typecast(value)
51
52
  #elsif type == Date then Date.parse(value.to_s)
52
53
  elsif type == Boolean then Boolean.mm_typecast(value)
@@ -50,6 +50,8 @@ module MongoMapper #:nodoc:
50
50
  # "created_at": "2006/08/01", "awesome": true,
51
51
  # "permalink": "1-konata-izumi"}
52
52
  def to_json(options = {})
53
+ apply_to_json_defaults(options)
54
+
53
55
  if include_root_in_json
54
56
  "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
55
57
  else
@@ -73,5 +75,18 @@ module MongoMapper #:nodoc:
73
75
  @json_class_name ||= name.demodulize.underscore.inspect
74
76
  end
75
77
  end
78
+
79
+ private
80
+ def apply_to_json_defaults(options)
81
+ unless options[:only]
82
+ methods = [options.delete(:methods)].flatten.compact
83
+ methods << :id
84
+ options[:methods] = methods.uniq
85
+ end
86
+
87
+ except = [options.delete(:except)].flatten.compact
88
+ except << :_id
89
+ options[:except] = except
90
+ end
76
91
  end
77
92
  end
data/lib/mongomapper.rb CHANGED
@@ -29,7 +29,7 @@ class MongoID < XGen::Mongo::Driver::ObjectID
29
29
  end
30
30
  rescue => exception
31
31
  if exception.message == 'illegal ObjectID format'
32
- raise MongoMapper::DocumentNotFound
32
+ raise MongoMapper::IllegalID
33
33
  else
34
34
  raise exception
35
35
  end
@@ -40,31 +40,38 @@ end
40
40
  dir = Pathname(__FILE__).dirname.expand_path + 'mongomapper'
41
41
 
42
42
  require dir + 'associations'
43
- require dir + 'associations/proxy'
44
- require dir + 'associations/array_proxy'
45
43
  require dir + 'associations/base'
44
+
45
+ require dir + 'associations/proxy'
46
+ require dir + 'associations/many_documents_proxy'
47
+
48
+ require dir + 'associations/belongs_to_proxy'
49
+ require dir + 'associations/belongs_to_polymorphic_proxy'
46
50
  require dir + 'associations/many_proxy'
51
+ require dir + 'associations/many_polymorphic_proxy'
47
52
  require dir + 'associations/many_embedded_proxy'
48
53
  require dir + 'associations/many_embedded_polymorphic_proxy'
49
- require dir + 'associations/belongs_to_proxy'
50
- require dir + 'associations/belongs_to_polymorphic_proxy'
54
+
51
55
  require dir + 'callbacks'
52
56
  require dir + 'finder_options'
53
57
  require dir + 'key'
54
58
  require dir + 'observing'
55
59
  require dir + 'pagination'
56
- require dir + 'rails_compatibility/document'
57
- require dir + 'rails_compatibility/embedded_document'
58
60
  require dir + 'save_with_validation'
59
61
  require dir + 'serialization'
60
62
  require dir + 'validations'
61
63
 
64
+ require dir + 'rails_compatibility/document'
65
+ require dir + 'rails_compatibility/embedded_document'
66
+
62
67
  require dir + 'embedded_document'
63
68
  require dir + 'document'
64
69
 
65
- module MongoMapper
66
- class DocumentNotFound < StandardError; end
67
- class DocumentNotValid < StandardError
70
+ module MongoMapper
71
+ DocumentNotFound = Class.new(StandardError)
72
+ IllegalID = Class.new(StandardError)
73
+
74
+ DocumentNotValid = Class.new(StandardError) do
68
75
  def initialize(document)
69
76
  @document = document
70
77
  super("Validation failed: #{@document.errors.full_messages.join(", ")}")
data/mongomapper.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{mongomapper}
5
- s.version = "0.3.1"
5
+ s.version = "0.3.2"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["John Nunemaker"]
9
- s.date = %q{2009-07-28}
9
+ s.date = %q{2009-08-06}
10
10
  s.default_executable = %q{mmconsole}
11
11
  s.email = %q{nunemaker@gmail.com}
12
12
  s.executables = ["mmconsole"]
@@ -24,12 +24,13 @@ Gem::Specification.new do |s|
24
24
  "bin/mmconsole",
25
25
  "lib/mongomapper.rb",
26
26
  "lib/mongomapper/associations.rb",
27
- "lib/mongomapper/associations/array_proxy.rb",
28
27
  "lib/mongomapper/associations/base.rb",
29
28
  "lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb",
30
29
  "lib/mongomapper/associations/belongs_to_proxy.rb",
30
+ "lib/mongomapper/associations/many_documents_proxy.rb",
31
31
  "lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb",
32
32
  "lib/mongomapper/associations/many_embedded_proxy.rb",
33
+ "lib/mongomapper/associations/many_polymorphic_proxy.rb",
33
34
  "lib/mongomapper/associations/many_proxy.rb",
34
35
  "lib/mongomapper/associations/proxy.rb",
35
36
  "lib/mongomapper/callbacks.rb",
@@ -47,6 +48,12 @@ Gem::Specification.new do |s|
47
48
  "lib/mongomapper/validations.rb",
48
49
  "mongomapper.gemspec",
49
50
  "test/NOTE_ON_TESTING",
51
+ "test/functional/associations/test_belongs_to_polymorphic_proxy.rb",
52
+ "test/functional/associations/test_belongs_to_proxy.rb",
53
+ "test/functional/associations/test_many_embedded_polymorphic_proxy.rb",
54
+ "test/functional/associations/test_many_embedded_proxy.rb",
55
+ "test/functional/associations/test_many_polymorphic_proxy.rb",
56
+ "test/functional/associations/test_many_proxy.rb",
50
57
  "test/functional/test_associations.rb",
51
58
  "test/functional/test_callbacks.rb",
52
59
  "test/functional/test_document.rb",
@@ -77,7 +84,13 @@ Gem::Specification.new do |s|
77
84
  s.rubygems_version = %q{1.3.1}
78
85
  s.summary = %q{Awesome gem for modeling your domain and storing it in mongo}
79
86
  s.test_files = [
80
- "test/functional/test_associations.rb",
87
+ "test/functional/associations/test_belongs_to_polymorphic_proxy.rb",
88
+ "test/functional/associations/test_belongs_to_proxy.rb",
89
+ "test/functional/associations/test_many_embedded_polymorphic_proxy.rb",
90
+ "test/functional/associations/test_many_embedded_proxy.rb",
91
+ "test/functional/associations/test_many_polymorphic_proxy.rb",
92
+ "test/functional/associations/test_many_proxy.rb",
93
+ "test/functional/test_associations.rb",
81
94
  "test/functional/test_callbacks.rb",
82
95
  "test/functional/test_document.rb",
83
96
  "test/functional/test_pagination.rb",
@@ -0,0 +1,39 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ clear_all_collections
7
+ end
8
+
9
+ should "default to nil" do
10
+ status = Status.new
11
+ status.target.should be_nil
12
+ end
13
+
14
+ should "be able to replace the association" do
15
+ status = Status.new
16
+ project = Project.new(:name => "mongomapper")
17
+ status.target = project
18
+ status.save.should be_true
19
+
20
+ from_db = Status.find(status.id)
21
+ from_db.target.should_not be_nil
22
+ from_db.target_id.should == project.id
23
+ from_db.target_type.should == "Project"
24
+ from_db.target.name.should == "mongomapper"
25
+ end
26
+
27
+ should "unset the association" do
28
+ status = Status.new
29
+ project = Project.new(:name => "mongomapper")
30
+ status.target = project
31
+ status.save.should be_true
32
+
33
+ from_db = Status.find(status.id)
34
+ from_db.target = nil
35
+ from_db.target_type.should be_nil
36
+ from_db.target_id.should be_nil
37
+ from_db.target.should be_nil
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class BelongsToProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ clear_all_collections
7
+ end
8
+
9
+ should "default to nil" do
10
+ status = Status.new
11
+ status.project.should be_nil
12
+ end
13
+
14
+ should "be able to replace the association" do
15
+ status = Status.new
16
+ project = Project.new(:name => "mongomapper")
17
+ status.project = project
18
+ status.save.should be_true
19
+
20
+ from_db = Status.find(status.id)
21
+ from_db.project.should_not be_nil
22
+ from_db.project.name.should == "mongomapper"
23
+ end
24
+
25
+ should "unset the association" do
26
+ status = Status.new
27
+ project = Project.new(:name => "mongomapper")
28
+ status.project = project
29
+ status.save.should be_true
30
+
31
+ from_db = Status.find(status.id)
32
+ from_db.project = nil
33
+ from_db.project.should be_nil
34
+ end
35
+ end