shingara-mongomapper 0.3.3

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 (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