mark_mapper 0.0.1

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 (211) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.rdoc +39 -0
  4. data/examples/attr_accessible.rb +24 -0
  5. data/examples/attr_protected.rb +24 -0
  6. data/examples/cache_key.rb +26 -0
  7. data/examples/custom_types.rb +26 -0
  8. data/examples/identity_map.rb +30 -0
  9. data/examples/identity_map/automatic.rb +2 -0
  10. data/examples/keys.rb +42 -0
  11. data/examples/modifiers/set.rb +27 -0
  12. data/examples/plugins.rb +40 -0
  13. data/examples/querying.rb +39 -0
  14. data/examples/sample_app.rb +43 -0
  15. data/examples/scopes.rb +56 -0
  16. data/examples/validating/embedded_docs.rb +31 -0
  17. data/lib/mark_mapper.rb +125 -0
  18. data/lib/mark_mapper/config.rb +90 -0
  19. data/lib/mark_mapper/connection.rb +60 -0
  20. data/lib/mark_mapper/criteria_hash.rb +194 -0
  21. data/lib/mark_mapper/document.rb +46 -0
  22. data/lib/mark_mapper/embedded_document.rb +32 -0
  23. data/lib/mark_mapper/exceptions.rb +33 -0
  24. data/lib/mark_mapper/extensions/array.rb +27 -0
  25. data/lib/mark_mapper/extensions/boolean.rb +45 -0
  26. data/lib/mark_mapper/extensions/date.rb +29 -0
  27. data/lib/mark_mapper/extensions/duplicable.rb +86 -0
  28. data/lib/mark_mapper/extensions/float.rb +18 -0
  29. data/lib/mark_mapper/extensions/hash.rb +26 -0
  30. data/lib/mark_mapper/extensions/integer.rb +27 -0
  31. data/lib/mark_mapper/extensions/kernel.rb +11 -0
  32. data/lib/mark_mapper/extensions/nil_class.rb +18 -0
  33. data/lib/mark_mapper/extensions/object.rb +30 -0
  34. data/lib/mark_mapper/extensions/object_id.rb +18 -0
  35. data/lib/mark_mapper/extensions/set.rb +20 -0
  36. data/lib/mark_mapper/extensions/string.rb +31 -0
  37. data/lib/mark_mapper/extensions/symbol.rb +87 -0
  38. data/lib/mark_mapper/extensions/time.rb +29 -0
  39. data/lib/mark_mapper/locale/en.yml +5 -0
  40. data/lib/mark_mapper/middleware/identity_map.rb +41 -0
  41. data/lib/mark_mapper/normalizers/criteria_hash_key.rb +17 -0
  42. data/lib/mark_mapper/normalizers/criteria_hash_value.rb +66 -0
  43. data/lib/mark_mapper/normalizers/fields_value.rb +26 -0
  44. data/lib/mark_mapper/normalizers/hash_key.rb +19 -0
  45. data/lib/mark_mapper/normalizers/integer.rb +19 -0
  46. data/lib/mark_mapper/normalizers/options_hash_value.rb +83 -0
  47. data/lib/mark_mapper/normalizers/sort_value.rb +55 -0
  48. data/lib/mark_mapper/options_hash.rb +103 -0
  49. data/lib/mark_mapper/pagination.rb +6 -0
  50. data/lib/mark_mapper/pagination/collection.rb +32 -0
  51. data/lib/mark_mapper/pagination/paginator.rb +46 -0
  52. data/lib/mark_mapper/plugins.rb +22 -0
  53. data/lib/mark_mapper/plugins/accessible.rb +61 -0
  54. data/lib/mark_mapper/plugins/active_model.rb +18 -0
  55. data/lib/mark_mapper/plugins/associations.rb +96 -0
  56. data/lib/mark_mapper/plugins/associations/base.rb +98 -0
  57. data/lib/mark_mapper/plugins/associations/belongs_to_association.rb +63 -0
  58. data/lib/mark_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +35 -0
  59. data/lib/mark_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
  60. data/lib/mark_mapper/plugins/associations/collection.rb +29 -0
  61. data/lib/mark_mapper/plugins/associations/embedded_collection.rb +44 -0
  62. data/lib/mark_mapper/plugins/associations/in_array_proxy.rb +133 -0
  63. data/lib/mark_mapper/plugins/associations/many_association.rb +63 -0
  64. data/lib/mark_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  65. data/lib/mark_mapper/plugins/associations/many_documents_proxy.rb +142 -0
  66. data/lib/mark_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
  67. data/lib/mark_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
  68. data/lib/mark_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
  69. data/lib/mark_mapper/plugins/associations/one_as_proxy.rb +22 -0
  70. data/lib/mark_mapper/plugins/associations/one_association.rb +48 -0
  71. data/lib/mark_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
  72. data/lib/mark_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
  73. data/lib/mark_mapper/plugins/associations/one_proxy.rb +95 -0
  74. data/lib/mark_mapper/plugins/associations/proxy.rb +138 -0
  75. data/lib/mark_mapper/plugins/associations/single_association.rb +46 -0
  76. data/lib/mark_mapper/plugins/caching.rb +21 -0
  77. data/lib/mark_mapper/plugins/callbacks.rb +42 -0
  78. data/lib/mark_mapper/plugins/clone.rb +24 -0
  79. data/lib/mark_mapper/plugins/counter_cache.rb +97 -0
  80. data/lib/mark_mapper/plugins/dirty.rb +61 -0
  81. data/lib/mark_mapper/plugins/document.rb +41 -0
  82. data/lib/mark_mapper/plugins/dumpable.rb +22 -0
  83. data/lib/mark_mapper/plugins/dynamic_querying.rb +45 -0
  84. data/lib/mark_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
  85. data/lib/mark_mapper/plugins/embedded_callbacks.rb +81 -0
  86. data/lib/mark_mapper/plugins/embedded_document.rb +53 -0
  87. data/lib/mark_mapper/plugins/equality.rb +23 -0
  88. data/lib/mark_mapper/plugins/identity_map.rb +144 -0
  89. data/lib/mark_mapper/plugins/indexable.rb +86 -0
  90. data/lib/mark_mapper/plugins/inspect.rb +16 -0
  91. data/lib/mark_mapper/plugins/keys.rb +470 -0
  92. data/lib/mark_mapper/plugins/keys/key.rb +134 -0
  93. data/lib/mark_mapper/plugins/keys/static.rb +45 -0
  94. data/lib/mark_mapper/plugins/logger.rb +18 -0
  95. data/lib/mark_mapper/plugins/modifiers.rb +140 -0
  96. data/lib/mark_mapper/plugins/pagination.rb +16 -0
  97. data/lib/mark_mapper/plugins/partial_updates.rb +77 -0
  98. data/lib/mark_mapper/plugins/persistence.rb +79 -0
  99. data/lib/mark_mapper/plugins/protected.rb +45 -0
  100. data/lib/mark_mapper/plugins/querying.rb +173 -0
  101. data/lib/mark_mapper/plugins/querying/decorated_markmapper_query.rb +75 -0
  102. data/lib/mark_mapper/plugins/rails.rb +79 -0
  103. data/lib/mark_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  104. data/lib/mark_mapper/plugins/sci.rb +82 -0
  105. data/lib/mark_mapper/plugins/scopes.rb +28 -0
  106. data/lib/mark_mapper/plugins/serialization.rb +109 -0
  107. data/lib/mark_mapper/plugins/timestamps.rb +29 -0
  108. data/lib/mark_mapper/plugins/touch.rb +18 -0
  109. data/lib/mark_mapper/plugins/userstamps.rb +18 -0
  110. data/lib/mark_mapper/plugins/validations.rb +96 -0
  111. data/lib/mark_mapper/query.rb +278 -0
  112. data/lib/mark_mapper/railtie.rb +52 -0
  113. data/lib/mark_mapper/railtie/database.rake +65 -0
  114. data/lib/mark_mapper/translation.rb +10 -0
  115. data/lib/mark_mapper/version.rb +4 -0
  116. data/lib/rails/generators/mark_mapper/config/config_generator.rb +37 -0
  117. data/lib/rails/generators/mark_mapper/config/templates/marklogic.yml +19 -0
  118. data/lib/rails/generators/mark_mapper/model/model_generator.rb +40 -0
  119. data/lib/rails/generators/mark_mapper/model/templates/model.rb +17 -0
  120. data/spec/config/mark_mapper.yml +6 -0
  121. data/spec/examples_spec.rb +25 -0
  122. data/spec/functional/accessible_spec.rb +198 -0
  123. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +64 -0
  124. data/spec/functional/associations/belongs_to_proxy_spec.rb +255 -0
  125. data/spec/functional/associations/in_array_proxy_spec.rb +349 -0
  126. data/spec/functional/associations/many_documents_as_proxy_spec.rb +230 -0
  127. data/spec/functional/associations/many_documents_proxy_spec.rb +968 -0
  128. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +238 -0
  129. data/spec/functional/associations/many_embedded_proxy_spec.rb +288 -0
  130. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +302 -0
  131. data/spec/functional/associations/one_as_proxy_spec.rb +489 -0
  132. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +207 -0
  133. data/spec/functional/associations/one_embedded_proxy_spec.rb +100 -0
  134. data/spec/functional/associations/one_proxy_spec.rb +406 -0
  135. data/spec/functional/associations_spec.rb +48 -0
  136. data/spec/functional/caching_spec.rb +75 -0
  137. data/spec/functional/callbacks_spec.rb +330 -0
  138. data/spec/functional/counter_cache_spec.rb +235 -0
  139. data/spec/functional/dirty_spec.rb +316 -0
  140. data/spec/functional/document_spec.rb +310 -0
  141. data/spec/functional/dumpable_spec.rb +24 -0
  142. data/spec/functional/dynamic_querying_spec.rb +75 -0
  143. data/spec/functional/embedded_document_spec.rb +316 -0
  144. data/spec/functional/equality_spec.rb +20 -0
  145. data/spec/functional/extensions_spec.rb +16 -0
  146. data/spec/functional/identity_map_spec.rb +483 -0
  147. data/spec/functional/keys_spec.rb +339 -0
  148. data/spec/functional/logger_spec.rb +20 -0
  149. data/spec/functional/modifiers_spec.rb +446 -0
  150. data/spec/functional/options_hash_spec.rb +41 -0
  151. data/spec/functional/pagination_spec.rb +89 -0
  152. data/spec/functional/partial_updates_spec.rb +530 -0
  153. data/spec/functional/protected_spec.rb +199 -0
  154. data/spec/functional/querying_spec.rb +984 -0
  155. data/spec/functional/rails_spec.rb +55 -0
  156. data/spec/functional/sci_spec.rb +374 -0
  157. data/spec/functional/scopes_spec.rb +204 -0
  158. data/spec/functional/static_keys_spec.rb +153 -0
  159. data/spec/functional/timestamps_spec.rb +97 -0
  160. data/spec/functional/touch_spec.rb +125 -0
  161. data/spec/functional/userstamps_spec.rb +46 -0
  162. data/spec/functional/validations_spec.rb +416 -0
  163. data/spec/quality_spec.rb +51 -0
  164. data/spec/spec_helper.rb +150 -0
  165. data/spec/support/matchers.rb +15 -0
  166. data/spec/support/models.rb +256 -0
  167. data/spec/symbol_operator_spec.rb +70 -0
  168. data/spec/symbol_spec.rb +9 -0
  169. data/spec/unit/associations/base_spec.rb +146 -0
  170. data/spec/unit/associations/belongs_to_association_spec.rb +30 -0
  171. data/spec/unit/associations/many_association_spec.rb +64 -0
  172. data/spec/unit/associations/one_association_spec.rb +48 -0
  173. data/spec/unit/associations/proxy_spec.rb +103 -0
  174. data/spec/unit/clone_spec.rb +79 -0
  175. data/spec/unit/config_generator_spec.rb +24 -0
  176. data/spec/unit/criteria_hash_spec.rb +218 -0
  177. data/spec/unit/document_spec.rb +251 -0
  178. data/spec/unit/dynamic_finder_spec.rb +125 -0
  179. data/spec/unit/embedded_document_spec.rb +676 -0
  180. data/spec/unit/equality_spec.rb +38 -0
  181. data/spec/unit/exceptions_spec.rb +12 -0
  182. data/spec/unit/extensions_spec.rb +368 -0
  183. data/spec/unit/identity_map_middleware_spec.rb +134 -0
  184. data/spec/unit/inspect_spec.rb +47 -0
  185. data/spec/unit/key_spec.rb +276 -0
  186. data/spec/unit/keys_spec.rb +155 -0
  187. data/spec/unit/mark_mapper_spec.rb +37 -0
  188. data/spec/unit/model_generator_spec.rb +45 -0
  189. data/spec/unit/normalizers/criteria_hash_key_spec.rb +37 -0
  190. data/spec/unit/normalizers/criteria_hash_value_spec.rb +200 -0
  191. data/spec/unit/normalizers/fields_value_spec.rb +45 -0
  192. data/spec/unit/normalizers/hash_key_spec.rb +15 -0
  193. data/spec/unit/normalizers/integer_spec.rb +24 -0
  194. data/spec/unit/normalizers/options_hash_value_spec.rb +99 -0
  195. data/spec/unit/normalizers/sort_value_spec.rb +98 -0
  196. data/spec/unit/options_hash_spec.rb +64 -0
  197. data/spec/unit/pagination/collection_spec.rb +30 -0
  198. data/spec/unit/pagination/paginator_spec.rb +118 -0
  199. data/spec/unit/pagination_spec.rb +11 -0
  200. data/spec/unit/plugins_spec.rb +89 -0
  201. data/spec/unit/query_spec.rb +837 -0
  202. data/spec/unit/rails_compatibility_spec.rb +40 -0
  203. data/spec/unit/rails_reflect_on_association_spec.rb +118 -0
  204. data/spec/unit/rails_spec.rb +188 -0
  205. data/spec/unit/serialization_spec.rb +169 -0
  206. data/spec/unit/serializers/json_serializer_spec.rb +218 -0
  207. data/spec/unit/serializers/xml_serializer_spec.rb +198 -0
  208. data/spec/unit/time_zones_spec.rb +44 -0
  209. data/spec/unit/translation_spec.rb +27 -0
  210. data/spec/unit/validations_spec.rb +588 -0
  211. metadata +307 -0
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ManyDocumentsAsProxy" do
4
+ before do
5
+ Post.collection.remove
6
+ PostComment.collection.remove
7
+ end
8
+
9
+ it "should default reader to empty array" do
10
+ Post.new.comments.should == []
11
+ end
12
+
13
+ it "should add type and id key to polymorphic class base" do
14
+ PostComment.keys.keys.should include('commentable_type')
15
+ PostComment.keys['commentable_type'].type.should == String
16
+ PostComment.keys.keys.should include('commentable_id')
17
+ PostComment.keys['commentable_id'].type.should == ObjectId
18
+ end
19
+
20
+ it "should allow adding to association like it was an array" do
21
+ post = Post.new
22
+ post.comments << PostComment.new(:body => 'foo bar')
23
+ post.comments << PostComment.new(:body => 'baz')
24
+ post.comments.concat PostComment.new(:body => 'baz')
25
+
26
+ post.comments.size.should == 3
27
+ end
28
+
29
+ it "should be able to replace the association" do
30
+ post = Post.new
31
+
32
+ lambda {
33
+ post.comments = [
34
+ PostComment.new(:body => 'foo'),
35
+ PostComment.new(:body => 'bar'),
36
+ PostComment.new(:body => 'baz')
37
+ ]
38
+ }.should change { PostComment.count }.by(3)
39
+
40
+ post = post.reload
41
+ post.comments.size.should == 3
42
+ bodies = post.comments.collect(&:body)
43
+ bodies.should include('foo')
44
+ bodies.should include('bar')
45
+ bodies.should include('baz')
46
+ end
47
+
48
+ context "build" do
49
+ it "should assign foreign key" do
50
+ post = Post.new
51
+ comment = post.comments.build
52
+ comment.commentable_id.should == post._id
53
+ end
54
+
55
+ it "should assign _type" do
56
+ post = Post.new
57
+ comment = post.comments.build
58
+ comment.commentable_type.should == "Post"
59
+ end
60
+
61
+ it "should allow assigning attributes" do
62
+ post = Post.new
63
+ comment = post.comments.build(:body => 'foo bar')
64
+ comment.body.should == 'foo bar'
65
+ end
66
+ end
67
+
68
+ context "create" do
69
+ it "should assign foreign key" do
70
+ post = Post.new
71
+ comment = post.comments.create
72
+ comment.commentable_id.should == post._id
73
+ end
74
+
75
+ it "should assign _type" do
76
+ post = Post.new
77
+ comment = post.comments.create
78
+ comment.commentable_type.should == "Post"
79
+ end
80
+
81
+ it "should save record" do
82
+ post = Post.new
83
+ lambda {
84
+ post.comments.create(:body => 'baz')
85
+ }.should change { PostComment.count }
86
+ end
87
+
88
+ it "should allow passing attributes" do
89
+ post = Post.create
90
+ comment = post.comments.create(:body => 'foo bar')
91
+ comment.body.should == 'foo bar'
92
+ end
93
+ end
94
+
95
+ context "count" do
96
+ it "should work scoped to association" do
97
+ post = Post.create
98
+ 3.times { post.comments.create(:body => 'foo bar') }
99
+
100
+ other_post = Post.create
101
+ 2.times { other_post.comments.create(:body => 'baz') }
102
+
103
+ post.comments.count.should == 3
104
+ other_post.comments.count.should == 2
105
+ end
106
+
107
+ it "should work with conditions" do
108
+ post = Post.create
109
+ post.comments.create(:body => 'foo bar')
110
+ post.comments.create(:body => 'baz')
111
+ post.comments.create(:body => 'foo bar')
112
+
113
+ post.comments.count(:body => 'foo bar').should == 2
114
+ end
115
+ end
116
+
117
+ context "Finding scoped to association" do
118
+ before do
119
+ @post = Post.new
120
+
121
+ @comment1 = PostComment.create(:body => 'comment1', :name => 'John')
122
+ @comment2 = PostComment.create(:body => 'comment2', :name => 'Steve')
123
+ @comment3 = PostComment.create(:body => 'comment3', :name => 'John')
124
+ @post.comments = [@comment1, @comment2]
125
+ @post.save
126
+
127
+ @post2 = Post.create(:body => "post #2")
128
+ @comment4 = PostComment.create(:body => 'comment1', :name => 'Chas')
129
+ @comment5 = PostComment.create(:body => 'comment2', :name => 'Dan')
130
+ @comment6 = PostComment.create(:body => 'comment3', :name => 'Ed')
131
+ @post2.comments = [@comment4, @comment5, @comment6]
132
+ @post2.save
133
+ end
134
+
135
+ context "with #all" do
136
+ it "should work" do
137
+ @post.comments.all.should include(@comment1)
138
+ @post.comments.all.should include(@comment2)
139
+ end
140
+
141
+ it "should work with conditions" do
142
+ comments = @post.comments.all(:body => 'comment1')
143
+ comments.should == [@comment1]
144
+ end
145
+
146
+ it "should work with order" do
147
+ comments = @post.comments.all(:order => 'body desc')
148
+ comments.should == [@comment2, @comment1]
149
+ end
150
+ end
151
+
152
+ context "with one id" do
153
+ it "should work for id in association" do
154
+ @post.comments.find(@comment2._id).should == @comment2
155
+ end
156
+
157
+ it "should not work for id not in association" do
158
+ expect {
159
+ @post.comments.find!(@comment5._id)
160
+ }.to raise_error(MarkMapper::DocumentNotFound)
161
+ end
162
+ end
163
+
164
+ context "with multiple ids" do
165
+ it "should work for ids in association" do
166
+ posts = @post.comments.find!(@comment1._id, @comment2._id)
167
+ posts.should =~ [@comment1, @comment2]
168
+ end
169
+
170
+ it "should not work for ids not in association" do
171
+ expect {
172
+ @post.comments.find!(@comment1._id, @comment2._id, @comment4._id)
173
+ }.to raise_error(MarkMapper::DocumentNotFound)
174
+ end
175
+ end
176
+
177
+ context "dynamic finders" do
178
+ it "should work with single key" do
179
+ @post.comments.find_by_body('comment1').should == @comment1
180
+ @post2.comments.find_by_body('comment1').should == @comment4
181
+ end
182
+
183
+ it "should work with multiple keys" do
184
+ @post.comments.find_by_body_and_name('comment1', 'John').should == @comment1
185
+ @post.comments.find_by_body_and_name('comment1', 'Frank').should be_nil
186
+ end
187
+
188
+ it "should raise error when using !" do
189
+ lambda {
190
+ @post.comments.find_by_body!('asdf')
191
+ }.should raise_error(MarkMapper::DocumentNotFound)
192
+ end
193
+
194
+ context "find_or_create_by" do
195
+ it "should not create document if found" do
196
+ lambda {
197
+ comment = @post.comments.find_or_create_by_name('Steve')
198
+ comment.commentable.should == @post
199
+ comment.should == @comment2
200
+ }.should_not change { PostComment.count }
201
+ end
202
+
203
+ it "should create document if not found" do
204
+ lambda {
205
+ @post.comments.find_or_create_by_name('Chas')
206
+ }.should change { PostComment.count }.by(1)
207
+ end
208
+ end
209
+ end
210
+
211
+ context "with #paginate" do
212
+ before do
213
+ @comments = @post2.comments.paginate(:per_page => 2, :page => 1, :order => 'name')
214
+ end
215
+
216
+ it "should return total pages" do
217
+ @comments.total_pages.should == 2
218
+ end
219
+
220
+ it "should return total entries" do
221
+ @comments.total_entries.should == 3
222
+ end
223
+
224
+ it "should return the subject" do
225
+ @comments.should include(@comment4)
226
+ @comments.should include(@comment5)
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,968 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe "ManyDocumentsProxy" do
4
+ before do
5
+ Project.collection.remove
6
+ Status.collection.remove
7
+
8
+ @pet_class = Doc do
9
+ key :name, String
10
+ key :owner_id, ObjectId
11
+ end
12
+
13
+ @owner_class = Doc do
14
+ key :name, String
15
+ end
16
+ @owner_class.many :pets, :class => @pet_class, :foreign_key => :owner_id, :order => 'name'
17
+ end
18
+
19
+ it "should return results if found via method_missing" do
20
+ @pet_class.class_eval do
21
+ def self.from_param(name)
22
+ find_by_name(name)
23
+ end
24
+
25
+ def self.all_from_param(names)
26
+ where(:name => names).all
27
+ end
28
+ end
29
+
30
+ instance = @owner_class.new
31
+ instance.pets.build(:name => "Foo")
32
+ instance.pets.build(:name => "Bar")
33
+ instance.save
34
+
35
+ instance.reload.pets.from_param("Foo").tap do |pet|
36
+ pet.should be_a @pet_class
37
+ pet.name.should == "Foo"
38
+ end
39
+
40
+ instance.reload.pets.all_from_param(["Foo", "Bar"]).tap do |pet|
41
+ pet.should be_a Array
42
+ pet.map(&:name).should =~ %w(Foo Bar)
43
+ end
44
+ end
45
+
46
+ it "should default reader to empty array" do
47
+ project = Project.new
48
+ project.statuses.should == []
49
+ end
50
+
51
+ it "should allow overriding association methods" do
52
+ @owner_class.class_eval do
53
+ def pets
54
+ super
55
+ end
56
+ end
57
+
58
+ instance = @owner_class.new
59
+ instance.pets.should == []
60
+ instance.pets.build
61
+ expect(instance.pets.empty?).to eq(false)
62
+ end
63
+
64
+ it "should be able to iterate associated documents in a callback" do
65
+ @owner_class.class_eval do
66
+ before_save :search_pets
67
+
68
+ def search_pets
69
+ pets.each { |p| p.name = "Animal" }
70
+ end
71
+ end
72
+
73
+ owner = @owner_class.new
74
+ sophie = owner.pets.build(:name => "Sophie")
75
+ pippa = owner.pets.build(:name => "Pippa")
76
+
77
+ owner.save
78
+ owner.reload
79
+ owner.pets.reload
80
+
81
+ pets = []
82
+ owner.pets.each { |p| pets << p }
83
+
84
+ pets.size.should == 2
85
+ pets.should include(sophie)
86
+ pets.should include(pippa)
87
+
88
+ sophie.reload.name.should == "Animal"
89
+ pippa.reload.name.should == "Animal"
90
+ end
91
+
92
+ it "should allow assignment of many associated documents using a hash" do
93
+ person_attributes = {
94
+ 'name' => 'Mr. Pet Lover',
95
+ 'pets' => [
96
+ {'name' => 'Jimmy', 'species' => 'Cocker Spainel'},
97
+ {'name' => 'Sasha', 'species' => 'Siberian Husky'},
98
+ ]
99
+ }
100
+
101
+ owner = @owner_class.new(person_attributes)
102
+ owner.name.should == 'Mr. Pet Lover'
103
+ owner.pets[0].name.should == 'Jimmy'
104
+ owner.pets[0].species.should == 'Cocker Spainel'
105
+ owner.pets[1].name.should == 'Sasha'
106
+ owner.pets[1].species.should == 'Siberian Husky'
107
+
108
+ owner.save.should be_truthy
109
+ owner.reload
110
+
111
+ owner.name.should == 'Mr. Pet Lover'
112
+ owner.pets[0].name.should == 'Jimmy'
113
+ owner.pets[0].species.should == 'Cocker Spainel'
114
+ owner.pets[1].name.should == 'Sasha'
115
+ owner.pets[1].species.should == 'Siberian Husky'
116
+ end
117
+
118
+ it "should allow adding to association like it was an array" do
119
+ project = Project.new
120
+ project.statuses << Status.new(:name => 'Foo1!')
121
+ project.statuses.push Status.new(:name => 'Foo2!')
122
+ project.statuses.concat Status.new(:name => 'Foo3!')
123
+ project.statuses.size.should == 3
124
+ end
125
+
126
+ context "replacing the association" do
127
+ context "with objects of the class" do
128
+ it "should work" do
129
+ project = Project.new
130
+ project.statuses = [Status.new(:name => "ready")]
131
+ project.save.should be_truthy
132
+
133
+ project.reload
134
+ project.statuses.size.should == 1
135
+ project.statuses[0].name.should == "ready"
136
+ end
137
+ end
138
+
139
+ context "with Hashes" do
140
+ it "should convert to objects of the class and work" do
141
+ project = Project.new
142
+ project.statuses = [{ 'name' => 'ready' }]
143
+ project.save.should be_truthy
144
+
145
+ project.reload
146
+ project.statuses.size.should == 1
147
+ project.statuses[0].name.should == "ready"
148
+ end
149
+ end
150
+
151
+ context "with :dependent" do
152
+ before do
153
+ @broker_class = Doc('Broker')
154
+ @property_class = Doc('Property') do
155
+ key :broker_id, ObjectId
156
+ belongs_to :broker
157
+ end
158
+ end
159
+
160
+ context "=> destroy" do
161
+ before do
162
+ @broker_class.many :properties, :class => @property_class, :dependent => :destroy
163
+
164
+ @broker = @broker_class.create(:name => "Bob")
165
+ @property1 = @property_class.create
166
+ @property2 = @property_class.create
167
+ @property3 = @property_class.create
168
+ @broker.properties << @property1
169
+ @broker.properties << @property2
170
+ @broker.properties << @property3
171
+ end
172
+
173
+ it "should call destroy the existing documents" do
174
+ expect(@broker.properties[0]).to receive(:destroy).once
175
+ expect(@broker.properties[1]).to receive(:destroy).once
176
+ expect(@broker.properties[2]).to receive(:destroy).once
177
+ @broker.properties = [@property_class.new]
178
+ end
179
+
180
+ it "should remove the existing document from the database" do
181
+ @property_class.count.should == 3
182
+ @broker.properties = []
183
+ @property_class.count.should == 0
184
+ end
185
+
186
+ it "should skip over documents that are the same" do
187
+ @broker.properties.each do |property|
188
+ if [@property3.id, @property1.id].include? property.id
189
+ expect(property).to receive(:destroy).never
190
+ else
191
+ expect(property).to receive(:destroy).once
192
+ end
193
+ end
194
+
195
+ # expect(@broker.properties[0]).to receive(:destroy).never
196
+ # expect(@broker.properties[1]).to receive(:destroy).once
197
+ # expect(@broker.properties[2]).to receive(:destroy).never
198
+ @broker.properties = [@property3, @property1]
199
+ end
200
+ end
201
+
202
+ context "=> delete_all" do
203
+ before do
204
+ @broker_class.many :properties, :class => @property_class, :dependent => :delete_all
205
+
206
+ @broker = @broker_class.create(:name => "Bob")
207
+ @property1 = @property_class.create
208
+ @property2 = @property_class.create
209
+ @property3 = @property_class.create
210
+ @broker.properties << @property1
211
+ @broker.properties << @property2
212
+ @broker.properties << @property3
213
+ end
214
+
215
+ it "should call delete the existing documents" do
216
+ expect(@broker.properties[0]).to receive(:delete).once
217
+ expect(@broker.properties[1]).to receive(:delete).once
218
+ expect(@broker.properties[2]).to receive(:delete).once
219
+ @broker.properties = [@property_class.new]
220
+ end
221
+
222
+ it "should remove the existing document from the database" do
223
+ @property_class.count.should == 3
224
+ @broker.properties = []
225
+ @property_class.count.should == 0
226
+ end
227
+
228
+ it "should skip over documents that are the same" do
229
+ @broker.properties.each do |property|
230
+ if [@property3.id, @property1.id].include? property.id
231
+ expect(property).to receive(:delete).never
232
+ else
233
+ expect(property).to receive(:delete).once
234
+ end
235
+ end
236
+ # expect(@broker.properties[0]).to receive(:delete).never
237
+ # expect(@broker.properties[1]).to receive(:delete).once
238
+ # expect(@broker.properties[2]).to receive(:delete).never
239
+ @broker.properties = [@property3, @property1]
240
+ end
241
+ end
242
+
243
+ context "=> nullify" do
244
+ before do
245
+ @broker_class.many :properties, :class => @property_class, :dependent => :nullify
246
+
247
+ @broker = @broker_class.create(:name => "Bob")
248
+ @property1 = @property_class.create
249
+ @property2 = @property_class.create
250
+ @property3 = @property_class.create
251
+ @broker.properties << @property1
252
+ @broker.properties << @property2
253
+ @broker.properties << @property3
254
+ end
255
+
256
+ it "should nullify the existing documents" do
257
+ @property1.reload.broker_id.should == @broker.id
258
+ @property2.reload.broker_id.should == @broker.id
259
+ @property3.reload.broker_id.should == @broker.id
260
+
261
+ @broker.properties = [@property_class.new]
262
+
263
+ @property1.reload.broker_id.should be_nil
264
+ @property2.reload.broker_id.should be_nil
265
+ @property3.reload.broker_id.should be_nil
266
+ end
267
+
268
+ it "should skip over documents that are the same" do
269
+ @broker.properties = [@property3, @property1]
270
+
271
+ @property1.reload.broker_id.should == @broker.id
272
+ @property2.reload.broker_id.should be_nil
273
+ @property3.reload.broker_id.should == @broker.id
274
+ end
275
+
276
+ it "should work" do
277
+ old_properties = @broker.properties
278
+ @broker.properties = [@property1, @property2, @property3]
279
+ old_properties.should == @broker.properties
280
+ end
281
+ end
282
+
283
+ context "unspecified" do
284
+ it "should nullify the existing documents" do
285
+ @broker_class.many :properties, :class => @property_class
286
+
287
+ @broker = @broker_class.create(:name => "Bob")
288
+ @property1 = @property_class.create
289
+ @property2 = @property_class.create
290
+ @property3 = @property_class.create
291
+ @broker.properties << @property1
292
+ @broker.properties << @property2
293
+ @broker.properties << @property3
294
+
295
+ @broker.properties = [@property_class.new]
296
+
297
+ @property1.reload.broker_id.should be_nil
298
+ @property2.reload.broker_id.should be_nil
299
+ @property3.reload.broker_id.should be_nil
300
+ end
301
+ end
302
+ end
303
+ end
304
+
305
+ context "using <<, push and concat" do
306
+ context "with objects of the class" do
307
+ it "should correctly assign foreign key" do
308
+ project = Project.new
309
+ project.statuses << Status.new(:name => '<<')
310
+ project.statuses.push Status.new(:name => 'push')
311
+ project.statuses.concat Status.new(:name => 'concat')
312
+
313
+ project.reload
314
+ project.statuses[0].project_id.should == project.id
315
+ project.statuses[1].project_id.should == project.id
316
+ project.statuses[2].project_id.should == project.id
317
+ end
318
+ end
319
+
320
+ context "with Hashes" do
321
+ it "should correctly convert to objects and assign foreign key" do
322
+ project = Project.new
323
+ project.statuses << { 'name' => '<<' }
324
+ project.statuses.push( { 'name' => 'push' })
325
+ project.statuses.concat({ 'name' => 'concat' })
326
+
327
+ project.reload
328
+ project.statuses[0].project_id.should == project.id
329
+ project.statuses[1].project_id.should == project.id
330
+ project.statuses[2].project_id.should == project.id
331
+ end
332
+ end
333
+ end
334
+
335
+ context "#build" do
336
+ it "should assign foreign key" do
337
+ project = Project.create
338
+ status = project.statuses.build
339
+ status.project_id.should == project.id
340
+ end
341
+
342
+ it "should allow assigning attributes" do
343
+ project = Project.create
344
+ status = project.statuses.build(:name => 'Foo')
345
+ status.name.should == 'Foo'
346
+ end
347
+
348
+ it "should reset cache" do
349
+ project = Project.create
350
+ project.statuses.size.should == 0
351
+ status = project.statuses.build(:name => 'Foo')
352
+ status.save!
353
+ project.statuses.size.should == 1
354
+ end
355
+
356
+ it "should update collection without save" do
357
+ project = Project.create
358
+ project.statuses.build(:name => 'Foo')
359
+ project.statuses.size.should == 1
360
+ end
361
+
362
+ it "should save built document when saving parent" do
363
+ project = Project.create
364
+ status = project.statuses.build(:name => 'Foo')
365
+ project.save!
366
+ status.should_not be_new
367
+ end
368
+
369
+ it "should not save the parent when building associations" do
370
+ project = Project.new
371
+ status = project.statuses.build(:name => 'Foo')
372
+ project.should be_new
373
+ end
374
+
375
+ it "should not save the built object" do
376
+ project = Project.new
377
+ status = project.statuses.build(:name => 'Foo')
378
+ status.should be_new
379
+ end
380
+
381
+ it "should accept a block" do
382
+ project = Project.new
383
+ status = project.statuses.build do |doc|
384
+ doc.name = "Foo"
385
+ end
386
+ project.statuses[0].name.should == "Foo"
387
+ end
388
+ end
389
+
390
+ context "#create" do
391
+ it "should assign foreign key" do
392
+ project = Project.create
393
+ status = project.statuses.create(:name => 'Foo!')
394
+ status.project_id.should == project.id
395
+ end
396
+
397
+ it "should save record" do
398
+ project = Project.create
399
+ lambda {
400
+ project.statuses.create(:name => 'Foo!')
401
+ }.should change { Status.count }
402
+ end
403
+
404
+ it "should allow passing attributes" do
405
+ project = Project.create
406
+ status = project.statuses.create(:name => 'Foo!')
407
+ status.name.should == 'Foo!'
408
+ end
409
+
410
+ it "should reset cache" do
411
+ project = Project.create
412
+ project.statuses.size.should == 0
413
+ project.statuses.create(:name => 'Foo!')
414
+ project.statuses.size.should == 1
415
+ end
416
+
417
+ it "should accept a block" do
418
+ project = Project.new
419
+ status = project.statuses.create do |doc|
420
+ doc.name = "Foo"
421
+ end
422
+ project.statuses.first.name.should == "Foo"
423
+ end
424
+ end
425
+
426
+ context "#create!" do
427
+ it "should assign foreign key" do
428
+ project = Project.create
429
+ status = project.statuses.create!(:name => 'Foo!')
430
+ status.project_id.should == project.id
431
+ end
432
+
433
+ it "should save record" do
434
+ project = Project.create
435
+ lambda {
436
+ project.statuses.create!(:name => 'Foo!')
437
+ }.should change { Status.count }
438
+ end
439
+
440
+ it "should allow passing attributes" do
441
+ project = Project.create
442
+ status = project.statuses.create!(:name => 'Foo!')
443
+ status.name.should == 'Foo!'
444
+ end
445
+
446
+ it "should raise exception if not valid" do
447
+ project = Project.create
448
+ expect {
449
+ project.statuses.create!(:name => nil)
450
+ }.to raise_error(MarkMapper::DocumentNotValid)
451
+ end
452
+
453
+ it "should reset cache" do
454
+ project = Project.create
455
+ project.statuses.size.should == 0
456
+ project.statuses.create!(:name => 'Foo!')
457
+ project.statuses.size.should == 1
458
+ end
459
+
460
+ it "should accept a block" do
461
+ project = Project.new
462
+ status = project.statuses.create! do |doc|
463
+ doc.name = "Foo"
464
+ end
465
+ status.name.should == "Foo"
466
+ end
467
+ end
468
+
469
+ context "count" do
470
+ it "should work scoped to association" do
471
+ project = Project.create
472
+ 3.times { project.statuses.create(:name => 'Foo!') }
473
+
474
+ other_project = Project.create
475
+ 2.times { other_project.statuses.create(:name => 'Foo!') }
476
+
477
+ project.statuses.count.should == 3
478
+ other_project.statuses.count.should == 2
479
+ end
480
+
481
+ it "should work with conditions" do
482
+ project = Project.create
483
+ project.statuses.create(:name => 'Foo')
484
+ project.statuses.create(:name => 'Other 1')
485
+ project.statuses.create(:name => 'Other 2')
486
+
487
+ project.statuses.count(:name => 'Foo').should == 1
488
+ end
489
+
490
+ it "should ignore unpersisted documents" do
491
+ project = Project.create
492
+ project.statuses.build(:name => 'Foo')
493
+ project.statuses.count.should == 0
494
+ end
495
+ end
496
+
497
+ context "size" do
498
+ it "should reflect both persisted and new documents" do
499
+ project = Project.create
500
+ 3.times { project.statuses.create(:name => 'Foo!') }
501
+ 2.times { project.statuses.build(:name => 'Foo!') }
502
+ project.statuses.size.should == 5
503
+ end
504
+ end
505
+
506
+ context "empty?" do
507
+ it "should be true with no associated docs" do
508
+ project = Project.create
509
+ project.statuses.empty?.should be_truthy
510
+ end
511
+
512
+ it "should be false if a document is built" do
513
+ project = Project.create
514
+ project.statuses.build(:name => 'Foo!')
515
+ project.statuses.empty?.should be_falsey
516
+ end
517
+
518
+ it "should be false if a document is created" do
519
+ project = Project.create
520
+ project.statuses.create(:name => 'Foo!')
521
+ project.statuses.empty?.should be_falsey
522
+ end
523
+ end
524
+
525
+ context "#to_a" do
526
+ it "should include persisted and new documents" do
527
+ project = Project.create
528
+ 3.times { project.statuses.create(:name => 'Foo!') }
529
+ 2.times { project.statuses.build(:name => 'Foo!') }
530
+ project.statuses.to_a.size.should == 5
531
+ end
532
+ end
533
+
534
+ context "#map" do
535
+ it "should include persisted and new documents" do
536
+ project = Project.create
537
+ 3.times { project.statuses.create(:name => 'Foo!') }
538
+ 2.times { project.statuses.build(:name => 'Foo!') }
539
+ project.statuses.map(&:name).size.should == 5
540
+ end
541
+ end
542
+
543
+ context "to_json" do
544
+ it "should work on association" do
545
+ project = Project.create
546
+ 3.times { |i| project.statuses.create(:name => i.to_s) }
547
+
548
+ JSON.parse(project.statuses.to_json).collect{|status| status["name"] }.sort.should == ["0","1","2"]
549
+ end
550
+ end
551
+
552
+ context "as_json" do
553
+ it "should work on association" do
554
+ project = Project.create
555
+ 3.times { |i| project.statuses.create(:name => i.to_s) }
556
+
557
+ project.statuses.as_json.collect{|status| status["name"] }.sort.should == ["0","1","2"]
558
+ end
559
+ end
560
+
561
+ context "Unassociating documents" do
562
+ before do
563
+ @project = Project.create
564
+ @project.statuses << Status.create(:name => '1')
565
+ @project.statuses << Status.create(:name => '2')
566
+
567
+ @project2 = Project.create
568
+ @project2.statuses << Status.create(:name => '1')
569
+ @project2.statuses << Status.create(:name => '2')
570
+ end
571
+
572
+ it "should work with destroy all" do
573
+ @project.statuses.count.should == 2
574
+ @project.statuses.destroy_all
575
+ @project.statuses.count.should == 0
576
+
577
+ @project2.statuses.count.should == 2
578
+ Status.count.should == 2
579
+ end
580
+
581
+ it "should work with destroy all and conditions" do
582
+ @project.statuses.count.should == 2
583
+ @project.statuses.destroy_all(:name => '1')
584
+ @project.statuses.count.should == 1
585
+
586
+ @project2.statuses.count.should == 2
587
+ Status.count.should == 3
588
+ end
589
+
590
+ it "should work with delete all" do
591
+ @project.statuses.count.should == 2
592
+ @project.statuses.delete_all
593
+ @project.statuses.count.should == 0
594
+
595
+ @project2.statuses.count.should == 2
596
+ Status.count.should == 2
597
+ end
598
+
599
+ it "should work with delete all and conditions" do
600
+ @project.statuses.count.should == 2
601
+ @project.statuses.delete_all(:name => '1')
602
+ @project.statuses.count.should == 1
603
+
604
+ @project2.statuses.count.should == 2
605
+ Status.count.should == 3
606
+ end
607
+
608
+ it "should work with nullify" do
609
+ @project.statuses.count.should == 2
610
+ @project.statuses.nullify
611
+ @project.statuses.count.should == 0
612
+
613
+ @project2.statuses.count.should == 2
614
+ Status.count.should == 4
615
+ Status.count(:name => '1').should == 2
616
+ Status.count(:name => '2').should == 2
617
+ end
618
+ end
619
+
620
+ context "Finding scoped to association" do
621
+ before do
622
+ @project1 = Project.new(:name => 'Project 1')
623
+ @brand_new = Status.create(:name => 'New', :position => 1 )
624
+ @complete = Status.create(:name => 'Complete', :position => 2)
625
+ @project1.statuses = [@brand_new, @complete]
626
+ @project1.save
627
+
628
+ @project2 = Project.create(:name => 'Project 2')
629
+ @in_progress = Status.create(:name => 'In Progress')
630
+ @archived = Status.create(:name => 'Archived')
631
+ @another_complete = Status.create(:name => 'Complete')
632
+ @project2.statuses = [@in_progress, @archived, @another_complete]
633
+ @project2.save
634
+ end
635
+
636
+ context "include?" do
637
+ it "should return true if in association" do
638
+ expect(@project1.statuses.include?(@brand_new)).to eq(true)
639
+ end
640
+
641
+ it "should return false if not in association" do
642
+ expect(@project1.statuses.include?(@in_progress)).to eq(false)
643
+ end
644
+ end
645
+
646
+ context "dynamic finders" do
647
+ it "should work with single key" do
648
+ @project1.statuses.find_by_name('New').should == @brand_new
649
+ @project1.statuses.find_by_name!('New').should == @brand_new
650
+ @project2.statuses.find_by_name('In Progress').should == @in_progress
651
+ @project2.statuses.find_by_name!('In Progress').should == @in_progress
652
+ end
653
+
654
+ it "should work with multiple keys" do
655
+ @project1.statuses.find_by_name_and_position('New', 1).should == @brand_new
656
+ @project1.statuses.find_by_name_and_position!('New', 1).should == @brand_new
657
+ @project1.statuses.find_by_name_and_position('New', 2).should be_nil
658
+ end
659
+
660
+ it "should raise error when using !" do
661
+ lambda {
662
+ @project1.statuses.find_by_name!('Fake')
663
+ }.should raise_error(MarkMapper::DocumentNotFound)
664
+ end
665
+
666
+ context "find_or_create_by" do
667
+ it "should not create document if found" do
668
+ lambda {
669
+ status = @project1.statuses.find_or_create_by_name('New')
670
+ status.project.should == @project1
671
+ status.should == @brand_new
672
+ }.should_not change { Status.count }
673
+ end
674
+
675
+ it "should create document if not found" do
676
+ lambda {
677
+ status = @project1.statuses.find_or_create_by_name('Delivered')
678
+ status.project.should == @project1
679
+ }.should change { Status.count }
680
+ end
681
+ end
682
+ end
683
+
684
+ context "sexy querying" do
685
+ it "should work with where" do
686
+ @project1.statuses.where(:name => 'New').all.should == [@brand_new]
687
+ end
688
+
689
+ it "should work with sort" do
690
+ @project1.statuses.sort(:name).all.should == [@complete, @brand_new]
691
+ end
692
+
693
+ it "should work with limit" do
694
+ @project1.statuses.sort(:name).limit(1).all.should == [@complete]
695
+ end
696
+
697
+ it "should work with skip" do
698
+ @project1.statuses.sort(:name).skip(1).all.should == [@brand_new]
699
+ end
700
+
701
+ it "should work with fields" do
702
+ @project1.statuses.fields(:position).all.each do |status|
703
+ status.position.should_not be_nil
704
+ status.name.should be_nil
705
+ end
706
+ end
707
+
708
+ it "should work with scopes" do
709
+ @project1.statuses.complete.all.should == [@complete]
710
+ end
711
+
712
+ it "should work with methods on class that return query" do
713
+ @project1.statuses.by_position(1).first.should == @brand_new
714
+ end
715
+
716
+ it "should not work with methods on class that do not return query" do
717
+ Status.class_eval { def self.foo; 'foo' end }
718
+ lambda { @project1.statuses.foo }.
719
+ should raise_error(NoMethodError)
720
+ end
721
+ end
722
+
723
+ context "all" do
724
+ it "should work" do
725
+ @project1.statuses.all(:order => "position asc").should == [@brand_new, @complete]
726
+ end
727
+
728
+ it "should work with conditions" do
729
+ @project1.statuses.all(:name => 'Complete').should == [@complete]
730
+ end
731
+ end
732
+
733
+ context "first" do
734
+ it "should work" do
735
+ @project1.statuses.first(:order => 'name').should == @complete
736
+ end
737
+
738
+ it "should work with conditions" do
739
+ @project1.statuses.first(:name => 'Complete').should == @complete
740
+ end
741
+ end
742
+
743
+ context "last" do
744
+ it "should work" do
745
+ @project1.statuses.last(:order => "position asc").should == @complete
746
+ end
747
+
748
+ it "should work with conditions" do
749
+ @project1.statuses.last(:order => 'position', :name => 'New').should == @brand_new
750
+ end
751
+ end
752
+
753
+ context "with one id" do
754
+ it "should work for id in association" do
755
+ @project1.statuses.find(@complete.id).should == @complete
756
+ end
757
+
758
+ it "should not work for id not in association" do
759
+ lambda {
760
+ @project1.statuses.find!(@archived.id)
761
+ }.should raise_error(MarkMapper::DocumentNotFound)
762
+ end
763
+ end
764
+
765
+ context "with multiple ids" do
766
+ it "should work for ids in association" do
767
+ statuses = @project1.statuses.find(@brand_new.id, @complete.id)
768
+ statuses.should == [@brand_new, @complete]
769
+ end
770
+
771
+ it "should not work for ids not in association" do
772
+ expect {
773
+ @project1.statuses.find!(@brand_new.id, @complete.id, @archived.id)
774
+ }.to raise_error(MarkMapper::DocumentNotFound)
775
+ end
776
+ end
777
+
778
+ context "with #paginate" do
779
+ before do
780
+ @statuses = @project2.statuses.paginate(:per_page => 2, :page => 1, :order => 'name asc')
781
+ end
782
+
783
+ it "should return total pages" do
784
+ @statuses.total_pages.should == 2
785
+ end
786
+
787
+ it "should return total entries" do
788
+ @statuses.total_entries.should == 3
789
+ end
790
+
791
+ it "should return the subject" do
792
+ @statuses.collect(&:name).should == %w(Archived Complete)
793
+ end
794
+ end
795
+ end
796
+
797
+ context "extending the association" do
798
+ it "should work using a block passed to many" do
799
+ project = Project.new(:name => "Some Project")
800
+ status1 = Status.new(:name => "New")
801
+ status2 = Status.new(:name => "Assigned")
802
+ status3 = Status.new(:name => "Closed")
803
+ project.statuses = [status1, status2, status3]
804
+ project.save
805
+
806
+ open_statuses = project.statuses.open
807
+ open_statuses.should include(status1)
808
+ open_statuses.should include(status2)
809
+ open_statuses.should_not include(status3)
810
+ end
811
+
812
+ it "should work using many's :extend option" do
813
+ project = Project.new(:name => "Some Project")
814
+ collaborator1 = Collaborator.new(:name => "zing")
815
+ collaborator2 = Collaborator.new(:name => "zang")
816
+ project.collaborators = [collaborator1, collaborator2]
817
+ project.save
818
+ [collaborator1, collaborator2].include?(project.collaborators.top).should be true# == collaborator1
819
+ end
820
+ end
821
+
822
+ context ":dependent" do
823
+ before do
824
+ # FIXME: make use of already defined models
825
+ class ::Property
826
+ include MarkMapper::Document
827
+ end
828
+ Property.collection.remove
829
+
830
+ class ::Thing
831
+ include MarkMapper::Document
832
+ key :name, String
833
+ end
834
+ Thing.collection.remove
835
+ end
836
+
837
+ after do
838
+ Object.send :remove_const, 'Property' if defined?(::Property)
839
+ Object.send :remove_const, 'Thing' if defined?(::Thing)
840
+ end
841
+
842
+ context "=> destroy" do
843
+ before do
844
+ Property.key :thing_id, ObjectId
845
+ Property.belongs_to :thing, :dependent => :destroy
846
+ Thing.many :properties, :dependent => :destroy
847
+
848
+ @thing = Thing.create(:name => "Tree")
849
+ @property1 = Property.create
850
+ @property2 = Property.create
851
+ @property3 = Property.create
852
+ @thing.properties << @property1
853
+ @thing.properties << @property2
854
+ @thing.properties << @property3
855
+ end
856
+
857
+ it "should should destroy the associated documents" do
858
+ @thing.properties.count.should == 3
859
+ @thing.destroy
860
+ @thing.properties.count.should == 0
861
+ Property.count.should == 0
862
+ end
863
+ end
864
+
865
+ context "=> delete_all" do
866
+ before do
867
+ Property.key :thing_id, ObjectId
868
+ Property.belongs_to :thing
869
+ Thing.has_many :properties, :dependent => :delete_all
870
+
871
+ @thing = Thing.create(:name => "Tree")
872
+ @property1 = Property.create
873
+ @property2 = Property.create
874
+ @property3 = Property.create
875
+ @thing.properties << @property1
876
+ @thing.properties << @property2
877
+ @thing.properties << @property3
878
+ end
879
+
880
+ it "should should delete associated documents" do
881
+ @thing.properties.count.should == 3
882
+ @thing.destroy
883
+ @thing.properties.count.should == 0
884
+ Property.count.should == 0
885
+ end
886
+ end
887
+
888
+ context "=> nullify" do
889
+ before do
890
+ Property.key :thing_id, ObjectId
891
+ Property.belongs_to :thing
892
+ Thing.has_many :properties, :dependent => :nullify
893
+
894
+ @thing = Thing.create(:name => "Tree")
895
+ @property1 = Property.create
896
+ @property2 = Property.create
897
+ @property3 = Property.create
898
+ @thing.properties << @property1
899
+ @thing.properties << @property2
900
+ @thing.properties << @property3
901
+ end
902
+
903
+ it "should should nullify relationship but not destroy associated documents" do
904
+ @thing.properties.count.should == 3
905
+ @thing.destroy
906
+ @thing.properties.count.should == 0
907
+ Property.count.should == 3
908
+ end
909
+ end
910
+
911
+ context "unspecified" do
912
+ before do
913
+ Property.key :thing_id, ObjectId
914
+ Property.belongs_to :thing
915
+ Thing.has_many :properties, :dependent => :nullify
916
+
917
+ @thing = Thing.create(:name => "Tree")
918
+ @property1 = Property.create
919
+ @property2 = Property.create
920
+ @property3 = Property.create
921
+ @thing.properties << @property1
922
+ @thing.properties << @property2
923
+ @thing.properties << @property3
924
+ end
925
+
926
+ it "should should nullify relationship but not destroy associated documents" do
927
+ @thing.properties.count.should == 3
928
+ @thing.destroy
929
+ @thing.properties.count.should == 0
930
+ Property.count.should == 3
931
+ end
932
+ end
933
+ end
934
+
935
+ context "namespaced foreign keys" do
936
+ before do
937
+ News::Paper.many :articles, :class_name => 'News::Article'
938
+ News::Article.belongs_to :paper, :class_name => 'News::Paper'
939
+
940
+ @paper = News::Paper.create
941
+ end
942
+
943
+ it "should properly infer the foreign key" do
944
+ article = @paper.articles.create
945
+ article.should respond_to(:paper_id)
946
+ article.paper_id.should == @paper.id
947
+ end
948
+ end
949
+
950
+ context "criteria" do
951
+ before do
952
+ News::Paper.many :articles, :class_name => 'News::Article'
953
+ News::Article.belongs_to :paper, :class_name => 'News::Paper'
954
+
955
+ @paper = News::Paper.create
956
+ end
957
+
958
+ it "should should find associated instances by an object ID" do
959
+ article = News::Article.create(:paper_id => @paper.id)
960
+ expect(@paper.articles.include?(article)).to eq(true)
961
+ end
962
+
963
+ it "should should find associated instances by a string" do
964
+ article = News::Article.create(:paper_id => @paper.id.to_s)
965
+ expect(@paper.articles.include?(article)).to eq(true)
966
+ end
967
+ end
968
+ end