mongo_mapper-unstable 2009.10.16 → 2009.10.31

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 (51) hide show
  1. data/.gitignore +3 -1
  2. data/README.rdoc +3 -0
  3. data/Rakefile +31 -65
  4. data/VERSION +1 -1
  5. data/lib/mongo_mapper/associations/base.rb +31 -4
  6. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +0 -2
  7. data/lib/mongo_mapper/associations/many_documents_proxy.rb +21 -15
  8. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +2 -2
  9. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +21 -36
  10. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +1 -1
  11. data/lib/mongo_mapper/associations/proxy.rb +1 -0
  12. data/lib/mongo_mapper/associations.rb +114 -17
  13. data/lib/mongo_mapper/callbacks.rb +18 -0
  14. data/lib/mongo_mapper/document.rb +230 -95
  15. data/lib/mongo_mapper/dynamic_finder.rb +1 -1
  16. data/lib/mongo_mapper/embedded_document.rb +7 -3
  17. data/lib/mongo_mapper/finder_options.rb +88 -56
  18. data/lib/mongo_mapper/pagination.rb +2 -0
  19. data/lib/mongo_mapper/serialization.rb +2 -3
  20. data/lib/mongo_mapper/serializers/json_serializer.rb +1 -1
  21. data/lib/mongo_mapper/support.rb +9 -0
  22. data/lib/mongo_mapper/validations.rb +14 -42
  23. data/lib/mongo_mapper.rb +15 -13
  24. data/mongo_mapper.gemspec +13 -13
  25. data/specs.watchr +2 -2
  26. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +5 -5
  27. data/test/functional/associations/test_belongs_to_proxy.rb +28 -30
  28. data/test/functional/associations/test_many_documents_as_proxy.rb +4 -4
  29. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +27 -3
  30. data/test/functional/associations/test_many_embedded_proxy.rb +58 -38
  31. data/test/functional/associations/test_many_polymorphic_proxy.rb +49 -7
  32. data/test/functional/associations/test_many_proxy.rb +65 -15
  33. data/test/functional/test_associations.rb +3 -3
  34. data/test/functional/test_binary.rb +1 -1
  35. data/test/functional/test_callbacks.rb +1 -1
  36. data/test/functional/test_dirty.rb +3 -3
  37. data/test/functional/test_document.rb +178 -57
  38. data/test/functional/test_embedded_document.rb +1 -1
  39. data/test/functional/test_pagination.rb +18 -18
  40. data/test/functional/test_rails_compatibility.rb +1 -1
  41. data/test/functional/test_validations.rb +80 -27
  42. data/test/models.rb +93 -17
  43. data/test/support/{test_timing.rb → timing.rb} +1 -1
  44. data/test/test_helper.rb +8 -11
  45. data/test/unit/test_association_base.rb +23 -1
  46. data/test/unit/test_document.rb +29 -12
  47. data/test/unit/test_embedded_document.rb +13 -4
  48. data/test/unit/test_finder_options.rb +74 -58
  49. data/test/unit/test_mongomapper.rb +2 -2
  50. data/test/unit/test_pagination.rb +4 -0
  51. metadata +7 -7
@@ -1,55 +1,35 @@
1
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
- field = field_normalized(field)
9
- case value
10
- when Array
11
- operator_present = field.to_s =~ /^\$/
12
- criteria[field] = if operator_present
13
- value
14
- else
15
- {'$in' => value}
16
- end
17
- when Hash
18
- criteria[field] = to_mongo_criteria(value, field)
19
- else
20
- criteria[field] = value
21
- end
22
- end
23
-
24
- criteria
25
- end
26
-
27
- def self.to_mongo_options(options)
28
- options = options.dup
29
- {
30
- :fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
31
- :skip => (options.delete(:skip) || options.delete(:offset) || 0).to_i,
32
- :limit => (options.delete(:limit) || 0).to_i,
33
- :sort => options.delete(:sort) || to_mongo_sort(options.delete(:order))
34
- }
2
+ # Controls the parsing and handling of options used by finders.
3
+ #
4
+ # == Important Note
5
+ #
6
+ # This class is private to MongoMapper and should not be considered part of
7
+ # MongoMapper's public API. Some documentation herein, however, may prove
8
+ # useful for understanding how MongoMapper handles the parsing of finder
9
+ # conditions and options.
10
+ #
11
+ # @private
12
+ class FinderOperator
13
+ def initialize(field, operator)
14
+ @field, @operator = field, operator
35
15
  end
