shingara-mongomapper 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +7 -0
  2. data/History +70 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +73 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +56 -0
  8. data/lib/mongomapper.rb +77 -0
  9. data/lib/mongomapper/associations.rb +84 -0
  10. data/lib/mongomapper/associations/base.rb +69 -0
  11. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  12. data/lib/mongomapper/associations/belongs_to_proxy.rb +22 -0
  13. data/lib/mongomapper/associations/many_documents_proxy.rb +103 -0
  14. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongomapper/associations/many_embedded_proxy.rb +17 -0
  16. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongomapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongomapper/associations/proxy.rb +63 -0
  19. data/lib/mongomapper/callbacks.rb +106 -0
  20. data/lib/mongomapper/document.rb +348 -0
  21. data/lib/mongomapper/dynamic_finder.rb +38 -0
  22. data/lib/mongomapper/embedded_document.rb +265 -0
  23. data/lib/mongomapper/finder_options.rb +85 -0
  24. data/lib/mongomapper/key.rb +76 -0
  25. data/lib/mongomapper/observing.rb +50 -0
  26. data/lib/mongomapper/pagination.rb +52 -0
  27. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  29. data/lib/mongomapper/save_with_validation.rb +19 -0
  30. data/lib/mongomapper/serialization.rb +55 -0
  31. data/lib/mongomapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongomapper/support.rb +30 -0
  33. data/lib/mongomapper/validations.rb +47 -0
  34. data/mongomapper.gemspec +142 -0
  35. data/test/NOTE_ON_TESTING +1 -0
  36. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
  37. data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
  38. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  39. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  40. data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
  41. data/test/functional/associations/test_many_proxy.rb +295 -0
  42. data/test/functional/test_associations.rb +47 -0
  43. data/test/functional/test_callbacks.rb +85 -0
  44. data/test/functional/test_document.rb +952 -0
  45. data/test/functional/test_pagination.rb +81 -0
  46. data/test/functional/test_rails_compatibility.rb +30 -0
  47. data/test/functional/test_validations.rb +172 -0
  48. data/test/models.rb +139 -0
  49. data/test/test_helper.rb +67 -0
  50. data/test/unit/serializers/test_json_serializer.rb +157 -0
  51. data/test/unit/test_association_base.rb +144 -0
  52. data/test/unit/test_document.rb +123 -0
  53. data/test/unit/test_embedded_document.rb +526 -0
  54. data/test/unit/test_finder_options.rb +183 -0
  55. data/test/unit/test_key.rb +247 -0
  56. data/test/unit/test_mongomapper.rb +28 -0
  57. data/test/unit/test_observing.rb +101 -0
  58. data/test/unit/test_pagination.rb +113 -0
  59. data/test/unit/test_rails_compatibility.rb +34 -0
  60. data/test/unit/test_serializations.rb +52 -0
  61. data/test/unit/test_validations.rb +259 -0
  62. metadata +189 -0
