jnunemaker-mongomapper 0.1.2 → 0.2.0

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.
@@ -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