jnunemaker-mongomapper 0.2.0 → 0.3.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 (55) hide show
  1. data/.gitignore +1 -0
  2. data/History +17 -0
  3. data/README.rdoc +6 -3
  4. data/Rakefile +3 -2
  5. data/VERSION +1 -1
  6. data/bin/mmconsole +56 -0
  7. data/lib/mongomapper.rb +48 -17
  8. data/lib/mongomapper/associations.rb +31 -39
  9. data/lib/mongomapper/associations/base.rb +40 -22
  10. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +33 -0
  11. data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
  12. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +34 -0
  13. data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +5 -5
  14. data/lib/mongomapper/associations/many_proxy.rb +55 -0
  15. data/lib/mongomapper/associations/proxy.rb +21 -14
  16. data/lib/mongomapper/callbacks.rb +1 -1
  17. data/lib/mongomapper/document.rb +82 -59
  18. data/lib/mongomapper/embedded_document.rb +121 -130
  19. data/lib/mongomapper/finder_options.rb +21 -6
  20. data/lib/mongomapper/key.rb +5 -7
  21. data/lib/mongomapper/observing.rb +1 -41
  22. data/lib/mongomapper/pagination.rb +52 -0
  23. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  24. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  25. data/lib/mongomapper/serialization.rb +1 -1
  26. data/mongomapper.gemspec +62 -36
  27. data/test/NOTE_ON_TESTING +1 -0
  28. data/test/functional/test_associations.rb +485 -0
  29. data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
  30. data/test/functional/test_document.rb +636 -0
  31. data/test/functional/test_pagination.rb +82 -0
  32. data/test/functional/test_rails_compatibility.rb +31 -0
  33. data/test/functional/test_validations.rb +172 -0
  34. data/test/models.rb +92 -0
  35. data/test/test_helper.rb +5 -0
  36. data/test/{serializers → unit/serializers}/test_json_serializer.rb +0 -0
  37. data/test/unit/test_association_base.rb +131 -0
  38. data/test/unit/test_document.rb +115 -0
  39. data/test/{test_embedded_document.rb → unit/test_embedded_document.rb} +158 -66
  40. data/test/{test_finder_options.rb → unit/test_finder_options.rb} +66 -0
  41. data/test/{test_key.rb → unit/test_key.rb} +13 -1
  42. data/test/unit/test_mongo_id.rb +35 -0
  43. data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
  44. data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
  45. data/test/unit/test_pagination.rb +113 -0
  46. data/test/unit/test_rails_compatibility.rb +34 -0
  47. data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
  48. data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
  49. metadata +68 -36
  50. data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
  51. data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
  52. data/lib/mongomapper/rails_compatibility.rb +0 -23
  53. data/test/test_associations.rb +0 -149
  54. data/test/test_document.rb +0 -944
  55. data/test/test_rails_compatibility.rb +0 -29
@@ -1,57 +1,104 @@
1
1
  require 'observer'
2
2
 
3
3
  module MongoMapper
4
- module EmbeddedDocument
5
- class NotImplemented < StandardError; end
6
-
4
+ module EmbeddedDocument
7
5
  def self.included(model)
8
6
  model.class_eval do
9
7
  extend ClassMethods
10
8
  include InstanceMethods
11
-
9
+
12
10
  extend Associations::ClassMethods
13
11
  include Associations::InstanceMethods
14
-
12
+
13
+ include RailsCompatibility::EmbeddedDocument
15
14
  include Validatable
16
15
  include Serialization
17
16
  end
18
17
  end
19
-
18
+
20
19
  module ClassMethods
20
+ def inherited(subclass)
21
+ (@subclasses ||= []) << subclass
22
+ end
23
+
24
+ def subclasses
25
+ @subclasses
26
+ end
27
+
21
28
  def keys
22
- @keys ||= HashWithIndifferentAccess.new
29
+ @keys ||= if parent = parent_model
30
+ parent.keys.dup
31
+ else
32
+ HashWithIndifferentAccess.new
33
+ end
23
34
  end
24
-
25
- def key(name, type, options={})
35
+
36
+ def key(name, type, options={})
26
37
  key = Key.new(name, type, options)
27
38
  keys[key.name] = key
39
+
40
+ create_accessors_for(key)
41
+ add_to_subclasses(name, type, options)
28
42
  apply_validations_for(key)
29
43
  create_indexes_for(key)
44
+
30
45
  key
31
46
  end
32
-
47
+
48
+ def create_accessors_for(key)
49
+ define_method(key.name) do
50
+ read_attribute(key.name)
51
+ end
52
+
53
+ define_method("#{key.name}_before_typecast") do
54
+ read_attribute_before_typecast(key.name)
55
+ end
56
+
57
+ define_method("#{key.name}=") do |value|
58
+ write_attribute(key.name, value)
59
+ end
60
+
61
+ define_method("#{key.name}?") do
62
+ read_attribute(key.name).present?
63
+ end
64
+ end
65
+
66
+ def add_to_subclasses(name, type, options)
67
+ return if subclasses.blank?
68
+
69
+ subclasses.each do |subclass|
70
+ subclass.key name, type, options
71
+ end
72
+ end
73
+
33
74
  def ensure_index(name_or_array, options={})
