mongo_mapper 0.5.8 → 0.6.0

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