36
16
 
37
- def self.field_normalized(field)
38
- if field.to_s == 'id'
39
- :_id
40
- else
41
- field
42
- end
17
+ def to_criteria(value)
18
+ {@field => {@operator => value}}
43
19
  end
44
-
20
+ end
21
+
22
+ class FinderOptions
45
23
  OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
46
-
47
- def initialize(options)
48
- raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
49
-
50
- options = options.symbolize_keys
51
- @options, @conditions = {}, options.delete(:conditions) || {}
24
+
25
+ def initialize(model, options)
26
+ raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
27
+ options.symbolize_keys!
52
28
 
29
+ @model = model
30
+ @options = {}
31
+ @conditions = options.delete(:conditions) || {}
32
+
53
33
  options.each_pair do |key, value|
54
34
  if OptionKeys.include?(key)
55
35
  @options[key] = value
@@ -57,42 +37,94 @@ module MongoMapper
57
37
  @conditions[key] = value
58
38
  end
59
39
  end
40
+
41
+ add_sci_scope
60
42
  end
61
43
 
44
+ # @return [Hash] Mongo compatible criteria options
45
+ #
46
+ # @see FinderOptions#to_mongo_criteria
62
47
  def criteria
63
- self.class.to_mongo_criteria(@conditions)
48
+ to_mongo_criteria(@conditions)
64
49
  end
65
50
 
51
+ # @return [Hash] Mongo compatible options
66
52
  def options
