mongo_mapper 0.5.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.
Files changed (69) hide show
  1. data/.gitignore +7 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +39 -0
  4. data/Rakefile +87 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +55 -0
  7. data/lib/mongo_mapper.rb +92 -0
  8. data/lib/mongo_mapper/associations.rb +86 -0
  9. data/lib/mongo_mapper/associations/base.rb +83 -0
  10. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  11. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  12. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
  13. data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
  14. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
  16. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongo_mapper/associations/proxy.rb +64 -0
  19. data/lib/mongo_mapper/callbacks.rb +106 -0
  20. data/lib/mongo_mapper/document.rb +317 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  22. data/lib/mongo_mapper/embedded_document.rb +354 -0
  23. data/lib/mongo_mapper/finder_options.rb +94 -0
  24. data/lib/mongo_mapper/key.rb +32 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +51 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/save_with_validation.rb +19 -0
  30. data/lib/mongo_mapper/serialization.rb +55 -0
  31. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongo_mapper/support.rb +157 -0
  33. data/lib/mongo_mapper/validations.rb +69 -0
  34. data/mongo_mapper.gemspec +156 -0
  35. data/test/NOTE_ON_TESTING +1 -0
  36. data/test/custom_matchers.rb +48 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +54 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +46 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
  40. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
  41. data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
  42. data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
  43. data/test/functional/associations/test_many_proxy.rb +331 -0
  44. data/test/functional/test_associations.rb +48 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_document.rb +951 -0
  48. data/test/functional/test_embedded_document.rb +97 -0
  49. data/test/functional/test_pagination.rb +87 -0
  50. data/test/functional/test_rails_compatibility.rb +30 -0
  51. data/test/functional/test_validations.rb +279 -0
  52. data/test/models.rb +169 -0
  53. data/test/test_helper.rb +29 -0
  54. data/test/unit/serializers/test_json_serializer.rb +189 -0
  55. data/test/unit/test_association_base.rb +144 -0
  56. data/test/unit/test_document.rb +165 -0
  57. data/test/unit/test_dynamic_finder.rb +125 -0
  58. data/test/unit/test_embedded_document.rb +645 -0
  59. data/test/unit/test_finder_options.rb +193 -0
  60. data/test/unit/test_key.rb +163 -0
  61. data/test/unit/test_mongomapper.rb +28 -0
  62. data/test/unit/test_observing.rb +101 -0
  63. data/test/unit/test_pagination.rb +109 -0
  64. data/test/unit/test_rails_compatibility.rb +39 -0
  65. data/test/unit/test_serializations.rb +52 -0
  66. data/test/unit/test_support.rb +272 -0
  67. data/test/unit/test_time_zones.rb +40 -0
  68. data/test/unit/test_validations.rb +503 -0
  69. metadata +204 -0
