jnunemaker-mongomapper 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,28 +1,33 @@
1
- module MongoMapper
1
+ require 'set'
2
+
3
+ module MongoMapper
2
4
  module Document
3
5
  def self.included(model)
4
6
  model.class_eval do
5
- include MongoMapper::EmbeddedDocument
7
+ include EmbeddedDocument
6
8
  include InstanceMethods
9
+ include Observing
10
+ include Callbacks
7
11
  include SaveWithValidation
8
12
  include RailsCompatibility
9
13
  extend ClassMethods
10
-
14
+
11
15
  key :_id, String
12
16
  key :created_at, Time
13
17
  key :updated_at, Time
14
-
15
- define_callbacks :before_create, :after_create,
16
- :before_update, :after_update,
17
- :before_save, :after_save,
18
- :before_destroy, :after_destroy
19
18
  end
19
+
20
+ descendants << model
21
+ end
22
+
23
+ def self.descendants
24
+ @descendants ||= Set.new
20
25
  end
21
-
26
+
22
27
  module ClassMethods
23
28
  def find(*args)
24
29
  options = args.extract_options!
25
-
30
+
26
31
  case args.first
27
32
  when :first then find_first(options)
28
33
  when :last then find_last(options)
@@ -30,41 +35,44 @@ module MongoMapper
30
35
  else find_from_ids(args)
31
36
  end
32
37
  end
33
-
38
+
34
39
  def first(options={})
35
40
  find_first(options)
36
41
  end
37
-
42
+
38
43
  def last(options={})
39
44
  find_last(options)
40
45
  end
41
-
46
+
42
47
  def all(options={})
43
48
  find_every(options)
44
49
  end
45
-
50
+
46
51
  def find_by_id(id)
47
52
  if doc = collection.find_first({:_id => id})
48
53
  new(doc)
49
54
  end
50
55
  end
51
-
56
+
52
57
  # TODO: remove the rescuing when ruby driver works correctly
53
58
  def count(conditions={})
54
- collection.count(conditions)
59
+ collection.count(FinderOptions.to_mongo_criteria(conditions))
55
60
  end
56
-
61
+
57
62
  def create(*docs)
58
63
  instances = []
59
- docs.flatten.each { |attrs| instances << new(attrs).save }
64
+ docs.flatten.each do |attrs|
65
+ doc = new(attrs); doc.save
66
+ instances << doc
67
+ end
60
68
  instances.size == 1 ? instances[0] : instances
61
69
  end
62
-
70
+
63
71
  # For updating single document
64
72
  # Person.update(1, {:foo => 'bar'})
65
73
  #
66
74
  # For updating multiple documents at once:
67
- # Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
75
+ # Person.update({'1' => {:foo => 'bar'}, '2' => {:baz => 'wick'}})
68
76
  def update(*args)
69
77
  updating_multiple = args.length == 1
70
78
  if updating_multiple
@@ -74,23 +82,23 @@ module MongoMapper
74
82
  update_single(id, attributes)
75
83
  end
76
84
  end
77
-
85
+
78
86
  def delete(*ids)
79
87
  collection.remove(:_id => {'$in' => ids.flatten})
80
88
  end
81
-
89
+
82
90
  def delete_all(conditions={})
83
- collection.remove(conditions)
91
+ collection.remove(FinderOptions.to_mongo_criteria(conditions))
84
92
  end
85
-
93
+
86
94
  def destroy(*ids)
87
95
  find_some(ids.flatten).each(&:destroy)
88
96
  end
89
-
97
+
90
98
  def destroy_all(conditions={})
91
99
  find(:all, :conditions => conditions).each(&:destroy)
92
100
  end
93
-
101
+
94
102
  def connection(mongo_connection=nil)
95
103
  if mongo_connection.nil?
96
104
  @connection ||= MongoMapper.connection
@@ -99,7 +107,7 @@ module MongoMapper
99
107
  end
100
108
  @connection
101
109
  end
102
-
110
+
103
111
  def database(name=nil)
104
112
  if name.nil?
105
113
  @database ||= MongoMapper.database
@@ -108,7 +116,7 @@ module MongoMapper
108
116
  end
109
117
  @database
110
118
  end
111
-
119
+
112
120
  def collection(name=nil)
