mongo_mapper 0.5.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 (69) hide show
  1. data/.gitignore +7 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +39 -0
  4. data/Rakefile +87 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +55 -0
  7. data/lib/mongo_mapper.rb +92 -0
  8. data/lib/mongo_mapper/associations.rb +86 -0
  9. data/lib/mongo_mapper/associations/base.rb +83 -0
  10. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  11. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  12. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
  13. data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
  14. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
  16. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongo_mapper/associations/proxy.rb +64 -0
  19. data/lib/mongo_mapper/callbacks.rb +106 -0
  20. data/lib/mongo_mapper/document.rb +317 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  22. data/lib/mongo_mapper/embedded_document.rb +354 -0
  23. data/lib/mongo_mapper/finder_options.rb +94 -0
  24. data/lib/mongo_mapper/key.rb +32 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +51 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/save_with_validation.rb +19 -0
  30. data/lib/mongo_mapper/serialization.rb +55 -0
  31. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongo_mapper/support.rb +157 -0
  33. data/lib/mongo_mapper/validations.rb +69 -0
  34. data/mongo_mapper.gemspec +156 -0
  35. data/test/NOTE_ON_TESTING +1 -0
  36. data/test/custom_matchers.rb +48 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +54 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +46 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
  40. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
  41. data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
  42. data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
  43. data/test/functional/associations/test_many_proxy.rb +331 -0
  44. data/test/functional/test_associations.rb +48 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_document.rb +951 -0
  48. data/test/functional/test_embedded_document.rb +97 -0
  49. data/test/functional/test_pagination.rb +87 -0
  50. data/test/functional/test_rails_compatibility.rb +30 -0
  51. data/test/functional/test_validations.rb +279 -0
  52. data/test/models.rb +169 -0
  53. data/test/test_helper.rb +29 -0
  54. data/test/unit/serializers/test_json_serializer.rb +189 -0
  55. data/test/unit/test_association_base.rb +144 -0
  56. data/test/unit/test_document.rb +165 -0
  57. data/test/unit/test_dynamic_finder.rb +125 -0
  58. data/test/unit/test_embedded_document.rb +645 -0
  59. data/test/unit/test_finder_options.rb +193 -0
  60. data/test/unit/test_key.rb +163 -0
  61. data/test/unit/test_mongomapper.rb +28 -0
  62. data/test/unit/test_observing.rb +101 -0
  63. data/test/unit/test_pagination.rb +109 -0
  64. data/test/unit/test_rails_compatibility.rb +39 -0
  65. data/test/unit/test_serializations.rb +52 -0
  66. data/test/unit/test_support.rb +272 -0
  67. data/test/unit/test_time_zones.rb +40 -0
  68. data/test/unit/test_validations.rb +503 -0
  69. metadata +204 -0
