jnunemaker-mongomapper 0.3.1 → 0.3.2

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.
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