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.
- data/.gitignore +7 -0
- data/History +70 -0
- data/LICENSE +20 -0
- data/README.rdoc +39 -0
- data/Rakefile +73 -0
- data/VERSION +1 -0
- data/bin/mmconsole +56 -0
- data/lib/mongomapper.rb +70 -0
- data/lib/mongomapper/associations.rb +84 -0
- data/lib/mongomapper/associations/base.rb +69 -0
- data/lib/mongomapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +22 -0
- data/lib/mongomapper/associations/many_documents_proxy.rb +103 -0
- data/lib/mongomapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
- data/lib/mongomapper/associations/many_embedded_proxy.rb +17 -0
- data/lib/mongomapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongomapper/associations/many_proxy.rb +6 -0
- data/lib/mongomapper/associations/proxy.rb +63 -0
- data/lib/mongomapper/callbacks.rb +106 -0
- data/lib/mongomapper/document.rb +337 -0
- data/lib/mongomapper/dynamic_finder.rb +38 -0
- data/lib/mongomapper/embedded_document.rb +267 -0
- data/lib/mongomapper/finder_options.rb +85 -0
- data/lib/mongomapper/key.rb +76 -0
- data/lib/mongomapper/observing.rb +50 -0
- data/lib/mongomapper/pagination.rb +52 -0
- data/lib/mongomapper/rails_compatibility/document.rb +15 -0
- data/lib/mongomapper/rails_compatibility/embedded_document.rb +25 -0
- data/lib/mongomapper/save_with_validation.rb +19 -0
- data/lib/mongomapper/serialization.rb +55 -0
- data/lib/mongomapper/serializers/json_serializer.rb +92 -0
- data/lib/mongomapper/support.rb +30 -0
- data/lib/mongomapper/validations.rb +61 -0
- data/mongomapper.gemspec +142 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +53 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +45 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +131 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +106 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +261 -0
- data/test/functional/associations/test_many_proxy.rb +295 -0
- data/test/functional/test_associations.rb +47 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_document.rb +952 -0
- data/test/functional/test_pagination.rb +81 -0
- data/test/functional/test_rails_compatibility.rb +30 -0
- data/test/functional/test_validations.rb +172 -0
- data/test/models.rb +139 -0
- data/test/test_helper.rb +67 -0
- data/test/unit/serializers/test_json_serializer.rb +157 -0
- data/test/unit/test_association_base.rb +144 -0
- data/test/unit/test_document.rb +123 -0
- data/test/unit/test_embedded_document.rb +526 -0
- data/test/unit/test_finder_options.rb +183 -0
- data/test/unit/test_key.rb +247 -0
- data/test/unit/test_mongomapper.rb +28 -0
- data/test/unit/test_observing.rb +101 -0
- data/test/unit/test_pagination.rb +113 -0
- data/test/unit/test_rails_compatibility.rb +34 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_validations.rb +500 -0
- 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
|