@@ -0,0 +1,34 @@
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.foreign_key}=", id)
11
+ @owner.send("#{@association.type_key_name}=", type)
12
+ reset
13
+ end
14
+
15
+ protected
16
+ def find_target
17
+ if proxy_id && proxy_class
18
+ proxy_class.find_by_id(proxy_id)
19
+ end
20
+ end
21
+
22
+ def proxy_id
23
+ @proxy_id ||= @owner.send(@association.foreign_key)
24
+ end
25
+
26
+ def proxy_class
27
+ @proxy_class ||= begin
28
+ klass = @owner.send(@association.type_key_name)
29
+ klass && klass.constantize
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class BelongsToProxy < Proxy
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id = doc.id
8
+ end
9
+
10
+ @owner.send("#{@association.foreign_key}=", id)
11
+ reset
12
+ end
13
+
14
+ protected
15
+ def find_target
16
+ if association_id = @owner.send(@association.foreign_key)
17
+ @association.klass.find_by_id(association_id)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsAsProxy < ManyDocumentsProxy
4
+ protected
5
+ def scoped_conditions
6
+ {as_type_name => @owner.class.name, as_id_name => @owner.id}
7
+ end
8
+
9
+ def apply_scope(doc)
10
+ ensure_owner_saved
11
+
12
+ doc.send("#{as_type_name}=", @owner.class.name)
13
+ doc.send("#{as_id_name}=", @owner.id)
14
+
15
+ doc
16
+ end
17
+
18
+ def as_type_name
19
+ @as_type_name ||= @association.options[:as].to_s + "_type"
20
+ end
21
+
22
+ def as_id_name
23
+ @as_id_name ||= @association.options[:as].to_s + "_id"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,116 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsProxy < Proxy
4
+ delegate :klass, :to => :@association
5
+ delegate :collection, :to => :klass
6
+
7
+ include MongoMapper::Finders
8
+
9
+ def find(*args)
10
+ options = args.extract_options!
11
+ klass.find(*args << scoped_options(options))
12
+ end
13
+
14
+ def paginate(options)
15
+ klass.paginate(scoped_options(options))
16
+ end
17
+
18
+ def all(options={})
19
+ find(:all, scoped_options(options))
20
+ end
21
+
22
+ def first(options={})
23
+ find(:first, scoped_options(options))
24
+ end
25
+
26
+ def last(options={})
27
+ find(:last, scoped_options(options))
28
+ end
29
+
30
+ def count(conditions={})
31
+ klass.count(conditions.deep_merge(scoped_conditions))
32
+ end
33
+
34
+ def replace(docs)
35
+ @target.map(&:destroy) if load_target
36
+ docs.each { |doc| apply_scope(doc).save }
37
+ reset
38
+ end
39
+
40
+ def <<(*docs)
41
+ ensure_owner_saved
42
+ flatten_deeper(docs).each { |doc| apply_scope(doc).save }
43
+ reset
44
+ end
45
+ alias_method :push, :<<
46
+ alias_method :concat, :<<
47
+
48
+ def build(attrs={})
49
+ doc = klass.new(attrs)
50
+ apply_scope(doc)
51
+ doc
52
+ end
53
+
54
+ def create(attrs={})
55
+ doc = klass.new(attrs)
56
+ apply_scope(doc).save
57
+ doc
58
+ end
59
+
60
+ def destroy_all(conditions={})
61
+ all(:conditions => conditions).map(&:destroy)
62
+ reset
63
+ end
64
+
65
+ def delete_all(conditions={})
66
+ klass.delete_all(conditions.deep_merge(scoped_conditions))
67
+ reset
68
+ end
69
+
70
+ def nullify
71
+ criteria = FinderOptions.to_mongo_criteria(scoped_conditions)
72
+ all(criteria).each do |doc|
73
+ doc.update_attributes self.foreign_key => nil
74
+ end
75
+ reset
76
+ end
77
+
78
+ def method_missing(method, *args)
79
+ finder = DynamicFinder.new(method)
80
+
81
+ if finder.found?
82
+ dynamic_find(finder, args)
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ protected
89
+ def scoped_conditions
90
+ {self.foreign_key => @owner.id}
91
+ end
92
+
93
+ def scoped_options(options)
94
+ options.deep_merge({:conditions => scoped_conditions})
95
+ end
96
+
97
+ def find_target
98
+ find(:all)
99
+ end
100
+
101
+ def ensure_owner_saved
102
+ @owner.save if @owner.new?
103
+ end
104
+
105
+ def apply_scope(doc)
106
+ ensure_owner_saved
107
+ doc.send("#{self.foreign_key}=", @owner.id)
108
+ doc
109
+ end
110
+
111
+ def foreign_key
112
+ @association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,33 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyEmbeddedPolymorphicProxy < Proxy
4
+ def replace(v)
5
+ @_values = v.map do |doc_or_hash|
6
+ if doc_or_hash.kind_of?(EmbeddedDocument)
7
+ doc = doc_or_hash
8
+ {@association.type_key_name => doc.class.name}.merge(doc.attributes)
9
+ else
10
+ doc_or_hash
11
+ end
12
+ end
13
+
14
+ reset
15
+ end
16
+
17
+ protected
18
+ def find_target
19
+ (@_values || []).map do |hash|
20
+ polymorphic_class(hash).new(hash)
21
+ end
22
+ end
23
+
24
+ def polymorphic_class(doc)
25
+ if class_name = doc[@association.type_key_name]
26
+ class_name.constantize
27
+ else
28
+ @association.klass
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,67 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyEmbeddedProxy < Proxy
4
+ def replace(v)
5
+ @_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
6
+ reset
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
16
+ 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
29
+ end
30
+
31
+ def <<(*docs)
32
+ if load_target
33
+ root = @owner._root_document || @owner
34
+ docs.each do |doc|
35
+ doc._root_document = root
36
+ @target << doc
37
+ end
38
+ end
39
+ end
40
+ alias_method :push, :<<
41
+ alias_method :concat, :<<
42
+
43
+ protected
44
+ def find_target
45
+ (@_values || []).map do |e|
46
+ child = @association.klass.new(e)
47
+ assign_parent_reference(child)
48
+ child
49
+ end
50
+ 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
62
+ end
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyProxy < ManyDocumentsProxy
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,64 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class Proxy < BasicObject
4
+ attr_reader :owner, :association
5
+
6
+ def initialize(owner, association)
7
+ @owner = owner
8
+ @association = association
9
+ reset
10
+ end
11
+
12
+ def respond_to?(*methods)
13
+ (load_target && @target.respond_to?(*methods))
14
+ end
15
+
16
+ def reset
17
+ @target = nil
18
+ end
19
+
20
+ def reload_target
21
+ reset
22
+ load_target
23
+ self
24
+ end
25
+
26
+ def send(method, *args)
27
+ return super if methods.include?(method.to_s)
28
+ load_target
29
+ @target.send(method, *args)
30
+ end
31
+
32
+ def replace(v)
33
+ raise NotImplementedError
34
+ end
35
+
36
+ protected
37
+ def method_missing(method, *args)
38
+ if load_target
39
+ if block_given?
40
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
41
+ else
42
+ @target.send(method, *args)
43
+ end
44
+ end
45
+ end
46
+
47
+ def load_target
48
+ @target ||= find_target
49
+ end
50
+
51
+ def find_target
52
+ raise NotImplementedError
53
+ end
54
+
55
+ # Array#flatten has problems with recursive arrays. Going one level
56
+ # deeper solves the majority of the problems.
57
+ def flatten_deeper(array)
58
+ array.collect do |element|
59
+ (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element
60
+ end.flatten
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,106 @@
1
+ module MongoMapper
2
+ module Callbacks
3
+ def self.included(model) #:nodoc:
4
+ model.class_eval do
5
+ extend Observable
6
+ include ActiveSupport::Callbacks
7
+
8
+ define_callbacks *%w(
9
+ before_save after_save before_create after_create before_update after_update before_validation
10
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
11
+ after_validation_on_update before_destroy after_destroy
12
+ )
13
+
14
+ [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
15
+ alias_method_chain method, :callbacks
16
+ end
17
+ end
18
+ end
19
+
20
+ def before_save() end
21
+
22
+ def after_save() end
23
+ def create_or_update_with_callbacks #:nodoc:
24
+ return false if callback(:before_save) == false
25
+ if result = create_or_update_without_callbacks
26
+ callback(:after_save)
27
+ end
28
+ result
29
+ end
30
+ private :create_or_update_with_callbacks
31
+
32
+ def before_create() end
33
+
34
+ def after_create() end
35
+ def create_with_callbacks #:nodoc:
36
+ return false if callback(:before_create) == false
37
+ result = create_without_callbacks
38
+ callback(:after_create)
39
+ result
40
+ end
41
+ private :create_with_callbacks
42
+
43
+ def before_update() end
44
+
45
+ def after_update() end
46
+
47
+ def update_with_callbacks(*args) #:nodoc:
48
+ return false if callback(:before_update) == false
49
+ result = update_without_callbacks(*args)
50
+ callback(:after_update)
51
+ result
52
+ end
53
+ private :update_with_callbacks
54
+
55
+ def before_validation() end
56
+
57
+ def after_validation() end
58
+
59
+ def before_validation_on_create() end
60
+
61
+ def after_validation_on_create() end
62
+
63
+ def before_validation_on_update() end
64
+
65
+ def after_validation_on_update() end
66
+
67
+ def valid_with_callbacks? #:nodoc:
68
+ return false if callback(:before_validation) == false
69
+ result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
70
+ return false if false == result
71
+
72
+ result = valid_without_callbacks?
73
+ callback(:after_validation)
74
+
75
+ new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
76
+ return result
77
+ end
78
+
79
+ def before_destroy() end
80
+
81
+ def after_destroy() end
82
+ def destroy_with_callbacks #:nodoc:
83
+ return false if callback(:before_destroy) == false
84
+ result = destroy_without_callbacks
85
+ callback(:after_destroy)
86
+ result
87
+ end
88
+
89
+ private
90
+ def callback(method)
91
+ result = run_callbacks(method) { |result, object| false == result }
92
+
93
+ if result != false && respond_to?(method)
94
+ result = send(method)
95
+ end
96
+
97
+ notify(method)
98
+ return result
99
+ end
100
+
101
+ def notify(method) #:nodoc:
102
+ self.class.changed
103
+ self.class.notify_observers(method, self)
104
+ end
105
+ end
106
+ end