crnixon-mongomapper 0.2.0 → 0.3.4

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 (68) hide show
  1. data/.gitignore +1 -0
  2. data/History +48 -0
  3. data/README.rdoc +5 -3
  4. data/Rakefile +6 -4
  5. data/VERSION +1 -1
  6. data/bin/mmconsole +56 -0
  7. data/lib/mongomapper.rb +29 -18
  8. data/lib/mongomapper/associations.rb +53 -38
  9. data/lib/mongomapper/associations/base.rb +53 -20
  10. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
  12. data/lib/mongomapper/associations/many_documents_as_proxy.rb +27 -0
  13. data/lib/mongomapper/associations/many_documents_proxy.rb +103 -0
  14. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +6 -8
  16. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongomapper/associations/{array_proxy.rb → many_proxy.rb} +1 -1
  18. data/lib/mongomapper/associations/proxy.rb +24 -21
  19. data/lib/mongomapper/callbacks.rb +1 -1
  20. data/lib/mongomapper/document.rb +160 -74
  21. data/lib/mongomapper/dynamic_finder.rb +38 -0
  22. data/lib/mongomapper/embedded_document.rb +154 -105
  23. data/lib/mongomapper/finder_options.rb +11 -7
  24. data/lib/mongomapper/key.rb +15 -21
  25. data/lib/mongomapper/pagination.rb +52 -0
  26. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  27. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  28. data/lib/mongomapper/serialization.rb +1 -1
  29. data/lib/mongomapper/serializers/json_serializer.rb +15 -0
  30. data/lib/mongomapper/support.rb +30 -0
  31. data/mongomapper.gemspec +87 -46
  32. data/test/NOTE_ON_TESTING +1 -0
  33. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
  34. data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
  35. data/test/functional/associations/test_many_documents_as_proxy.rb +253 -0
  36. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  37. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  38. data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
  39. data/test/functional/associations/test_many_proxy.rb +295 -0
  40. data/test/functional/test_associations.rb +47 -0
  41. data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
  42. data/test/functional/test_document.rb +952 -0
  43. data/test/functional/test_pagination.rb +81 -0
  44. data/test/functional/test_rails_compatibility.rb +30 -0
  45. data/test/functional/test_validations.rb +172 -0
  46. data/test/models.rb +169 -0
  47. data/test/test_helper.rb +7 -2
  48. data/test/unit/serializers/test_json_serializer.rb +189 -0
  49. data/test/unit/test_association_base.rb +144 -0
  50. data/test/unit/test_document.rb +123 -0
  51. data/test/unit/test_embedded_document.rb +526 -0
  52. data/test/{test_finder_options.rb → unit/test_finder_options.rb} +36 -1
  53. data/test/{test_key.rb → unit/test_key.rb} +59 -12
  54. data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
  55. data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
  56. data/test/unit/test_pagination.rb +113 -0
  57. data/test/unit/test_rails_compatibility.rb +34 -0
  58. data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
  59. data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
  60. metadata +81 -43
  61. data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
  62. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
  63. data/lib/mongomapper/rails_compatibility.rb +0 -23
  64. data/test/serializers/test_json_serializer.rb +0 -104
  65. data/test/test_associations.rb +0 -174
  66. data/test/test_document.rb +0 -944
  67. data/test/test_embedded_document.rb +0 -253
  68. data/test/test_rails_compatibility.rb +0 -29
