jnunemaker-mongomapper 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +1 -0
  2. data/History +17 -0
  3. data/README.rdoc +6 -3
  4. data/Rakefile +3 -2
  5. data/VERSION +1 -1
  6. data/bin/mmconsole +56 -0
  7. data/lib/mongomapper.rb +48 -17
  8. data/lib/mongomapper/associations.rb +31 -39
  9. data/lib/mongomapper/associations/base.rb +40 -22
  10. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +33 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
  12. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +34 -0
  13. data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +5 -5
  14. data/lib/mongomapper/associations/many_proxy.rb +55 -0
  15. data/lib/mongomapper/associations/proxy.rb +21 -14
  16. data/lib/mongomapper/callbacks.rb +1 -1
  17. data/lib/mongomapper/document.rb +82 -59
  18. data/lib/mongomapper/embedded_document.rb +121 -130
  19. data/lib/mongomapper/finder_options.rb +21 -6
  20. data/lib/mongomapper/key.rb +5 -7
  21. data/lib/mongomapper/observing.rb +1 -41
  22. data/lib/mongomapper/pagination.rb +52 -0
  23. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  24. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  25. data/lib/mongomapper/serialization.rb +1 -1
  26. data/mongomapper.gemspec +62 -36
  27. data/test/NOTE_ON_TESTING +1 -0
  28. data/test/functional/test_associations.rb +485 -0
  29. data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
  30. data/test/functional/test_document.rb +636 -0
  31. data/test/functional/test_pagination.rb +82 -0
  32. data/test/functional/test_rails_compatibility.rb +31 -0
  33. data/test/functional/test_validations.rb +172 -0
  34. data/test/models.rb +92 -0
  35. data/test/test_helper.rb +5 -0
  36. data/test/{serializers → unit/serializers}/test_json_serializer.rb +0 -0
  37. data/test/unit/test_association_base.rb +131 -0
  38. data/test/unit/test_document.rb +115 -0
  39. data/test/{test_embedded_document.rb → unit/test_embedded_document.rb} +158 -66
  40. data/test/{test_finder_options.rb → unit/test_finder_options.rb} +66 -0
  41. data/test/{test_key.rb → unit/test_key.rb} +13 -1
  42. data/test/unit/test_mongo_id.rb +35 -0
  43. data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
  44. data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
  45. data/test/unit/test_pagination.rb +113 -0
  46. data/test/unit/test_rails_compatibility.rb +34 -0
  47. data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
  48. data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
  49. metadata +68 -36
  50. data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
  51. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
  52. data/lib/mongomapper/rails_compatibility.rb +0 -23
  53. data/test/test_associations.rb +0 -149
  54. data/test/test_document.rb +0 -944
  55. data/test/test_rails_compatibility.rb +0 -29
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ coverage
4
4
  rdoc
5
5
  pkg
6
6
  *~
7
+ *.gem
data/History CHANGED
@@ -1,3 +1,20 @@
1
+ 0.3.0 7/28/2009
2
+ * 5 major additions, 3 minor additions, 3 bug fix, and other miscellany
3
+ * BACKWORDS COMPATIBILITY BREAK: _id is now stored in binary form (recommended by mongodb team) instead of string, api is the same everywhere as before but data stored with string id's previous to change will need to be updated
4
+ * Added Document#paginate which works just like find but adds pagination (dcu did basics and I pimped)
5
+ * Added a basic console for playing around with MongoMapper (dcu)
6
+ * Embedded associations can now be deeply nested (Keith Hanson)
7
+ * Added support for many polymorphic documents (Felipe Coury and Me)
8
+ * Fixed bug where conditions that disallowed using $in, $all and $any with an array
9
+ * Bumped version of validatable so :if validation option supports symbol/string to proc.
10
+ * Document#create with no attributes now creates a document as long as it is valid
11
+ * Now defining accessor methods when key is declared rather than using method missing and all that jazz
12
+ * Attributes now have boolean methods that return true or false based on whether they have value present
13
+ * Added scoped finds and pagination on many document association.
14
+ * find first and last now use natural order which is more reliable.
15
+ * Updated to latest ruby driver (0.10.1)
16
+
17
+
1
18
  0.2.0 7/7/2009
2
19
  * 2 major additions (observers, associations), several minor additions, and a few bug fixes
3
20
  * Added observers
data/README.rdoc CHANGED
@@ -2,15 +2,18 @@
2
2
 
3
3
  Awesome gem for modeling your domain and storing it in mongo.
4
4
 
