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.
- data/.gitignore +1 -0
- data/History +17 -0
- data/README.rdoc +6 -3
- data/Rakefile +3 -2
- data/VERSION +1 -1
- data/bin/mmconsole +56 -0
- data/lib/mongomapper.rb +48 -17
- data/lib/mongomapper/associations.rb +31 -39
- data/lib/mongomapper/associations/base.rb +40 -22
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +33 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
- data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +34 -0
- data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +5 -5
- data/lib/mongomapper/associations/many_proxy.rb +55 -0
- data/lib/mongomapper/associations/proxy.rb +21 -14
- data/lib/mongomapper/callbacks.rb +1 -1
- data/lib/mongomapper/document.rb +82 -59
- data/lib/mongomapper/embedded_document.rb +121 -130
- data/lib/mongomapper/finder_options.rb +21 -6
- data/lib/mongomapper/key.rb +5 -7
- data/lib/mongomapper/observing.rb +1 -41
- data/lib/mongomapper/pagination.rb +52 -0
- data/lib/mongomapper/rails_compatibility/document.rb +15 -0
- data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
- data/lib/mongomapper/serialization.rb +1 -1
- data/mongomapper.gemspec +62 -36
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/test_associations.rb +485 -0
- data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
- data/test/functional/test_document.rb +636 -0
- data/test/functional/test_pagination.rb +82 -0
- data/test/functional/test_rails_compatibility.rb +31 -0
- data/test/functional/test_validations.rb +172 -0
- data/test/models.rb +92 -0
- data/test/test_helper.rb +5 -0
- data/test/{serializers → unit/serializers}/test_json_serializer.rb +0 -0
- data/test/unit/test_association_base.rb +131 -0
- data/test/unit/test_document.rb +115 -0
- data/test/{test_embedded_document.rb → unit/test_embedded_document.rb} +158 -66
- data/test/{test_finder_options.rb → unit/test_finder_options.rb} +66 -0
- data/test/{test_key.rb → unit/test_key.rb} +13 -1
- data/test/unit/test_mongo_id.rb +35 -0
- data/test/{test_mongomapper.rb → unit/test_mongomapper.rb} +0 -0
- data/test/{test_observing.rb → unit/test_observing.rb} +0 -0
- data/test/unit/test_pagination.rb +113 -0
- data/test/unit/test_rails_compatibility.rb +34 -0
- data/test/{test_serializations.rb → unit/test_serializations.rb} +0 -2
- data/test/{test_validations.rb → unit/test_validations.rb} +0 -134
- metadata +68 -36
- data/lib/mongomapper/associations/has_many_proxy.rb +0 -28
- data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +0 -31
- data/lib/mongomapper/rails_compatibility.rb +0 -23
- data/test/test_associations.rb +0 -149
- data/test/test_document.rb +0 -944
- 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 ||=
|
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
|
-
|
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 |
|
98
|
-
|
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
|
-
|
109
|
-
name, key
|
110
|
-
|
111
|
-
|
112
|
-
|
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 =
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
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
|
-
|
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
|
|
data/lib/mongomapper/key.rb
CHANGED
@@ -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,
|
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
|
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
|
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
|