@@ -0,0 +1,38 @@
1
+ module MongoMapper
2
+ class DynamicFinder
3
+ attr_reader :options
4
+
5
+ def initialize(model, method)
6
+ @model = model
7
+ @options = {}
8
+ @options[:method] = method
9
+ match
10
+ end
11
+
12
+ def valid?
13
+ @options[:finder].present?
14
+ end
15
+
16
+ protected
17
+ def match
18
+ @options[:finder] = :first
19
+
20
+ case @options[:method].to_s
21
+ when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
22
+ @options[:finder] = :last if $1 == 'last_by'
23
+ @options[:finder] = :all if $1 == 'all_by'
24
+ names = $2
25
+ when /^find_by_([_a-zA-Z]\w*)\!$/
26
+ @options[:bang] = true
27
+ names = $1
28
+ when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
29
+ @options[:instantiator] = $1 == 'initialize' ? :new : :create
30
+ names = $2
31
+ else
32
+ @options[:finder] = nil
33
+ end
34
+
35
+ @options[:attribute_names] = names && names.split('_and_')
36
+ end
37
+ end
38
+ end
@@ -2,8 +2,6 @@ require 'observer'
2
2
 
3
3
  module MongoMapper
4
4
  module EmbeddedDocument
5
- class NotImplemented < StandardError; end
6
-
7
5
  def self.included(model)
8
6
  model.class_eval do
9
7
  extend ClassMethods
@@ -12,22 +10,56 @@ module MongoMapper
12
10
  extend Associations::ClassMethods
13
11
  include Associations::InstanceMethods
14
12
 
13
+ include RailsCompatibility::EmbeddedDocument
15
14
  include Validatable
16
15
  include Serialization
16
+
17
+ key :_id, String
17
18
  end
18
19
  end
19
20
 
20
21
  module ClassMethods
22
+ def inherited(subclass)
23
+ unless subclass.embeddable?
24
+ subclass.collection(self.collection.name)
25
+ end
26
+
27
+ (@subclasses ||= []) << subclass
28
+ end
29
+
30
+ def subclasses
31
+ @subclasses
32
+ end
33
+
21
34
  def keys
22
- @keys ||= HashWithIndifferentAccess.new
35
+ @keys ||= if parent = parent_model
36
+ parent.keys.dup
37
+ else
38
+ HashWithIndifferentAccess.new
39
+ end
23
40
  end
24
41
 
25
- def key(name, type, options={})
26
- key = Key.new(name, type, options)
27
- keys[key.name] = key
28
- apply_validations_for(key)
29
- create_indexes_for(key)
30
- key
42
+ def key(*args)
43
+ key = Key.new(*args)
44
+
45
+ if keys[key.name].blank?
46
+ keys[key.name] = key
47
+
48
+ create_accessors_for(key)
49
+ add_to_subclasses(*args)
50
+ apply_validations_for(key)
51
+ create_indexes_for(key)
52
+
53
+ key
54
+ end
55
+ end
56
+
57
+ def add_to_subclasses(*args)
58
+ return if subclasses.blank?
59
+
60
+ subclasses.each do |subclass|
61
+ subclass.key(*args)
62
+ end
31
63
  end
32
64
 
33
65
  def ensure_index(name_or_array, options={})
@@ -44,7 +76,42 @@ module MongoMapper
44
76
  !self.ancestors.include?(Document)
45
77
  end
46
78
 
79
+ def parent_model
80
+ (ancestors - [self,EmbeddedDocument]).find do |parent_class|
81
+ parent_class.ancestors.include?(EmbeddedDocument)
82
+ end
83
+ end
84
+
47
85
  private
