mongo_mapper_ign 0.7.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 +10 -0
- data/LICENSE +20 -0
- data/README.rdoc +35 -0
- data/Rakefile +37 -0
- data/bin/mmconsole +60 -0
- data/lib/mongo_mapper.rb +116 -0
- data/lib/mongo_mapper/document.rb +313 -0
- data/lib/mongo_mapper/embedded_document.rb +70 -0
- data/lib/mongo_mapper/plugins.rb +35 -0
- data/lib/mongo_mapper/plugins/associations.rb +114 -0
- data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
- data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
- data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
- data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
- data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +39 -0
- data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +144 -0
- data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
- data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +129 -0
- data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
- data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
- data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
- data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +41 -0
- data/lib/mongo_mapper/plugins/associations/one_proxy.rb +69 -0
- data/lib/mongo_mapper/plugins/associations/proxy.rb +124 -0
- data/lib/mongo_mapper/plugins/callbacks.rb +240 -0
- data/lib/mongo_mapper/plugins/clone.rb +13 -0
- data/lib/mongo_mapper/plugins/descendants.rb +16 -0
- data/lib/mongo_mapper/plugins/dirty.rb +119 -0
- data/lib/mongo_mapper/plugins/equality.rb +23 -0
- data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
- data/lib/mongo_mapper/plugins/inspect.rb +14 -0
- data/lib/mongo_mapper/plugins/keys.rb +345 -0
- data/lib/mongo_mapper/plugins/logger.rb +17 -0
- data/lib/mongo_mapper/plugins/modifiers.rb +107 -0
- data/lib/mongo_mapper/plugins/pagination.rb +24 -0
- data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
- data/lib/mongo_mapper/plugins/persistence.rb +68 -0
- data/lib/mongo_mapper/plugins/protected.rb +45 -0
- data/lib/mongo_mapper/plugins/rails.rb +57 -0
- data/lib/mongo_mapper/plugins/serialization.rb +91 -0
- data/lib/mongo_mapper/plugins/serialization/array.rb +56 -0
- data/lib/mongo_mapper/plugins/serialization/xml_serializer.rb +240 -0
- data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
- data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
- data/lib/mongo_mapper/plugins/validations.rb +46 -0
- data/lib/mongo_mapper/query.rb +143 -0
- data/lib/mongo_mapper/support.rb +218 -0
- data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
- data/lib/mongo_mapper/support/find.rb +77 -0
- data/lib/mongo_mapper/version.rb +3 -0
- data/mongo_mapper.gemspec +214 -0
- data/mongo_mapper_ign.gemspec +217 -0
- data/performance/read_write.rb +52 -0
- data/specs.watchr +51 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/active_model_lint_test.rb +13 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
- data/test/functional/associations/test_in_array_proxy.rb +325 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
- data/test/functional/associations/test_many_documents_proxy.rb +536 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
- data/test/functional/associations/test_one_embedded_proxy.rb +68 -0
- data/test/functional/associations/test_one_proxy.rb +196 -0
- data/test/functional/test_associations.rb +44 -0
- data/test/functional/test_binary.rb +27 -0
- data/test/functional/test_callbacks.rb +151 -0
- data/test/functional/test_dirty.rb +163 -0
- data/test/functional/test_document.rb +1219 -0
- data/test/functional/test_embedded_document.rb +210 -0
- data/test/functional/test_identity_map.rb +507 -0
- data/test/functional/test_indexing.rb +44 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_modifiers.rb +394 -0
- data/test/functional/test_pagination.rb +93 -0
- data/test/functional/test_protected.rb +163 -0
- data/test/functional/test_string_id_compatibility.rb +67 -0
- data/test/functional/test_timestamps.rb +64 -0
- data/test/functional/test_userstamps.rb +28 -0
- data/test/functional/test_validations.rb +342 -0
- data/test/models.rb +227 -0
- data/test/support/custom_matchers.rb +37 -0
- data/test/support/timing.rb +16 -0
- data/test/test_helper.rb +64 -0
- data/test/unit/associations/test_base.rb +212 -0
- data/test/unit/associations/test_proxy.rb +105 -0
- data/test/unit/serializers/test_json_serializer.rb +202 -0
- data/test/unit/test_descendant_appends.rb +71 -0
- data/test/unit/test_document.rb +225 -0
- data/test/unit/test_dynamic_finder.rb +123 -0
- data/test/unit/test_embedded_document.rb +657 -0
- data/test/unit/test_keys.rb +185 -0
- data/test/unit/test_mongo_mapper.rb +118 -0
- data/test/unit/test_pagination.rb +160 -0
- data/test/unit/test_plugins.rb +50 -0
- data/test/unit/test_query.rb +374 -0
- data/test/unit/test_rails.rb +181 -0
- data/test/unit/test_rails_compatibility.rb +52 -0
- data/test/unit/test_serialization.rb +51 -0
- data/test/unit/test_support.rb +382 -0
- data/test/unit/test_time_zones.rb +39 -0
- data/test/unit/test_validations.rb +544 -0
- metadata +327 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Credit: http://github.com/tsxn26/array-xml_serialization
|
|
2
|
+
|
|
3
|
+
# Extends the XML serialization support in activesupport to allow for
|
|
4
|
+
# arrays containing strings, symbols, and integers.
|
|
5
|
+
|
|
6
|
+
# Forces elements in arrays to be output using to_xml on each element if the
|
|
7
|
+
# element responds to to_xml. If an element does not respond to to_xml then a
|
|
8
|
+
# nested XML tag is created with the element's to_s value and the singlarized name
|
|
9
|
+
# of the array as the tag name.
|
|
10
|
+
|
|
11
|
+
require 'rubygems'
|
|
12
|
+
require 'activesupport'
|
|
13
|
+
require 'builder'
|
|
14
|
+
|
|
15
|
+
class Array
|
|
16
|
+
def to_xml(options = {})
|
|
17
|
+
#raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
|
|
18
|
+
require 'builder' unless defined?(Builder)
|
|
19
|
+
|
|
20
|
+
options = options.dup
|
|
21
|
+
options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
|
|
22
|
+
options[:children] ||= options[:root].singularize
|
|
23
|
+
options[:indent] ||= 2
|
|
24
|
+
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
|
25
|
+
|
|
26
|
+
root = options.delete(:root).to_s
|
|
27
|
+
children = options.delete(:children)
|
|
28
|
+
|
|
29
|
+
if !options.has_key?(:dasherize) || options[:dasherize]
|
|
30
|
+
root = root.dasherize
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
|
34
|
+
|
|
35
|
+
opts = options.merge({ :root => children })
|
|
36
|
+
|
|
37
|
+
root = root.pluralize
|
|
38
|
+
|
|
39
|
+
xml = options[:builder]
|
|
40
|
+
if empty?
|
|
41
|
+
xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
|
|
42
|
+
else
|
|
43
|
+
xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) do
|
|
44
|
+
yield xml if block_given?
|
|
45
|
+
each do |e|
|
|
46
|
+
if e.respond_to? :to_xml
|
|
47
|
+
e.to_xml(opts.merge({ :skip_instruct => true }))
|
|
48
|
+
else
|
|
49
|
+
xml.tag!(root.singularize, e.to_s)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
module MongoMapper
|
|
2
|
+
module Plugins
|
|
3
|
+
module Serialization
|
|
4
|
+
|
|
5
|
+
class XmlSerializer #:nodoc:
|
|
6
|
+
attr_reader :options
|
|
7
|
+
|
|
8
|
+
def initialize(record, options = {})
|
|
9
|
+
@record, @options = record, options.dup
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def builder
|
|
13
|
+
@builder ||= begin
|
|
14
|
+
options[:indent] ||= 2
|
|
15
|
+
builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
|
16
|
+
|
|
17
|
+
unless options[:skip_instruct]
|
|
18
|
+
builder.instruct!
|
|
19
|
+
options[:skip_instruct] = true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
builder
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def root
|
|
27
|
+
root = (options[:root] || @record.class.to_s.underscore).to_s
|
|
28
|
+
dasherize? ? root.dasherize : root
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def dasherize?
|
|
32
|
+
!options.has_key?(:dasherize) || options[:dasherize]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# To replicate the behavior in ActiveRecord#attributes,
|
|
37
|
+
# :except takes precedence over :only. If :only is not set
|
|
38
|
+
# for a N level model but is set for the N+1 level models,
|
|
39
|
+
# then because :except is set to a default value, the second
|
|
40
|
+
# level model can have both :except and :only set. So if
|
|
41
|
+
# :only is set, always delete :except.
|
|
42
|
+
def serializable_attributes
|
|
43
|
+
#attribute_names = @record.attributes.keys # This includes all attributes including associations
|
|
44
|
+
attribute_names = @record.class.keys.keys # This includes just keys
|
|
45
|
+
idex = attribute_names.index("_id")
|
|
46
|
+
attribute_names[idex] = "id" if idex
|
|
47
|
+
|
|
48
|
+
if options[:only]
|
|
49
|
+
options.delete(:except)
|
|
50
|
+
attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
|
|
51
|
+
else
|
|
52
|
+
options[:except] = Array(options[:except])
|
|
53
|
+
attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
attribute_names.collect { |name| Attribute.new(name, @record) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def serializable_method_attributes
|
|
60
|
+
Array(options[:methods]).collect { |name| MethodAttribute.new(name.to_s, @record) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_attributes
|
|
64
|
+
(serializable_attributes + serializable_method_attributes).each do |attribute|
|
|
65
|
+
add_tag(attribute)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def add_includes
|
|
70
|
+
if include_associations = options.delete(:include)
|
|
71
|
+
root_only_or_except = { :except => options[:except],
|
|
72
|
+
:only => options[:only] }
|
|
73
|
+
|
|
74
|
+
include_has_options = include_associations.is_a?(Hash)
|
|
75
|
+
|
|
76
|
+
for association in include_has_options ? include_associations.keys : Array(include_associations)
|
|
77
|
+
association_options = include_has_options ? include_associations[association] : root_only_or_except
|
|
78
|
+
|
|
79
|
+
opts = options.merge(association_options)
|
|
80
|
+
|
|
81
|
+
case @record.class.associations[association].type
|
|
82
|
+
when :many, :has_and_belongs_to_many
|
|
83
|
+
records = @record.send(association).to_a
|
|
84
|
+
unless records.empty?
|
|
85
|
+
tag = association.to_s
|
|
86
|
+
tag = tag.dasherize if dasherize?
|
|
87
|
+
|
|
88
|
+
builder.tag!(tag) do
|
|
89
|
+
records.each { |r| r.to_xml(opts.merge(:root=>r.class.to_s.underscore)) }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
when :has_one, :belongs_to
|
|
93
|
+
if record = @record.send(association)
|
|
94
|
+
record.to_xml(opts.merge(:root => association))
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
options[:include] = include_associations
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def add_procs
|
|
104
|
+
if procs = options.delete(:procs)
|
|
105
|
+
[ *procs ].each do |proc|
|
|
106
|
+
proc.call(options)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def add_tag(attribute)
|
|
113
|
+
if attribute.type == :array
|
|
114
|
+
builder.tag!(
|
|
115
|
+
dasherize? ? attribute.name.dasherize.pluralize : attribute.name.pluralize,
|
|
116
|
+
attribute.decorations(!options[:skip_types])
|
|
117
|
+
) do |x|
|
|
118
|
+
attribute.value.each do |val|
|
|
119
|
+
if val.respond_to? :to_xml
|
|
120
|
+
x << val.to_xml(:skip_instruct => true, :root => attribute.name.dasherize.singularize)
|
|
121
|
+
else
|
|
122
|
+
x.tag!(
|
|
123
|
+
dasherize? ? attribute.name.dasherize.singularize : attribute.name.singularize,
|
|
124
|
+
val.to_s
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
else
|
|
130
|
+
builder.tag!(
|
|
131
|
+
dasherize? ? attribute.name.dasherize : attribute.name,
|
|
132
|
+
attribute.value.to_s,
|
|
133
|
+
attribute.decorations(!options[:skip_types])
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def serialize
|
|
139
|
+
args = [root]
|
|
140
|
+
if options[:namespace]
|
|
141
|
+
args << {:xmlns=>options[:namespace]}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
builder.tag!(*args) do
|
|
145
|
+
add_attributes
|
|
146
|
+
add_includes
|
|
147
|
+
add_procs
|
|
148
|
+
yield builder if block_given?
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
alias_method :to_s, :serialize
|
|
153
|
+
|
|
154
|
+
class Attribute #:nodoc:
|
|
155
|
+
attr_reader :name, :value, :type
|
|
156
|
+
|
|
157
|
+
def initialize(name, record)
|
|
158
|
+
@name, @record = name, record
|
|
159
|
+
|
|
160
|
+
@type = compute_type
|
|
161
|
+
@value = compute_value
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# There is a significant speed improvement if the value
|
|
165
|
+
# does not need to be escaped, as #tag! escapes all values
|
|
166
|
+
# to ensure that valid XML is generated. For known binary
|
|
167
|
+
# values, it is at least an order of magnitude faster to
|
|
168
|
+
# Base64 encode binary values and directly put them in the
|
|
169
|
+
# output XML than to pass the original value or the Base64
|
|
170
|
+
# encoded value to the #tag! method. It definitely makes
|
|
171
|
+
# no sense to Base64 encode the value and then give it to
|
|
172
|
+
# #tag!, since that just adds additional overhead.
|
|
173
|
+
def needs_encoding?
|
|
174
|
+
![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def decorations(include_types = true)
|
|
178
|
+
decorations = {}
|
|
179
|
+
|
|
180
|
+
if type == :binary
|
|
181
|
+
decorations[:encoding] = 'base64'
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
if include_types && type != :string
|
|
185
|
+
decorations[:type] = type
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
decorations
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
protected
|
|
192
|
+
def compute_type
|
|
193
|
+
if name == "id"
|
|
194
|
+
return :object_id
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
#type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
|
|
198
|
+
v = @record.class.keys[name]
|
|
199
|
+
#puts "Value type is........... #{v.type.to_s} #{v.type.to_s.blank?}"
|
|
200
|
+
|
|
201
|
+
#type = @record.send(name).class
|
|
202
|
+
|
|
203
|
+
type = v.nil? ? :yaml : (v.type.to_s.blank? ? :key : v.type.to_s.underscore.to_sym)
|
|
204
|
+
|
|
205
|
+
case type
|
|
206
|
+
when :text
|
|
207
|
+
:string
|
|
208
|
+
when :time
|
|
209
|
+
:datetime
|
|
210
|
+
# when :array
|
|
211
|
+
# :yaml
|
|
212
|
+
else
|
|
213
|
+
type
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def compute_value
|
|
218
|
+
n = name == "id" ? "_id" : name
|
|
219
|
+
|
|
220
|
+
value = @record.send(n)
|
|
221
|
+
|
|
222
|
+
if formatter = Hash::XML_FORMATTING[type.to_s]
|
|
223
|
+
value ? formatter.call(value) : nil
|
|
224
|
+
else
|
|
225
|
+
value
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
class MethodAttribute < Attribute #:nodoc:
|
|
231
|
+
protected
|
|
232
|
+
def compute_type
|
|
233
|
+
Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module MongoMapper
|
|
2
|
+
module Plugins
|
|
3
|
+
module Timestamps
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def timestamps!
|
|
6
|
+
key :created_at, Time
|
|
7
|
+
key :updated_at, Time
|
|
8
|
+
class_eval { before_save :update_timestamps }
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module InstanceMethods
|
|
13
|
+
def update_timestamps
|
|
14
|
+
now = Time.now.utc
|
|
15
|
+
self[:created_at] = now if new? && !created_at?
|
|
16
|
+
self[:updated_at] = now
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module MongoMapper
|
|
2
|
+
module Plugins
|
|
3
|
+
module Userstamps
|
|
4
|
+
module ClassMethods
|
|
5
|
+
def userstamps!
|
|
6
|
+
key :creator_id, ObjectId
|
|
7
|
+
key :updater_id, ObjectId
|
|
8
|
+
belongs_to :creator, :class_name => 'User'
|
|
9
|
+
belongs_to :updater, :class_name => 'User'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module MongoMapper
|
|
2
|
+
module Plugins
|
|
3
|
+
module Validations
|
|
4
|
+
def self.configure(model)
|
|
5
|
+
model.class_eval { include Validatable }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module DocumentMacros
|
|
9
|
+
def validates_uniqueness_of(*args)
|
|
10
|
+
add_validations(args, MongoMapper::Plugins::Validations::ValidatesUniquenessOf)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class ValidatesUniquenessOf < Validatable::ValidationBase
|
|
15
|
+
option :scope, :case_sensitive
|
|
16
|
+
default :case_sensitive => true
|
|
17
|
+
|
|
18
|
+
def valid?(instance)
|
|
19
|
+
value = instance[attribute]
|
|
20
|
+
return true if allow_blank && value.blank?
|
|
21
|
+
return true if allow_nil && value.nil?
|
|
22
|
+
base_conditions = case_sensitive ? {self.attribute => value} : {}
|
|
23
|
+
doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
|
|
24
|
+
doc.nil? || instance._id == doc._id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def message(instance)
|
|
28
|
+
super || "has already been taken"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def scope_conditions(instance)
|
|
32
|
+
return {} unless scope
|
|
33
|
+
Array(scope).inject({}) do |conditions, key|
|
|
34
|
+
conditions.merge(key => instance[key])
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def where_conditions(instance)
|
|
39
|
+
conditions = {}
|
|
40
|
+
conditions[attribute] = /^#{Regexp.escape(instance[attribute].to_s)}$/i unless case_sensitive
|
|
41
|
+
conditions
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module MongoMapper
|
|
2
|
+
# IMPORTANT
|
|
3
|
+
# This class is private to MongoMapper and should not be considered part of MongoMapper's public API.
|
|
4
|
+
#
|
|
5
|
+
class Query
|
|
6
|
+
OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
|
|
7
|
+
|
|
8
|
+
attr_reader :model
|
|
9
|
+
|
|
10
|
+
def initialize(model, options)
|
|
11
|
+
raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
|
|
12
|
+
@model, @options, @conditions, @original_options = model, {}, {}, options
|
|
13
|
+
separate_options_and_conditions
|
|
14
|
+
add_sci_condition
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def criteria
|
|
18
|
+
to_criteria(@conditions)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def options
|
|
22
|
+
fields = @options[:fields] || @options[:select]
|
|
23
|
+
skip = @options[:skip] || @options[:offset] || 0
|
|
24
|
+
limit = @options[:limit] || 0
|
|
25
|
+
sort = @options[:sort] || normalized_sort(@options[:order])
|
|
26
|
+
|
|
27
|
+
{:fields => to_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_a
|
|
31
|
+
[criteria, options]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
def separate_options_and_conditions
|
|
36
|
+
@original_options.each_pair do |key, value|
|
|
37
|
+
key = key.respond_to?(:to_sym) ? key.to_sym : key
|
|
38
|
+
|
|
39
|
+
if OptionKeys.include?(key)
|
|
40
|
+
@options[key] = value
|
|
41
|
+
elsif key == :conditions
|
|
42
|
+
@conditions.update(value)
|
|
43
|
+
else
|
|
44
|
+
@conditions[key] = value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# adds _type single collection inheritance scope for models that need it
|
|
50
|
+
def add_sci_condition
|
|
51
|
+
@conditions[:_type] = model.to_s if model.single_collection_inherited?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def modifier?(field)
|
|
55
|
+
field.to_s =~ /^\$/
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def symbol_operator?(object)
|
|
59
|
+
object.respond_to?(:field, :operator)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_criteria(conditions, parent_key=nil)
|
|
63
|
+
criteria = {}
|
|
64
|
+
|
|
65
|
+
conditions.each_pair do |key, value|
|
|
66
|
+
key = normalized_key(key)
|
|
67
|
+
|
|
68
|
+
if model.object_id_key?(key)
|
|
69
|
+
case value
|
|
70
|
+
when String
|
|
71
|
+
value = ObjectId.to_mongo(value)
|
|
72
|
+
when Array
|
|
73
|
+
value.map! { |id| ObjectId.to_mongo(id) }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if symbol_operator?(key)
|
|
78
|
+
key, value = normalized_key(key.field), {"$#{key.operator}" => value}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
criteria[key] = normalized_value(criteria, key, value)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
criteria
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def to_fields(keys)
|
|
88
|
+
return keys if keys.is_a?(Hash)
|
|
89
|
+
return nil if keys.blank?
|
|
90
|
+
|
|
91
|
+
if keys.respond_to?(:flatten, :compact)
|
|
92
|
+
keys.flatten.compact
|
|
93
|
+
else
|
|
94
|
+
keys.split(',').map { |key| key.strip }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_order(key, direction=nil)
|
|
99
|
+
[normalized_key(key).to_s, normalized_direction(direction)]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def normalized_key(key)
|
|
103
|
+
key.to_s == 'id' ? :_id : key
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# TODO: this is getting heavy enough to move to a class
|
|
107
|
+
def normalized_value(criteria, key, value)
|
|
108
|
+
case value
|
|
109
|
+
when Array, Set
|
|
110
|
+
modifier?(key) ? value.to_a : {'$in' => value.to_a}
|
|
111
|
+
when Hash
|
|
112
|
+
if criteria[key].kind_of?(Hash)
|
|
113
|
+
criteria[key].dup.merge(to_criteria(value, key))
|
|
114
|
+
else
|
|
115
|
+
to_criteria(value, key)
|
|
116
|
+
end
|
|
117
|
+
when Time
|
|
118
|
+
value.utc
|
|
119
|
+
else
|
|
120
|
+
value
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def normalized_direction(direction)
|
|
125
|
+
direction ||= 'asc'
|
|
126
|
+
direction.downcase == 'asc' ? Mongo::ASCENDING : Mongo::DESCENDING
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def normalized_sort(sort)
|
|
130
|
+
return if sort.blank?
|
|
131
|
+
|
|
132
|
+
if sort.respond_to?(:all?) && sort.all? { |s| symbol_operator?(s) }
|
|
133
|
+
sort.map { |s| to_order(s.field, s.operator) }
|
|
134
|
+
elsif symbol_operator?(sort)
|
|
135
|
+
[to_order(sort.field, sort.operator)]
|
|
136
|
+
else
|
|
137
|
+
sort.split(',').map do |str|
|
|
138
|
+
to_order(*str.strip.split(' '))
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|