5
- == Note on Releases
5
+ Releases are tagged on github and also released as gems on github and rubyforge. Master is pushed to whenever I add a patch or a new feature. To build from master, you can clone the code, generate the updated gemspec, build the gem and install.
6
6
 
7
- Releases are tagged on github and also released as gems on github and rubyforge. Master is pushed to whenever I add a patch or a new feature. To build from master, you can clone the code and run 'rake install' to install the gem.
7
+ * rake gemspec
8
+ * gem build mongomapper.gemspec
9
+ * gem install the gem that was built
8
10
 
9
11
  == Note on Patches/Pull Requests
10
12
 
11
13
  * Fork the project.
12
14
  * Make your feature addition or bug fix.
13
- * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
15
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
16
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
14
17
  * Send me a pull request. Bonus points for topic branches.
15
18
 
16
19
  == Dependencies
data/Rakefile CHANGED
@@ -12,8 +12,9 @@ begin
12
12
  gem.rubyforge_project = "mongomapper"
13
13
 
14
14
  gem.add_dependency('activesupport')
15
- gem.add_dependency('mongodb-mongo', '0.9')
16
- gem.add_dependency('jnunemaker-validatable', '1.7.1')
15
+ gem.add_dependency('mongodb-mongo', '0.10.1')
16
+ gem.add_dependency('jnunemaker-validatable', '1.7.2')
17
+ gem.add_dependency('deep_merge', '0.1.0')
17
18
 
18
19
  gem.add_development_dependency('mocha', '0.9.4')
19
20
  gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/bin/mmconsole ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.dirname(__FILE__)+"/../lib"
3
+
4
+ require 'mongomapper'
5
+ require 'irb'
6
+
7
+ IRB.setup(nil)
8
+ irb = IRB::Irb.new
9
+
10
+ IRB.conf[:MAIN_CONTEXT] = irb.context
11
+
12
+ irb.context.evaluate("require 'irb/completion'", 0)
13
+ irb.context.evaluate(%@
14
+ include XGen::Mongo::Driver
15
+ include MongoMapper
16
+
17
+ MongoMapper.database = "mmtest"
18
+ $db = MongoMapper.database
19
+
20
+ @, 0)
21
+
22
+ puts %@
23
+ Welcome to the MongoMapper Console!
24
+
25
+ Example 1:
26
+ things = $db.collection("things")
27
+ things.insert("name" => "Raw Thing")
28
+ things.insert("name" => "Another Thing", "date" => Time.now)
29
+
30
+ cursor = things.find("name" => "Raw Thing")
31
+ puts cursor.next_object.inspect
32
+
33
+ Example 2:
34
+ class Thing
35
+ include MongoMapper::Document
36
+ key :name, String, :required => true
37
+ key :date, Time
38
+ end
39
+
40
+ thing = Thing.new
41
+ thing.name = "My thing"
42
+ thing.date = Time.now
43
+ thing.save
44
+
45
+ all_things = Thing.all
46
+ puts all_things.map { |object| object.name }.inspect
47
+
48
+ @
49
+
50
+ trap("SIGINT") do
51
+ irb.signal_handle
52
+ end
53
+ catch(:IRB_EXIT) do
54
+ irb.eval_input
55
+ end
56
+
data/lib/mongomapper.rb CHANGED
@@ -2,38 +2,69 @@ require 'pathname'
2
2
  require 'rubygems'
3
3
 
4
4
  gem 'activesupport'
5
- gem 'mongodb-mongo', '0.9'
6
- gem 'jnunemaker-validatable', '1.7.1'
5
+ gem 'mongodb-mongo', '0.10.1'
6
+ gem 'jnunemaker-validatable', '1.7.2'
7
+ gem 'deep_merge', '0.1.0'
7
8
 
8
9
  require 'activesupport'
9
10
  require 'mongo'
10
11
  require 'validatable'
12
+ require 'deep_merge'
11
13
 
12
- dir = Pathname(__FILE__).dirname.expand_path + 'mongomapper'
14
+ class BasicObject #:nodoc:
15
+ instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
16
+ end unless defined?(BasicObject)
13
17
 