86
+ def accessors_module
87
+ if const_defined?('MongoMapperKeys') && constants.include?( 'MongoMapperKeys' )
88
+ const_get 'MongoMapperKeys'
89
+ else
90
+ const_set 'MongoMapperKeys', Module.new
91
+ end
92
+ end
93
+
94
+ def create_accessors_for(key)
95
+ accessors_module.module_eval <<-end_eval
96
+ def #{key.name}
97
+ read_attribute( :'#{key.name}' )
98
+ end
99
+
100
+ def #{key.name}_before_typecast
101
+ read_attribute_before_typecast(:'#{key.name}')
102
+ end
103
+
104
+ def #{key.name}=(value)
105
+ write_attribute(:'#{key.name}', value)
106
+ end
107
+
108
+ def #{key.name}?
109
+ read_attribute(:#{key.name}).present?
110
+ end
111
+ end_eval
112
+ include accessors_module
113
+ end
114
+
48
115
  def create_indexes_for(key)
49
116
  ensure_index key.name if key.options[:index]
50
117
  end
@@ -55,7 +122,7 @@ module MongoMapper
55
122
  if key.options[:required]
56
123
  validates_presence_of(attribute)
57
124
  end
58
-
125
+
59
126
  if key.options[:unique]
60
127
  validates_uniqueness_of(attribute)
61
128
  end
@@ -81,146 +148,128 @@ module MongoMapper
81
148
  validates_length_of(attribute, length_options)
82
149
  end
83
150
  end
84
-
85
151
  end
86
152
 
87
153
  module InstanceMethods
88
154
  def initialize(attrs={})
89
155
  unless attrs.nil?
90
- initialize_associations(attrs)
156
+ self.class.associations.each_pair do |name, association|
157
+ if collection = attrs.delete(name)
158
+ send("#{association.name}=", collection)
159
+ end
160
+ end
161
+
91
162
  self.attributes = attrs
92
163
  end
164
+
165
+ if self.class.embeddable? && read_attribute(:_id).blank?
166
+ write_attribute :_id, XGen::Mongo::Driver::ObjectID.new.to_s
167
+ end
93
168
  end
94
169
 
95
170
  def attributes=(attrs)
96
171
  return if attrs.blank?
97
- attrs.each_pair do |key_name, value|
98
- if writer?(key_name)
99
- write_attribute(key_name, value)
172
+ attrs.each_pair do |name, value|
173
+ writer_method = "#{name}="
174
+
175
+ if respond_to?(writer_method)
176
+ self.send(writer_method, value)
100
177
  else
101
- writer_method ="#{key_name}="
102
- self.send(writer_method, value) if respond_to?(writer_method)
178
+ self[name.to_s] = value
103
179
  end
104
180
  end
105
181
  end
106
182
 
107
183
  def attributes
108
- self.class.keys.inject(HashWithIndifferentAccess.new) do |attributes, key_hash|
109
- name, key = key_hash
110
- value = value_for_key(key)
111
- attributes[name] = value unless value.nil?
112
- attributes
184
+ attrs = HashWithIndifferentAccess.new
185
+ self.class.keys.each_pair do |name, key|
186
+ value =
187
+ if key.native?
188
+ read_attribute(key.name)
189
+ else
190
+ if embedded_document = read_attribute(key.name)
191
+ embedded_document.attributes
192
+ end
193
+ end
194
+
195
+ attrs[name] = value unless value.nil?
113
196
  end
197
+ attrs.merge!(embedded_association_attributes)
114
198
  end
115
-
116
- def reader?(name)
117
- defined_key_names.include?(name.to_s)
118
- end
119
-
120
- def writer?(name)
121
- name = name.to_s
122
- name = name.chop if name.ends_with?('=')
123
- reader?(name)
124
- end
125
-
126
- def before_typecast_reader?(name)
127
- name.to_s.match(/^(.*)_before_typecast$/) && reader?($1)
199
+
200
+ def attributes_with_associations
201
+ self.class.associations.inject(attributes) do |associations, key_hash|
202
+ name, association = key_hash
203
+ value = self.send(name).map(&:attributes_with_associations)
204
+ associations[name] = value unless value.nil?
205
+ associations
206
+ end
128
207
  end
208
+
129
209
 
130
210
  def [](name)
131
211
  read_attribute(name)
132
212
  end
133
213
 
134
214
  def []=(name, value)
215
+ ensure_key_exists(name)
135
216
  write_attribute(name, value)
136
217
  end
137
218
 
138
- def method_missing(method, *args, &block)
139
- attribute = method.to_s
219
+ def ==(other)
220
+ other.is_a?(self.class) && id == other.id
221
+ end
140
222
 
141
- if reader?(attribute)
142
- read_attribute(attribute)
143
- elsif writer?(attribute)
144
- write_attribute(attribute.chop, args[0])
145
- elsif before_typecast_reader?(attribute)
146
- read_attribute_before_typecast(attribute.gsub(/_before_typecast$/, ''))
147
- else
148
- super
149
- end
223
+ def id
224
+ read_attribute(:_id)
150
225
  end
151
226
 
152
- def ==(other)
153
- other.is_a?(self.class) && attributes == other.attributes
227
+ def id=(value)
228
+ @using_custom_id = true
229
+ write_attribute :_id, value
230
+ end
231
+
232
+ def using_custom_id?
233
+ !!@using_custom_id
154
234
  end
155
235
 
156
236
  def inspect
157
- attributes_as_nice_string = defined_key_names.collect do |name|
237
+ attributes_as_nice_string = self.class.keys.keys.collect do |name|
158
238
  "#{name}: #{read_attribute(name)}"
159
239
  end.join(", ")
160
240
  "#<#{self.class} #{attributes_as_nice_string}>"
161
241
  end
162
242
 
163
- alias :respond_to_without_attributes? :respond_to?
164
-
165
- def respond_to?(method, include_private=false)
166
- return true if reader?(method) || writer?(method) || before_typecast_reader?(method)
167
- super
168
- end
169
-
170
- private
171
- def value_for_key(key)
172
- if key.native?
173
- read_attribute(key.name)
174
- else
175
- embedded_document = read_attribute(key.name)
176
- embedded_document && embedded_document.attributes
243
+ private
244
+ def ensure_key_exists(name)
245
+ self.class.key(name) unless respond_to?("#{name}=")
177
246
  end
178
- end
179
-
180
- def read_attribute(name)
181
- defined_key(name).get(instance_variable_get("@#{name}"))
182
- end
183
-
184
- def read_attribute_before_typecast(name)
185
- instance_variable_get("@#{name}_before_typecast")
186
- end
187
-
188
- def write_attribute(name, value)
189
- instance_variable_set "@#{name}_before_typecast", value
190
- instance_variable_set "@#{name}", defined_key(name).set(value)
191
- end
192
247
 
193
- def defined_key(name)
194
- self.class.keys[name]
195
- end
248
+ def read_attribute(name)
249
+ value = self.class.keys[name].get(instance_variable_get("@#{name}"))
250
+ instance_variable_set "@#{name}", value if !frozen?
251
+ value
252
+ end
196
253
 
197
- def defined_key_names
198
- self.class.keys.keys
199
- end
254
+ def read_attribute_before_typecast(name)
255
+ instance_variable_get("@#{name}_before_typecast")
256
+ end
200
257
 
201
- def only_defined_keys(hash={})
202
- defined_key_names = defined_key_names()
203
- hash.delete_if { |k, v| !defined_key_names.include?(k.to_s) }
204
- end
205
-
206
- def embedded_association_attributes
207
- attributes = HashWithIndifferentAccess.new
208
- self.class.associations.each_pair do |name, association|
209
- if association.type == :many && association.klass.embeddable?
210
- vals = instance_variable_get(association.ivar)
211
- attributes[name] = vals.collect { |item| item.attributes } if vals
212
- end
258
+ def write_attribute(name, value)
259
+ instance_variable_set "@#{name}_before_typecast", value
260
+ instance_variable_set "@#{name}", self.class.keys[name].set(value)
213
261
  end
214
- attributes
215
- end
216
262
 
217
- def initialize_associations(attrs={})
218
- self.class.associations.each_pair do |name, association|
219
- if collection = attrs.delete(name)
220
- __send__("#{association.name}=", collection)
263
+ def embedded_association_attributes
264
+ returning HashWithIndifferentAccess.new do |attrs|
265
+ self.class.associations.each_pair do |name, association|
266
+ next unless association.embeddable?
267
+ next unless documents = instance_variable_get(association.ivar)
268
+
269
+ attrs[name] = documents.collect { |doc| doc.attributes }
270
+ end
221
271
  end
222
272
  end
223
- end
224
- end
225
- end
226
- end
273
+ end # InstanceMethods
274
+ end # EmbeddedDocument
275
+ end # MongoMapper
@@ -2,30 +2,34 @@ module MongoMapper
2
2
  class FinderOptions
3
3
  attr_reader :options
4
4
 
5
- def self.to_mongo_criteria(conditions)
6
- conditions = conditions.dup
5
+ def self.to_mongo_criteria(conditions, parent_key=nil)
7
6
  criteria = {}
8
7
  conditions.each_pair do |field, value|
9
8
  case value
10
9
  when Array
11
- criteria[field] = {'$in' => value}
10
+ operator_present = field.to_s =~ /^\$/
11
+ criteria[field] = if operator_present
12
+ value
13
+ else
14
+ {'$in' => value}
15
+ end
12
16
  when Hash
13
- criteria[field] = to_mongo_criteria(value)
14
- else
17
+ criteria[field] = to_mongo_criteria(value, field)
18
+ else
15
19
  criteria[field] = value
16
20
  end
17
21
  end
18
22
 
19
23
  criteria
20
24
  end
21
-
25
+
22
26
  def self.to_mongo_options(options)
23
27
  options = options.dup
24
28
  {
25
29
  :fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
26
30
  :offset => (options.delete(:offset) || 0).to_i,
27
31
  :limit => (options.delete(:limit) || 0).to_i,
28
- :sort => to_mongo_sort(options.delete(:order))
32
+ :sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
29
33
  }
30
34
  end
31
35
 
@@ -1,17 +1,15 @@
1
- class Boolean; end
2
- class Ref; end
3
-
4
1
  module MongoMapper
5
2
  class Key
6
3
  # DateTime and Date are currently not supported by mongo's bson so just use Time
7
- NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash, Ref]
4
+ NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash]
8
5
 
