mongo_mapper 0.5.8 → 0.6.0

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 (47) hide show
  1. data/Rakefile +4 -4
  2. data/VERSION +1 -1
  3. data/bin/mmconsole +10 -5
  4. data/lib/mongo_mapper.rb +28 -5
  5. data/lib/mongo_mapper/associations.rb +113 -12
  6. data/lib/mongo_mapper/associations/base.rb +24 -9
  7. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +1 -1
  8. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +1 -1
  9. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +2 -2
  10. data/lib/mongo_mapper/associations/many_documents_proxy.rb +7 -2
  11. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +22 -36
  12. data/lib/mongo_mapper/associations/proxy.rb +11 -6
  13. data/lib/mongo_mapper/document.rb +37 -21
  14. data/lib/mongo_mapper/embedded_document.rb +32 -18
  15. data/lib/mongo_mapper/finder_options.rb +19 -12
  16. data/lib/mongo_mapper/rails_compatibility/document.rb +4 -0
  17. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +4 -0
  18. data/lib/mongo_mapper/support.rb +18 -46
  19. data/lib/mongo_mapper/types.rb +64 -0
  20. data/lib/mongo_mapper/validations.rb +13 -43
  21. data/mongo_mapper.gemspec +13 -10
  22. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +10 -10
  23. data/test/functional/associations/test_belongs_to_proxy.rb +29 -30
  24. data/test/functional/associations/test_many_documents_as_proxy.rb +13 -12
  25. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +34 -34
  26. data/test/functional/associations/test_many_embedded_proxy.rb +69 -74
  27. data/test/functional/associations/test_many_polymorphic_proxy.rb +10 -10
  28. data/test/functional/associations/test_many_proxy.rb +14 -15
  29. data/test/functional/test_associations.rb +4 -4
  30. data/test/functional/test_binary.rb +1 -1
  31. data/test/functional/test_dirty.rb +6 -6
  32. data/test/functional/test_document.rb +76 -69
  33. data/test/functional/test_embedded_document.rb +15 -14
  34. data/test/functional/test_pagination.rb +9 -1
  35. data/test/functional/test_string_id_compatibility.rb +72 -0
  36. data/test/functional/test_validations.rb +56 -7
  37. data/test/models.rb +7 -7
  38. data/test/test_helper.rb +2 -5
  39. data/test/unit/test_association_base.rb +6 -1
  40. data/test/unit/test_document.rb +22 -13
  41. data/test/unit/test_embedded_document.rb +47 -5
  42. data/test/unit/test_finder_options.rb +22 -3
  43. data/test/unit/test_mongo_mapper.rb +65 -0
  44. data/test/unit/test_rails_compatibility.rb +14 -0
  45. data/test/unit/test_support.rb +45 -0
  46. metadata +9 -6
  47. data/test/unit/test_mongomapper.rb +0 -28
data/Rakefile CHANGED
@@ -13,12 +13,12 @@ Jeweler::Tasks.new do |gem|
13
13
 
14
14
  gem.add_dependency('activesupport', '>= 2.3')
15
15
  gem.add_dependency('mongo', '0.16')
16
- gem.add_dependency('jnunemaker-validatable', '1.8.0')
16
+ gem.add_dependency('jnunemaker-validatable', '1.8.1')
17
17
 
18
18
  gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
19
19
  gem.add_development_dependency('shoulda', '2.10.2')
20
20
  gem.add_development_dependency('timecop', '0.3.1')
21
- gem.add_development_dependency('mocha', '0.9.4')
21
+ gem.add_development_dependency('mocha', '0.9.8')
22
22
  end
23
23
 
24
24
  Jeweler::GemcutterTasks.new
@@ -51,5 +51,5 @@ task :default => :test
51
51
  task :test => :check_dependencies
52
52
 
53
53
  YARD::Rake::YardocTask.new(:doc) do |t|