14
- require dir + 'key'
15
- require dir + 'finder_options'
16
- require dir + 'rails_compatibility'
17
- require dir + 'save_with_validation'
18
- require dir + 'serialization'
19
- require dir + 'callbacks'
20
- require dir + 'observing'
21
- require dir + 'validations'
18
+ class Boolean
19
+ def self.mm_typecast(value)
20
+ ['true', 't', '1'].include?(value.to_s.downcase)
21
+ end
22
+ end
23
+
24
+ class MongoID < XGen::Mongo::Driver::ObjectID
25
+ def self.mm_typecast(value)
26
+ begin
27
+ if value.is_a?(XGen::Mongo::Driver::ObjectID)
28
+ value
29
+ else
30
+ XGen::Mongo::Driver::ObjectID::from_string(value.to_s)
31
+ end
32
+ rescue => exception
33
+ if exception.message == 'illegal ObjectID format'
34
+ raise MongoMapper::DocumentNotFound
35
+ else
36
+ raise exception
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ dir = Pathname(__FILE__).dirname.expand_path + 'mongomapper'
22
43
 
44
+ require dir + 'associations'
23
45
  require dir + 'associations/proxy'
24
46
  require dir + 'associations/array_proxy'
25
47
  require dir + 'associations/base'
26
-
27
- require dir + 'associations/has_many_proxy'
28
- require dir + 'associations/has_many_embedded_proxy'
48
+ require dir + 'associations/many_proxy'
49
+ require dir + 'associations/many_embedded_proxy'
50
+ require dir + 'associations/many_embedded_polymorphic_proxy'
29
51
  require dir + 'associations/belongs_to_proxy'
30
- require dir + 'associations/polymorphic_belongs_to_proxy'
31
- require dir + 'associations'
52
+ require dir + 'associations/belongs_to_polymorphic_proxy'
53
+ require dir + 'callbacks'
54
+ require dir + 'finder_options'
55
+ require dir + 'key'
56
+ require dir + 'observing'
57
+ require dir + 'pagination'
58
+ require dir + 'rails_compatibility/document'
59
+ require dir + 'rails_compatibility/embedded_document'
60
+ require dir + 'save_with_validation'
61
+ require dir + 'serialization'
62
+ require dir + 'validations'
32
63
 
33
64
  require dir + 'embedded_document'
34
65
  require dir + 'document'
35
66
 
36
- module MongoMapper
67
+ module MongoMapper
37
68
  class DocumentNotFound < StandardError; end
38
69
  class DocumentNotValid < StandardError
39
70
  def initialize(document)
@@ -2,33 +2,12 @@ module MongoMapper
2
2
  module Associations
3
3
  module ClassMethods
4
4
  def belongs_to(association_id, options = {})
5
- association = create_association(:belongs_to, association_id, options)
6
-
7
- ref_id = "#{association_id}_id"
8
- key ref_id, String
9
-
10
- define_method("#{ref_id}=") do |value|
11
- write_attribute(ref_id, value)
12
- end
13
-
14
- if options[:polymorphic]
15
- ref_type = "#{association_id}_type"
16
- key ref_type, String
17
-
18
- define_method("#{ref_type}=") do |value|
19
- write_attribute(ref_type, value)
20
- end
21
- end
22
-
23
- define_association_methods(association)
24
-
5
+ create_association(:belongs_to, association_id, options)
25
6
  self
26
7
  end
27
8
 
28
9
  def many(association_id, options = {})
29
- association = create_association(:many, association_id, options)
30
- define_association_methods(association)
31
-
10
+ create_association(:many, association_id, options)
32
11
  self
33
12
  end
34
13
 
@@ -37,31 +16,44 @@ module MongoMapper
37
16
  end
38
17
 
39
18
  private
40
- def create_association(type, name, options)
41
- association = Associations::Base.new(type, name, options)
42
- associations[association.name] = association
43
- association
44
- end
45
-
46
- def define_association_methods(association)
47
- define_method(association.name) do
48
- get_proxy(association)
19
+ def create_association(type, name, options)
20
+ association = Associations::Base.new(type, name, options)
21
+ associations[association.name] = association
22
+ define_association_methods(association)
23
+ define_association_keys(association)
24
+ association
49
25
  end
50
-
51
- define_method("#{association.name}=") do |value|
52
- get_proxy(association).replace(value)
53
- value
26
+
27
+ def define_association_methods(association)
28
+ define_method(association.name) do
29
+ get_proxy(association)
30
+ end
31
+
32
+ define_method("#{association.name}=") do |value|
33
+ get_proxy(association).replace(value)
34
+ value
35
+ end
36
+ end
37
+
38
+ def define_association_keys(association)
39
+ if association.belongs_to?
40
+ key(association.belongs_to_key_name, String)
41
+ key(association.type_key_name, String) if association.polymorphic?
42
+ end
43
+
44
+ if association.many? && association.polymorphic?
45
+ association.klass.send(:key, association.type_key_name, String)
46
+ end
54
47
  end