67
- self.class.to_mongo_options(@options)
53
+ options = @options.dup
54
+
55
+ fields = options.delete(:fields) || options.delete(:select)
56
+ skip = options.delete(:skip) || options.delete(:offset) || 0
57
+ limit = options.delete(:limit) || 0
58
+ sort = options.delete(:sort) || convert_order_to_sort(options.delete(:order))
59
+
60
+ {:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
68
61
  end
69
62
 
63
+ # @return [Array<Hash>] Mongo criteria and options enclosed in an Array
70
64
  def to_a
71
65
  [criteria, options]
72
66
  end
73
-
67
+
74
68
  private
75
- def self.to_mongo_fields(fields)
69
+ def to_mongo_criteria(conditions, parent_key=nil)
70
+ criteria = {}
71
+
72
+ conditions.each_pair do |field, value|
73
+ field = normalized_field(field)
74
+ if field.is_a?(FinderOperator)
75
+ criteria.merge!(field.to_criteria(value))
76
+ next
77
+ end
78
+ case value
79
+ when Array
80
+ operator_present = field.to_s =~ /^\$/
81
+ criteria[field] = operator?(field) ? value : {'$in' => value}
82
+ when Hash
83
+ criteria[field] = to_mongo_criteria(value, field)
84
+ else
85
+ criteria[field] = value
86
+ end
87
+ end
88
+
89
+ criteria
90
+ end
91
+
92
+ def operator?(field)
93
+ field.to_s =~ /^\$/
94
+ end
95
+
96
+ def normalized_field(field)
97
+ field.to_s == 'id' ? :_id : field
98
+ end
99
+
100
+ # adds _type single collection inheritance scope for models that need it
101
+ def add_sci_scope
102
+ if @model.single_collection_inherited?
103
+ @conditions[:_type] = @model.to_s
104
+ end
105
+ end
106
+
107
+ def to_mongo_fields(fields)
76
108
  return if fields.blank?
77
-
109
+
78
110
  if fields.is_a?(String)
79
111
  fields.split(',').map { |field| field.strip }
80
112
  else
81
113
  fields.flatten.compact
82
114
  end
83
115
  end
84
-
85
- def self.to_mongo_sort(sort)
116
+
117
+ def convert_order_to_sort(sort)
86
118
  return if sort.blank?
87
119
  pieces = sort.split(',')
88
120
  pieces.map { |s| to_mongo_sort_piece(s) }
89
121
  end
90
-
91
- def self.to_mongo_sort_piece(str)
122
+
123
+ def to_mongo_sort_piece(str)
92
124
  field, direction = str.strip.split(' ')
93
125
  direction ||= 'ASC'
94
126
  direction = direction.upcase == 'ASC' ? 1 : -1
95
127
  [field, direction]
96
128
  end
97
129
  end
98
- end
130
+ end
@@ -30,6 +30,8 @@ module MongoMapper
30
30
  def skip
31
31
  (current_page - 1) * per_page
32
32
  end
33
+ alias offset skip # for will paginate support
34
+
33
35
 
34
36
  def method_missing(name, *args, &block)
35
37
  @subject.send(name, *args, &block)
@@ -5,7 +5,7 @@ module MongoMapper #:nodoc:
5
5
  class Serializer #:nodoc:
6
6
  attr_reader :options
7
7
 
8
- def initialize(record, options = {})
8
+ def initialize(record, options={})
9
9
  @record, @options = record, options.dup
10
10
  end
11
11
 
@@ -51,5 +51,4 @@ module MongoMapper #:nodoc:
51
51
  end
52
52
  end
53
53
 
54
- dir = Pathname(__FILE__).dirname.expand_path + 'serializers'
55
- require dir + 'json_serializer'
54
+ require 'mongo_mapper/serializers/json_serializer'
@@ -49,7 +49,7 @@ module MongoMapper #:nodoc:
49
49
  # # => {"id": 1, "name": "Konata Izumi", "age": 16,
50
50
  # "created_at": "2006/08/01", "awesome": true,
51
51
  # "permalink": "1-konata-izumi"}
52
- def to_json(options = {})
52
+ def to_json(options={})
53
53
  apply_to_json_defaults(options)
54
54
 
55
55
  if include_root_in_json
@@ -1,4 +1,5 @@
1
1
  class BasicObject #:nodoc:
2
+ alias_method :proxy_extend, :extend
2
3
  instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|^methods$|instance_eval|proxy_|^object_id$)/ }
3
4
  end unless defined?(BasicObject)
4
5
 
@@ -129,6 +130,14 @@ class String
129
130
  end
130
131
  end
131
132
 
133
+ class Symbol
134
+ %w{gt lt gte lte ne in nin mod size where exists}.each do |operator|
135
+ define_method operator do
136
+ MongoMapper::FinderOperator.new(self, "$#{operator}")
137
+ end
138
+ end
139
+ end
140
+
132
141
  class Time
133
142
  def self.to_mongo(value)
134
143
  if value.nil? || value == ''
@@ -4,65 +4,37 @@ module MongoMapper
4
4
  def validates_uniqueness_of(*args)
5
5
  add_validations(args, MongoMapper::Validations::ValidatesUniquenessOf)
6
6
  end
7
-
8
- def validates_exclusion_of(*args)
9
- add_validations(args, MongoMapper::Validations::ValidatesExclusionOf)
10
- end
11
-
12
- def validates_inclusion_of(*args)
13
- add_validations(args, MongoMapper::Validations::ValidatesInclusionOf)
14
- end
15
7
  end
16
8
 
17
9
  class ValidatesUniquenessOf < Validatable::ValidationBase
18
- option :scope
19
-
10
+ option :scope, :case_sensitive
11
+ default :case_sensitive => true
12
+
20
13
  def valid?(instance)
