jmonteiro-mongo_mapper 0.1.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 (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/jmonteiro-mongo_mapper.gemspec +195 -0
  8. data/lib/mongo_mapper.rb +128 -0
  9. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  10. data/lib/mongo_mapper/document.rb +402 -0
  11. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  12. data/lib/mongo_mapper/embedded_document.rb +61 -0
  13. data/lib/mongo_mapper/finder_options.rb +127 -0
  14. data/lib/mongo_mapper/plugins.rb +19 -0
  15. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  16. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +23 -0
  19. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  20. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +49 -0
  21. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  23. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  25. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  26. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  27. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +66 -0
  28. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  29. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  30. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  31. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  32. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  33. data/lib/mongo_mapper/plugins/equality.rb +11 -0
  34. data/lib/mongo_mapper/plugins/identity_map.rb +66 -0
  35. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  36. data/lib/mongo_mapper/plugins/keys.rb +295 -0
  37. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  38. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  39. data/lib/mongo_mapper/plugins/protected.rb +31 -0
  40. data/lib/mongo_mapper/plugins/rails.rb +80 -0
  41. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  42. data/lib/mongo_mapper/plugins/validations.rb +48 -0
  43. data/lib/mongo_mapper/support.rb +213 -0
  44. data/performance/read_write.rb +52 -0
  45. data/specs.watchr +51 -0
  46. data/test/NOTE_ON_TESTING +1 -0
  47. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  48. data/test/functional/associations/test_belongs_to_proxy.rb +93 -0
  49. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  50. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  51. data/test/functional/associations/test_many_documents_proxy.rb +437 -0
  52. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +175 -0
  53. data/test/functional/associations/test_many_embedded_proxy.rb +216 -0
  54. data/test/functional/associations/test_many_polymorphic_proxy.rb +340 -0
  55. data/test/functional/associations/test_one_proxy.rb +149 -0
  56. data/test/functional/test_associations.rb +44 -0
  57. data/test/functional/test_binary.rb +27 -0
  58. data/test/functional/test_callbacks.rb +81 -0
  59. data/test/functional/test_dirty.rb +156 -0
  60. data/test/functional/test_document.rb +1171 -0
  61. data/test/functional/test_embedded_document.rb +125 -0
  62. data/test/functional/test_identity_map.rb +233 -0
  63. data/test/functional/test_logger.rb +20 -0
  64. data/test/functional/test_modifiers.rb +252 -0
  65. data/test/functional/test_pagination.rb +93 -0
  66. data/test/functional/test_protected.rb +41 -0
  67. data/test/functional/test_string_id_compatibility.rb +67 -0
  68. data/test/functional/test_validations.rb +329 -0
  69. data/test/models.rb +232 -0
  70. data/test/support/custom_matchers.rb +55 -0
  71. data/test/support/timing.rb +16 -0
  72. data/test/test_helper.rb +60 -0
  73. data/test/unit/associations/test_base.rb +207 -0
  74. data/test/unit/associations/test_proxy.rb +103 -0
  75. data/test/unit/serializers/test_json_serializer.rb +189 -0
  76. data/test/unit/test_descendant_appends.rb +71 -0
  77. data/test/unit/test_document.rb +203 -0
  78. data/test/unit/test_dynamic_finder.rb +125 -0
  79. data/test/unit/test_embedded_document.rb +628 -0
  80. data/test/unit/test_finder_options.rb +325 -0
  81. data/test/unit/test_keys.rb +169 -0
  82. data/test/unit/test_mongo_mapper.rb +65 -0
  83. data/test/unit/test_pagination.rb +127 -0
  84. data/test/unit/test_plugins.rb +42 -0
  85. data/test/unit/test_rails.rb +139 -0
  86. data/test/unit/test_rails_compatibility.rb +42 -0
  87. data/test/unit/test_serialization.rb +51 -0
  88. data/test/unit/test_support.rb +350 -0
  89. data/test/unit/test_time_zones.rb +39 -0
  90. data/test/unit/test_validations.rb +492 -0
  91. metadata +260 -0
@@ -0,0 +1,61 @@
1
+ module MongoMapper
2
+ module EmbeddedDocument
3
+ extend DescendantAppends
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+
10
+ extend Plugins
11
+ plugin Plugins::Associations
12
+ plugin Plugins::Clone
13
+ plugin Plugins::Descendants
14
+ plugin Plugins::Equality
15
+ plugin Plugins::Inspect
16
+ plugin Plugins::Keys
17
+ plugin Plugins::Logger
18
+ plugin Plugins::Protected
19
+ plugin Plugins::Rails
20
+ plugin Plugins::Serialization
21
+ plugin Plugins::Validations
22
+
23
+ attr_accessor :_root_document
24
+ end
25
+
26
+ super
27
+ end
28
+
29
+ module ClassMethods
30
+ def embeddable?
31
+ true
32
+ end
33
+ end
34
+
35
+ module InstanceMethods
36
+ def save(options={})
37
+ if result = _root_document.try(:save, options)
38
+ @new = false
39
+ end
40
+ result
41
+ end
42
+
43
+ def save!(options={})
44
+ if result = _root_document.try(:save!, options)
45
+ @new = false
46
+ end
47
+ result
48
+ end
49
+
50
+ def update_attributes(attrs={})
51
+ self.attributes = attrs
52
+ self.save
53
+ end
54
+
55
+ def update_attributes!(attrs={})
56
+ self.attributes = attrs
57
+ self.save!
58
+ end
59
+ end # InstanceMethods
60
+ end # EmbeddedDocument
61
+ end # MongoMapper
@@ -0,0 +1,127 @@
1
+ module MongoMapper
2
+ # = Important Note
3
+ # This class is private to MongoMapper and should not be considered part of
4
+ # MongoMapper's public API.
5
+ #
6
+ class FinderOptions
7
+ OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
8
+
9
+ def self.normalized_field(field)
10
+ field.to_s == 'id' ? :_id : field
11
+ end
12
+
13
+ def self.normalized_order_direction(direction)
14
+ direction ||= 'ASC'
15
+ direction.upcase == 'ASC' ? 1 : -1
16
+ end
17
+
18
+ def initialize(model, options)
19
+ raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
20
+
21
+ @model = model
22
+ @options = {}
23
+ @conditions = {}
24
+
25
+ options.each_pair do |key, value|
26
+ key = key.respond_to?(:to_sym) ? key.to_sym : key
27
+ if OptionKeys.include?(key)
28
+ @options[key] = value
29
+ elsif key == :conditions
30
+ @conditions.merge!(value)
31
+ else
32
+ @conditions[key] = value
33
+ end
34
+ end
35
+
36
+ add_sci_scope
37
+ end
38
+
39
+ def criteria
40
+ to_mongo_criteria(@conditions)
41
+ end
42
+
43
+ def options
44
+ fields = @options[:fields] || @options[:select]
45
+ skip = @options[:skip] || @options[:offset] || 0
46
+ limit = @options[:limit] || 0
47
+ sort = @options[:sort] || convert_order_to_sort(@options[:order])
48
+
49
+ {:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
50
+ end
51
+
52
+ def to_a
53
+ [criteria, options]
54
+ end
55
+
56
+ private
57
+ def to_mongo_criteria(conditions, parent_key=nil)
58
+ criteria = {}
59
+
60
+ conditions.each_pair do |field, value|
61
+ field = self.class.normalized_field(field)
62
+
63
+ if @model.object_id_key?(field) && value.is_a?(String)
64
+ value = Mongo::ObjectID.from_string(value)
65
+ end
66
+
67
+ if field.is_a?(SymbolOperator)
68
+ criteria.update(field.to_mm_criteria(value))
69
+ next
70
+ end
71
+
72
+ case value
73
+ when Array
74
+ criteria[field] = operator?(field) ? value : {'$in' => value}
75
+ when Hash
76
+ criteria[field] = to_mongo_criteria(value, field)
77
+ when Time
78
+ criteria[field] = value.utc
79
+ else
80
+ criteria[field] = value
81
+ end
82
+ end
83
+
84
+ criteria
85
+ end
86
+
87
+ def operator?(field)
88
+ field.to_s =~ /^\$/
89
+ end
90
+
91
+ # adds _type single collection inheritance scope for models that need it
92
+ def add_sci_scope
93
+ if @model.single_collection_inherited?
94
+ @conditions[:_type] = @model.to_s
95
+ end
96
+ end
97
+
98
+ def to_mongo_fields(fields)
99
+ return if fields.blank?
100
+
101
+ if fields.respond_to?(:flatten, :compact)
102
+ fields.flatten.compact
103
+ else
104
+ fields.split(',').map { |field| field.strip }
105
+ end
106
+ end
107
+
108
+ def convert_order_to_sort(sort)
109
+ return if sort.blank?
110
+
111
+ if sort.respond_to?(:all?) && sort.all? { |s| s.respond_to?(:to_mm_order) }
112
+ sort.map { |s| s.to_mm_order }
113
+ elsif sort.respond_to?(:to_mm_order)
114
+ [sort.to_mm_order]
115
+ else
116
+ pieces = sort.split(',')
117
+ pieces.map { |s| to_mongo_sort_piece(s) }
118
+ end
119
+ end
120
+
121
+ def to_mongo_sort_piece(str)
122
+ field, direction = str.strip.split(' ')
123
+ direction = FinderOptions.normalized_order_direction(direction)
124
+ [field, direction]
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,19 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ def plugins
4
+ @plugins ||= []
5
+ end
6
+
7
+ def plugin(mod)
8
+ if mod.const_defined?(:ClassMethods)
9
+ extend mod::ClassMethods
10
+ end
11
+
12
+ if mod.const_defined?(:InstanceMethods)
13
+ include mod::InstanceMethods
14
+ end
15
+
16
+ plugins << mod
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,104 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Associations
4
+ module ClassMethods
5
+ def belongs_to(association_id, options={}, &extension)
6
+ create_association(:belongs_to, association_id, options, &extension)
7
+ end
8
+
9
+ def many(association_id, options={}, &extension)
10
+ create_association(:many, association_id, options, &extension)
11
+ end
12
+
13
+ def one(association_id, options={}, &extension)
14
+ create_association(:one, association_id, options, &extension)
15
+ end
16
+
17
+ def associations
18
+ @associations ||= HashWithIndifferentAccess.new
19
+ end
20
+
21
+ def associations=(hash)
22
+ @associations = hash
23
+ end
24
+
25
+ def inherited(subclass)
26
+ subclass.associations = associations.dup
27
+ super
28
+ end
29
+
30
+ private
31
+ def create_association(type, name, options, &extension)
32
+ association = Associations::Base.new(type, name, options, &extension)
33
+ associations[association.name] = association
34
+
35
+ define_method(association.name) do
36
+ get_proxy(association)
37
+ end
38
+
39
+ define_method("#{association.name}=") do |value|
40
+ get_proxy(association).replace(value)
41
+ value
42
+ end
43
+
44
+ if association.one? || association.belongs_to?
45
+ define_method("#{association.name}?") do
46
+ get_proxy(association).present?
47
+ end
48
+ end
49
+
50
+ if association.options[:dependent] && association.many? && !association.embeddable?
51
+ after_destroy do |doc|
52
+ case association.options[:dependent]
53
+ when :destroy
54
+ doc.get_proxy(association).destroy_all
55
+ when :delete_all
56
+ doc.get_proxy(association).delete_all
57
+ when :nullify
58
+ doc.get_proxy(association).nullify
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ module InstanceMethods
66
+ def associations
67
+ self.class.associations
68
+ end
69
+
70
+ # @api private?
71
+ def embedded_associations
72
+ associations.select do |name, association|
73
+ association.embeddable?
74
+ end.map do |name, association|
75
+ association
76
+ end
77
+ end
78
+
79
+ def get_proxy(association)
80
+ unless proxy = self.instance_variable_get(association.ivar)
81
+ proxy = association.proxy_class.new(self, association)
82
+ self.instance_variable_set(association.ivar, proxy)
83
+ end
84
+
85
+ proxy
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ require 'mongo_mapper/plugins/associations/base'
93
+ require 'mongo_mapper/plugins/associations/proxy'
94
+ require 'mongo_mapper/plugins/associations/collection'
95
+ require 'mongo_mapper/plugins/associations/embedded_collection'
96
+ require 'mongo_mapper/plugins/associations/many_documents_proxy'
97
+ require 'mongo_mapper/plugins/associations/belongs_to_proxy'
98
+ require 'mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy'
99
+ require 'mongo_mapper/plugins/associations/many_polymorphic_proxy'
100
+ require 'mongo_mapper/plugins/associations/many_embedded_proxy'
101
+ require 'mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy'
102
+ require 'mongo_mapper/plugins/associations/many_documents_as_proxy'
103
+ require 'mongo_mapper/plugins/associations/one_proxy'
104
+ require 'mongo_mapper/plugins/associations/in_array_proxy'
@@ -0,0 +1,121 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Associations
4
+ class Base
5
+ attr_reader :type, :name, :options, :finder_options
6
+
7
+ # Options that should not be considered MongoDB query options/criteria
8
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :in, :polymorphic]
9
+
10
+ def initialize(type, name, options={}, &extension)
11
+ @type, @name, @options, @finder_options = type, name, {}, {}
12
+ options.symbolize_keys!
13
+
14
+ options[:extend] = modulized_extensions(extension, options[:extend])
15
+
16
+ options.each_pair do |key, value|
17
+ if AssociationOptions.include?(key)
18
+ @options[key] = value
19
+ else
20
+ @finder_options[key] = value
21
+ end
22
+ end
23
+ end
24
+
25
+ def class_name
26
+ @class_name ||= begin
27
+ if cn = options[:class_name]
28
+ cn
29
+ elsif many?
30
+ name.to_s.singularize.camelize
31
+ else
32
+ name.to_s.camelize
33
+ end
34
+ end
35
+ end
36
+
37
+ def klass
38
+ @klass ||= options[:class] || class_name.constantize
39
+ end
40
+
41
+ def many?
42
+ @many_type ||= @type == :many
43
+ end
44
+
45
+ def belongs_to?
46
+ @belongs_to_type ||= @type == :belongs_to
47
+ end
48
+
49
+ def one?
50
+ @one_type ||= @type == :one
51
+ end
52
+
53
+ def polymorphic?
54
+ !!@options[:polymorphic]
55
+ end
56
+
57
+ def as?
58
+ !!@options[:as]
59
+ end
60
+
61
+ def in_array?
62
+ !!@options[:in]
63
+ end
64
+
65
+ def embeddable?
66
+ many? && klass.embeddable?
67
+ end
68
+
69
+ def type_key_name
70
+ @type_key_name ||= many? ? '_type' : "#{as}_type"
71
+ end
72
+
73
+ def as
74
+ @options[:as] || self.name
75
+ end
76
+
77
+ def foreign_key
78
+ @options[:foreign_key] || "#{name}_id"
79
+ end
80
+
81
+ def ivar
82
+ @ivar ||= "@_#{name}"
83
+ end
84
+
85
+ def proxy_class
86
+ @proxy_class ||= begin
87
+ if many?
88
+ if self.klass.embeddable?
89
+ polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
90
+ else
91
+ if polymorphic?
92
+ ManyPolymorphicProxy
93
+ elsif as?
94
+ ManyDocumentsAsProxy
95
+ elsif in_array?
96
+ InArrayProxy
97
+ else
98
+ ManyDocumentsProxy
99
+ end
100
+ end
101
+ elsif one?
102
+ OneProxy
103
+ else
104
+ polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
105
+ end
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ # @param [Array<Module, Proc>] extensions a collection of Modules or
112
+ # Procs that extend the behaviour of this association.
113
+ def modulized_extensions(*extensions)
114
+ extensions.flatten.compact.map do |extension|
115
+ Proc === extension ? Module.new(&extension) : extension
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,28 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Associations
4
+ class BelongsToPolymorphicProxy < Proxy
5
+ def replace(doc)
6
+ if doc
7
+ doc.save if doc.new?
8
+ id, type = doc.id, doc.class.name
9
+ end
10
+
11
+ owner[reflection.foreign_key] = id
12
+ owner[reflection.type_key_name] = type
13
+ reset
14
+ end
15
+
16
+ protected
17
+ def find_target
18
+ return nil if association_class.nil? || owner[reflection.foreign_key].nil?
19
+ association_class.first(:id => owner[reflection.foreign_key])
20
+ end
21
+
22
+ def association_class
23
+ proxy_owner[reflection.type_key_name] ? proxy_owner[reflection.type_key_name].constantize : nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end