55
- end
56
48
  end
57
49
 
58
50
  module InstanceMethods
59
51
  def get_proxy(association)
60
- proxy = self.instance_variable_get(association.ivar)
61
- if proxy.nil?
52
+ unless proxy = self.instance_variable_get(association.ivar)
62
53
  proxy = association.proxy_class.new(self, association)
63
54
  self.instance_variable_set(association.ivar, proxy)
64
55
  end
56
+
65
57
  proxy
66
58
  end
67
59
  end
@@ -8,43 +8,61 @@ module MongoMapper
8
8
  @type = type
9
9
  @name = name
10
10
  end
11
-
12
- def klass
13
- class_name.constantize
14
- end
15
-
11
+
16
12
  def class_name
17
13
  @class_name ||= begin
18
14
  if cn = options[:class_name]
19
15
  cn
20
- elsif @type == :many
16
+ elsif many?
21
17
  name.to_s.singularize.camelize
22
18
  else
23
19
  name.to_s.camelize
24
20
  end
25
21
  end
26
22
  end
27
-
23
+
24
+ def klass
25
+ @klass ||= class_name.constantize
26
+ end
27
+
28
+ def many?
29
+ @many_type ||= @type == :many
30
+ end
31
+
32
+ def belongs_to?
33
+ @belongs_to_type ||= @type == :belongs_to
34
+ end
35
+
36
+ def polymorphic?
37
+ !!@options[:polymorphic]
38
+ end
39
+
40
+ def type_key_name
41
+ @type_key_name ||= many? ? '_type' : "#{name}_type"
42
+ end
43
+
44
+ def belongs_to_key_name
45
+ "#{name}_id"
46
+ end
47
+
28
48
  def ivar
29
49
  @ivar ||= "@_#{name}"
30
50
  end
51
+
52
+ def embeddable?
53
+ many? && klass.embeddable?
54
+ end
31
55
 
32
56
  def proxy_class
33
- case @type
34
- when :belongs_to
35
- if @options[:polymorphic]
36
- PolymorphicBelongsToProxy
37
- else
38
- BelongsToProxy
39
- end
40
- when :many
41
- if self.klass.embeddable?
42
- HasManyEmbeddedProxy
43
- else
44
- HasManyProxy
45
- end
46
- end
47
- end
57
+ @proxy_class ||= begin
58
+ if many?
59
+ return ManyProxy unless self.klass.embeddable?
60
+ polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
61
+ else
62
+ polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
63
+ end
64
+ end # end begin
65
+ end # end proxy_class
48
66
  end
49
67
  end
50
68
  end
@@ -0,0 +1,33 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class BelongsToPolymorphicProxy < Proxy
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id, type = doc.id, doc.class.name
8
+ end
9
+
10
+ @owner.send("#{@association.belongs_to_key_name}=", id)
11
+ @owner.send("#{@association.type_key_name}=", type)
12
+
13
+ reload_target
14
+ end
15
+
16
+ protected
17
+ def find_target
18
+ proxy_class.find(proxy_id) if proxy_id && proxy_class
19
+ end
20
+
21
+ def proxy_id
22
+ @proxy_id ||= @owner.send(@association.belongs_to_key_name)
23
+ end
24
+
25
+ def proxy_class
26
+ @proxy_class ||= begin
27
+ klass = @owner.send(@association.type_key_name)
28
+ klass && klass.constantize
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,26 +1,22 @@
1
1
  module MongoMapper
2
2
  module Associations
3
3
  class BelongsToProxy < Proxy
4
- def replace(v)
5
- ref_id = "#{@association.name}_id"
6
-
7
- if v
8
- v.save if v.new?
9
- @owner.__send__(:write_attribute, ref_id, v.id)
10
- else
11
- @owner.__send__(:write_attribute, ref_id, nil)
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id = doc.id
12
8
  end
13
-
9
+
10
+ @owner.send("#{@association.belongs_to_key_name}=", id)
14
11
  reload_target
15
12
  end
16
13
 
17
14
  protected
18
- def find_target
19
- ref = @owner.__send__(:read_attribute, "#{@association.name}_id")
20
- if ref
21
- @association.klass.find(ref)
15
+ def find_target
16
+ if ref = @owner.send(@association.belongs_to_key_name)
17
+ @association.klass.find(ref)
18
+ end
22
19
  end
23
- end
24
20
  end
25
21
  end
26
22
  end