mongo_odm 0.1.8
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/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.rdoc +251 -0
- data/Rakefile +70 -0
- data/lib/mongo_odm.rb +48 -0
- data/lib/mongo_odm/collection.rb +42 -0
- data/lib/mongo_odm/core_ext/conversions.rb +262 -0
- data/lib/mongo_odm/cursor.rb +11 -0
- data/lib/mongo_odm/document.rb +34 -0
- data/lib/mongo_odm/document/associations.rb +39 -0
- data/lib/mongo_odm/document/associations/has_many.rb +19 -0
- data/lib/mongo_odm/document/associations/has_one.rb +19 -0
- data/lib/mongo_odm/document/attribute_methods.rb +103 -0
- data/lib/mongo_odm/document/attribute_methods/dirty.rb +51 -0
- data/lib/mongo_odm/document/attribute_methods/localization.rb +67 -0
- data/lib/mongo_odm/document/attribute_methods/query.rb +35 -0
- data/lib/mongo_odm/document/attribute_methods/read.rb +39 -0
- data/lib/mongo_odm/document/attribute_methods/write.rb +37 -0
- data/lib/mongo_odm/document/callbacks.rb +29 -0
- data/lib/mongo_odm/document/fields.rb +66 -0
- data/lib/mongo_odm/document/inspect.rb +28 -0
- data/lib/mongo_odm/document/persistence.rb +96 -0
- data/lib/mongo_odm/document/validations.rb +46 -0
- data/lib/mongo_odm/errors.rb +18 -0
- data/lib/mongo_odm/version.rb +7 -0
- data/spec/models/00-blank_slate.rb +4 -0
- data/spec/models/01-shape.rb +8 -0
- data/spec/models/02-circle.rb +7 -0
- data/spec/models/03-big_red_circle.rb +8 -0
- data/spec/mongo_odm/core_ext/conversions_spec.rb +423 -0
- data/spec/mongo_odm/document/fields_spec.rb +187 -0
- data/spec/mongo_odm/document/inspect_spec.rb +19 -0
- data/spec/mongo_odm/document_spec.rb +12 -0
- data/spec/mongo_odm/mongo_odm_spec.rb +84 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +12 -0
- metadata +185 -0
@@ -0,0 +1,262 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/big_decimal'
|
3
|
+
require 'active_support/core_ext/date/conversions'
|
4
|
+
require 'active_support/core_ext/date_time/calculations'
|
5
|
+
require 'active_support/core_ext/date_time/conversions'
|
6
|
+
require 'active_support/core_ext/string/conversions'
|
7
|
+
require 'active_support/core_ext/time/conversions'
|
8
|
+
|
9
|
+
# The type_cast class method is called when:
|
10
|
+
# * An attribute of the same class is assigned
|
11
|
+
# * An attribute of the same class is readed from the Mongo driver
|
12
|
+
#
|
13
|
+
# The to_mongo instance method is called when:
|
14
|
+
# * An attribute of the same class is sent to the Mongo driver
|
15
|
+
|
16
|
+
# @private
|
17
|
+
class Mongo::ObjectID
|
18
|
+
def self.type_cast(value)
|
19
|
+
return nil if value.nil?
|
20
|
+
return value if value.is_a?(Mongo::ObjectID)
|
21
|
+
value.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_mongo
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
class BSON::ObjectID
|
31
|
+
def self.type_cast(value)
|
32
|
+
return nil if value.nil?
|
33
|
+
return value if value.is_a?(BSON::ObjectID)
|
34
|
+
value.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_mongo
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @private
|
43
|
+
class Array
|
44
|
+
def self.type_cast(value)
|
45
|
+
return nil if value.nil?
|
46
|
+
value.to_a.map {|elem| MongoODM.instanciate(elem)}
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_mongo
|
50
|
+
self.map {|elem| elem.to_mongo}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @private
|
55
|
+
class Class
|
56
|
+
def self.type_cast(value)
|
57
|
+
return nil if value.nil?
|
58
|
+
value.to_s.constantize
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_mongo
|
62
|
+
self.name
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @private
|
67
|
+
class Symbol
|
68
|
+
def self.type_cast(value)
|
69
|
+
return nil if value.nil?
|
70
|
+
value.to_s.intern
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_mongo
|
74
|
+
self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @private
|
79
|
+
class Integer
|
80
|
+
def self.type_cast(value)
|
81
|
+
return nil if value.nil?
|
82
|
+
value.to_i
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_mongo
|
86
|
+
self
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @private
|
91
|
+
class Float
|
92
|
+
def self.type_cast(value)
|
93
|
+
return nil if value.nil?
|
94
|
+
value.to_f
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_mongo
|
98
|
+
self
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @private
|
103
|
+
class BigDecimal
|
104
|
+
def self.type_cast(value)
|
105
|
+
return nil if value.nil?
|
106
|
+
value.is_a?(BigDecimal) ? value : new(value.to_s)
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_mongo
|
110
|
+
self.to_s
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @private
|
115
|
+
class String
|
116
|
+
def self.type_cast(value)
|
117
|
+
return nil if value.nil?
|
118
|
+
value.to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_mongo
|
122
|
+
self
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @private
|
127
|
+
class Date
|
128
|
+
def self.type_cast(value)
|
129
|
+
return nil if value.nil?
|
130
|
+
value.to_date
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_mongo
|
134
|
+
Time.utc(self.year, self.month, self.day)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @private
|
139
|
+
class DateTime
|
140
|
+
def self.type_cast(value)
|
141
|
+
return nil if value.nil?
|
142
|
+
value.to_datetime
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_mongo
|
146
|
+
datetime = self.utc
|
147
|
+
Time.utc(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# @private
|
152
|
+
class TrueClass
|
153
|
+
def self.type_cast(value)
|
154
|
+
return nil if value.nil?
|
155
|
+
true
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_mongo
|
159
|
+
self
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# @private
|
164
|
+
class FalseClass
|
165
|
+
def self.type_cast(value)
|
166
|
+
return nil if value.nil?
|
167
|
+
false
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_mongo
|
171
|
+
self
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @private
|
176
|
+
class Time
|
177
|
+
def self.type_cast(value)
|
178
|
+
return nil if value.nil?
|
179
|
+
value.to_time
|
180
|
+
end
|
181
|
+
|
182
|
+
def to_mongo
|
183
|
+
self.utc
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Stand-in for true/false property types.
|
188
|
+
# @private
|
189
|
+
module Boolean
|
190
|
+
def self.type_cast(value)
|
191
|
+
case value
|
192
|
+
when NilClass
|
193
|
+
nil
|
194
|
+
when Numeric
|
195
|
+
!value.zero?
|
196
|
+
when TrueClass, FalseClass
|
197
|
+
value
|
198
|
+
when /^\s*t/i
|
199
|
+
true
|
200
|
+
when /^\s*f/i
|
201
|
+
false
|
202
|
+
else
|
203
|
+
value.present?
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @private
|
209
|
+
class Numeric
|
210
|
+
def self.type_cast(value)
|
211
|
+
return nil if value.nil?
|
212
|
+
float_value = value.to_f
|
213
|
+
int_value = value.to_i
|
214
|
+
float_value == int_value ? int_value : float_value
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# @private
|
219
|
+
class Hash
|
220
|
+
def self.type_cast(value)
|
221
|
+
return nil if value.nil?
|
222
|
+
Hash[value.to_hash.map{|k,v| [MongoODM.instanciate(k), MongoODM.instanciate(v)]}]
|
223
|
+
end
|
224
|
+
|
225
|
+
def to_mongo
|
226
|
+
Hash[self.map{|k,v| [k.to_mongo, v.to_mongo]}]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# @private
|
231
|
+
class HashWithIndifferentAccess
|
232
|
+
def self.type_cast(value)
|
233
|
+
Hash.type_cast(value).with_indifferent_access
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_mongo
|
237
|
+
Hash[self.map{|k,v| [k.to_mongo, v.to_mongo]}]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
# @private
|
242
|
+
class Regexp
|
243
|
+
def self.type_cast(value)
|
244
|
+
return nil if value.nil?
|
245
|
+
new(value)
|
246
|
+
end
|
247
|
+
|
248
|
+
def to_mongo
|
249
|
+
self
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# @private
|
254
|
+
class NilClass
|
255
|
+
def self.type_cast(value)
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
|
259
|
+
def to_mongo
|
260
|
+
nil
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'mongo_odm'
|
3
|
+
|
4
|
+
module MongoODM
|
5
|
+
module Document
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :Associations
|
11
|
+
autoload :AttributeMethods
|
12
|
+
autoload :Callbacks
|
13
|
+
autoload :Fields
|
14
|
+
autoload :Inspect
|
15
|
+
autoload :Persistence
|
16
|
+
autoload :Validations
|
17
|
+
|
18
|
+
included do
|
19
|
+
include ActiveModel::Conversion
|
20
|
+
include ActiveModel::Serializers::JSON
|
21
|
+
include ActiveModel::Serializers::Xml
|
22
|
+
include ActiveModel::Observing
|
23
|
+
include ActiveModel::Translation
|
24
|
+
include MongoODM::Document::Persistence
|
25
|
+
include MongoODM::Document::AttributeMethods
|
26
|
+
include MongoODM::Document::Fields
|
27
|
+
include MongoODM::Document::Inspect
|
28
|
+
include MongoODM::Document::Associations
|
29
|
+
include MongoODM::Document::Callbacks
|
30
|
+
include MongoODM::Document::Validations
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module MongoODM
|
4
|
+
module Document
|
5
|
+
module Associations
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
extend ActiveSupport::Autoload
|
9
|
+
|
10
|
+
autoload :HasOne
|
11
|
+
autoload :HasMany
|
12
|
+
|
13
|
+
included do
|
14
|
+
class_inheritable_accessor :associations
|
15
|
+
self.associations = {}
|
16
|
+
|
17
|
+
include MongoODM::Document::Associations::HasOne
|
18
|
+
include MongoODM::Document::Associations::HasMany
|
19
|
+
end
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def associations
|
23
|
+
self.class.associations
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def associate(type, options)
|
29
|
+
name = options.name.to_s
|
30
|
+
associations[name] = MetaData.new(type, options)
|
31
|
+
define_method(name) { memoized(name) { type.instantiate(self, options) } }
|
32
|
+
define_method("#{name}=") { |object| reset(name) { type.update(object, self, options) } }
|
33
|
+
end
|
34
|
+
protected :associate
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
3
|
+
|
4
|
+
module MongoODM
|
5
|
+
module Document
|
6
|
+
module AttributeMethods
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
extend ActiveSupport::Autoload
|
10
|
+
|
11
|
+
autoload :Read
|
12
|
+
autoload :Write
|
13
|
+
autoload :Query
|
14
|
+
autoload :Dirty
|
15
|
+
autoload :Localization
|
16
|
+
|
17
|
+
included do
|
18
|
+
include ActiveModel::AttributeMethods
|
19
|
+
include MongoODM::Document::AttributeMethods::Read
|
20
|
+
include MongoODM::Document::AttributeMethods::Write
|
21
|
+
include MongoODM::Document::AttributeMethods::Query
|
22
|
+
include MongoODM::Document::AttributeMethods::Dirty
|
23
|
+
include MongoODM::Document::AttributeMethods::Localization
|
24
|
+
end
|
25
|
+
|
26
|
+
module InstanceMethods
|
27
|
+
attr_reader :attributes
|
28
|
+
|
29
|
+
def initialize(attrs = {})
|
30
|
+
@attributes = {:_class => self.class.name}.with_indifferent_access
|
31
|
+
load_attributes_or_defaults(attrs)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def force_attributes=(new_attributes)
|
36
|
+
send(:attributes=, new_attributes, true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def attributes=(new_attributes, auto_generate_attributes = false)
|
40
|
+
return if new_attributes.blank?
|
41
|
+
new_attributes.each do |name, value|
|
42
|
+
if respond_to?(:"#{name}=")
|
43
|
+
send(:"#{name}=", value)
|
44
|
+
else
|
45
|
+
auto_generate_attributes ? write_attribute(name, value) : raise(MongoODM::Errors::UnknownFieldError, "unknown field: #{name}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def freeze
|
51
|
+
@attributes.freeze; super
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_attribute?(name)
|
55
|
+
@attributes.has_key?(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_attributes_or_defaults(attrs)
|
59
|
+
attrs = self.class.default_attributes.merge(attrs)
|
60
|
+
self.force_attributes = attrs
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove_attribute(name)
|
64
|
+
@attributes.delete(name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def method_missing(method_id, *args, &block)
|
68
|
+
# If we haven't generated any methods yet, generate them, then
|
69
|
+
# see if we've created the method we're looking for.
|
70
|
+
if !self.class.attribute_methods_generated?
|
71
|
+
self.class.define_attribute_methods_for_fields
|
72
|
+
method_name = method_id.to_s
|
73
|
+
guard_private_attribute_method!(method_name, args)
|
74
|
+
send(method_id, *args, &block)
|
75
|
+
else
|
76
|
+
super
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def respond_to?(*args)
|
81
|
+
self.class.define_attribute_methods_for_fields
|
82
|
+
super
|
83
|
+
end
|
84
|
+
|
85
|
+
def attribute_method?(attr_name)
|
86
|
+
attr_name == '_id' || attributes.include?(attr_name)
|
87
|
+
end
|
88
|
+
protected :attribute_method?
|
89
|
+
end
|
90
|
+
|
91
|
+
module ClassMethods
|
92
|
+
def default_attributes
|
93
|
+
HashWithIndifferentAccess[fields.values.map{|field| [field.name, field.default]}]
|
94
|
+
end
|
95
|
+
|
96
|
+
def define_attribute_methods_for_fields
|
97
|
+
define_attribute_methods(fields.keys + [:_id, :_class])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|