jnunemaker-mongomapper 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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