34
75
  keys_to_index = if name_or_array.is_a?(Array)
35
76
  name_or_array.map { |pair| [pair[0], pair[1]] }
36
77
  else
37
78
  name_or_array
38
79
  end
39
-
80
+
40
81
  collection.create_index(keys_to_index, options.delete(:unique))
41
82
  end
42
-
83
+
43
84
  def embeddable?
44
85
  !self.ancestors.include?(Document)
45
86
  end
46
-
87
+
88
+ def parent_model
89
+ if parent = ancestors[1]
90
+ parent if parent.ancestors.include?(EmbeddedDocument)
91
+ end
92
+ end
93
+
47
94
  private
48
95
  def create_indexes_for(key)
49
96
  ensure_index key.name if key.options[:index]
50
97
  end
51
-
98
+
52
99
  def apply_validations_for(key)
53
100
  attribute = key.name.to_sym
54
-
101
+
55
102
  if key.options[:required]
56
103
  validates_presence_of(attribute)
57
104
  end
@@ -59,16 +106,16 @@ module MongoMapper
59
106
  if key.options[:unique]
60
107
  validates_uniqueness_of(attribute)
61
108
  end
62
-
109
+
63
110
  if key.options[:numeric]
64
111
  number_options = key.type == Integer ? {:only_integer => true} : {}
65
112
  validates_numericality_of(attribute, number_options)
66
113
  end
67
-
114
+
68
115
  if key.options[:format]
69
116
  validates_format_of(attribute, :with => key.options[:format])
70
117
  end
71
-
118
+
72
119
  if key.options[:length]
73
120
  length_options = case key.options[:length]
74
121
  when Integer
@@ -81,147 +128,91 @@ module MongoMapper
81
128
  validates_length_of(attribute, length_options)
82
129
  end
83
130
  end
84
-
85
131
  end
86
-
132
+
87
133
  module InstanceMethods
88
134
  def initialize(attrs={})
89
135
  unless attrs.nil?
90
- initialize_associations(attrs)
136
+ self.class.associations.each_pair do |name, association|
137
+ if collection = attrs.delete(name)
138
+ send("#{association.name}=", collection)
139
+ end
140
+ end
141
+
91
142
  self.attributes = attrs
92
143
  end
93
144
  end
94
-
145
+
95
146
  def attributes=(attrs)
96
147
  return if attrs.blank?
97
- attrs.each_pair do |key_name, value|
98
- if writer?(key_name)
99
- write_attribute(key_name, value)
100
- else
101
- writer_method ="#{key_name}="
102
- self.send(writer_method, value) if respond_to?(writer_method)
103
- end
148
+ attrs.each_pair do |method, value|
149
+ self.send("#{method}=", value)
104
150
  end
105
151
  end
106
-
152
+
107
153
  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
154
+ returning HashWithIndifferentAccess.new do |attributes|
155
+ self.class.keys.each_pair do |name, key|
156
+ value = value_for_key(key)
157
+ attributes[name] = value unless value.nil?
158
+ end
159
+
160
+ attributes.merge!(embedded_association_attributes)
113
161
  end
114
162
  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)
128
- end
129
-
163
+
130
164
  def [](name)
131
165
  read_attribute(name)
132
166
  end
133
-
167
+
134
168
  def []=(name, value)
135
169
  write_attribute(name, value)
136
170
  end
137
-
138
- def method_missing(method, *args, &block)
139
- attribute = method.to_s
140
-
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
150
- end
151
-
171
+
152
172
  def ==(other)
153
173
  other.is_a?(self.class) && attributes == other.attributes
154
174
  end
155
-
175
+
156
176
  def inspect
157
- attributes_as_nice_string = defined_key_names.collect do |name|
177
+ attributes_as_nice_string = self.class.keys.keys.collect do |name|
158
178
  "#{name}: #{read_attribute(name)}"
159
179
  end.join(", ")
160
180
  "#<#{self.class} #{attributes_as_nice_string}>"
161
181
  end
162
-
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
177
- 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
-
193
- def defined_key(name)
194
- self.class.keys[name]
195
- end
196
-
197
- def defined_key_names
198
- self.class.keys.keys
199
- end
200
-
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
182
+
183
+ private
184
+ def value_for_key(key)
185
+ if key.native?
186
+ read_attribute(key.name)
187
+ else
188
+ embedded_document = read_attribute(key.name)
189
+ embedded_document && embedded_document.attributes
212
190
  end
213
191
  end