21
- doc = instance.class.find(:first, :conditions => {self.attribute => instance[attribute]}.merge(scope_conditions(instance)), :limit => 1)
14
+ value = instance[attribute]
15
+ return true if allow_blank && value.blank?
16
+ base_conditions = case_sensitive ? {self.attribute => value} : {}
17
+ doc = instance.class.first(base_conditions.merge(scope_conditions(instance)).merge(where_conditions(instance)))
22
18
  doc.nil? || instance.id == doc.id
23
19
  end
24
20
 
25
21
  def message(instance)
26
22
  super || "has already been taken"
27
23
  end
28
-
24
+
29
25
  def scope_conditions(instance)
30
26
  return {} unless scope
31
27
  Array(scope).inject({}) do |conditions, key|
32
28
  conditions.merge(key => instance[key])
33
29
  end
34
30
  end
35
- end
36
-
37
- class ValidatesExclusionOf < Validatable::ValidationBase
38
- required_option :within
39
-
40
- def valid?(instance)
41
- value = instance[attribute]
42
- return true if allow_nil && value.nil?
43
- return true if allow_blank && value.blank?
44
-
45
- !within.include?(instance[attribute])
46
- end
47
-
48
- def message(instance)
49
- super || "is reserved"
50
- end
51
- end
52
31
 
53
- class ValidatesInclusionOf < Validatable::ValidationBase
54
- required_option :within
55
-
56
- def valid?(instance)
57
- value = instance[attribute]
58
- return true if allow_nil && value.nil?
59
- return true if allow_blank && value.blank?
60
-
61
- within.include?(value)
62
- end
63
-
64
- def message(instance)
65
- super || "is not in the list"
32
+ def where_conditions(instance)
33
+ conditions = {}
34
+ unless case_sensitive
35
+ conditions.merge!({'$where' => "this.#{attribute}.toLowerCase() == '#{instance[attribute].downcase}'"})
36
+ end
37
+ conditions
66
38
  end
67
39
  end
68
40
  end
data/lib/mongo_mapper.rb CHANGED
@@ -1,16 +1,19 @@
1
- require 'rubygems'
2
-
3
- gem 'activesupport', '>= 2.3'
4
- gem 'mongo', '0.15.1'
5
- gem 'jnunemaker-validatable', '1.7.4'
6
-
7
- require 'activesupport'
1
+ require 'active_support'
8
2
  require 'mongo'
9
3
  require 'validatable'
10
4
 
11
5
  module MongoMapper
12
- DocumentNotFound = Class.new(StandardError)
13
- DocumentNotValid = Class.new(StandardError) do
6
+ # generic MM error
7
+ class MongoMapperError < StandardError; end
8
+
9
+ # raised when key expected to exist but not found
10
+ class KeyNotFound < MongoMapperError; end
11
+
12
+ # raised when document expected but not found
13
+ class DocumentNotFound < MongoMapperError; end
14
+
15
+ # raised when document not valid and using !
16
+ class DocumentNotValid < MongoMapperError
14
17
  def initialize(document)
15
18
  @document = document
16
19
  super("Validation failed: #{@document.errors.full_messages.join(", ")}")
@@ -60,13 +63,12 @@ module MongoMapper
60
63
  module Finders
61
64
  def dynamic_find(finder, args)
62
65
  attributes = {}
63
- find_options = args.extract_options!.deep_merge(:conditions => attributes)
64
-
65
66
  finder.attributes.each_with_index do |attr, index|
66
67
  attributes[attr] = args[index]
67
68
  end
68
-
69
- result = find(finder.finder, find_options)
69
+
70
+ options = args.extract_options!.merge(attributes)
71
+ result = find(finder.finder, options)
70
72
 
71
73
  if result.nil?
72
74
  if finder.bang
