mongo_mapper-unstable 2009.10.16 → 2009.10.31

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