@@ -0,0 +1,193 @@
1
+ require 'test_helper'
2
+
3
+ class FinderOptionsTest < Test::Unit::TestCase
4
+ include MongoMapper
5
+
6
+ should "raise error if provided something other than a hash" do
7
+ lambda { FinderOptions.new }.should raise_error(ArgumentError)
8
+ lambda { FinderOptions.new(1) }.should raise_error(ArgumentError)
9
+ end
10
+
11
+ should "have symbolize the keys of the hash provided" do
12
+ FinderOptions.new('offset' => 1).options.keys.map do |key|
13
+ key.should be_instance_of(Symbol)
14
+ end
15
+ end
16
+
17
+ context "#criteria" do
18
+ should "convert conditions to criteria" do
19
+ FinderOptions.expects(:to_mongo_criteria).with(:foo => 1).returns({})
20
+ FinderOptions.new(:conditions => {:foo => 1}).criteria
21
+ end
22
+ end
23
+
24
+ context "#options" do
25
+ should "convert options to mongo options" do
26
+ FinderOptions.expects(:to_mongo_options).with(:order => 'foo asc', :select => 'foo,bar').returns({})
27
+ FinderOptions.new(:order => 'foo asc', :select => 'foo,bar').options
28
+ end
29
+ end
30
+
31
+ context "Converting conditions to criteria" do
32
+ should "work with simple criteria" do
33
+ FinderOptions.to_mongo_criteria(:foo => 'bar').should == {
34
+ :foo => 'bar'
35
+ }
36
+
37
+ FinderOptions.to_mongo_criteria(:foo => 'bar', :baz => 'wick').should == {
38
+ :foo => 'bar',
39
+ :baz => 'wick'
40
+ }
41
+ end
42
+
43
+ should "convert id to _id" do
44
+ FinderOptions.to_mongo_criteria(:id => '1').should == {
45
+ :_id => '1'
46
+ }
47
+ end
48
+
49
+ should "use $in for arrays" do
50
+ FinderOptions.to_mongo_criteria(:foo => [1,2,3]).should == {
51
+ :foo => {'$in' => [1,2,3]}
52
+ }
53
+ end
54
+
55
+ should "not use $in for arrays if already using array operator" do
56
+ FinderOptions.to_mongo_criteria(:foo => {'$all' => [1,2,3]}).should == {
57
+ :foo => {'$all' => [1,2,3]}
58
+ }
59
+
60
+ FinderOptions.to_mongo_criteria(:foo => {'$any' => [1,2,3]}).should == {
61
+ :foo => {'$any' => [1,2,3]}
62
+ }
63
+ end
64
+
65
+ should "work arbitrarily deep" do
66
+ FinderOptions.to_mongo_criteria(:foo => {:bar => [1,2,3]}).should == {
67
+ :foo => {:bar => {'$in' => [1,2,3]}}
68
+ }
69
+
70
+ FinderOptions.to_mongo_criteria(:foo => {:bar => {'$any' => [1,2,3]}}).should == {
71
+ :foo => {:bar => {'$any' => [1,2,3]}}
72
+ }
73
+ end
74
+ end
75
+
76
+ context "ordering" do
77
+ should "single field with ascending direction" do
78
+ hash = OrderedHash.new
79
+ hash[:foo] = 1
80
+ FinderOptions.to_mongo_options(:order => 'foo asc')[:sort].should == hash
81
+ FinderOptions.to_mongo_options(:order => 'foo ASC')[:sort].should == hash
82
+ end
83
+
84
+ should "single field with descending direction" do
85
+ hash = OrderedHash.new
86
+ hash[:foo] = -1
87
+ FinderOptions.to_mongo_options(:order => 'foo desc')[:sort].should == hash
88
+ FinderOptions.to_mongo_options(:order => 'foo DESC')[:sort].should == hash
89
+ end
90
+
91
+ should "convert field without direction to ascending" do
92
+ hash = OrderedHash.new
93
+ hash[:foo] = 1
94
+ FinderOptions.to_mongo_options(:order => 'foo')[:sort].should == hash
95
+ end
96
+
97
+ should "convert multiple fields with directions" do
98
+ hash = OrderedHash.new
99
+ hash[:foo] = -1
100
+ hash[:bar] = 1
101
+ hash[:baz] = -1
102
+ FinderOptions.to_mongo_options(:order => 'foo desc, bar asc, baz desc')[:sort].should == hash
103
+ end
104
+
105
+ should "convert multiple fields with some missing directions" do
106
+ hash = OrderedHash.new
107
+ hash[:foo] = -1
108
+ hash[:bar] = 1
109
+ hash[:baz] = 1
110
+ FinderOptions.to_mongo_options(:order => 'foo desc, bar, baz')[:sort].should == hash
111
+ end
112
+
113
+ should "just use sort if sort and order are present" do
114
+ FinderOptions.to_mongo_options(:sort => {'$natural' => 1}, :order => 'foo asc')[:sort].should == {
115
+ '$natural' => 1
116
+ }
117
+ end
118
+
119
+ should "convert natural in order to proper" do
120
+ hash = OrderedHash.new
121
+ hash[:'$natural'] = 1
122
+ FinderOptions.to_mongo_options(:order => '$natural asc')[:sort].should == hash
123
+ hash[:'$natural'] = -1
124
+ FinderOptions.to_mongo_options(:order => '$natural desc')[:sort].should == hash
125
+ end
126
+
127
+ should "work for natural order ascending" do
128
+ FinderOptions.to_mongo_options(:sort => {'$natural' => 1})[:sort]['$natural'].should == 1
129
+ end
130
+
131
+ should "work for natural order descending" do
132
+ FinderOptions.to_mongo_options(:sort => {'$natural' => -1})[:sort]['$natural'].should == -1
133
+ end
134
+ end
135
+
136
+ context "skip" do
137
+ should "default to 0" do
138
+ FinderOptions.to_mongo_options({})[:skip].should == 0
139
+ end
140
+
141
+ should "use skip provided" do
142
+ FinderOptions.to_mongo_options(:skip => 2)[:skip].should == 2
143
+ end
144
+
145
+ should "covert string to integer" do
146
+ FinderOptions.to_mongo_options(:skip => '2')[:skip].should == 2
147
+ end
148
+
149
+ should "convert offset to skip" do
150
+ FinderOptions.to_mongo_options(:offset => 1)[:skip].should == 1
151
+ end
152
+ end
153
+
154
+ context "limit" do
155
+ should "default to 0" do
156
+ FinderOptions.to_mongo_options({})[:limit].should == 0
157
+ end
158
+
159
+ should "use limit provided" do
160
+ FinderOptions.to_mongo_options(:limit => 2)[:limit].should == 2
161
+ end
162
+
163
+ should "covert string to integer" do
164
+ FinderOptions.to_mongo_options(:limit => '2')[:limit].should == 2
165
+ end
166
+ end
167
+
168
+ context "fields" do
169
+ should "default to nil" do
170
+ FinderOptions.to_mongo_options({})[:fields].should be(nil)
171
+ end
172
+
173
+ should "be converted to nil if empty string" do
174
+ FinderOptions.to_mongo_options(:fields => '')[:fields].should be(nil)
175
+ end
176
+
177
+ should "be converted to nil if []" do
178
+ FinderOptions.to_mongo_options(:fields => [])[:fields].should be(nil)
179
+ end
180
+
181
+ should "should work with array" do
182
+ FinderOptions.to_mongo_options({:fields => %w(a b)})[:fields].should == %w(a b)
183
+ end
184
+
185
+ should "convert comma separated list to array" do
186
+ FinderOptions.to_mongo_options({:fields => 'a, b'})[:fields].should == %w(a b)
187
+ end
188
+
189
+ should "also work as select" do
190
+ FinderOptions.new(:select => %w(a b)).options[:fields].should == %w(a b)
191
+ end
192
+ end
193
+ end # FinderOptionsTest
@@ -0,0 +1,163 @@
1
+ require 'test_helper'
2
+
3
+ class Address
4
+ include MongoMapper::EmbeddedDocument
5
+
6
+ key :address, String
7
+ key :city, String
8
+ key :state, String
9
+ key :zip, Integer
10
+ end
11
+
12
+ class FooType < Struct.new(:bar)
13
+ def self.to_mongo(value)
14
+ 'to_mongo'
15
+ end
16
+
17
+ def self.from_mongo(value)
18
+ 'from_mongo'
19
+ end
20
+ end
21
+
22
+ class KeyTest < Test::Unit::TestCase
23
+ include MongoMapper
24
+
25
+ context "Initializing a new key" do
26
+ should "allow setting the name" do
27
+ Key.new(:foo, String).name.should == 'foo'
28
+ end
29
+
30
+ should "allow setting the type" do
31
+ Key.new(:foo, Integer).type.should be(Integer)
32
+ end
33
+
34
+ should "allow setting options" do
35
+ Key.new(:foo, Integer, :required => true).options[:required].should be(true)
36
+ end
37
+
38
+ should "default options to {}" do
39
+ Key.new(:foo, Integer, nil).options.should == {}
40
+ end
41
+
42
+ should "symbolize option keys" do
43
+ Key.new(:foo, Integer, 'required' => true).options[:required].should be(true)
44
+ end
45
+
46
+ should "work with just name" do
47
+ key = Key.new(:foo)
48
+ key.name.should == 'foo'
49
+ end
50
+
51
+ should "work with name and type" do
52
+ key = Key.new(:foo, String)
53
+ key.name.should == 'foo'
54
+ key.type.should == String
55
+ end
56
+
57
+ should "work with name, type, and options" do
58
+ key = Key.new(:foo, String, :required => true)
59
+ key.name.should == 'foo'
60
+ key.type.should == String
61
+ key.options[:required].should be_true
62
+ end
63
+
64
+ should "work with name and options" do
65
+ key = Key.new(:foo, :required => true)
66
+ key.name.should == 'foo'
67
+ key.options[:required].should be_true
68
+ end
69
+ end
70
+
71
+ context "A key" do
72
+ should "be equal to another key with same name and type" do
73
+ Key.new(:name, String).should == Key.new(:name, String)
74
+ end
75
+
76
+ should "not be equal to another key with different name" do
77
+ Key.new(:name, String).should_not == Key.new(:foo, String)
78
+ end
79
+
80
+ should "not be equal to another key with different type" do
81
+ Key.new(:name, String).should_not == Key.new(:name, Integer)
82
+ end
83
+
84
+ should "know if it is a embedded_document" do
85
+ klass = Class.new do
86
+ include MongoMapper::EmbeddedDocument
87
+ end
88
+ Key.new(:name, klass).embeddable?.should be_true
89
+ end
90
+
91
+ should "know if it is not a embedded_document" do
92
+ Key.new(:name, String).embeddable?.should be_false
93
+ end
94
+ end
95
+
96
+ context "setting a value with a custom type" do
97
+ should "correctly typecast" do
98
+ key = Key.new(:foo, FooType)
99
+ key.set("something").should == 'to_mongo'
100
+ end
101
+
102
+ should "correctly typecast if object of that type is given" do
103
+ key = Key.new(:foo, FooType)
104
+ key.set(FooType.new('something')).should == 'to_mongo'
105
+ end
106
+ end
107
+
108
+ context "getting a value with a custom type" do
109
+ should "use #from_mongo to convert back to custom type" do
110
+ key = Key.new(:foo, FooType)
111
+ key.get('something').should == 'from_mongo'
112
+ end
113
+ end
114
+
115
+ context "getting a value" do
116
+ should "work with a type" do
117
+ key = Key.new(:foo, String)
118
+ key.get('bar').should == 'bar'
119
+ end
120
+
121
+ should "work without type" do
122
+ key = Key.new(:foo)
123
+ key.get([1, '2']).should == [1, '2']
124
+ key.get(false).should == false
125
+ key.get({}).should == {}
126
+ end
127
+
128
+ context "for a embedded_document" do
129
+ should "default to nil" do
130
+ key = Key.new(:foo, Address)
131
+ key.get(nil).should be_nil
132
+ end
133
+
134
+ should "return instance if instance" do
135
+ address = Address.new(:city => 'South Bend', :state => 'IN', :zip => 46544)
136
+ key = Key.new(:foo, Address)
137
+ key.get(address).should == address
138
+ end
139
+ end
140
+ end
141
+
142
+ context "getting a value with a default set" do
143
+ setup do
144
+ @key = Key.new(:foo, String, :default => 'baz')
145
+ end
146
+
147
+ should "return default value if value nil" do
148
+ @key.get(nil).should == 'baz'
149
+ end
150
+
151
+ should "return value if not blank" do
152
+ @key.get('foobar').should == 'foobar'
153
+ end
154
+
155
+ should "work with Boolean type and false value" do
156
+ Key.new(:active, Boolean, :default => false).get(nil).should be_false
157
+ end
158
+
159
+ should "work with Boolean type and true value" do
160
+ Key.new(:active, Boolean, :default => true).get(nil).should be_true
161
+ end
162
+ end
163
+ end # KeyTest
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class Address; end
4
+
5
+ class MongoMapperTest < Test::Unit::TestCase
6
+ should "be able to write and read connection" do
7
+ conn = Mongo::Connection.new
8
+ MongoMapper.connection = conn
9
+ MongoMapper.connection.should == conn
10
+ end
11
+
12
+ should "default connection to new mongo ruby driver" do
13
+ MongoMapper.connection = nil
14
+ MongoMapper.connection.should be_instance_of(Mongo::Connection)
15
+ end
16
+
17
+ should "be able to write and read default database" do
18
+ MongoMapper.database = DefaultDatabase
19
+ MongoMapper.database.should be_instance_of(Mongo::DB)
20
+ MongoMapper.database.name.should == DefaultDatabase
21
+ end
22
+
23
+ should "have document not found error" do
24
+ lambda {
25
+ MongoMapper::DocumentNotFound
26
+ }.should_not raise_error
27
+ end
28
+ end
@@ -0,0 +1,101 @@
1
+ require 'test_helper'
2
+
3
+ class Comment
4
+ include MongoMapper::Document
5
+
6
+ key :name, String
7
+ key :body, String
8
+
9
+ attr_accessor :callers
10
+ before_validation :record_callers
11
+
12
+ def after_validation
13
+ record_callers
14
+ end
15
+
16
+ def record_callers
17
+ callers << self.class if callers
18
+ end
19
+ end
20
+
21
+ class Article
22
+ include MongoMapper::Document
23
+
24
+ key :title, String
25
+ key :body, String
26
+ end
27
+
28
+ class CommentObserver < MongoMapper::Observer
29
+ attr_accessor :callers
30
+
31
+ def after_validation(model)
32
+ callers << self.class if callers
33
+ end
34
+ end
35
+
36
+ class AuditObserver < MongoMapper::Observer
37
+ observe Article, Comment
38
+ attr_reader :document
39
+
40
+ def after_validation(document)
41
+ @document = document
42
+ end
43
+ end
44
+
45
+ class GlobalObserver < MongoMapper::Observer
46
+ observe Article, Comment
47
+ attr_reader :document
48
+
49
+ def before_save(document)
50
+ @document = document
51
+ end
52
+ end
53
+
54
+ class NonAutomaticObserver < MongoMapper::Observer
55
+ observe Comment
56
+ attr_reader :comment
57
+
58
+ def after_validation(comment)
59
+ @comment = comment
60
+ end
61
+ end
62
+
63
+ class ObserverTest < Test::Unit::TestCase
64
+ should "fire model callbacks before observer" do
65
+ callers = []
66
+ comment = Comment.new
67
+ comment.callers = callers
68
+
69
+ CommentObserver.instance.callers = callers
70
+
71
+ comment.valid?
72
+ callers.should == [Comment, Comment, CommentObserver]
73
+ end
74
+
75
+ should "automatically observe model based on name when possible" do
76
+ CommentObserver.observed_class.should == Comment
77
+ end
78
+
79
+ should "be able to observe other models using observe" do
80
+ obs = NonAutomaticObserver.instance
81
+ comment = Comment.new(:name => 'John Nunemaker', :body => 'is awesome')
82
+ comment.valid?
83
+ obs.comment.name.should == 'John Nunemaker'
84
+ obs.comment.body.should == 'is awesome'
85
+ end
86
+
87
+ should "be able to observe multiple models" do
88
+ obs = AuditObserver.instance
89
+ comment = Comment.new(:name => 'Steve Smith', :body => 'is awesome')
90
+ comment.valid?
91
+
92
+ obs.document.name.should == 'Steve Smith'
93
+ obs.document.body.should == 'is awesome'
94
+
95
+ article = Article.new(:title => 'Ordered List Is Awesome', :body => 'Learn to accept it!')
96
+ article.valid?
97
+
98
+ obs.document.title.should == 'Ordered List Is Awesome'
99
+ obs.document.body.should == 'Learn to accept it!'
100
+ end
101
+ end