data/mongo_mapper.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongo_mapper}
8
- s.version = "0.5.5"
8
+ s.version = "0.5.8"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Nunemaker"]
12
- s.date = %q{2009-10-16}
12
+ s.date = %q{2009-10-29}
13
13
  s.default_executable = %q{mmconsole}
14
14
  s.email = %q{nunemaker@gmail.com}
15
15
  s.executables = ["mmconsole"]
@@ -73,7 +73,7 @@ Gem::Specification.new do |s|
73
73
  "test/functional/test_validations.rb",
74
74
  "test/models.rb",
75
75
  "test/support/custom_matchers.rb",
76
- "test/support/test_timing.rb",
76
+ "test/support/timing.rb",
77
77
  "test/test_helper.rb",
78
78
  "test/unit/serializers/test_json_serializer.rb",
79
79
  "test/unit/test_association_base.rb",
@@ -94,7 +94,6 @@ Gem::Specification.new do |s|
94
94
  s.homepage = %q{http://github.com/jnunemaker/mongomapper}
95
95
  s.rdoc_options = ["--charset=UTF-8"]
96
96
  s.require_paths = ["lib"]
97
- s.rubyforge_project = %q{mongomapper}
98
97
  s.rubygems_version = %q{1.3.5}
99
98
  s.summary = %q{Awesome gem for modeling your domain and storing it in mongo}
100
99
  s.test_files = [
@@ -117,7 +116,7 @@ Gem::Specification.new do |s|
117
116
  "test/functional/test_validations.rb",
118
117
  "test/models.rb",
119
118
  "test/support/custom_matchers.rb",
120
- "test/support/test_timing.rb",
119
+ "test/support/timing.rb",
121
120
  "test/test_helper.rb",
122
121
  "test/unit/serializers/test_json_serializer.rb",
123
122
  "test/unit/test_association_base.rb",
@@ -142,16 +141,16 @@ Gem::Specification.new do |s|
142
141
 
143
142
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
144
143
  s.add_runtime_dependency(%q<activesupport>, [">= 2.3"])
145
- s.add_runtime_dependency(%q<mongo>, ["= 0.15.1"])
146
- s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.7.4"])
144
+ s.add_runtime_dependency(%q<mongo>, ["= 0.16"])
145
+ s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.8.0"])
147
146
  s.add_development_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
148
147
  s.add_development_dependency(%q<shoulda>, ["= 2.10.2"])
149
148
  s.add_development_dependency(%q<timecop>, ["= 0.3.1"])
150
149
  s.add_development_dependency(%q<mocha>, ["= 0.9.4"])
151
150
  else
152
151
  s.add_dependency(%q<activesupport>, [">= 2.3"])
153
- s.add_dependency(%q<mongo>, ["= 0.15.1"])
154
- s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.4"])
152
+ s.add_dependency(%q<mongo>, ["= 0.16"])
153
+ s.add_dependency(%q<jnunemaker-validatable>, ["= 1.8.0"])
155
154
  s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
156
155
  s.add_dependency(%q<shoulda>, ["= 2.10.2"])
157
156
  s.add_dependency(%q<timecop>, ["= 0.3.1"])
@@ -159,11 +158,12 @@ Gem::Specification.new do |s|
159
158
  end
160
159
  else
161
160
  s.add_dependency(%q<activesupport>, [">= 2.3"])
162
- s.add_dependency(%q<mongo>, ["= 0.15.1"])
163
- s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.4"])
161
+ s.add_dependency(%q<mongo>, ["= 0.16"])
162
+ s.add_dependency(%q<jnunemaker-validatable>, ["= 1.8.0"])
164
163
  s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
165
164
  s.add_dependency(%q<shoulda>, ["= 2.10.2"])
166
165
  s.add_dependency(%q<timecop>, ["= 0.3.1"])
167
166
  s.add_dependency(%q<mocha>, ["= 0.9.4"])
168
167
  end
169
168
  end
169
+
data/specs.watchr CHANGED
@@ -4,7 +4,7 @@ def run(cmd)
4
4
  end
5
5
 
6
6
  def run_test_file(file)
7
- run "ruby -Itest #{file}"
7
+ run %Q(ruby -I"lib:test" -rubygems #{file})
8
8
  end
9
9
 
10
10
  def run_all_tests
@@ -12,7 +12,7 @@ def run_all_tests
12
12
  end
13
13
 
14
14
  def related_test_files(path)
15
- Dir['test/**/*.rb'].select { |file| file =~ /#{File.basename(path)}/ }
15
+ Dir['test/**/*.rb'].select { |file| file =~ /test_#{File.basename(path)}/ }
16
16
  end
17
17
 
18
18
  watch('test/test_helper\.rb') { run_all_tests }
@@ -3,8 +3,8 @@ require 'models'
3
3
 
4
4
  class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
5
5
  def setup
6
- Status.collection.clear
7
- Project.collection.clear
6
+ Status.collection.remove
7
+ Project.collection.remove
8
8
  end
9
9
 
10
10
  should "default to nil" do
@@ -14,7 +14,7 @@ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
14
14
  end
15
15
 
16
16
  should "be able to replace the association" do
17
- status = Status.new
17
+ status = Status.new(:name => 'Foo!')
18
18
  project = Project.new(:name => "mongomapper")
19
19
  status.target = project
20
20
  status.save.should be_true
@@ -27,7 +27,7 @@ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
27
27
  end
28
28
 
29
29
  should "unset the association" do
30
- status = Status.new
30
+ status = Status.new(:name => 'Foo!')
31
31
  project = Project.new(:name => "mongomapper")
32
32
  status.target = project
33
33
  status.save.should be_true
@@ -41,7 +41,7 @@ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
41
41
 
42
42
  context "association id set but document not found" do
43
43
  setup do
44
- @status = Status.new
44
+ @status = Status.new(:name => 'Foo!')
45
45
  project = Project.new(:name => "mongomapper")
46
46
  @status.target = project
47
47
  @status.save.should be_true
@@ -2,48 +2,46 @@ require 'test_helper'
2
2
  require 'models'
3
3
 
4
4
  class BelongsToProxyTest < Test::Unit::TestCase
5
- def setup
6
- Status.collection.clear
7
- Project.collection.clear
5
+ def setup
6
+ @post_class = Class.new do
7
+ include MongoMapper::Document
8
+ end
9
+
10
+ @comment_class = Class.new do
11
+ include MongoMapper::Document
12
+ key :post_id, String
13
+ end
14
+ @comment_class.belongs_to :post, :class => @post_class
15
+
16
+ @post_class.collection.remove
17
+ @comment_class.collection.remove
8
18
  end
9
19
 
10
20
  should "default to nil" do
11
- status = Status.new
12
- status.project.nil?.should == true
13
- status.project.inspect.should == 'nil'
21
+ @comment_class.new.post.nil?.should be_true
14
22
  end
15
23
 
16
24
  should "be able to replace the association" do
17
- status = Status.new
18
- project = Project.new(:name => "mongomapper")
19
- status.project = project
20
- status.save.should be_true
25
+ post = @post_class.new(:name => 'mongomapper')
26
+ comment = @comment_class.new(:name => 'Foo!', :post => post)
27
+ comment.save.should be_true
21
28
 
22
- from_db = Status.find(status.id)
23
- from_db.project.nil?.should be_false
24
- from_db.project.name.should == "mongomapper"
29
+ comment = comment.reload
30
+ comment.post.should == post
31
+ comment.post.nil?.should be_false
25
32
  end
26
33
 
27
34
  should "unset the association" do
28
- status = Status.new
29
- project = Project.new(:name => "mongomapper")
30
- status.project = project
31
- status.save.should be_true
35
+ post = @post_class.new(:name => 'mongomapper')
36
+ comment = @comment_class.new(:name => 'Foo!', :post => post)
37
+ comment.save.should be_true
32
38
 
33
- from_db = Status.find(status.id)
34
- from_db.project = nil
35
- from_db.project.nil?.should be_true
36
- from_db.project.inspect.should == 'nil'
39
+ comment = comment.reload
40
+ comment.post = nil
41
+ comment.post.nil?.should be_true
37
42
  end
38
43
 
39
- context "association id set but document not found" do
40
- setup do
41
- @status = Status.new(:name => 'Foo', :project_id => '1234')
42
- end
43
-
44
- should "return nil instead of raising error" do
45
- @status.project.nil?.should be_true
46
- @status.project.inspect.should == 'nil'
47
- end
44
+ should "return nil if id set but document not found" do
45
+ @comment_class.new(:name => 'Foo', :post_id => '1234').post.nil?.should be_true
48
46
  end
49
47
  end
@@ -3,8 +3,8 @@ require 'models'
3
3
 
4
4
  class ManyDocumentsAsProxyTest < Test::Unit::TestCase
5
5
  def setup
6
- Post.collection.clear
7
- PostComment.collection.clear
6
+ Post.collection.remove
7
+ PostComment.collection.remove
8
8
  end
9
9
 
10
10
  should "default reader to empty array" do
@@ -137,7 +137,7 @@ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
137
137
  end
138
138
 
139
139
  should "work with conditions" do
140
- comments = @post.comments.find(:all, :conditions => {:body => 'comment1'})
140
+ comments = @post.comments.find(:all, :body => 'comment1')
141
141
  comments.should == [@comment1]
142
142
  end
143
143
 
@@ -154,7 +154,7 @@ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
154
154
  end
155
155
 
156
156
  should "work with conditions" do
157
- comments = @post.comments.all(:conditions => {:body => 'comment1'})
157
+ comments = @post.comments.all(:body => 'comment1')
158
158
  comments.should == [@comment1]
159
159
  end
160
160
 
@@ -3,8 +3,8 @@ require 'models'
3
3
 
4
4
  class ManyEmbeddedPolymorphicProxyTest < Test::Unit::TestCase
5
5
  def setup
6
- Catalog.collection.clear
7
- TrModels::Fleet.collection.clear
6
+ Catalog.collection.remove
7
+ TrModels::Fleet.collection.remove
8
8
  end
9
9
 
10
10
  should "default reader to empty array" do
@@ -129,4 +129,28 @@ class ManyEmbeddedPolymorphicProxyTest < Test::Unit::TestCase
129
129
  from_db.transports[2].icu.should == true
130
130
  end
131
131
  end
132
- end
132
+
133
+ context "extending the association" do
134
+ should "work using a block passed to many" do
135
+ catalog = Catalog.new
136
+ medias = catalog.medias = [
137
+ Video.new("file" => "video.mpg", "length" => 3600, :visible => true),
138
+ Music.new("file" => "music.mp3", "bitrate" => "128kbps", :visible => true),
139
+ Image.new("file" => "image.png", "width" => 800, "height" => 600, :visible => false)
140
+ ]
141
+ catalog.save
142
+ catalog.medias.visible.should == [medias[0], medias[1]]
143
+ end
144
+
145
+ should "work using many's :extend option" do
146
+ fleet = TrModels::Fleet.new
147
+ transports = fleet.transports = [
148
+ TrModels::Car.new("license_plate" => "ABC1223", "model" => "Honda Civic", "year" => 2003, :purchased_on => 2.years.ago.to_date),
149
+ TrModels::Bus.new("license_plate" => "XYZ9090", "max_passengers" => 51, :purchased_on => 3.years.ago.to_date),
150
+ TrModels::Ambulance.new("license_plate" => "HDD3030", "icu" => true, :purchased_on => 1.year.ago.to_date)
151
+ ]
152
+ fleet.save
153
+ fleet.transports.to_be_replaced.should == [transports[1]]
154
+ end
155
+ end
156
+ end