shingara-mongomapper 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/.gitignore +7 -0
  2. data/History +70 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +73 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +56 -0
  8. data/lib/mongomapper.rb +77 -0
  9. data/lib/mongomapper/associations.rb +84 -0
  10. data/lib/mongomapper/associations/base.rb +69 -0
  11. data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  12. data/lib/mongomapper/associations/belongs_to_proxy.rb +22 -0
  13. data/lib/mongomapper/associations/many_documents_proxy.rb +103 -0
  14. data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongomapper/associations/many_embedded_proxy.rb +17 -0
  16. data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongomapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongomapper/associations/proxy.rb +63 -0
  19. data/lib/mongomapper/callbacks.rb +106 -0
  20. data/lib/mongomapper/document.rb +348 -0
  21. data/lib/mongomapper/dynamic_finder.rb +38 -0
  22. data/lib/mongomapper/embedded_document.rb +265 -0
  23. data/lib/mongomapper/finder_options.rb +85 -0
  24. data/lib/mongomapper/key.rb +76 -0
  25. data/lib/mongomapper/observing.rb +50 -0
  26. data/lib/mongomapper/pagination.rb +52 -0
  27. data/lib/mongomapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
  29. data/lib/mongomapper/save_with_validation.rb +19 -0
  30. data/lib/mongomapper/serialization.rb +55 -0
  31. data/lib/mongomapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongomapper/support.rb +30 -0
  33. data/lib/mongomapper/validations.rb +47 -0
  34. data/mongomapper.gemspec +142 -0
  35. data/test/NOTE_ON_TESTING +1 -0
  36. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
  37. data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
  38. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
  39. data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
  40. data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
  41. data/test/functional/associations/test_many_proxy.rb +295 -0
  42. data/test/functional/test_associations.rb +47 -0
  43. data/test/functional/test_callbacks.rb +85 -0
  44. data/test/functional/test_document.rb +952 -0
  45. data/test/functional/test_pagination.rb +81 -0
  46. data/test/functional/test_rails_compatibility.rb +30 -0
  47. data/test/functional/test_validations.rb +172 -0
  48. data/test/models.rb +139 -0
  49. data/test/test_helper.rb +67 -0
  50. data/test/unit/serializers/test_json_serializer.rb +157 -0
  51. data/test/unit/test_association_base.rb +144 -0
  52. data/test/unit/test_document.rb +123 -0
  53. data/test/unit/test_embedded_document.rb +526 -0
  54. data/test/unit/test_finder_options.rb +183 -0
  55. data/test/unit/test_key.rb +247 -0
  56. data/test/unit/test_mongomapper.rb +28 -0
  57. data/test/unit/test_observing.rb +101 -0
  58. data/test/unit/test_pagination.rb +113 -0
  59. data/test/unit/test_rails_compatibility.rb +34 -0
  60. data/test/unit/test_serializations.rb +52 -0
  61. data/test/unit/test_validations.rb +259 -0
  62. metadata +189 -0
