hashrocket-mongomapper 0.3.3

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 (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 +70 -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 +337 -0
  21. data/lib/mongomapper/dynamic_finder.rb +38 -0
  22. data/lib/mongomapper/embedded_document.rb +267 -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 +61 -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 +500 -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