54
- t.options = ["--legacy"]
55
- end
54
+ t.options = ["--legacy"] if RUBY_VERSION < "1.9.0"
55
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.8
1
+ 0.6.0
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
- $:.unshift File.dirname(__FILE__)+"/../lib"
3
-
4
- require 'mongo_mapper'
5
- require 'irb'
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+
4
+ begin
5
+ require 'mongo_mapper'
6
+ require 'irb'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ retry
10
+ end
6
11
 
7
12
  IRB.setup(nil)
8
13
  irb = IRB::Irb.new
@@ -43,12 +48,12 @@ Example 2:
43
48
 
44
49
  all_things = Thing.all
45
50
  puts all_things.map { |object| object.name }.inspect
46
-
47
51
  @
48
52
 
49
53
  trap("SIGINT") do
50
54
  irb.signal_handle
51
55
  end
56
+
52
57
  catch(:IRB_EXIT) do
53
58
  irb.eval_input
54
59
  end
@@ -15,28 +15,32 @@ module MongoMapper
15
15
  # raised when document not valid and using !
16
16
  class DocumentNotValid < MongoMapperError
17
17
  def initialize(document)
18
- @document = document
19
- super("Validation failed: #{@document.errors.full_messages.join(", ")}")
18
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
20
19
  end
21
20
  end
22
21
 
22
+ # @api public
23
23
  def self.connection
24
24
  @@connection ||= Mongo::Connection.new
25
25
  end
26
-
26
+
27
+ # @api public
27
28
  def self.connection=(new_connection)
28
29
  @@connection = new_connection
29
30
  end
30
31
 
32
+ # @api public
31
33
  def self.logger
32
34
  connection.logger
33
35
  end
34
-
36
+
37
+ # @api public
35
38
  def self.database=(name)
36
39
  @@database = nil
37
40
  @@database_name = name
38
41
  end
39
-
42
+
43
+ # @api public
40
44
  def self.database
41
45
  if @@database_name.blank?
42
46
  raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
@@ -45,14 +49,17 @@ module MongoMapper
45
49
  @@database ||= MongoMapper.connection.db(@@database_name)
46
50
  end
47
51
 
52
+ # @api private
48
53
  def self.ensured_indexes
49
54
  @@ensured_indexes ||= []
50
55
  end
51
56
 
57
+ # @api private
52
58
  def self.ensure_index(klass, keys, options={})
53
59
  ensured_indexes << {:klass => klass, :keys => keys, :options => options}
54
60
  end
55
61
 
62
+ # @api public
56
63
  def self.ensure_indexes!
57
64
  ensured_indexes.each do |index|
58
65
  unique = index[:options].delete(:unique)
@@ -60,6 +67,7 @@ module MongoMapper
60
67
  end
61
68
  end
62
69
 
70
+ # @api private
63
71
  module Finders
64
72
  def dynamic_find(finder, args)
65
73
  attributes = {}
@@ -83,9 +91,24 @@ module MongoMapper
83
91
  end
84
92
  end
85
93
  end
94
+
95
+ # @api private
96
+ def self.use_time_zone?
97
+ Time.respond_to?(:zone) && Time.zone ? true : false
98
+ end
99
+
100
+ # @api private
101
+ def self.time_class
102
+ use_time_zone? ? Time.zone : Time
103
+ end
104
+
105
+ def self.normalize_object_id(value)
106
+ value.is_a?(String) ? Mongo::ObjectID.from_string(value) : value
107
+ end
86
108
  end
87
109
 
88
110
  require 'mongo_mapper/support'
111
+ require 'mongo_mapper/types'
89
112
  require 'mongo_mapper/associations'
90
113
  require 'mongo_mapper/associations/base'
91
114
  require 'mongo_mapper/associations/proxy'
@@ -1,13 +1,121 @@
1
1
  module MongoMapper
2
2
  module Associations
3
3
  module ClassMethods
