jnunemaker-mongomapper 0.3.2 → 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.
@@ -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
@@ -1,36 +1,36 @@
1
1
  require 'observer'
2
2
 
3
3
  module MongoMapper
4
- module EmbeddedDocument
4
+ module EmbeddedDocument
5
5
  def self.included(model)
6
6
  model.class_eval do
7
7
  extend ClassMethods
8
8
  include InstanceMethods
9
-
9
+
10
10
  extend Associations::ClassMethods
11
11
  include Associations::InstanceMethods
12
-
12
+
13
13
  include RailsCompatibility::EmbeddedDocument
14
14
  include Validatable
15
15
  include Serialization
16
16
 
17
- key :_id, MongoID
17
+ key :_id, String
18
18
  end
19
19
  end
20
-
20
+
21
21
  module ClassMethods
22
22
  def inherited(subclass)
23
23
  unless subclass.embeddable?
24
24
  subclass.collection(self.collection.name)
25
25
  end
26
-
26
+
27
27
  (@subclasses ||= []) << subclass
28
28
  end
29
-
29
+
30
30
  def subclasses
31
31
  @subclasses
32
32
  end
33
-
33
+
34
34
  def keys
35
35
  @keys ||= if parent = parent_model
36
36
  parent.keys.dup
@@ -38,90 +38,93 @@ module MongoMapper
38
38
  HashWithIndifferentAccess.new
39
39
  end
40
40
  end
41
-
42
- def key(name, type, options={})
43
- key = Key.new(name, type, options)
44
- keys[key.name] = key
45
-
46
- create_accessors_for(key)
47
- add_to_subclasses(name, type, options)
48
- apply_validations_for(key)
49
- create_indexes_for(key)
50
-
51
- key
52
- end
53
-
54
- def create_accessors_for(key)
55
- define_method(key.name) do
56
- read_attribute(key.name)
57
- end
58
-
59
- define_method("#{key.name}_before_typecast") do
60
- read_attribute_before_typecast(key.name)
61
- end
62
-
63
- define_method("#{key.name}=") do |value|
64
- write_attribute(key.name, value)
65
- end
41
+
42
+ def key(*args)
43
+ key = Key.new(*args)
66
44
 
67
- define_method("#{key.name}?") do
68
- read_attribute(key.name).present?
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
69
54
  end
70
55
  end
71
-
72
- def add_to_subclasses(name, type, options)
56
+
57
+ def add_to_subclasses(*args)
73
58
  return if subclasses.blank?
74
-
59
+
75
60
  subclasses.each do |subclass|
76
- subclass.key name, type, options
61
+ subclass.key(*args)
77
62
  end
78
63
  end
79
-
64
+
80
65
  def ensure_index(name_or_array, options={})
81
66
  keys_to_index = if name_or_array.is_a?(Array)
82
67
  name_or_array.map { |pair| [pair[0], pair[1]] }
83
68
  else
84
69
  name_or_array
85
70
  end
86
-
71
+
87
72
  collection.create_index(keys_to_index, options.delete(:unique))
88
73
  end
89
-
74
+
90
75
  def embeddable?
91
76
  !self.ancestors.include?(Document)
92
77
  end
93
-
78
+
94
79
  def parent_model
95
80
  if parent = ancestors[1]
96
81
  parent if parent.ancestors.include?(EmbeddedDocument)
97
82
  end
98
83
  end
99
-
84
+
100
85
  private
86
+ def create_accessors_for(key)
87
+ define_method(key.name) do
88
+ read_attribute(key.name)
89
+ end
90
+
91
+ define_method("#{key.name}_before_typecast") do
92
+ read_attribute_before_typecast(key.name)
93
+ end
94
+
95
+ define_method("#{key.name}=") do |value|
96
+ write_attribute(key.name, value)
97
+ end
98
+
99
+ define_method("#{key.name}?") do
100
+ read_attribute(key.name).present?
101
+ end
102
+ end
103
+
101
104
  def create_indexes_for(key)
102
105
  ensure_index key.name if key.options[:index]
103
106
  end
104
-
107
+
105
108
  def apply_validations_for(key)
106
109
  attribute = key.name.to_sym
107
-
110
+
108
111
  if key.options[:required]
109
112
  validates_presence_of(attribute)
110
113
  end
111
-
114
+
112
115
  if key.options[:unique]
113
116
  validates_uniqueness_of(attribute)
114
117
  end