113
121
  if name.nil?
114
122
  @collection ||= database.collection(self.to_s.demodulize.tableize)
@@ -117,7 +125,19 @@ module MongoMapper
117
125
  end
118
126
  @collection
119
127
  end
128
+
129
+ def validates_uniqueness_of(*args)
130
+ add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
131
+ end
120
132
 
133
+ def validates_exclusion_of(*args)
134
+ add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
135
+ end
136
+
137
+ def validates_inclusion_of(*args)
138
+ add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
139
+ end
140
+
121
141
  private
122
142
  def find_every(options)
123
143
  criteria, options = FinderOptions.new(options).to_a
@@ -153,15 +173,15 @@ module MongoMapper
153
173
  find_some(ids)
154
174
  end
155
175
  end
156
-
176
+
157
177
  def update_single(id, attrs)
158
178
  if id.blank? || attrs.blank? || !attrs.is_a?(Hash)
159
179
  raise ArgumentError, "Updating a single document requires an id and a hash of attributes"
160
180
  end
161
-
181
+
162
182
  find(id).update_attributes(attrs)
163
183
  end
164
-
184
+
165
185
  def update_multiple(docs)
166
186
  unless docs.is_a?(Hash)
167
187
  raise ArgumentError, "Updating multiple documents takes 1 argument and it must be hash"
@@ -171,63 +191,72 @@ module MongoMapper
171
191
  instances
172
192
  end
173
193
  end
174
-
194
+
175
195
  module InstanceMethods
176
196
  def collection
177
197
  self.class.collection
178
198
  end
179
-
199
+
180
200
  def new?
181
201
  read_attribute('_id').blank? || self.class.find_by_id(id).blank?
182
202
  end
183
-
203
+
184
204
  def save
185
- run_callbacks(:before_save)
186
- new? ? create : update
187
- run_callbacks(:after_save)
188
- self
205
+ create_or_update
206
+ end
207
+
208
+ def save!
209
+ create_or_update || raise(DocumentNotValid.new(self))
189
210
  end
190
-
211
+
191
212
  def update_attributes(attrs={})
192
213
  self.attributes = attrs
193
214
  save
215
+ self
194
216
  end
195
-
217
+
196
218
  def destroy
197
- run_callbacks(:before_destroy)
198
219
  collection.remove(:_id => id) unless new?
199
- run_callbacks(:after_destroy)
200
220
  freeze
201
221
  end
202
-
222
+
203
223
  def ==(other)
204
224
  other.is_a?(self.class) && id == other.id
205
225
  end
206
-
226
+
227
+ def id
228
+ read_attribute('_id')
229
+ end
230
+
207
231
  private
232
+ def create_or_update
233
+ result = new? ? create : update
234
+ result != false
235
+ end
236
+
208
237
  def create
209
238
  write_attribute('_id', generate_id) if read_attribute('_id').blank?
210
239
  update_timestamps
211
- run_callbacks(:before_create)
212
- collection.insert(attributes.merge!(embedded_association_attributes))
213
- run_callbacks(:after_create)
240
+ save_to_collection
214
241
  end
215
-
242
+
216
243
  def update
217
244
  update_timestamps
218
- run_callbacks(:before_update)
219
- collection.modify({:_id => id}, attributes.merge!(embedded_association_attributes))
220
- run_callbacks(:after_update)
245
+ save_to_collection
221
246
  end
222
-
247
+
248
+ def save_to_collection
249
+ collection.save(attributes.merge!(embedded_association_attributes))
250
+ end
251
+
223
252
  def update_timestamps
224
253
  write_attribute('created_at', Time.now.utc) if new?
225
254
  write_attribute('updated_at', Time.now.utc)
226
255
  end
227
-
256
+
228
257
  def generate_id
229
258
  XGen::Mongo::Driver::ObjectID.new
230
259
  end
231
260
  end
232
261
  end # Document
233
- end # MongoMapper
262
+ end # MongoMapper
@@ -1,96 +1,64 @@
1
+ require 'observer'
2
+
1
3
  module MongoMapper
2
4
  module EmbeddedDocument
3
5
  class NotImplemented < StandardError; end
4
-
6
+
5
7
  def self.included(model)