4
- def belongs_to(association_id, options={})
5
- create_association(:belongs_to, association_id, options)
4
+ ##
5
+ # This macro allows you define a "belongs-to" relationship between one
6
+ # document and some other document.
7
+ #
8
+ # == Requirements
9
+ #
10
+ # Usage of this macro requires that your document define a key that can
11
+ # be used to store the ID of the target document that is the parent of
12
+ # this document.
13
+ #
14
+ # == Conventions
15
+ #
16
+ # The following is a list of the conventions used by MongoMapper in
17
+ # defining a belongs-to relationship. Each can likely be overridden via
18
+ # the +options+ parameter.
19
+ #
20
+ # * The name of your belongs-to association is the lowercase, singular
21
+ # name of the target class
22
+ # * A key with the name of your association exists with an "_id" suffix
23
+ # to store the ID of the target of this relationship
24
+ #
25
+ # @param [Symbol] association_id The name of this association
26
+ # @param [Hash] options Optional parameters that define the
27
+ # characteristics of this relationship. These are often used to
28
+ # override MongoMapper conventions.
29
+ # @option options [Boolean] :polymorphic (false) Set this option to
30
+ # <code>true</code> to define a relationship that can be between this
31
+ # document and any other type of document. Note that you *must* also
32
+ # have a key on your document to store the type of document in this
33
+ # relationship.
34
+ # @option options [String] :class_name If your relationship doesn't use
35
+ # the name of some class, you *must* use this option to indicate the
36
+ # target class for this relationship.
37
+ # @option options [Symbol] :foreign_key Use this option to specify a
38
+ # non-conventional key that stores the ID of the parent in this
39
+ # relationship
40
+ #
41
+ # @example Conventional, and simple, usage of <code>belongs_to</code>
42
+ # class Novel
43
+ # include MongoMapper::Document
44
+ #
45
+ # key :author_id, String # our "foreign key"
46
+ #
47
+ # belongs_to :author
48
+ # end
49
+ #
50
+ # @example Using :foreign_key and :class_name
51
+ # class Pet
52
+ # include MongoMapper::Document
53
+ #
54
+ # key :person_id, String
55
+ #
56
+ # belongs_to :owner,
57
+ # :foreign_key => :person_id,
58
+ # :class_name => "Person"
59
+ # end
60
+ #
61
+ # @example Defining a polymorphic belongs-to relationship
62
+ # class Vehicle
63
+ # include MongoMapper::Document
64
+ #
65
+ # key :owner_id, String
66
+ # key :owner_type, String
67
+ #
68
+ # belongs_to :owner,
69
+ # :polymorphic => true
70
+ # end
71
+ #
72
+ # @example Non-standard polymorphic belongs-to relationship
73
+ # class Vehicle
74
+ # include MongoMapper::Document
75
+ #
76
+ # key :person_id, String
77
+ # key :person_type, String
78
+ #
79
+ # belongs_to :owner,
80
+ # :polymorphic => true,
81
+ # :foreign_key => "person_id",
82
+ # :type_key_name => "person_type"
83
+ # end
84
+ def belongs_to(association_id, options={}, &extension)
85
+ create_association(:belongs_to, association_id, options, &extension)
6
86
  self
7
87
  end
8
88
 
9
- def many(association_id, options = {}, &block)
10
- create_association(:many, association_id, options, &block)
89
+ ##
90
+ # This macro allows you to define a "has-many" relationship between a
91
+ # document, and numerous child documents.
92
+ #
93
+ # == Conventions
94
+ #
95
+ # The following is a list of the conventions used by MongoMapper in
96
+ # defining this relationship. Each can likely be overridden via the
97
+ # +options+ parameter.
98
+ #
99
+ # * The name of your association is the lowercase, *plural* name of the
100
+ # target class
101
+ # * Your target class must have a "foreign key" bearing the name of this
102
+ # class suffixed by "_id"
103
+ #
104
+ # @param [Symbol] association_id The name of this association
105
+ # @param [Hash] options Optional parameters that define the
106
+ # characteristics of this relationship. These are often used to
107
+ # override MongoMapper conventions.
108
+ # @option options [String] :class_name If your relationship doesn't use
109
+ # the name of some class, you *must* use this option to indicate the
110
+ # target class for this relationship.
111
+ # @option options [Symbol] :foreign_key Use this option to specify a
112
+ # non-conventional key that stores the ID of the parent in this
113
+ # relationship
114
+ # @option options [#to_s] :as Used when the target relationship is
115
+ # polymorphic (i.e. the +belongs_to+ has set <tt>:polymorphic</tt> to
116
+ # +true+). See examples for usage.
117
+ def many(association_id, options={}, &extension)
118
+ create_association(:many, association_id, options, &extension)
11
119
  self