115
-
118
+
116
119
  if key.options[:numeric]
117
120
  number_options = key.type == Integer ? {:only_integer => true} : {}
118
121
  validates_numericality_of(attribute, number_options)
119
122
  end
120
-
123
+
121
124
  if key.options[:format]
122
125
  validates_format_of(attribute, :with => key.options[:format])
123
126
  end
124
-
127
+
125
128
  if key.options[:length]
126
129
  length_options = case key.options[:length]
127
130
  when Integer
@@ -135,7 +138,7 @@ module MongoMapper
135
138
  end
136
139
  end
137
140
  end
138
-
141
+
139
142
  module InstanceMethods
140
143
  def initialize(attrs={})
141
144
  unless attrs.nil?
@@ -144,90 +147,104 @@ module MongoMapper
144
147
  send("#{association.name}=", collection)
145
148
  end
146
149
  end
147
-
150
+
148
151
  self.attributes = attrs
149
152
  end
150
-
153
+
151
154
  if self.class.embeddable? && read_attribute(:_id).blank?
152
- write_attribute :_id, XGen::Mongo::Driver::ObjectID.new
155
+ write_attribute :_id, XGen::Mongo::Driver::ObjectID.new.to_s
153
156
  end
154
157
  end
155
-
158
+
156
159
  def attributes=(attrs)
157
160
  return if attrs.blank?
158
- attrs.each_pair do |method, value|
159
- self.send("#{method}=", value)
161
+ attrs.each_pair do |name, value|
162
+ writer_method = "#{name}="
163
+
164
+ if respond_to?(writer_method)
165
+ self.send(writer_method, value)
166
+ else
167
+ self[name.to_s] = value
168
+ end
160
169
  end
161
170
  end
162
-
171
+
163
172
  def attributes
164
- returning HashWithIndifferentAccess.new do |attributes|
165
- self.class.keys.each_pair do |name, key|
166
- value = value_for_key(key)
167
- attributes[name] = value unless value.nil?
168
- end
173
+ attrs = HashWithIndifferentAccess.new
174
+ self.class.keys.each_pair do |name, key|
175
+ value =
176
+ if key.native?
177
+ read_attribute(key.name)
178
+ else
179
+ if embedded_document = read_attribute(key.name)
180
+ embedded_document.attributes
181
+ end
182
+ end
169
183
 
170
- attributes.merge!(embedded_association_attributes)
184
+ attrs[name] = value unless value.nil?
171
185
  end
186
+ attrs.merge!(embedded_association_attributes)
172
187
  end
173
-
188
+
174
189
  def [](name)
175
190
  read_attribute(name)
176
191
  end
177
-
192
+
178
193
  def []=(name, value)
194
+ ensure_key_exists(name)
179
195
  write_attribute(name, value)
180
196
  end
181
-
197
+
182
198
  def ==(other)
183
199
  other.is_a?(self.class) && id == other.id
184
200
  end
185
-
201
+
186
202
  def id
187
- self._id.to_s
203
+ read_attribute(:_id)
188
204
  end
189
-
205
+
190
206
  def id=(value)
191
- self._id = value
207
+ @using_custom_id = true
208
+ write_attribute :_id, value
192
209
  end
193
210
 
211
+ def using_custom_id?
212
+ !!@using_custom_id
213
+ end
214
+
194
215
  def inspect
195
216
  attributes_as_nice_string = self.class.keys.keys.collect do |name|
196
217
  "#{name}: #{read_attribute(name)}"
197
218
  end.join(", ")
198
219
  "#<#{self.class} #{attributes_as_nice_string}>"
199
220
  end
200
-
221
+
201
222
  private
202
- def value_for_key(key)
203
- if key.native?
204
- read_attribute(key.name)
205
- else
206
- embedded_document = read_attribute(key.name)
207
- embedded_document && embedded_document.attributes
208
- end
223
+ def ensure_key_exists(name)
224
+ self.class.key(name) unless respond_to?("#{name}=")
209
225
  end
210
-
226
+
211
227
  def read_attribute(name)
212
228
  value = self.class.keys[name].get(instance_variable_get("@#{name}"))
213
- instance_variable_set "@#{name}", value
229
+ instance_variable_set "@#{name}", value if !frozen?
230
+ value
214
231
  end
215
-
232
+
216
233
  def read_attribute_before_typecast(name)
217
234
  instance_variable_get("@#{name}_before_typecast")
218
235
  end