9
6
  attr_accessor :name, :type, :options, :default_value
10
-
11
- def initialize(name, type, options={})
12
- @name, @type = name.to_s, type
13
- self.options = options.symbolize_keys
14
- self.default_value = options.delete(:default)
7
+
8
+ def initialize(*args)
9
+ options = args.extract_options!
10
+ @name, @type = args.shift.to_s, args.shift
11
+ self.options = (options || {}).symbolize_keys
12
+ self.default_value = self.options.delete(:default)
15
13
  end
16
14
 
17
15
  def ==(other)
@@ -23,11 +21,11 @@ module MongoMapper
23
21
  end
24
22
 
25
23
  def native?
26
- @native ||= NativeTypes.include?(type)
24
+ @native ||= NativeTypes.include?(type) || type.nil?
27
25
  end
28
26
 
29
27
  def embedded_document?
30
- type.ancestors.include?(EmbeddedDocument) && !type.ancestors.include?(Document)
28
+ type.respond_to?(:embeddable?) && type.embeddable?
31
29
  end
32
30
 
33
31
  def get(value)
@@ -43,25 +41,21 @@ module MongoMapper
43
41
 
44
42
  private
45
43
  def typecast(value)
44
+ return value if type.nil?
46
45
  return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
46
+ return value.utc if type == Time && value.kind_of?(type)
47
47
  return value if value.kind_of?(type) || value.nil?
48
48
  begin
49
49
  if type == String then value.to_s
50
50
  elsif type == Float then value.to_f
51
51
  elsif type == Array then value.to_a
52
- elsif type == Time then Time.parse(value.to_s)
53
- #elsif type == Date then Date.parse(value.to_s)
54
- elsif type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
52
+ elsif type == Time then Time.parse(value.to_s).utc
53
+ elsif type == Boolean then Boolean.mm_typecast(value)
55
54
  elsif type == Integer
56
55
  # ganked from datamapper
57
56
  value_to_i = value.to_i
58
- if value_to_i == 0 && value != '0'
59
- value_to_s = value.to_s
60
- begin
61
- Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
62
- rescue ArgumentError
63
- nil
64
- end
57
+ if value_to_i == 0
58
+ value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
65
59
  else
66
60
  value_to_i
67
61
  end