12
120
  end
13
121
 
@@ -19,19 +127,12 @@ module MongoMapper
19
127
 
20
128
  private
21
129
  def create_association(type, name, options, &extension)
22
- options[:extend] = modulized_extensions(extension, options[:extend])
23
- association = Associations::Base.new(type, name, options)
130
+ association = Associations::Base.new(type, name, options, &extension)
24
131
  associations[association.name] = association
25
132
  define_association_methods(association)
26
133
  define_dependent_callback(association)
27
134
  association
28
135
  end
29
-
30
- def modulized_extensions(*extensions)
31
- extensions.flatten.compact.map do |extension|
32
- Proc === extension ? Module.new(&extension) : extension
33
- end
34
- end
35
136
 
36
137
  def define_association_methods(association)
37
138
  define_method(association.name) do
@@ -1,15 +1,20 @@
1
1
  module MongoMapper
2
2
  module Associations
3
+ # Base class for keeping track of associations.
4
+ #
5
+ # @private
3
6
  class Base
4
7
  attr_reader :type, :name, :options, :finder_options
5
-
8
+
6
9
  # Options that should not be considered MongoDB query options/criteria
7
- AssociationOptions = [:as, :class_name, :dependent, :extend, :foreign_key, :polymorphic]
8
-
9
- def initialize(type, name, options={})
10
- @type, @name = type, name
11
- @options, @finder_options = {}, {}
12
-
10
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :polymorphic]
11
+
12
+ def initialize(type, name, options={}, &extension)
13
+ @type, @name, @options, @finder_options = type, name, {}, {}
14
+ options.symbolize_keys!
15
+
16
+ options[:extend] = modulized_extensions(extension, options[:extend])
17
+
13
18
  options.each_pair do |key, value|
14
19
  if AssociationOptions.include?(key)
15
20
  @options[key] = value
@@ -32,13 +37,13 @@ module MongoMapper
32
37
  end
33
38
 
34
39
  def klass
35
- @klass ||= class_name.constantize
40
+ @klass ||= options[:class] || class_name.constantize
36
41
  end
37
42
 
38
43
  def many?
39
44
  @many_type ||= @type == :many
40
45
  end
41
-
46
+
42
47
  def belongs_to?
43
48
  @belongs_to_type ||= @type == :belongs_to
44
49
  end
@@ -90,6 +95,16 @@ module MongoMapper
90
95
  end
91
96
  end # end begin
92
97
  end # end proxy_class
98
+
99
+ private
100
+
101
+ # @param [Array<Module, Proc>] extensions a collection of Modules or
102
+ # Procs that extend the behaviour of this association.
103
+ def modulized_extensions(*extensions)
104
+ extensions.flatten.compact.map do |extension|
105
+ Proc === extension ? Module.new(&extension) : extension
106
+ end
107
+ end
93
108
  end
94
109
  end
95
110
  end
@@ -4,7 +4,7 @@ module MongoMapper
4
4
  def replace(doc)
5
5
  if doc
6
6
  doc.save if doc.new?
7
- id, type = doc.id, doc.class.name
7
+ id, type = doc._id, doc.class.name
8
8
  end
9
9
 
10
10
  @owner.send("#{@association.foreign_key}=", id)
@@ -4,7 +4,7 @@ module MongoMapper
4
4
  def replace(doc)
5
5
  if doc
6
6
  doc.save if doc.new?
7
- id = doc.id
7
+ id = doc._id
8
8
  end
9
9
 
10
10
  @owner.send("#{@association.foreign_key}=", id)