219
-
236
+
220
237
  def write_attribute(name, value)
221
238
  instance_variable_set "@#{name}_before_typecast", value
222
239
  instance_variable_set "@#{name}", self.class.keys[name].set(value)
223
240
  end
224
-
241
+
225
242
  def embedded_association_attributes
226
243
  returning HashWithIndifferentAccess.new do |attrs|
227
244
  self.class.associations.each_pair do |name, association|
228
245
  next unless association.embeddable?
229
246
  next unless documents = instance_variable_get(association.ivar)
230
-
247
+
231
248
  attrs[name] = documents.collect { |doc| doc.attributes }
232
249
  end
233
250
  end
@@ -7,26 +7,15 @@ module MongoMapper
7
7
  conditions.each_pair do |field, value|
8
8
  case value
9
9
  when Array
10
- operator_present = field.to_s =~ /^\$/
11
-
12
- dealing_with_ids = field.to_s == '_id' ||
13
- (parent_key && parent_key.to_s == '_id')
14
-
15
- criteria[field] = if dealing_with_ids
16
- ids = value.map { |id| MongoID.mm_typecast(id) }
17
- operator_present ? ids : {'$in' => ids}
18
- elsif operator_present
10
+ operator_present = field.to_s =~ /^\$/
11
+ criteria[field] = if operator_present
19
12
  value
20
13
  else
21
14
  {'$in' => value}
22
15
  end
23
16
  when Hash
24
17
  criteria[field] = to_mongo_criteria(value, field)
25
- else
26
- if field.to_s == '_id'
27
- value = MongoID.mm_typecast(value)
28
- end
29
-
18
+ else
30
19
  criteria[field] = value
31
20
  end
32
21
  end
@@ -1,12 +1,13 @@
1
1
  module MongoMapper
2
2
  class Key
3
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, MongoID]
4
+ NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash]
5
5
 
6
6
  attr_accessor :name, :type, :options, :default_value
7
-
8
- def initialize(name, type, options={})
9
- @name, @type = name.to_s, type
7
+
8
+ def initialize(*args)
9
+ options = args.extract_options!
10
+ @name, @type = args.shift.to_s, args.shift
10
11
  self.options = (options || {}).symbolize_keys
11
12
  self.default_value = self.options.delete(:default)
12
13
  end
@@ -20,11 +21,11 @@ module MongoMapper
20
21
  end
21
22
 
22
23
  def native?
23
- @native ||= NativeTypes.include?(type)
24
+ @native ||= NativeTypes.include?(type) || type.nil?
24
25
  end
25
26
 
26
27
  def embedded_document?
27
- type.ancestors.include?(EmbeddedDocument) && !type.ancestors.include?(Document)
28
+ type.respond_to?(:embeddable?) && type.embeddable?
28
29
  end
29
30
 
30
31
  def get(value)
@@ -40,6 +41,7 @@ module MongoMapper
40
41
 
41
42
  private
42
43
  def typecast(value)
44
+ return value if type.nil?
43
45
  return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
44
46
  return value.utc if type == Time && value.kind_of?(type)
45
47
  return value if value.kind_of?(type) || value.nil?
@@ -48,19 +50,12 @@ module MongoMapper
48
50
  elsif type == Float then value.to_f
49
51
  elsif type == Array then value.to_a
50
52
  elsif type == Time then Time.parse(value.to_s).utc
51
- elsif type == MongoID then MongoID.mm_typecast(value)
52
- #elsif type == Date then Date.parse(value.to_s)
53
53
  elsif type == Boolean then Boolean.mm_typecast(value)
54
54
  elsif type == Integer
55
55
  # ganked from datamapper
56
56
  value_to_i = value.to_i
57
- if value_to_i == 0 && value != '0'
58
- value_to_s = value.to_s
59
- begin
60
- Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
61
- rescue ArgumentError
62
- nil
63
- end
57
+ if value_to_i == 0
58
+ value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
64
59
  else
65
60
  value_to_i
66
61
  end