@@ -0,0 +1,157 @@
1
+ require 'test_helper'
2
+
3
+ class JsonSerializationTest < Test::Unit::TestCase
4
+ class Contact
5
+ include MongoMapper::Document
6
+ key :name, String
7
+ key :age, Integer
8
+ key :created_at, Time
9
+ key :awesome, Boolean
10
+ key :preferences, Hash
11
+ end
12
+
13
+ def setup
14
+ Contact.include_root_in_json = false
15
+ @contact = Contact.new(
16
+ :name => 'Konata Izumi',
17
+ :age => 16,
18
+ :created_at => Time.utc(2006, 8, 1),
19
+ :awesome => true,
20
+ :preferences => { :shows => 'anime' }
21
+ )
22
+ end
23
+
24
+ should "include demodulized root" do
25
+ Contact.include_root_in_json = true
26
+ assert_match %r{^\{"contact": \{}, @contact.to_json
27
+ end
28
+
29
+ should "encode all encodable attributes" do
30
+ json = @contact.to_json
31
+
32
+ assert_no_match %r{"_id"}, json
33
+ assert_match %r{"name":"Konata Izumi"}, json
34
+ assert_match %r{"age":16}, json
35
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
36
+ assert_match %r{"awesome":true}, json
37
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
38
+ end
39
+
40
+ should "allow attribute filtering with only" do
41
+ json = @contact.to_json(:only => [:name, :age])
42
+
43
+ assert_no_match %r{"_id"}, json
44
+ assert_match %r{"name":"Konata Izumi"}, json
45
+ assert_match %r{"age":16}, json
46
+ assert_no_match %r{"awesome"}, json
47
+ assert_no_match %r{"created_at"}, json
48
+ assert_no_match %r{"preferences"}, json
49
+ end
50
+
51
+ should "allow attribute filtering with except" do
52
+ json = @contact.to_json(:except => [:name, :age])
53
+
54
+ assert_no_match %r{"_id"}, json
55
+ assert_no_match %r{"name"}, json
56
+ assert_no_match %r{"age"}, json
57
+ assert_match %r{"awesome"}, json
58
+ assert_match %r{"created_at"}, json
59
+ assert_match %r{"preferences"}, json
60
+ end
61
+
62
+ context "_id key" do
63
+ should "not be included by default" do
64
+ json = @contact.to_json
65
+ assert_no_match %r{"_id":}, json
66
+ end
67
+
68
+ should "not be included even if :except is used" do
69
+ json = @contact.to_json(:except => :name)
70
+ assert_no_match %r{"_id":}, json
71
+ end
72
+ end
73
+
74
+ context "id method" do
75
+ setup do
76
+ def @contact.label; "Has cheezburger"; end
77
+ def @contact.favorite_quote; "Constraints are liberating"; end
78
+ end
79
+
80
+ should "be included by default" do
81
+ json = @contact.to_json
82
+ assert_match %r{"id"}, json
83
+ end
84
+
85
+ should "be included when single method included" do
86
+ json = @contact.to_json(:methods => :label)
87
+ assert_match %r{"id"}, json
88
+ assert_match %r{"label":"Has cheezburger"}, json
89
+ assert_match %r{"name":"Konata Izumi"}, json
90
+ assert_no_match %r{"favorite_quote":"Constraints are liberating"}, json
91
+ end
92
+
93
+ should "be included when multiple methods included" do
94
+ json = @contact.to_json(:methods => [:label, :favorite_quote])
95
+ assert_match %r{"id"}, json
96
+ assert_match %r{"label":"Has cheezburger"}, json
97
+ assert_match %r{"favorite_quote":"Constraints are liberating"}, json
98
+ assert_match %r{"name":"Konata Izumi"}, json
99
+ end
100
+
101
+ should "not be included if :only is present" do
102
+ json = @contact.to_json(:only => :name)
103
+ assert_no_match %r{"id":}, json
104
+ end
105
+ end
106
+
107
+ context "including methods" do
108
+ setup do
109
+ def @contact.label; "Has cheezburger"; end
110
+ def @contact.favorite_quote; "Constraints are liberating"; end
111
+ end
112
+
113
+ should "include single method" do
114
+ json = @contact.to_json(:methods => :label)
115
+ assert_match %r{"label":"Has cheezburger"}, json
116
+ end
117
+
118
+ should "include multiple methods" do
119
+ json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
120
+ assert_match %r{"label":"Has cheezburger"}, json
121
+ assert_match %r{"favorite_quote":"Constraints are liberating"}, json
122
+ assert_match %r{"name":"Konata Izumi"}, json
123
+ assert_no_match %r{"age":16}, json
124
+ assert_no_match %r{"awesome"}, json
125
+ assert_no_match %r{"created_at"}, json
126
+ assert_no_match %r{"preferences"}, json
127
+ end
128
+ end
129
+
130
+ context "array of records" do
131
+ setup do
132
+ @contacts = [
133
+ Contact.new(:name => 'David', :age => 39),
134
+ Contact.new(:name => 'Mary', :age => 14)
135
+ ]
136
+ end
137
+
138
+ should "allow attribute filtering with only" do
139
+ assert_equal %([{"name":"David"},{"name":"Mary"}]), @contacts.to_json(:only => :name)
140
+ end
141
+
142
+ should "allow attribute filtering with except" do
143
+ json = @contacts.to_json(:except => [:name, :preferences, :awesome, :created_at, :updated_at])
144
+ assert_equal %([{"id":null,"age":39},{"id":null,"age":14}]), json
145
+ end
146
+ end
147
+
148
+ should "allow options for hash of records" do
149
+ contacts = {
150
+ 1 => Contact.new(:name => 'David', :age => 39),
151
+ 2 => Contact.new(:name => 'Mary', :age => 14)
152
+ }
153
+
154
+ assert_equal %({"1":{"name":"David"}}), contacts.to_json(:only => [1, :name])
155
+ end
156
+
157
+ end
@@ -0,0 +1,144 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class FooMonster; end
5
+
6
+ class AssociationBaseTest < Test::Unit::TestCase
7
+ include MongoMapper::Associations
8
+
9
+ should "initialize with type and name" do
10
+ base = Base.new(:many, :foos)
11
+ base.type.should == :many
12
+ base.name.should == :foos
13
+ end
14
+
15
+ should "also allow options when initializing" do
16
+ base = Base.new(:many, :foos, :polymorphic => true)
17
+ base.options[:polymorphic].should be_true
18
+ end
19
+
20
+ context "class_name" do
21
+ should "work for belongs_to" do
22
+ Base.new(:belongs_to, :user).class_name.should == 'User'
23
+ end
24
+
25
+ should "work for many" do
26
+ Base.new(:many, :smart_people).class_name.should == 'SmartPerson'
27
+ end
28
+
29
+ should "be changeable using class_name option" do
30
+ base = Base.new(:many, :smart_people, :class_name => 'IntelligentPerson')
31
+ base.class_name.should == 'IntelligentPerson'
32
+ end
33
+ end
34
+
35
+ context "klass" do
36
+ should "be class_name constantized" do
37
+ Base.new(:belongs_to, :foo_monster).klass.should == FooMonster
38
+ end
39
+ end
40
+
41
+ context "many?" do
42
+ should "be true if many" do
43
+ Base.new(:many, :foos).many?.should be_true
44
+ end
45
+
46
+ should "be false if not many" do
47
+ Base.new(:belongs_to, :foo).many?.should be_false
48
+ end
49
+ end
50
+
51
+ context "belongs_to?" do
52
+ should "be true if belongs_to" do
53
+ Base.new(:belongs_to, :foo).belongs_to?.should be_true
54
+ end
55
+
56
+ should "be false if not belongs_to" do
57
+ Base.new(:many, :foos).belongs_to?.should be_false
58
+ end
59
+ end
60
+
61
+ context "polymorphic?" do
62
+ should "be true if polymorphic" do
63
+ Base.new(:many, :foos, :polymorphic => true).polymorphic?.should be_true
64
+ end
65
+
66
+ should "be false if not polymorphic" do
67
+ Base.new(:many, :bars).polymorphic?.should be_false
68
+ end
69
+ end
70
+
71
+ context "type_key_name" do
72
+ should "be _type for many" do
73
+ Base.new(:many, :foos).type_key_name.should == '_type'
74
+ end
75
+
76
+ should "be association name _ type for belongs_to" do
77
+ Base.new(:belongs_to, :foo).type_key_name.should == 'foo_type'
78
+ end
79
+ end
80
+
81
+ context "foreign_key" do
82
+ should "default to assocation_name_id" do
83
+ base = Base.new(:belongs_to, :foo)
84
+ base.foreign_key.should == 'foo_id'
85
+ end
86
+
87
+ should "be overridable with :foreign_key option" do
88
+ base = Base.new(:belongs_to, :foo, :foreign_key => 'foobar_id')
89
+ base.foreign_key.should == 'foobar_id'
90
+ end
91
+ end
92
+
93
+ should "have ivar that is association name" do
94
+ Base.new(:belongs_to, :foo).ivar.should == '@_foo'
95
+ end
96
+
97
+ context "embeddable?" do
98
+ should "be true if class is embeddable" do
99
+ base = Base.new(:many, :medias)
100
+ base.embeddable?.should be_true
101
+ end
102
+
103
+ should "be false if class is not embeddable" do
104
+ base = Base.new(:many, :statuses)
105
+ base.embeddable?.should be_false
106
+
107
+ base = Base.new(:belongs_to, :project)
108
+ base.embeddable?.should be_false
109
+ end
110
+ end
111
+
112
+ context "proxy_class" do
113
+ should "be ManyProxy for many" do
114
+ base = Base.new(:many, :statuses)
115
+ base.proxy_class.should == ManyProxy
116
+ end
117
+
118
+ should "be ManyPolymorphicProxy for polymorphic many" do
119
+ base = Base.new(:many, :messages, :polymorphic => true)
120
+ base.proxy_class.should == ManyPolymorphicProxy
121
+ end
122
+
123
+ should "be ManyEmbeddedProxy for many embedded" do
124
+ base = Base.new(:many, :medias)
125
+ base.proxy_class.should == ManyEmbeddedProxy
126
+ end
127
+
128
+ should "be ManyEmbeddedPolymorphicProxy for polymorphic many embedded" do
129
+ base = Base.new(:many, :medias, :polymorphic => true)
130
+ base.proxy_class.should == ManyEmbeddedPolymorphicProxy
131
+ end
132
+
133
+ should "be BelongsToProxy for belongs_to" do
134
+ base = Base.new(:belongs_to, :project)
135
+ base.proxy_class.should == BelongsToProxy
136
+ end
137
+
138
+ should "be BelongsToPolymorphicProxy for polymorphic belongs_to" do
139
+ base = Base.new(:belongs_to, :target, :polymorphic => true)
140
+ base.proxy_class.should == BelongsToPolymorphicProxy
141
+ end
142
+ end
143
+
144
+ end
@@ -0,0 +1,123 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class DocumentTest < Test::Unit::TestCase
5
+ context "The Document Class" do
6
+ setup do
7
+ @document = Class.new do
8
+ include MongoMapper::Document
9
+ end
10
+ end
11
+
12
+ should "track its descendants" do
13
+ MongoMapper::Document.descendants.should include(@document)
14
+ end
15
+
16
+ should "use default database by default" do
17
+ @document.database.should == MongoMapper.database
18
+ end
19
+
20
+ should "have a connection" do
21
+ @document.connection.should be_instance_of(XGen::Mongo::Driver::Mongo)
22
+ end
23
+
24
+ should "allow setting different connection without affecting the default" do
25
+ conn = XGen::Mongo::Driver::Mongo.new
26
+ @document.connection conn
27
+ @document.connection.should == conn
28
+ @document.connection.should_not == MongoMapper.connection
29
+ end
30
+
31
+ should "allow setting a different database without affecting the default" do
32
+ @document.database AlternateDatabase
33
+ @document.database.name.should == AlternateDatabase
34
+
35
+ another_document = Class.new do
36
+ include MongoMapper::Document
37
+ end
38
+ another_document.database.should == MongoMapper.database
39
+ end
40
+
41
+ should "default collection name to class name tableized" do
42
+ class Item
43
+ include MongoMapper::Document
44
+ end
45
+
46
+ Item.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
47
+ Item.collection.name.should == 'items'
48
+ end
49
+
50
+ should "allow setting the collection name" do
51
+ @document.collection('foobar')
52
+ @document.collection.should be_instance_of(XGen::Mongo::Driver::Collection)
53
+ @document.collection.name.should == 'foobar'
54
+ end
55
+ end # Document class
56
+
57
+ context "Documents that inherit from other documents" do
58
+ should "default collection to inherited class" do
59
+ Message.collection.name.should == 'messages'
60
+ Enter.collection.name.should == 'messages'
61
+ Exit.collection.name.should == 'messages'
62
+ Chat.collection.name.should == 'messages'
63
+ end
64
+
65
+ should "track subclasses" do
66
+ Message.subclasses.should == [Enter, Exit, Chat]
67
+ end
68
+ end
69
+
70
+ context "An instance of a document" do
71
+ setup do
72
+ @document = Class.new do
73
+ include MongoMapper::Document
74
+
75
+ key :name, String
76
+ key :age, Integer
77
+ end
78
+ @document.collection.clear
79
+ end
80
+
81
+ should "have access to the class's collection" do
82
+ doc = @document.new
83
+ doc.collection.should == @document.collection
84
+ end
85
+
86
+ should "use default values if defined for keys" do
87
+ @document.key :active, Boolean, :default => true
88
+
89
+ @document.new.active.should be_true
90
+ @document.new(:active => false).active.should be_false
91
+ end
92
+
93
+ context "new?" do
94
+ should "be true if no id" do
95
+ @document.new.new?.should be_true
96
+ end
97
+
98
+ should "be true if id but using custom id and not saved yet" do
99
+ doc = @document.new
100
+ doc.id = '1234'
101
+ doc.new?.should be_true
102
+ end
103
+ end
104
+
105
+ context "equality" do
106
+ should "be equal if id and class are the same" do
107
+ (@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
108
+ end
109
+
110
+ should "not be equal if class same but id different" do
111
+ (@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
112
+ end
113
+
114
+ should "not be equal if id same but class different" do
115
+ @another_document = Class.new do
116
+ include MongoMapper::Document
117
+ end
118
+
119
+ (@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
120
+ end
121
+ end
122
+ end # instance of a document
123
+ end # DocumentTest
@@ -0,0 +1,526 @@
1
+ require 'test_helper'
2
+
3
+ class Grandparent
4
+ include MongoMapper::EmbeddedDocument
5
+ key :grandparent, String
6
+ end
7
+
8
+ class Parent < Grandparent
9
+ include MongoMapper::EmbeddedDocument
10
+ key :parent, String
11
+ end
12
+
13
+ class Child < Parent
14
+ include MongoMapper::EmbeddedDocument
15
+ key :child, String
16
+ end
17
+
18
+ module KeyOverride
19
+ def other_child
20
+ read_attribute(:other_child) || "special result"
21
+ end
22
+
23
+ def other_child=(value)
24
+ super(value + " modified")
25
+ end
26
+ end
27
+
28
+ class OtherChild < Parent
29
+ include MongoMapper::EmbeddedDocument
30
+ include KeyOverride
31
+
32
+ key :other_child, String
33
+ end
34
+
35
+ class EmbeddedDocumentTest < Test::Unit::TestCase
36
+ context "Including MongoMapper::EmbeddedDocument" do
37
+ setup do
38
+ @klass = Class.new do
39
+ include MongoMapper::EmbeddedDocument
40
+ end
41
+ end
42
+
43
+ should "add _id key" do
44
+ @klass.keys['_id'].should_not be_nil
45
+ end
46
+ end
47
+
48
+ context "parent_model" do
49
+ should "be nil if none of parents ancestors include EmbeddedDocument" do
50
+ parent = Class.new
51
+ document = Class.new(parent) do
52
+ include MongoMapper::EmbeddedDocument
53
+ end
54
+ document.parent_model.should be_nil
55
+ end
56
+
57
+ should "work when other modules have been included" do
58
+ grandparent = Class.new
59
+ parent = Class.new grandparent do
60
+ include MongoMapper::EmbeddedDocument
61
+ end
62
+
63
+ example_module = Module.new
64
+ document = Class.new(parent) do
65
+ include MongoMapper::EmbeddedDocument
66
+ include example_module
67
+ end
68
+
69
+ document.parent_model.should == parent
70
+ end
71
+
72
+ should "find parent" do
73
+ Parent.parent_model.should == Grandparent
74
+ Child.parent_model.should == Parent
75
+ end
76
+ end
77
+
78
+ context "defining a key" do
79
+ setup do
80
+ @document = Class.new do
81
+ include MongoMapper::EmbeddedDocument
82
+ end
83
+ end
84
+
85
+ should "work with name" do
86
+ key = @document.key(:name)
87
+ key.name.should == 'name'
88
+ end
89
+
90
+ should "work with name and type" do
91
+ key = @document.key(:name, String)
92
+ key.name.should == 'name'
93
+ key.type.should == String
94
+ end
95
+
96
+ should "work with name, type and options" do
97
+ key = @document.key(:name, String, :required => true)
98
+ key.name.should == 'name'
99
+ key.type.should == String
100
+ key.options[:required].should be_true
101
+ end
102
+
103
+ should "work with name and options" do
104
+ key = @document.key(:name, :required => true)
105
+ key.name.should == 'name'
106
+ key.options[:required].should be_true
107
+ end
108
+
109
+ should "be tracked per document" do
110
+ @document.key(:name, String)
111
+ @document.key(:age, Integer)
112
+ @document.keys['name'].name.should == 'name'
113
+ @document.keys['name'].type.should == String
114
+ @document.keys['age'].name.should == 'age'
115
+ @document.keys['age'].type.should == Integer
116
+ end
117
+
118
+ should "not be redefinable" do
119
+ @document.key(:foo, String)
120
+ @document.keys['foo'].type.should == String
121
+ @document.key(:foo, Integer)
122
+ @document.keys['foo'].type.should == String
123
+ end
124
+
125
+ should "create reader method" do
126
+ @document.new.should_not respond_to(:foo)
127
+ @document.key(:foo, String)
128
+ @document.new.should respond_to(:foo)
129
+ end
130
+
131
+ should "create reader before typecast method" do
132
+ @document.new.should_not respond_to(:foo_before_typecast)
133
+ @document.key(:foo, String)
134
+ @document.new.should respond_to(:foo_before_typecast)
135
+ end
136
+
137
+ should "create writer method" do
138
+ @document.new.should_not respond_to(:foo=)
139
+ @document.key(:foo, String)
140
+ @document.new.should respond_to(:foo=)
141
+ end
142
+
143
+ should "create boolean method" do
144
+ @document.new.should_not respond_to(:foo?)
145
+ @document.key(:foo, String)
146
+ @document.new.should respond_to(:foo?)
147
+ end
148
+ end
149
+
150
+ context "keys" do
151
+ should "be inherited" do
152
+ Grandparent.keys.keys.sort.should == ['_id', 'grandparent']
153
+ Parent.keys.keys.sort.should == ['_id', 'grandparent', 'parent']
154
+ Child.keys.keys.sort.should == ['_id', 'child', 'grandparent', 'parent']
155
+ end
156
+
157
+ should "propogate to subclasses if key added after class definition" do
158
+ Grandparent.key :_type, String
159
+
160
+ Grandparent.keys.keys.sort.should == ['_id', '_type', 'grandparent']
161
+ Parent.keys.keys.sort.should == ['_id', '_type', 'grandparent', 'parent']
162
+ Child.keys.keys.sort.should == ['_id', '_type', 'child', 'grandparent', 'parent']
163
+ end
164
+
165
+ should "not add anonymous objects to the ancestor tree" do
166
+ OtherChild.ancestors.any? { |a| a.name.blank? }.should be_false
167
+ end
168
+
169
+ should "not include descendant keys" do
170
+ lambda { Parent.new.other_child }.should raise_error
171
+ end
172
+ end
173
+
174
+ context "subclasses" do
175
+ should "default to nil" do
176
+ Child.subclasses.should be_nil
177
+ end
178
+
179
+ should "be recorded" do
180
+ Grandparent.subclasses.should == [Parent]
181
+ Parent.subclasses.should == [Child, OtherChild]
182
+ end
183
+ end
184
+
185
+ context "Applying default values for keys" do
186
+ setup do
187
+ @document = Class.new do
188
+ include MongoMapper::EmbeddedDocument
189
+
190
+ key :name, String, :default => 'foo'
191
+ key :age, Integer, :default => 20
192
+ key :net_worth, Float, :default => 100.00
193
+ key :active, Boolean, :default => true
194
+ key :smart, Boolean, :default => false
195
+ key :skills, Array, :default => [1]
196
+ key :options, Hash, :default => {'foo' => 'bar'}
197
+ end
198
+
199
+ @doc = @document.new
200
+ end
201
+
202
+ should "work for strings" do
203
+ @doc.name.should == 'foo'
204
+ end
205
+
206
+ should "work for integers" do
207
+ @doc.age.should == 20
208
+ end
209
+
210
+ should "work for floats" do
211
+ @doc.net_worth.should == 100.00
212
+ end
213
+
214
+ should "work for booleans" do
215
+ @doc.active.should == true
216
+ @doc.smart.should == false
217
+ end
218
+
219
+ should "work for arrays" do
220
+ @doc.skills.should == [1]
221
+ @doc.skills << 2
222
+ @doc.skills.should == [1, 2]
223
+ end
224
+
225
+ should "work for hashes" do
226
+ @doc.options['foo'].should == 'bar'
227
+ @doc.options['baz'] = 'wick'
228
+ @doc.options['baz'].should == 'wick'
229
+ end
230
+ end
231
+
232
+ context "An instance of an embedded document" do
233
+ setup do
234
+ @document = Class.new do
235
+ include MongoMapper::EmbeddedDocument
236
+
237
+ key :name, String
238
+ key :age, Integer
239
+ end
240
+ end
241
+
242
+ should "automatically have an _id key" do
243
+ @document.keys.keys.should include('_id')
244
+ end
245
+
246
+ should "have id method that sets _id" do
247
+ doc = @document.new
248
+ doc.id.should == doc._id.to_s
249
+ end
250
+
251
+ context "setting custom id" do
252
+ should "set _id" do
253
+ doc = @document.new(:id => '1234')
254
+ doc._id.should == '1234'
255
+ end
256
+
257
+ should "know that custom id is set" do
258
+ doc = @document.new
259
+ doc.using_custom_id?.should be_false
260
+ doc.id = '1234'
261
+ doc.using_custom_id?.should be_true
262
+ end
263
+ end
264
+
265
+ context "being initialized" do
266
+ should "accept a hash that sets keys and values" do
267
+ doc = @document.new(:name => 'John', :age => 23)
268
+ doc.attributes.keys.sort.should == ['_id', 'age', 'name']
269
+ doc.attributes['name'].should == 'John'
270
+ doc.attributes['age'].should == 23
271
+ end
272
+
273
+ should "be able to assign keys dynamically" do
274
+ doc = @document.new(:name => 'John', :skills => ['ruby', 'rails'])
275
+ doc.name.should == 'John'
276
+ doc.skills.should == ['ruby', 'rails']
277
+ end
278
+
279
+ should "not throw error if initialized with nil" do
280
+ lambda {
281
+ @document.new(nil)
282
+ }.should_not raise_error
283
+ end
284
+ end
285
+
286
+ context "mass assigning keys" do
287
+ should "update values for keys provided" do
288
+ doc = @document.new(:name => 'foobar', :age => 10)
289
+ doc.attributes = {:name => 'new value', :age => 5}
290
+ doc.attributes[:name].should == 'new value'
291
+ doc.attributes[:age].should == 5
292
+ end
293
+
294
+ should "not update values for keys that were not provided" do
295
+ doc = @document.new(:name => 'foobar', :age => 10)
296
+ doc.attributes = {:name => 'new value'}
297
+ doc.attributes[:name].should == 'new value'
298
+ doc.attributes[:age].should == 10
299
+ end
300
+
301
+ should "not ignore keys that have methods defined" do
302
+ @document.class_eval do
303
+ attr_writer :password
304
+
305
+ def passwd
306
+ @password
307
+ end
308
+ end
309
+
310
+ doc = @document.new(:name => 'foobar', :password => 'secret')
311
+ doc.passwd.should == 'secret'
312
+ end
313
+
314
+ should "typecast key values" do
315
+ doc = @document.new(:name => 1234, :age => '21')
316
+ doc.name.should == '1234'
317
+ doc.age.should == 21
318
+ end
319
+ end
320
+
321
+ context "attributes" do
322
+ should "default to hash with _id" do
323
+ doc = @document.new
324
+ doc.attributes.keys.should == ['_id']
325
+ end
326
+
327
+ should "return all keys that aren't nil" do
328
+ doc = @document.new(:name => 'string', :age => nil)
329
+ doc.attributes.keys.sort.should == ['_id', 'name']
330
+ doc.attributes.values.should include('string')
331
+ end
332
+ end
333
+
334
+ context "key shorcut access" do
335
+ should "be able to read key with []" do
336
+ doc = @document.new(:name => 'string')
337
+ doc[:name].should == 'string'
338
+ end
339
+
340
+ context "[]=" do
341
+ should "write key value for existing key" do
342
+ doc = @document.new
343
+ doc[:name] = 'string'
344
+ doc[:name].should == 'string'
345
+ end
346
+
347
+ should "create key and write value for missing key" do
348
+ doc = @document.new
349
+ doc[:foo] = 'string'
350
+ @document.keys.keys.include?('foo').should be_true
351
+ doc[:foo].should == 'string'
352
+ end
353
+ end
354
+ end
355
+
356
+ context "indifferent access" do
357
+ should "be enabled for keys" do
358
+ doc = @document.new(:name => 'string')
359
+ doc.attributes[:name].should == 'string'
360
+ doc.attributes['name'].should == 'string'
361
+ end
362
+ end
363
+
364
+ context "reading an attribute" do
365
+ should "work for defined keys" do
366
+ doc = @document.new(:name => 'string')
367
+ doc.name.should == 'string'
368
+ end
369
+
370
+ should "raise no method error for undefined keys" do
371
+ doc = @document.new
372
+ lambda { doc.fart }.should raise_error(NoMethodError)
373
+ end
374
+
375
+ should "be accessible for use in the model" do
376
+ @document.class_eval do
377
+ def name_and_age
378
+ "#{read_attribute(:name)} (#{read_attribute(:age)})"
379
+ end
380
+ end
381
+
382
+ doc = @document.new(:name => 'John', :age => 27)
383
+ doc.name_and_age.should == 'John (27)'
384
+ end
385
+
386
+ should "set instance variable" do
387
+ @document.key :foo, Array
388
+ doc = @document.new
389
+ doc.instance_variable_get("@foo").should be_nil
390
+ doc.foo
391
+ doc.instance_variable_get("@foo").should == []
392
+ end
393
+
394
+ should "not set instance variable if frozen" do
395
+ @document.key :foo, Array
396
+ doc = @document.new
397
+ doc.instance_variable_get("@foo").should be_nil
398
+ doc.freeze
399
+ doc.foo
400
+ doc.instance_variable_get("@foo").should be_nil
401
+ end
402
+
403
+ should "be overrideable by modules" do
404
+ @document = Class.new do
405
+ include MongoMapper::Document
406
+ key :other_child, String
407
+ end
408
+
409
+ child = @document.new
410
+ child.other_child.should be_nil
411
+
412
+ @document.send :include, KeyOverride
413
+
414
+ overriden_child = @document.new
415
+ overriden_child.other_child.should == 'special result'
416
+ end
417
+ end
418
+
419
+ context "reading an attribute before typcasting" do
420
+ should "work for defined keys" do
421
+ doc = @document.new(:name => 12)
422
+ doc.name_before_typecast.should == 12
423
+ end
424
+
425
+ should "raise no method error for undefined keys" do
426
+ doc = @document.new
427
+ lambda { doc.foo_before_typecast }.should raise_error(NoMethodError)
428
+ end
429
+
430
+ should "be accessible for use in a document" do
431
+ @document.class_eval do
432
+ def untypcasted_name
433
+ read_attribute_before_typecast(:name)
434
+ end
435
+ end
436
+
437
+ doc = @document.new(:name => 12)
438
+ doc.name.should == '12'
439
+ doc.untypcasted_name.should == 12
440
+ end
441
+ end
442
+
443
+ context "writing an attribute" do
444
+ should "work for defined keys" do
445
+ doc = @document.new
446
+ doc.name = 'John'
447
+ doc.name.should == 'John'
448
+ end
449
+
450
+ should "raise no method error for undefined keys" do
451
+ doc = @document.new
452
+ lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
453
+ end
454
+
455
+ should "typecast value" do
456
+ doc = @document.new
457
+ doc.name = 1234
458
+ doc.name.should == '1234'
459
+ doc.age = '21'
460
+ doc.age.should == 21
461
+ end
462
+
463
+ should "be accessible for use in the model" do
464
+ @document.class_eval do
465
+ def name_and_age=(new_value)
466
+ new_value.match(/([^\(\s]+) \((.*)\)/)
467
+ write_attribute :name, $1
468
+ write_attribute :age, $2
469
+ end
470
+ end
471
+
472
+ doc = @document.new
473
+ doc.name_and_age = 'Frank (62)'
474
+ doc.name.should == 'Frank'
475
+ doc.age.should == 62
476
+ end
477
+
478
+ should "be overrideable by modules" do
479
+ @document = Class.new do
480
+ include MongoMapper::Document
481
+ key :other_child, String
482
+ end
483
+
484
+ child = @document.new(:other_child => 'foo')
485
+ child.other_child.should == 'foo'
486
+
487
+ @document.send :include, KeyOverride
488
+
489
+ overriden_child = @document.new(:other_child => 'foo')
490
+ overriden_child.other_child.should == 'foo modified'
491
+ end
492
+ end # writing an attribute
493
+
494
+ context "checking if an attributes value is present" do
495
+ should "work for defined keys" do
496
+ doc = @document.new
497
+ doc.name?.should be_false
498
+ doc.name = 'John'
499
+ doc.name?.should be_true
500
+ end
501
+
502
+ should "raise no method error for undefined keys" do
503
+ doc = @document.new
504
+ lambda { doc.fart? }.should raise_error(NoMethodError)
505
+ end
506
+ end
507
+
508
+ context "equality" do
509
+ should "be equal if id and class are the same" do
510
+ (@document.new('_id' => 1) == @document.new('_id' => 1)).should be(true)
511
+ end
512
+
513
+ should "not be equal if class same but id different" do
514
+ (@document.new('_id' => 1) == @document.new('_id' => 2)).should be(false)
515
+ end
516
+
517
+ should "not be equal if id same but class different" do
518
+ @another_document = Class.new do
519
+ include MongoMapper::Document
520
+ end
521
+
522
+ (@document.new('_id' => 1) == @another_document.new('_id' => 1)).should be(false)
523
+ end
524
+ end
525
+ end # instance of a embedded document
526
+ end