6
8
  model.class_eval do
7
- include InstanceMethods
8
9
  extend ClassMethods
10
+ include InstanceMethods
11
+
12
+ extend Associations::ClassMethods
13
+ include Associations::InstanceMethods
14
+
9
15
  include Validatable
10
- include ActiveSupport::Callbacks
11
- include MongoMapper::Serialization
16
+ include Serialization
12
17
  end
13
18
  end
14
-
19
+
15
20
  module ClassMethods
16
- class Association
17
- attr_accessor :name
18
-
19
- def initialize(name)
20
- @name = name.to_s
21
- end
22
-
23
- def klass
24
- @klass ||= name.classify.constantize
25
- end
26
-
27
- def ivar
28
- @ivar ||= "@#{name}"
29
- end
30
- end
31
-
32
- def many(association_name)
33
- association = Association.new(association_name)
34
- associations[association.name] = association
35
- class_eval <<-EOS
36
- def #{association.name}
37
- #{association.ivar} ||= []
38
- #{association.ivar}
39
- end
40
- EOS
41
- end
42
-
43
- def associations
44
- @associations ||= HashWithIndifferentAccess.new
45
- end
46
-
47
21
  def keys
48
22
  @keys ||= HashWithIndifferentAccess.new
49
23
  end
50
-
24
+
51
25
  def key(name, type, options={})
52
26
  key = Key.new(name, type, options)
53
27
  keys[key.name] = key
54
28
  apply_validations_for(key)
55
- define_embedded_document_accessors_for(key)
56
29
  create_indexes_for(key)
57
30
  key
58
31
  end
59
-
32
+
60
33
  def ensure_index(name_or_array, options={})
61
34
  keys_to_index = if name_or_array.is_a?(Array)
62
35
  name_or_array.map { |pair| [pair[0], pair[1]] }
63
36
  else
64
37
  name_or_array
65
38
  end
66
-
39
+
67
40
  collection.create_index(keys_to_index, options.delete(:unique))
68
41
  end
69
-
70
- private
71
- def define_embedded_document_accessors_for(key)
72
- return unless key.embedded_document?
73
- instance_var = "@#{key.name}"
74
-
75
- define_method(key.name) do
76
- key.get(instance_variable_get(instance_var))
77
- end
78
-
79
- define_method("#{key.name}=") do |value|
80
- instance_variable_set(instance_var, key.get(value))
81
- end
42
+
43
+ def embeddable?
44
+ !self.ancestors.include?(Document)
82
45
  end
83
-
46
+
47
+ private
84
48
  def create_indexes_for(key)
85
49
  ensure_index key.name if key.options[:index]
86
50
  end
87
-
51
+
88
52
  def apply_validations_for(key)
89
53
  attribute = key.name.to_sym
90
54
 
91
55
  if key.options[:required]
92
56
  validates_presence_of(attribute)
93
57
  end
58
+
59
+ if key.options[:unique]
60
+ validates_uniqueness_of(attribute)
61
+ end
94
62
 
95
63
  if key.options[:numeric]
96
64
  number_options = key.type == Integer ? {:only_integer => true} : {}
@@ -114,15 +82,18 @@ module MongoMapper
114
82
  end
115
83
  end
116
84
 
117
- end
118
-
85
+ end
86
+
119
87
  module InstanceMethods
120
88
  def initialize(attrs={})
121
- initialize_embedded_associations(attrs)
122
- self.attributes = attrs
89
+ unless attrs.nil?
90
+ initialize_associations(attrs)
91
+ self.attributes = attrs
92
+ end
123
93
  end
124
-
94
+
125
95
  def attributes=(attrs)
96
+ return if attrs.blank?
126
97
  attrs.each_pair do |key_name, value|
127
98
  if writer?(key_name)
128
99
  write_attribute(key_name, value)
@@ -132,7 +103,7 @@ module MongoMapper
132
103
  end
133
104
  end
134
105
  end
135
-
106
+
136
107
  def attributes
137
108
  self.class.keys.inject(HashWithIndifferentAccess.new) do |attributes, key_hash|
138
109
  name, key = key_hash
@@ -141,36 +112,32 @@ module MongoMapper
141
112
  attributes
142
113
  end
143
114
  end