@@ -0,0 +1,30 @@
1
+ class BasicObject #:nodoc:
2
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|instance_eval|proxy_|^object_id$)/ }
3
+ end unless defined?(BasicObject)
4
+
5
+ class Boolean
6
+ def self.mm_typecast(value)
7
+ ['true', 't', '1'].include?(value.to_s.downcase)
8
+ end
9
+ end
10
+
11
+ class Object
12
+ # The hidden singleton lurks behind everyone
13
+ def metaclass
14
+ class << self; self end
15
+ end
16
+
17
+ def meta_eval(&blk)
18
+ metaclass.instance_eval(&blk)
19
+ end
20
+
21
+ # Adds methods to a metaclass
22
+ def meta_def(name, &blk)
23
+ meta_eval { define_method(name, &blk) }
24
+ end
25
+
26
+ # Defines an instance method within a class
27
+ def class_def(name, &blk)
28
+ class_eval { define_method(name, &blk) }
29
+ end
30
+ end
data/lib/mongomapper.rb CHANGED
@@ -2,43 +2,17 @@ require 'pathname'
2
2
  require 'rubygems'
3
3
 
4
4
  gem 'activesupport'
5
- gem 'mongodb-mongo', '0.10.1'
5
+ gem 'mongodb-mongo', '0.11.1'
6
6
  gem 'jnunemaker-validatable', '1.7.2'
7
7
 
8
8
  require 'activesupport'
9
9
  require 'mongo'
10
10
  require 'validatable'
11
11
 
12
- class BasicObject #:nodoc:
13
- instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval/ }
14
- end unless defined?(BasicObject)
15
-
16
- class Boolean
17
- def self.mm_typecast(value)
18
- ['true', 't', '1'].include?(value.to_s.downcase)
19
- end
20
- end
21
-
22
- class MongoID < XGen::Mongo::Driver::ObjectID
23
- def self.mm_typecast(value)
24
- begin
25
- if value.is_a?(XGen::Mongo::Driver::ObjectID)
26
- value
27
- else
28
- XGen::Mongo::Driver::ObjectID::from_string(value.to_s)
29
- end
30
- rescue => exception
31
- if exception.message == 'illegal ObjectID format'
32
- raise MongoMapper::IllegalID
33
- else
34
- raise exception
35
- end
36
- end
37
- end
38
- end
39
-
40
12
  dir = Pathname(__FILE__).dirname.expand_path + 'mongomapper'
41
13
 
14
+ require dir + 'support'
15
+
42
16
  require dir + 'associations'
43
17
  require dir + 'associations/base'
44
18
 
@@ -54,6 +28,7 @@ require dir + 'associations/many_embedded_polymorphic_proxy'
54
28
 
55
29
  require dir + 'callbacks'
56
30
  require dir + 'finder_options'
31
+ require dir + 'dynamic_finder'
57
32
  require dir + 'key'
58
33
  require dir + 'observing'
59
34
  require dir + 'pagination'
@@ -69,8 +44,6 @@ require dir + 'document'
69
44
 
70
45
  module MongoMapper
71
46
  DocumentNotFound = Class.new(StandardError)
72
- IllegalID = Class.new(StandardError)
73
-
74
47
  DocumentNotValid = Class.new(StandardError) do
75
48
  def initialize(document)
76
49
  @document = document
data/mongomapper.gemspec CHANGED
@@ -1,12 +1,15 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
1
4
  # -*- encoding: utf-8 -*-
2
5
 
3
6
  Gem::Specification.new do |s|
4
7
  s.name = %q{mongomapper}
5
- s.version = "0.3.2"
8
+ s.version = "0.3.3"
6
9
 
7
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
11
  s.authors = ["John Nunemaker"]
9
- s.date = %q{2009-08-06}
12
+ s.date = %q{2009-08-16}
10
13
  s.default_executable = %q{mmconsole}
11
14
  s.email = %q{nunemaker@gmail.com}
12
15
  s.executables = ["mmconsole"]
@@ -35,6 +38,7 @@ Gem::Specification.new do |s|
35
38
  "lib/mongomapper/associations/proxy.rb",
36
39
  "lib/mongomapper/callbacks.rb",
37
40
  "lib/mongomapper/document.rb",
41
+ "lib/mongomapper/dynamic_finder.rb",
38
42
  "lib/mongomapper/embedded_document.rb",
39
43
  "lib/mongomapper/finder_options.rb",
40
44
  "lib/mongomapper/key.rb",
@@ -45,6 +49,7 @@ Gem::Specification.new do |s|
45
49
  "lib/mongomapper/save_with_validation.rb",
46
50
  "lib/mongomapper/serialization.rb",
47
51
  "lib/mongomapper/serializers/json_serializer.rb",
52
+ "lib/mongomapper/support.rb",
48
53
  "lib/mongomapper/validations.rb",
