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.
Files changed (105) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +35 -0
  4. data/Rakefile +37 -0
  5. data/bin/mmconsole +60 -0
  6. data/lib/mongo_mapper.rb +116 -0
  7. data/lib/mongo_mapper/document.rb +313 -0
  8. data/lib/mongo_mapper/embedded_document.rb +70 -0
  9. data/lib/mongo_mapper/plugins.rb +35 -0
  10. data/lib/mongo_mapper/plugins/associations.rb +114 -0
  11. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  12. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  13. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  14. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  15. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +39 -0
  16. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +144 -0
  17. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +129 -0
  19. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  20. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  21. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  22. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +41 -0
  23. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +69 -0
  24. data/lib/mongo_mapper/plugins/associations/proxy.rb +124 -0
  25. data/lib/mongo_mapper/plugins/callbacks.rb +240 -0
  26. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  27. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  28. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  29. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  30. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  31. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  32. data/lib/mongo_mapper/plugins/keys.rb +345 -0
  33. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  34. data/lib/mongo_mapper/plugins/modifiers.rb +107 -0
  35. data/lib/mongo_mapper/plugins/pagination.rb +24 -0
  36. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  37. data/lib/mongo_mapper/plugins/persistence.rb +68 -0
  38. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  39. data/lib/mongo_mapper/plugins/rails.rb +57 -0
  40. data/lib/mongo_mapper/plugins/serialization.rb +91 -0
  41. data/lib/mongo_mapper/plugins/serialization/array.rb +56 -0
  42. data/lib/mongo_mapper/plugins/serialization/xml_serializer.rb +240 -0
  43. data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
  44. data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
  45. data/lib/mongo_mapper/plugins/validations.rb +46 -0
  46. data/lib/mongo_mapper/query.rb +143 -0
  47. data/lib/mongo_mapper/support.rb +218 -0
  48. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  49. data/lib/mongo_mapper/support/find.rb +77 -0
  50. data/lib/mongo_mapper/version.rb +3 -0
  51. data/mongo_mapper.gemspec +214 -0
  52. data/mongo_mapper_ign.gemspec +217 -0
  53. data/performance/read_write.rb +52 -0
  54. data/specs.watchr +51 -0
  55. data/test/NOTE_ON_TESTING +1 -0
  56. data/test/active_model_lint_test.rb +13 -0
  57. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  58. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  59. data/test/functional/associations/test_in_array_proxy.rb +325 -0
  60. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  61. data/test/functional/associations/test_many_documents_proxy.rb +536 -0
  62. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  63. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  64. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  65. data/test/functional/associations/test_one_embedded_proxy.rb +68 -0
  66. data/test/functional/associations/test_one_proxy.rb +196 -0
  67. data/test/functional/test_associations.rb +44 -0
  68. data/test/functional/test_binary.rb +27 -0
  69. data/test/functional/test_callbacks.rb +151 -0
  70. data/test/functional/test_dirty.rb +163 -0
  71. data/test/functional/test_document.rb +1219 -0
  72. data/test/functional/test_embedded_document.rb +210 -0
  73. data/test/functional/test_identity_map.rb +507 -0
  74. data/test/functional/test_indexing.rb +44 -0
  75. data/test/functional/test_logger.rb +20 -0
  76. data/test/functional/test_modifiers.rb +394 -0
  77. data/test/functional/test_pagination.rb +93 -0
  78. data/test/functional/test_protected.rb +163 -0
  79. data/test/functional/test_string_id_compatibility.rb +67 -0
  80. data/test/functional/test_timestamps.rb +64 -0
  81. data/test/functional/test_userstamps.rb +28 -0
  82. data/test/functional/test_validations.rb +342 -0
  83. data/test/models.rb +227 -0
  84. data/test/support/custom_matchers.rb +37 -0
  85. data/test/support/timing.rb +16 -0
  86. data/test/test_helper.rb +64 -0
  87. data/test/unit/associations/test_base.rb +212 -0
  88. data/test/unit/associations/test_proxy.rb +105 -0
  89. data/test/unit/serializers/test_json_serializer.rb +202 -0
  90. data/test/unit/test_descendant_appends.rb +71 -0
  91. data/test/unit/test_document.rb +225 -0
  92. data/test/unit/test_dynamic_finder.rb +123 -0
  93. data/test/unit/test_embedded_document.rb +657 -0
  94. data/test/unit/test_keys.rb +185 -0
  95. data/test/unit/test_mongo_mapper.rb +118 -0
  96. data/test/unit/test_pagination.rb +160 -0
  97. data/test/unit/test_plugins.rb +50 -0
  98. data/test/unit/test_query.rb +374 -0
  99. data/test/unit/test_rails.rb +181 -0
  100. data/test/unit/test_rails_compatibility.rb +52 -0
  101. data/test/unit/test_serialization.rb +51 -0
  102. data/test/unit/test_support.rb +382 -0
  103. data/test/unit/test_time_zones.rb +39 -0
  104. data/test/unit/test_validations.rb +544 -0
  105. 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