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,14 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Inspect
4
+ module InstanceMethods
5
+ def inspect
6
+ attributes_as_nice_string = key_names.collect do |name|
7
+ "#{name}: #{self[name].inspect}"
8
+ end.join(", ")
9
+ "#<#{self.class} #{attributes_as_nice_string}>"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,295 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Keys
4
+ module ClassMethods
5
+ def inherited(descendant)
6
+ descendant.instance_variable_set(:@keys, keys.dup)
7
+ super
8
+ end
9
+
10
+ def keys
11
+ @keys ||= HashWithIndifferentAccess.new
12
+ end
13
+
14
+ def key(*args)
15
+ key = Key.new(*args)
16
+ keys[key.name] = key
17
+
18
+ create_accessors_for(key)
19
+ create_key_in_descendants(*args)
20
+ create_indexes_for(key)
21
+ create_validations_for(key)
22
+
23
+ key
24
+ end
25
+
26
+ def using_object_id?
27
+ object_id_key?(:_id)
28
+ end
29
+
30
+ def object_id_key?(name)
31
+ key = keys[name.to_s]
32
+ key && key.type == ObjectId
33
+ end
34
+
35
+ def to_mongo(instance)
36
+ return nil if instance.nil?
37
+ instance.to_mongo
38
+ end
39
+
40
+ def from_mongo(value)
41
+ return nil if value.nil?
42
+ value.is_a?(self) ? value : load(value)
43
+ end
44
+
45
+ def load(attrs)
46
+ begin
47
+ klass = attrs['_type'].present? ? attrs['_type'].constantize : self
48
+ doc = klass.new(attrs)
49
+ doc.instance_variable_set("@new", false)
50
+ doc
51
+ rescue NameError
52
+ doc = new(attrs)
53
+ doc.instance_variable_set("@new", false)
54
+ doc
55
+ end
56
+ end
57
+
58
+ private
59
+ def accessors_module
60
+ module_defined = if method(:const_defined?).arity == 1 # Ruby 1.9 compat check
61
+ const_defined?('MongoMapperKeys')
62
+ else
63
+ const_defined?('MongoMapperKeys', false)
64
+ end
65
+
66
+ if module_defined
67
+ const_get 'MongoMapperKeys'
68
+ else
69
+ const_set 'MongoMapperKeys', Module.new
70
+ end
71
+ end
72
+
73
+ def create_accessors_for(key)
74
+ accessors_module.module_eval <<-end_eval
75
+ def #{key.name}
76
+ read_key(:#{key.name})
77
+ end
78
+
79
+ def #{key.name}_before_typecast
80
+ read_key_before_typecast(:#{key.name})
81
+ end
82
+
83
+ def #{key.name}=(value)
84
+ write_key(:#{key.name}, value)
85
+ end
86
+
87
+ def #{key.name}?
88
+ read_key(:#{key.name}).present?
89
+ end
90
+ end_eval
91
+
92
+ include accessors_module
93
+ end
94
+
95
+ def create_key_in_descendants(*args)
96
+ return if descendants.blank?
97
+ descendants.each { |descendant| descendant.key(*args) }
98
+ end
99
+
100
+ def create_indexes_for(key)
101
+ ensure_index key.name if key.options[:index] && !key.embeddable?
102
+ end
103
+
104
+ def create_validations_for(key)
105
+ attribute = key.name.to_sym
106
+
107
+ if key.options[:required]
108
+ validates_presence_of(attribute)
109
+ end
110
+
111
+ if key.options[:unique]
112
+ validates_uniqueness_of(attribute)
113
+ end
114
+
115
+ if key.options[:numeric]
116
+ number_options = key.type == Integer ? {:only_integer => true} : {}
117
+ validates_numericality_of(attribute, number_options)
118
+ end
119
+
120
+ if key.options[:format]
121
+ validates_format_of(attribute, :with => key.options[:format])
122
+ end
123
+
124
+ if key.options[:length]
125
+ length_options = case key.options[:length]
126
+ when Integer
127
+ {:minimum => 0, :maximum => key.options[:length]}
128
+ when Range
129
+ {:within => key.options[:length]}
130
+ when Hash
131
+ key.options[:length]
132
+ end
133
+ validates_length_of(attribute, length_options)
134
+ end
135
+ end
136
+ end
137
+
138
+ module InstanceMethods
139
+ def self.included(model)
140
+ model.key :_id, ObjectId
141
+ end
142
+
143
+ def initialize(attrs={})
144
+ unless attrs.nil?
145
+ provided_keys = attrs.keys.map { |k| k.to_s }
146
+ unless provided_keys.include?('_id') || provided_keys.include?('id')
147
+ write_key :_id, Mongo::ObjectID.new
148
+ end
149
+ end
150
+
151
+ @new = true
152
+ self._type = self.class.name if respond_to?(:_type=)
153
+ self.attributes = attrs
154
+ end
155
+
156
+ def new?
157
+ @new
158
+ end
159
+
160
+ def attributes=(attrs)
161
+ return if attrs.blank?
162
+
163
+ attrs.each_pair do |name, value|
164
+ writer_method = "#{name}="
165
+
166
+ if respond_to?(writer_method)
167
+ self.send(writer_method, value)
168
+ else
169
+ self[name.to_s] = value
170
+ end
171
+ end
172
+ end
173
+
174
+ def attributes
175
+ attrs = HashWithIndifferentAccess.new
176
+
177
+ keys.each_pair do |name, key|
178
+ value = key.set(self[key.name])
179
+ attrs[name] = value
180
+ end
181
+
182
+ embedded_associations.each do |association|
183
+ if documents = instance_variable_get(association.ivar)
184
+ attrs[association.name] = documents.map { |document| document.to_mongo }
185
+ end
186
+ end
187
+
188
+ attrs
189
+ end
190
+ alias :to_mongo :attributes
191
+
192
+ def id
193
+ _id
194
+ end
195
+
196
+ def id=(value)
197
+ if self.class.using_object_id?
198
+ value = MongoMapper.normalize_object_id(value)
199
+ end
200
+
201
+ self[:_id] = value
202
+ end
203
+
204
+ def [](name)
205
+ read_key(name)
206
+ end
207
+
208
+ def []=(name, value)
209
+ ensure_key_exists(name)
210
+ write_key(name, value)
211
+ end
212
+
213
+ # @api public
214
+ def keys
215
+ self.class.keys
216
+ end
217
+
218
+ # @api private?
219
+ def key_names
220
+ keys.keys
221
+ end
222
+
223
+ # @api private?
224
+ def non_embedded_keys
225
+ keys.values.select { |key| !key.embeddable? }
226
+ end
227
+
228
+ # @api private?
229
+ def embedded_keys
230
+ keys.values.select { |key| key.embeddable? }
231
+ end
232
+
233
+ private
234
+ def ensure_key_exists(name)
235
+ self.class.key(name) unless respond_to?("#{name}=")
236
+ end
237
+
238
+ def read_key(name)
239
+ if key = keys[name]
240
+ var_name = "@#{name}"
241
+ value = key.get(instance_variable_get(var_name))
242
+ instance_variable_set(var_name, value)
243
+ else
244
+ raise KeyNotFound, "Could not find key: #{name.inspect}"
245
+ end
246
+ end
247
+
248
+ def read_key_before_typecast(name)
249
+ instance_variable_get("@#{name}_before_typecast")
250
+ end
251
+
252
+ def write_key(name, value)
253
+ key = keys[name]
254
+ instance_variable_set "@#{name}_before_typecast", value
255
+ instance_variable_set "@#{name}", key.set(value)
256
+ end
257
+ end
258
+
259
+ class Key
260
+ attr_accessor :name, :type, :options, :default_value
261
+
262
+ def initialize(*args)
263
+ options = args.extract_options!
264
+ @name, @type = args.shift.to_s, args.shift
265
+ self.options = (options || {}).symbolize_keys
266
+ self.default_value = self.options.delete(:default)
267
+ end
268
+
269
+ def ==(other)
270
+ @name == other.name && @type == other.type
271
+ end
272
+
273
+ def embeddable?
274
+ type.respond_to?(:embeddable?) && type.embeddable? ? true : false
275
+ end
276
+
277
+ def number?
278
+ [Integer, Float].include?(type)
279
+ end
280
+
281
+ def get(value)
282
+ if value.nil? && !default_value.nil?
283
+ return default_value
284
+ end
285
+
286
+ type.from_mongo(value)
287
+ end
288
+
289
+ def set(value)
290
+ type.to_mongo(value)
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,17 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Logger
4
+ module ClassMethods
5
+ def logger
6
+ MongoMapper.logger
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def logger
12
+ self.class.logger
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,85 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Pagination
4
+ module ClassMethods
5
+ def per_page
6
+ 25
7
+ end
8
+
9
+ def paginate(options)
10
+ per_page = options.delete(:per_page) || self.per_page
11
+ page = options.delete(:page)
12
+ total_entries = count(options)
13
+ pagination = Pagination::PaginationProxy.new(total_entries, page, per_page)
14
+
15
+ options.merge!(:limit => pagination.limit, :skip => pagination.skip)
16
+ pagination.subject = find_every(options)
17
+ pagination
18
+ end
19
+ end
20
+
21
+ class PaginationProxy
22
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
23
+
24
+ attr_accessor :subject
25
+ attr_reader :total_entries, :per_page, :current_page
26
+ alias limit per_page
27
+
28
+ def initialize(total_entries, current_page, per_page=nil)
29
+ @total_entries = total_entries.to_i
30
+ self.per_page = per_page
31
+ self.current_page = current_page
32
+ end
33
+
34
+ def total_pages
35
+ (total_entries / per_page.to_f).ceil
36
+ end
37
+
38
+ def out_of_bounds?
39
+ current_page > total_pages
40
+ end
41
+
42
+ def previous_page
43
+ current_page > 1 ? (current_page - 1) : nil
44
+ end
45
+
46
+ def next_page
47
+ current_page < total_pages ? (current_page + 1) : nil
48
+ end
49
+
50
+ def skip
51
+ (current_page - 1) * per_page
52
+ end
53
+ alias offset skip # for will paginate support
54
+
55
+ def send(method, *args, &block)
56
+ if respond_to?(method)
57
+ super
58
+ else
59
+ subject.send(method, *args, &block)
60
+ end
61
+ end
62
+
63
+ def ===(other)
64
+ other === subject
65
+ end
66
+
67
+ def method_missing(name, *args, &block)
68
+ @subject.send(name, *args, &block)
69
+ end
70
+
71
+ private
72
+ def per_page=(value)
73
+ value = 25 if value.blank?
74
+ @per_page = value.to_i
75
+ end
76
+
77
+ def current_page=(value)
78
+ value = value.to_i
79
+ value = 1 if value < 1
80
+ @current_page = value
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,31 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Protected
4
+ module ClassMethods
5
+ def attr_protected( *attrs )
6
+ self.write_inheritable_attribute( :attr_protected, Set.new(attrs) + (protected_attributes || []) )
7
+ end
8
+ def protected_attributes
9
+ self.read_inheritable_attribute( :attr_protected )
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+ def update_attributes( attrs = {} )
15
+ super filter_protected_attrs(attrs)
16
+ end
17
+
18
+ def update_attributes!( attrs = {} )
19
+ super filter_protected_attrs(attrs)
20
+ end
21
+
22
+ protected
23
+ def filter_protected_attrs( attrs )
24
+ prot_attrs = self.class.protected_attributes
25
+ return attrs unless prot_attrs
26
+ attrs.dup.delete_if { |key, val| prot_attrs.include?(key) }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module Rails
4
+ module InstanceMethods
5
+ def to_param
6
+ id.to_s
7
+ end
8
+
9
+ def new_record?
10
+ new?
11
+ end
12
+
13
+ def read_attribute(name)
14
+ self[name]
15
+ end
16
+
17
+ def read_attribute_before_typecast(name)
18
+ read_key_before_typecast(name)
19
+ end
20
+
21
+ def write_attribute(name, value)
22
+ self[name] = value
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def has_one(*args)
28
+ one(*args)
29
+ end
30
+
31
+ def has_many(*args)
32
+ many(*args)
33
+ end
34
+
35
+ def column_names
36
+ keys.keys
37
+ end
38
+
39
+ def self_and_descendants#nodoc:
40
+ klass = self
41
+ classes = [klass]
42
+ while klass != klass.base_class
43
+ classes << klass = klass.superclass
44
+ end
45
+ classes
46
+ rescue
47
+ [self]
48
+ end
49
+
50
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
51
+ # Person.human_attribute_name("first_name") # => "First name"
52
+ # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
53
+ # module now.
54
+ # Specify +options+ with additional translating options.
55
+ def human_attribute_name(attribute_key_name, options = {})
56
+ defaults = self_and_descendants.map do |klass|
57
+ :"#{klass.name.underscore}.#{attribute_key_name}"
58
+ end
59
+ defaults << options[:default] if options[:default]
60
+ defaults.flatten!
61
+ defaults << attribute_key_name.to_s.humanize
62
+ options[:count] ||= 1
63
+ I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:mongomapper, :attributes]))
64
+ end
65
+
66
+ # Transform the modelname into a more humane format, using I18n.
67
+ # Defaults to the basic humanize method.
68
+ # Default scope of the translation is mongomapper.models
69
+ # Specify +options+ with additional translating options.
70
+ def human_name(options = {})
71
+ defaults = self_and_descendants.map do |klass|
72
+ :"#{klass.name.underscore}"
73
+ end
74
+ defaults << self.name.humanize
75
+ I18n.translate(defaults.shift, {:scope => [:mongomapper, :models], :count => 1, :default => defaults}.merge(options))
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end