144
-
115
+
145
116
  def reader?(name)
146
117
  defined_key_names.include?(name.to_s)
147
118
  end
148
-
119
+
149
120
  def writer?(name)
150
121
  name = name.to_s
151
122
  name = name.chop if name.ends_with?('=')
152
123
  reader?(name)
153
124
  end
154
-
125
+
155
126
  def before_typecast_reader?(name)
156
127
  name.to_s.match(/^(.*)_before_typecast$/) && reader?($1)
157
128
  end
158
-
129
+
159
130
  def [](name)
160
131
  read_attribute(name)
161
132
  end
162
-
133
+
163
134
  def []=(name, value)
164
135
  write_attribute(name, value)
165
136
  end
166
-
167
- def id
168
- read_attribute('_id')
169
- end
170
-
137
+
171
138
  def method_missing(method, *args, &block)
172
139
  attribute = method.to_s
173
-
140
+
174
141
  if reader?(attribute)
175
142
  read_attribute(attribute)
176
143
  elsif writer?(attribute)
@@ -181,24 +148,26 @@ module MongoMapper
181
148
  super
182
149
  end
183
150
  end
184
-
151
+
185
152
  def ==(other)
186
153
  other.is_a?(self.class) && attributes == other.attributes
187
154
  end
188
-
155
+
189
156
  def inspect
190
157
  attributes_as_nice_string = defined_key_names.collect do |name|
191
158
  "#{name}: #{read_attribute(name)}"
192
159
  end.join(", ")
193
160
  "#<#{self.class} #{attributes_as_nice_string}>"
194
161
  end
195
-
162
+
163
+ alias :respond_to_without_attributes? :respond_to?
164
+
196
165
  def respond_to?(method, include_private=false)
197
166
  return true if reader?(method) || writer?(method) || before_typecast_reader?(method)
198
167
  super
199
168
  end
200
-
201
- private
169
+
170
+ private
202
171
  def value_for_key(key)
203
172
  if key.native?
204
173
  read_attribute(key.name)
@@ -207,15 +176,15 @@ module MongoMapper
207
176
  embedded_document && embedded_document.attributes
208
177
  end
209
178
  end
210
-
179
+
211
180
  def read_attribute(name)
212
181
  defined_key(name).get(instance_variable_get("@#{name}"))
213
182
  end
214
-
183
+
215
184
  def read_attribute_before_typecast(name)
216
185
  instance_variable_get("@#{name}_before_typecast")
217
186
  end
218
-
187
+
219
188
  def write_attribute(name, value)
220
189
  instance_variable_set "@#{name}_before_typecast", value
221
190
  instance_variable_set "@#{name}", defined_key(name).set(value)
@@ -224,34 +193,35 @@ module MongoMapper
224
193
  def defined_key(name)
225
194
  self.class.keys[name]
226
195
  end
227
-
196
+
228
197
  def defined_key_names
229
198
  self.class.keys.keys
230
199
  end
231
-
200
+
232
201
  def only_defined_keys(hash={})
233
202
  defined_key_names = defined_key_names()
234
203
  hash.delete_if { |k, v| !defined_key_names.include?(k.to_s) }
235
204
  end
236
-
205
+
237
206
  def embedded_association_attributes
238
207
  attributes = HashWithIndifferentAccess.new
239
208
  self.class.associations.each_pair do |name, association|
240
- attributes[name] = send(name).collect { |item| item.attributes }
209
+ if association.type == :many && association.klass.embeddable?
210
+ vals = instance_variable_get(association.ivar)
211
+ attributes[name] = vals.collect { |item| item.attributes } if vals
212
+ end
241
213
  end
214
+
242
215
  attributes
243
216
  end
244
-
245
- def initialize_embedded_associations(attrs={})
217
+
218
+ def initialize_associations(attrs={})
246
219
  self.class.associations.each_pair do |name, association|
247
220
  if collection = attrs.delete(name)
248
- association_value = collection.collect do |item|
249
- association.klass.new(item)
250
- end
251
- instance_variable_set(association.ivar, association_value)
221
+ __send__("#{association.name}=", collection)
252
222
  end
253
223
  end
254
224
  end
255
225
  end
256
226
  end
257
- end
227
+ end