214
-
215
- attributes
216
- end
217
-
218
- def initialize_associations(attrs={})
219
- self.class.associations.each_pair do |name, association|
220
- if collection = attrs.delete(name)
221
- __send__("#{association.name}=", collection)
192
+
193
+ def read_attribute(name)
194
+ self.class.keys[name].get(instance_variable_get("@#{name}"))
195
+ end
196
+
197
+ def read_attribute_before_typecast(name)
198
+ instance_variable_get("@#{name}_before_typecast")
199
+ end
200
+
201
+ def write_attribute(name, value)
202
+ instance_variable_set "@#{name}_before_typecast", value
203
+ instance_variable_set "@#{name}", self.class.keys[name].set(value)
204
+ end
205
+
206
+ def embedded_association_attributes
207
+ returning HashWithIndifferentAccess.new do |attrs|
208
+ self.class.associations.each_pair do |name, association|
209
+ next unless association.embeddable?
210
+ next unless documents = instance_variable_get(association.ivar)
211
+
212
+ attrs[name] = documents.collect { |doc| doc.attributes }
213
+ end
222
214
  end
223
215
  end
224
- end
225
- end
226
- end
227
- end
216
+ end # InstanceMethods
217
+ end # EmbeddedDocument
218
+ end # MongoMapper
@@ -2,30 +2,45 @@ 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
+
12
+ dealing_with_ids = field.to_s == '_id' ||
13
+ (parent_key && parent_key.to_s == '_id')
14
+
15
+ criteria[field] = if dealing_with_ids
16
+ ids = value.map { |id| MongoID.mm_typecast(id) }
17
+ operator_present ? ids : {'$in' => ids}
18
+ elsif operator_present
19
+ value
20
+ else
21
+ {'$in' => value}
22
+ end
12
23
  when Hash
13
- criteria[field] = to_mongo_criteria(value)
24
+ criteria[field] = to_mongo_criteria(value, field)
14
25
  else
26
+ if field.to_s == '_id'
27
+ value = MongoID.mm_typecast(value)
28
+ end
29
+
15
30
  criteria[field] = value
16
31
  end
17
32
  end
18
33
 
19
34
  criteria
20
35
  end
21
-
36
+
22
37
  def self.to_mongo_options(options)
23
38
  options = options.dup
24
39
  {
25
40
  :fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
26
41
  :offset => (options.delete(:offset) || 0).to_i,
27
42
  :limit => (options.delete(:limit) || 0).to_i,
28
- :sort => to_mongo_sort(options.delete(:order))
43
+ :sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
29
44
  }
30
45
  end
31
46
 
@@ -1,17 +1,14 @@
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, MongoID]
8
5
 
9
6
  attr_accessor :name, :type, :options, :default_value
10
7
 
11
8
  def initialize(name, type, options={})
12
9
  @name, @type = name.to_s, type
13
- self.options = options.symbolize_keys
14
- self.default_value = options.delete(:default)
10
+ self.options = (options || {}).symbolize_keys
11
+ self.default_value = self.options.delete(:default)
15
12
  end
16
13
 
17
14
  def ==(other)
@@ -50,8 +47,9 @@ module MongoMapper
50
47
  elsif type == Float then value.to_f
51
48
  elsif type == Array then value.to_a
52
49
  elsif type == Time then Time.parse(value.to_s)
50
+ elsif type == MongoID then MongoID.mm_typecast(value)
53
51
  #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 == Boolean then Boolean.mm_typecast(value)
55
53
  elsif type == Integer
56
54
  # ganked from datamapper
57
55
  value_to_i = value.to_i
@@ -7,39 +7,8 @@ module MongoMapper
7
7
  def self.included(model)
8
8
  model.class_eval do
9
9
  extend Observable
10
- extend ClassMethods
11
10
  end
12
11
  end
13
-
14
- module ClassMethods
15
- def observers=(*observers)
16
- @observers = observers.flatten
17
- end
18
-
19
- def observers
20
- @observers ||= []
21
- end
22
-
23
- def instantiate_observers
24
- return if @observers.blank?
25
- @observers.each do |observer|
26
- if observer.respond_to?(:to_sym) # Symbol or String
27
- observer.to_s.camelize.constantize.instance
28
- elsif observer.respond_to?(:instance)
29
- observer.instance
30
- else
31
- raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
32
- end
33
- end
34
- end
35
-
36
- protected
37
- def inherited(subclass)
38
- super
39
- changed
40
- notify_observers :observed_class_inherited, subclass
41
- end
42
- end
43
12
  end
44
13
 
45
14
  class Observer
@@ -62,27 +31,18 @@ module MongoMapper
62
31
  end
63
32
 
64
33
  def initialize
65
- Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
34
+ Set.new(observed_classes).each { |klass| add_observer! klass }
66
35
  end
67
36
 
68
37
  def update(observed_method, object) #:nodoc:
69
38
  send(observed_method, object) if respond_to?(observed_method)
70
39
  end
71
40
 
72
- def observed_class_inherited(subclass) #:nodoc:
73
- self.class.observe(observed_classes + [subclass])
74
- add_observer!(subclass)
75
- end
76
-
77
41
  protected
78
42
  def observed_classes
79
43
  Set.new([self.class.observed_class].compact.flatten)
80
44
  end
81
45
 
82
- def observed_subclasses
83
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
84
- end
85
-
86
46
  def add_observer!(klass)
87
47
  klass.add_observer(self)
88
48
  end