@@ -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
@@ -0,0 +1,265 @@
1
+ require 'observer'
2
+
3
+ module MongoMapper
4
+ module EmbeddedDocument
5
+ def self.included(model)
6
+ model.class_eval do
7
+ extend ClassMethods
8
+ include InstanceMethods
9
+
10
+ extend Associations::ClassMethods
11
+ include Associations::InstanceMethods
12
+
13
+ include RailsCompatibility::EmbeddedDocument
14
+ include Validatable
15
+ include Serialization
16
+
17
+ key :_id, String
18
+ end
19
+ end
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
+
34
+ def keys
35
+ @keys ||= if parent = parent_model
36
+ parent.keys.dup
37
+ else
38
+ HashWithIndifferentAccess.new
39
+ end
40
+ end
41
+
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
63
+ end
64
+
65
+ def ensure_index(name_or_array, options={})
66
+ keys_to_index = if name_or_array.is_a?(Array)
67
+ name_or_array.map { |pair| [pair[0], pair[1]] }
68
+ else
69
+ name_or_array
70
+ end
71
+
72
+ collection.create_index(keys_to_index, options.delete(:unique))
73
+ end
74
+
75
+ def embeddable?
76
+ !self.ancestors.include?(Document)
77
+ end
78
+
79
+ def parent_model
80
+ (ancestors - [self,EmbeddedDocument]).find do |parent_class|
81
+ parent_class.ancestors.include?(EmbeddedDocument)
82
+ end
83
+ end
84
+
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
+
115
+ def create_indexes_for(key)
116
+ ensure_index key.name if key.options[:index]
117
+ end
118
+
119
+ def apply_validations_for(key)
120
+ attribute = key.name.to_sym
121
+
122
+ if key.options[:required]
123
+ validates_presence_of(attribute)
124
+ end
125
+
126
+ if key.options[:unique]
127
+ validates_uniqueness_of(attribute)
128
+ end
129
+
130
+ if key.options[:numeric]
131
+ number_options = key.type == Integer ? {:only_integer => true} : {}
132
+ validates_numericality_of(attribute, number_options)
133
+ end
134
+
135
+ if key.options[:format]
136
+ validates_format_of(attribute, :with => key.options[:format])
137
+ end
138
+
139
+ if key.options[:length]
140
+ length_options = case key.options[:length]
141
+ when Integer
142
+ {:minimum => 0, :maximum => key.options[:length]}
143
+ when Range
144
+ {:within => key.options[:length]}
145
+ when Hash
146
+ key.options[:length]
147
+ end
148
+ validates_length_of(attribute, length_options)
149
+ end
150
+ end
151
+ end
152
+
153
+ module InstanceMethods
154
+ def initialize(attrs={})
155
+ unless attrs.nil?
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
+
162
+ self.attributes = attrs
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
168
+ end
169
+
170
+ def attributes=(attrs)
171
+ return if attrs.blank?
172
+ attrs.each_pair do |name, value|
173
+ writer_method = "#{name}="
174
+
175
+ if respond_to?(writer_method)
176
+ self.send(writer_method, value)
177
+ else
178
+ self[name.to_s] = value
179
+ end
180
+ end
181
+ end
182
+
183
+ def attributes
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?
196
+ end
197
+ attrs.merge!(embedded_association_attributes)
198
+ end
199
+
200
+ def [](name)
201
+ read_attribute(name)
202
+ end
203
+
204
+ def []=(name, value)
205
+ ensure_key_exists(name)
206
+ write_attribute(name, value)
207
+ end
208
+
209
+ def ==(other)
210
+ other.is_a?(self.class) && id == other.id
211
+ end
212
+
213
+ def id
214
+ read_attribute(:_id)
215
+ end
216
+
217
+ def id=(value)
218
+ @using_custom_id = true
219
+ write_attribute :_id, value
220
+ end
221
+
222
+ def using_custom_id?
223
+ !!@using_custom_id
224
+ end
225
+
226
+ def inspect
227
+ attributes_as_nice_string = self.class.keys.keys.collect do |name|
228
+ "#{name}: #{read_attribute(name)}"
229
+ end.join(", ")
230
+ "#<#{self.class} #{attributes_as_nice_string}>"
231
+ end
232
+
233
+ private
234
+ def ensure_key_exists(name)
235
+ self.class.key(name) unless respond_to?("#{name}=")
236
+ end
237
+
238
+ def read_attribute(name)
239
+ value = self.class.keys[name].get(instance_variable_get("@#{name}"))
240
+ instance_variable_set "@#{name}", value if !frozen?
241
+ value
242
+ end
243
+
244
+ def read_attribute_before_typecast(name)
245
+ instance_variable_get("@#{name}_before_typecast")
246
+ end
247
+
248
+ def write_attribute(name, value)
249
+ instance_variable_set "@#{name}_before_typecast", value
250
+ instance_variable_set "@#{name}", self.class.keys[name].set(value)
251
+ end
252
+
253
+ def embedded_association_attributes
254
+ returning HashWithIndifferentAccess.new do |attrs|
255
+ self.class.associations.each_pair do |name, association|
256
+ next unless association.embeddable?
257
+ next unless documents = instance_variable_get(association.ivar)
258
+
259
+ attrs[name] = documents.collect { |doc| doc.attributes }
260
+ end
261
+ end
262
+ end
263
+ end # InstanceMethods
264
+ end # EmbeddedDocument
265
+ end # MongoMapper
@@ -0,0 +1,85 @@
1
+ module MongoMapper
2
+ class FinderOptions
3
+ attr_reader :options
4
+
5
+ def self.to_mongo_criteria(conditions, parent_key=nil)
6
+ criteria = {}
7
+ conditions.each_pair do |field, value|
8
+ case value
9
+ when Array
10
+ operator_present = field.to_s =~ /^\$/
11
+ criteria[field] = if operator_present
12
+ value
13
+ else
14
+ {'$in' => value}
15
+ end
16
+ when Hash
17
+ criteria[field] = to_mongo_criteria(value, field)
18
+ else
19
+ criteria[field] = value
20
+ end
21
+ end
22
+
23
+ criteria
24
+ end
25
+
26
+ def self.to_mongo_options(options)
27
+ options = options.dup
28
+ {
29
+ :fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
30
+ :offset => (options.delete(:offset) || 0).to_i,
31
+ :limit => (options.delete(:limit) || 0).to_i,
32
+ :sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
33
+ }
34
+ end
35
+
36
+ def initialize(options)
37
+ raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
38
+ @options = options.symbolize_keys
39
+ @conditions = @options.delete(:conditions) || {}
40
+ end
41
+
42
+ def criteria
43
+ self.class.to_mongo_criteria(@conditions)
44
+ end
45
+
46
+ def options
47
+ self.class.to_mongo_options(@options)
48
+ end
49
+
50
+ def to_a
51
+ [criteria, options]
52
+ end
53
+
54
+ private
55
+ def self.to_mongo_fields(fields)
56
+ return if fields.blank?
57
+
58
+ if fields.is_a?(String)
59
+ fields.split(',').map { |field| field.strip }
60
+ else
61
+ fields.flatten.compact
62
+ end
63
+ end
64
+
65
+ def self.to_mongo_sort(sort)
66
+ return if sort.blank?
67
+ pieces = sort.split(',')
68
+ pairs = pieces.map { |s| to_mongo_sort_piece(s) }
69
+
70
+ hash = OrderedHash.new
71
+ pairs.each do |pair|
72
+ field, sort_direction = pair
73
+ hash[field] = sort_direction
74
+ end
75
+ hash.symbolize_keys
76
+ end
77
+
78
+ def self.to_mongo_sort_piece(str)
79
+ field, direction = str.strip.split(' ')
80
+ direction ||= 'ASC'
81
+ direction = direction.upcase == 'ASC' ? 1 : -1
82
+ [field, direction]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,76 @@
1
+ module MongoMapper
2
+ class Key
3
+ # DateTime and Date are currently not supported by mongo's bson so just use Time
4
+ NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash]
5
+
6
+ attr_accessor :name, :type, :options, :default_value
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)
13
+ end
14
+
15
+ def ==(other)
16
+ @name == other.name && @type == other.type
17
+ end
18
+
19
+ def set(value)
20
+ typecast(value)
21
+ end
22
+
23
+ def native?
24
+ @native ||= NativeTypes.include?(type) || type.nil?
25
+ end
26
+
27
+ def embedded_document?
28
+ type.respond_to?(:embeddable?) && type.embeddable?
29
+ end
30
+
31
+ def get(value)
32
+ return default_value if value.nil? && !default_value.nil?
33
+ if type == Array
34
+ value || []
35
+ elsif type == Hash
36
+ HashWithIndifferentAccess.new(value || {})
37
+ else
38
+ value
39
+ end
40
+ end
41
+
42
+ private
43
+ def typecast(value)
44
+ return value if type.nil?
45
+ return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
46
+ return value.utc if type == Time && value.kind_of?(type)
47
+ return value if value.kind_of?(type) || value.nil?
48
+ begin
49
+ if type == String then value.to_s
50
+ elsif type == Float then value.to_f
51
+ elsif type == Array then value.to_a
52
+ elsif type == Time then Time.parse(value.to_s).utc
53
+ elsif type == Boolean then Boolean.mm_typecast(value)
54
+ elsif type == Integer
55
+ # ganked from datamapper
56
+ value_to_i = value.to_i
57
+ if value_to_i == 0
58
+ value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
59
+ else
60
+ value_to_i
61
+ end
62
+ elsif embedded_document?
63
+ typecast_embedded_document(value)
64
+ else
65
+ value
66
+ end
67
+ rescue
68
+ value
69
+ end
70
+ end
71
+
72
+ def typecast_embedded_document(value)
73
+ value.is_a?(type) ? value : type.new(value)
74
+ end
75
+ end
76
+ end