@@ -3,13 +3,13 @@ module MongoMapper
3
3
  class ManyDocumentsAsProxy < ManyDocumentsProxy
4
4
  protected
5
5
  def scoped_conditions
6
- {as_type_name => @owner.class.name, as_id_name => @owner.id}
6
+ {as_type_name => @owner.class.name, as_id_name => @owner._id}
7
7
  end
8
8
 
9
9
  def apply_scope(doc)
10
10
  ensure_owner_saved
11
11
  doc.send("#{as_type_name}=", @owner.class.name)
12
- doc.send("#{as_id_name}=", @owner.id)
12
+ doc.send("#{as_id_name}=", @owner._id)
13
13
  doc
14
14
  end
15
15
 
@@ -10,6 +10,11 @@ module MongoMapper
10
10
  options = args.extract_options!
11
11
  klass.find(*args << scoped_options(options))
12
12
  end
13
+
14
+ def find!(*args)
15
+ options = args.extract_options!
16
+ klass.find!(*args << scoped_options(options))
17
+ end
13
18
 
14
19
  def paginate(options)
15
20
  klass.paginate(scoped_options(options))
@@ -93,7 +98,7 @@ module MongoMapper
93
98
 
94
99
  protected
95
100
  def scoped_conditions
96
- {self.foreign_key => @owner.id}
101
+ {self.foreign_key => @owner._id}
97
102
  end
98
103
 
99
104
  def scoped_options(options)
@@ -110,7 +115,7 @@ module MongoMapper
110
115
 
111
116
  def apply_scope(doc)
112
117
  ensure_owner_saved
113
- doc.send("#{self.foreign_key}=", @owner.id)
118
+ doc.send("#{self.foreign_key}=", @owner._id)
114
119
  doc
115
120
  end
116
121
 
@@ -5,34 +5,24 @@ module MongoMapper
5
5
  @_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
6
6
  reset
7
7
  end
8
-
9
- def build(opts={})
10
- owner = @owner
11
- child = @association.klass.new(opts)
12
- assign_parent_reference(child)
13
- child._root_document = owner
14
- self << child
15
- child
8
+
9
+ def build(attributes={})
10
+ doc = @association.klass.new(attributes)
11
+ assign_root_document(doc)
12
+ self << doc
13
+ doc
16
14
  end
17
-
18
- def find(opts)
19
- case opts
20
- when :all
21
- self
22
- when String
23
- if load_target
24
- child = @target.detect {|item| item.id == opts}
25
- assign_parent_reference(child)
26
- child
27
- end
28
- end
15
+
16
+ # TODO: test that both string and oid version work
17
+ def find(id)
18
+ load_target
19
+ @target.detect { |item| item.id == id || item._id == id }
29
20
  end
30
21
 
31
22
  def <<(*docs)
32
23
  if load_target
33
- root = @owner._root_document || @owner
34
24
  docs.each do |doc|
35
- doc._root_document = root
25
+ assign_root_document(doc)
36
26
  @target << doc
37
27
  end
38
28
  end
@@ -40,28 +30,24 @@ module MongoMapper
40
30
  alias_method :push, :<<
41
31
  alias_method :concat, :<<
42
32
 
43
- protected
33
+ private
44
34
  def find_target
45
35
  (@_values || []).map do |e|
46
36
  child = @association.klass.new(e)
47
- assign_parent_reference(child)
37
+ assign_root_document(child)
48
38
  child
49
39
  end
50
40
  end
51
-
52
- private
53
-
54
- def assign_parent_reference(child)
55
- return unless child && @owner
56
- return if @owner.class.name.blank?
57
- owner = @owner
58
- child.class_eval do
59
- define_method(owner.class.name.underscore) do
60
- owner
61
- end
41
+
42
+ def root_document
43
+ @owner._root_document || @owner
44
+ end
45
+
46
+ def assign_root_document(*docs)
47
+ docs.each do |doc|
48
+ doc._root_document = root_document
62
49
  end
63
50
  end
64
-
65
51
  end
66
52
  end
67
53
  end