jmonteiro-mongo_mapper 0.1.0

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