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.
- data/.gitignore +1 -0
- data/History +48 -0
- data/README.rdoc +5 -3
- data/Rakefile +6 -4
- data/VERSION +1 -1
- data/bin/mmconsole +56 -0
- data/lib/mongomapper.rb +29 -18
- data/lib/mongomapper/associations.rb +53 -38
- data/lib/mongomapper/associations/base.rb +53 -20
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +10 -14
- data/lib/mongomapper/associations/many_documents_as_proxy.rb +27 -0
- data/lib/mongomapper/associations/many_documents_proxy.rb +103 -0
- data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongomapper/associations/{has_many_embedded_proxy.rb → many_embedded_proxy.rb} +6 -8
- data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongomapper/associations/{array_proxy.rb → many_proxy.rb} +1 -1
- data/lib/mongomapper/associations/proxy.rb +24 -21
- data/lib/mongomapper/callbacks.rb +1 -1
- data/lib/mongomapper/document.rb +160 -74
- data/lib/mongomapper/dynamic_finder.rb +38 -0
- data/lib/mongomapper/embedded_document.rb +154 -105
- data/lib/mongomapper/finder_options.rb +11 -7
- data/lib/mongomapper/key.rb +15 -21
- 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/lib/mongomapper/serializers/json_serializer.rb +15 -0
- data/lib/mongomapper/support.rb +30 -0
- data/mongomapper.gemspec +87 -46
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +253 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
- data/test/functional/associations/test_many_proxy.rb +295 -0
- data/test/functional/test_associations.rb +47 -0
- data/test/{test_callbacks.rb → functional/test_callbacks.rb} +2 -1
- data/test/functional/test_document.rb +952 -0
- data/test/functional/test_pagination.rb +81 -0
- data/test/functional/test_rails_compatibility.rb +30 -0
- data/test/functional/test_validations.rb +172 -0
- data/test/models.rb +169 -0
- data/test/test_helper.rb +7 -2
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_association_base.rb +144 -0
- data/test/unit/test_document.rb +123 -0
- data/test/unit/test_embedded_document.rb +526 -0
- data/test/{test_finder_options.rb → unit/test_finder_options.rb} +36 -1
- data/test/{test_key.rb → unit/test_key.rb} +59 -12
- 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 +81 -43
- 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/serializers/test_json_serializer.rb +0 -104
- data/test/test_associations.rb +0 -174
- data/test/test_document.rb +0 -944
- data/test/test_embedded_document.rb +0 -253
- 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 ||=
|
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(
|
26
|
-
key = Key.new(
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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 |
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
-
|
109
|
-
|
110
|
-
value =
|
111
|
-
|
112
|
-
|
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
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
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
|
139
|
-
|
219
|
+
def ==(other)
|
220
|
+
other.is_a?(self.class) && id == other.id
|
221
|
+
end
|
140
222
|
|
141
|
-
|
142
|
-
|
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
|
153
|
-
|
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 =
|
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
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
198
|
-
|
199
|
-
|
254
|
+
def read_attribute_before_typecast(name)
|
255
|
+
instance_variable_get("@#{name}_before_typecast")
|
256
|
+
end
|
200
257
|
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
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
|
|
data/lib/mongomapper/key.rb
CHANGED
@@ -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
|
4
|
+
NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash]
|
8
5
|
|
9
6
|
attr_accessor :name, :type, :options, :default_value
|
10
|
-
|
11
|
-
def initialize(
|
12
|
-
|
13
|
-
|
14
|
-
self.
|
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.
|
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
|
-
|
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
|
59
|
-
|
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
|