49
54
  "mongomapper.gemspec",
50
55
  "test/NOTE_ON_TESTING",
@@ -68,7 +73,6 @@ Gem::Specification.new do |s|
68
73
  "test/unit/test_embedded_document.rb",
69
74
  "test/unit/test_finder_options.rb",
70
75
  "test/unit/test_key.rb",
71
- "test/unit/test_mongo_id.rb",
72
76
  "test/unit/test_mongomapper.rb",
73
77
  "test/unit/test_observing.rb",
74
78
  "test/unit/test_pagination.rb",
@@ -76,12 +80,11 @@ Gem::Specification.new do |s|
76
80
  "test/unit/test_serializations.rb",
77
81
  "test/unit/test_validations.rb"
78
82
  ]
79
- s.has_rdoc = true
80
83
  s.homepage = %q{http://github.com/jnunemaker/mongomapper}
81
84
  s.rdoc_options = ["--charset=UTF-8"]
82
85
  s.require_paths = ["lib"]
83
86
  s.rubyforge_project = %q{mongomapper}
84
- s.rubygems_version = %q{1.3.1}
87
+ s.rubygems_version = %q{1.3.5}
85
88
  s.summary = %q{Awesome gem for modeling your domain and storing it in mongo}
86
89
  s.test_files = [
87
90
  "test/functional/associations/test_belongs_to_polymorphic_proxy.rb",
@@ -104,7 +107,6 @@ Gem::Specification.new do |s|
104
107
  "test/unit/test_embedded_document.rb",
105
108
  "test/unit/test_finder_options.rb",
106
109
  "test/unit/test_key.rb",
107
- "test/unit/test_mongo_id.rb",
108
110
  "test/unit/test_mongomapper.rb",
109
111
  "test/unit/test_observing.rb",
110
112
  "test/unit/test_pagination.rb",
@@ -115,24 +117,24 @@ Gem::Specification.new do |s|
115
117
 
116
118
  if s.respond_to? :specification_version then
117
119
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
118
- s.specification_version = 2
120
+ s.specification_version = 3
119
121
 
120
122
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
121
123
  s.add_runtime_dependency(%q<activesupport>, [">= 0"])
122
- s.add_runtime_dependency(%q<mongodb-mongo>, ["= 0.10.1"])
124
+ s.add_runtime_dependency(%q<mongodb-mongo>, ["= 0.11.1"])
123
125
  s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.7.2"])
124
126
  s.add_development_dependency(%q<mocha>, ["= 0.9.4"])
125
127
  s.add_development_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
126
128
  else
127
129
  s.add_dependency(%q<activesupport>, [">= 0"])
128
- s.add_dependency(%q<mongodb-mongo>, ["= 0.10.1"])
130
+ s.add_dependency(%q<mongodb-mongo>, ["= 0.11.1"])
129
131
  s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.2"])
130
132
  s.add_dependency(%q<mocha>, ["= 0.9.4"])
131
133
  s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
132
134
  end
133
135
  else
134
136
  s.add_dependency(%q<activesupport>, [">= 0"])
135
- s.add_dependency(%q<mongodb-mongo>, ["= 0.10.1"])
137
+ s.add_dependency(%q<mongodb-mongo>, ["= 0.11.1"])
136
138
  s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.2"])
137
139
  s.add_dependency(%q<mocha>, ["= 0.9.4"])
138
140
  s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
@@ -36,4 +36,18 @@ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
36
36
  from_db.target_id.should be_nil
37
37
  from_db.target.should be_nil
38
38
  end
39
+
40
+ context "association id set but document not found" do
41
+ setup do
42
+ @status = Status.new
43
+ project = Project.new(:name => "mongomapper")
44
+ @status.target = project
45
+ @status.save.should be_true
46
+ project.destroy
47
+ end
48
+
49
+ should "return nil instead of raising error" do
50
+ @status.target.should be_nil
51
+ end
52
+ end
39
53
  end
@@ -32,4 +32,14 @@ class BelongsToProxyTest < Test::Unit::TestCase
32
32
  from_db.project = nil
33
33
  from_db.project.should be_nil
34
34
  end
35
+
36
+ context "association id set but document not found" do
37
+ setup do
38
+ @status = Status.new(:name => 'Foo', :project_id => '1234')
39
+ end
40
+
41
+ should "return nil instead of raising error" do
42
+ @status.project.should be_nil
43
+ end
44
+ end
35
45
  end