mongo_mapper_ign 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
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