a tiny mvc framework for php using php-activerecord
1<?php
2include 'helpers/config.php';
3use ActiveRecord\DateTime;
4
5class DirtyAuthor extends ActiveRecord\Model
6{
7 static $table = 'authors';
8 static $before_save = 'before_save';
9
10 public function before_save()
11 {
12 $this->name = 'i saved';
13 }
14};
15
16class AuthorWithoutSequence extends ActiveRecord\Model
17{
18 static $table = 'authors';
19 static $sequence = 'invalid_seq';
20}
21
22class AuthorExplicitSequence extends ActiveRecord\Model
23{
24 static $sequence = 'blah_seq';
25}
26
27class ActiveRecordWriteTest extends DatabaseTest
28{
29 private function make_new_book_and($save=true)
30 {
31 $book = new Book();
32 $book->name = 'rivers cuomo';
33 $book->special = 1;
34
35 if ($save)
36 $book->save();
37
38 return $book;
39 }
40
41 public function test_save()
42 {
43 $venue = new Venue(array('name' => 'Tito'));
44 $venue->save();
45 }
46
47 public function test_insert()
48 {
49 $author = new Author(array('name' => 'Blah Blah'));
50 $author->save();
51 $this->assert_not_null(Author::find($author->id));
52 }
53
54 /**
55 * @expectedException ActiveRecord\DatabaseException
56 */
57 public function test_insert_with_no_sequence_defined()
58 {
59 if (!$this->conn->supports_sequences())
60 throw new ActiveRecord\DatabaseException('');
61
62 AuthorWithoutSequence::create(array('name' => 'Bob!'));
63 }
64
65 public function test_insert_should_quote_keys()
66 {
67 $author = new Author(array('name' => 'Blah Blah'));
68 $author->save();
69 $this->assert_true(strpos($author->connection()->last_query,$author->connection()->quote_name('updated_at')) !== false);
70 }
71
72 public function test_save_auto_increment_id()
73 {
74 $venue = new Venue(array('name' => 'Bob'));
75 $venue->save();
76 $this->assert_true($venue->id > 0);
77 }
78
79 public function test_sequence_was_set()
80 {
81 if ($this->conn->supports_sequences())
82 $this->assert_equals($this->conn->get_sequence_name('authors','author_id'),Author::table()->sequence);
83 else
84 $this->assert_null(Author::table()->sequence);
85 }
86
87 public function test_sequence_was_explicitly_set()
88 {
89 if ($this->conn->supports_sequences())
90 $this->assert_equals(AuthorExplicitSequence::$sequence,AuthorExplicitSequence::table()->sequence);
91 else
92 $this->assert_null(Author::table()->sequence);
93 }
94
95 public function test_delete()
96 {
97 $author = Author::find(1);
98 $author->delete();
99
100 $this->assert_false(Author::exists(1));
101 }
102
103 public function test_delete_by_find_all()
104 {
105 $books = Book::all();
106
107 foreach ($books as $model)
108 $model->delete();
109
110 $res = Book::all();
111 $this->assert_equals(0,count($res));
112 }
113
114 public function test_update()
115 {
116 $book = Book::find(1);
117 $new_name = 'new name';
118 $book->name = $new_name;
119 $book->save();
120
121 $this->assert_same($new_name, $book->name);
122 $this->assert_same($new_name, $book->name, Book::find(1)->name);
123 }
124
125 public function test_update_should_quote_keys()
126 {
127 $book = Book::find(1);
128 $book->name = 'new name';
129 $book->save();
130 $this->assert_true(strpos($book->connection()->last_query,$book->connection()->quote_name('name')) !== false);
131 }
132
133 public function test_update_attributes()
134 {
135 $book = Book::find(1);
136 $new_name = 'How to lose friends and alienate people'; // jax i'm worried about you
137 $attrs = array('name' => $new_name);
138 $book->update_attributes($attrs);
139
140 $this->assert_same($new_name, $book->name);
141 $this->assert_same($new_name, $book->name, Book::find(1)->name);
142 }
143
144 /**
145 * @expectedException ActiveRecord\UndefinedPropertyException
146 */
147 public function test_update_attributes_undefined_property()
148 {
149 $book = Book::find(1);
150 $book->update_attributes(array('name' => 'new name', 'invalid_attribute' => true , 'another_invalid_attribute' => 'blah'));
151 }
152
153 public function test_update_attribute()
154 {
155 $book = Book::find(1);
156 $new_name = 'some stupid self-help book';
157 $book->update_attribute('name', $new_name);
158
159 $this->assert_same($new_name, $book->name);
160 $this->assert_same($new_name, $book->name, Book::find(1)->name);
161 }
162
163 /**
164 * @expectedException ActiveRecord\UndefinedPropertyException
165 */
166 public function test_update_attribute_undefined_property()
167 {
168 $book = Book::find(1);
169 $book->update_attribute('invalid_attribute', true);
170 }
171
172 public function test_save_null_value()
173 {
174 $book = Book::first();
175 $book->name = null;
176 $book->save();
177 $this->assert_same(null,Book::find($book->id)->name);
178 }
179
180 public function test_save_blank_value()
181 {
182 // oracle doesn't do blanks. probably an option to enable?
183 if ($this->conn instanceof ActiveRecord\OciAdapter)
184 return;
185
186 $book = Book::find(1);
187 $book->name = '';
188 $book->save();
189 $this->assert_same('',Book::find(1)->name);
190 }
191
192 public function test_dirty_attributes()
193 {
194 $book = $this->make_new_book_and(false);
195 $this->assert_equals(array('name','special'),array_keys($book->dirty_attributes()));
196 }
197
198 public function test_dirty_attributes_cleared_after_saving()
199 {
200 $book = $this->make_new_book_and();
201 $this->assert_true(strpos($book->table()->last_sql,'name') !== false);
202 $this->assert_true(strpos($book->table()->last_sql,'special') !== false);
203 $this->assert_equals(null,$book->dirty_attributes());
204 }
205
206 public function test_dirty_attributes_cleared_after_inserting()
207 {
208 $book = $this->make_new_book_and();
209 $this->assert_equals(null,$book->dirty_attributes());
210 }
211
212 public function test_no_dirty_attributes_but_still_insert_record()
213 {
214 $book = new Book;
215 $this->assert_equals(null,$book->dirty_attributes());
216 $book->save();
217 $this->assert_equals(null,$book->dirty_attributes());
218 $this->assert_not_null($book->id);
219 }
220
221 public function test_dirty_attributes_cleared_after_updating()
222 {
223 $book = Book::first();
224 $book->name = 'rivers cuomo';
225 $book->save();
226 $this->assert_equals(null,$book->dirty_attributes());
227 }
228
229 public function test_dirty_attributes_after_reloading()
230 {
231 $book = Book::first();
232 $book->name = 'rivers cuomo';
233 $book->reload();
234 $this->assert_equals(null,$book->dirty_attributes());
235 }
236
237 public function test_dirty_attributes_with_mass_assignment()
238 {
239 $book = Book::first();
240 $book->set_attributes(array('name' => 'rivers cuomo'));
241 $this->assert_equals(array('name'), array_keys($book->dirty_attributes()));
242 }
243
244 public function test_timestamps_set_before_save()
245 {
246 $author = new Author;
247 $author->save();
248 $this->assert_not_null($author->created_at, $author->updated_at);
249
250 $author->reload();
251 $this->assert_not_null($author->created_at, $author->updated_at);
252 }
253
254 public function test_timestamps_updated_at_only_set_before_update()
255 {
256 $author = new Author();
257 $author->save();
258 $created_at = $author->created_at;
259 $updated_at = $author->updated_at;
260 sleep(1);
261
262 $author->name = 'test';
263 $author->save();
264
265 $this->assert_not_null($author->updated_at);
266 $this->assert_same($created_at, $author->created_at);
267 $this->assert_not_equals($updated_at, $author->updated_at);
268 }
269
270 public function test_create()
271 {
272 $author = Author::create(array('name' => 'Blah Blah'));
273 $this->assert_not_null(Author::find($author->id));
274 }
275
276 public function test_create_should_set_created_at()
277 {
278 $author = Author::create(array('name' => 'Blah Blah'));
279 $this->assert_not_null($author->created_at);
280 }
281
282 /**
283 * @expectedException ActiveRecord\ActiveRecordException
284 */
285 public function test_update_with_no_primary_key_defined()
286 {
287 Author::table()->pk = array();
288 $author = Author::first();
289 $author->name = 'blahhhhhhhhhh';
290 $author->save();
291 }
292
293 /**
294 * @expectedException ActiveRecord\ActiveRecordException
295 */
296 public function test_delete_with_no_primary_key_defined()
297 {
298 Author::table()->pk = array();
299 $author = author::first();
300 $author->delete();
301 }
302
303 public function test_inserting_with_explicit_pk()
304 {
305 $author = Author::create(array('author_id' => 9999, 'name' => 'blah'));
306 $this->assert_equals(9999,$author->author_id);
307 }
308
309 /**
310 * @expectedException ActiveRecord\ReadOnlyException
311 */
312 public function test_readonly()
313 {
314 $author = Author::first(array('readonly' => true));
315 $author->save();
316 }
317
318 public function test_modified_attributes_in_before_handlers_get_saved()
319 {
320 $author = DirtyAuthor::first();
321 $author->encrypted_password = 'coco';
322 $author->save();
323 $this->assert_equals('i saved',DirtyAuthor::find($author->id)->name);
324 }
325
326 public function test_is_dirty()
327 {
328 $author = Author::first();
329 $this->assert_equals(false,$author->is_dirty());
330
331 $author->name = 'coco';
332 $this->assert_equals(true,$author->is_dirty());
333 }
334
335 public function test_set_date_flags_dirty()
336 {
337 $author = Author::create(array('some_date' => new DateTime()));
338 $author = Author::find($author->id);
339 $author->some_date->setDate(2010,1,1);
340 $this->assert_has_keys('some_date', $author->dirty_attributes());
341 }
342
343 public function test_set_date_flags_dirty_with_php_datetime()
344 {
345 $author = Author::create(array('some_date' => new \DateTime()));
346 $author = Author::find($author->id);
347 $author->some_date->setDate(2010,1,1);
348 $this->assert_has_keys('some_date', $author->dirty_attributes());
349 }
350
351 public function test_delete_all_with_conditions_as_string()
352 {
353 $num_affected = Author::delete_all(array('conditions' => 'parent_author_id = 2'));
354 $this->assert_equals(2, $num_affected);
355 }
356
357 public function test_delete_all_with_conditions_as_hash()
358 {
359 $num_affected = Author::delete_all(array('conditions' => array('parent_author_id' => 2)));
360 $this->assert_equals(2, $num_affected);
361 }
362
363 public function test_delete_all_with_conditions_as_array()
364 {
365 $num_affected = Author::delete_all(array('conditions' => array('parent_author_id = ?', 2)));
366 $this->assert_equals(2, $num_affected);
367 }
368
369 public function test_delete_all_with_limit_and_order()
370 {
371 if (!$this->conn->accepts_limit_and_order_for_update_and_delete())
372 $this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE clause');
373
374 $num_affected = Author::delete_all(array('conditions' => array('parent_author_id = ?', 2), 'limit' => 1, 'order' => 'name asc'));
375 $this->assert_equals(1, $num_affected);
376 $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false);
377 }
378
379 public function test_update_all_with_set_as_string()
380 {
381 $num_affected = Author::update_all(array('set' => 'parent_author_id = 2'));
382 $this->assert_equals(2, $num_affected);
383 $this->assert_equals(4, Author::count_by_parent_author_id(2));
384 }
385
386 public function test_update_all_with_set_as_hash()
387 {
388 $num_affected = Author::update_all(array('set' => array('parent_author_id' => 2)));
389 $this->assert_equals(2, $num_affected);
390 }
391
392 /**
393 * TODO: not implemented
394 public function test_update_all_with_set_as_array()
395 {
396 $num_affected = Author::update_all(array('set' => array('parent_author_id = ?', 2)));
397 $this->assert_equals(2, $num_affected);
398 }
399 */
400
401 public function test_update_all_with_conditions_as_string()
402 {
403 $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => 'name = "Tito"'));
404 $this->assert_equals(1, $num_affected);
405 }
406
407 public function test_update_all_with_conditions_as_hash()
408 {
409 $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => array('name' => "Tito")));
410 $this->assert_equals(1, $num_affected);
411 }
412
413 public function test_update_all_with_conditions_as_array()
414 {
415 $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'conditions' => array('name = ?', "Tito")));
416 $this->assert_equals(1, $num_affected);
417 }
418
419 public function test_update_all_with_limit_and_order()
420 {
421 if (!$this->conn->accepts_limit_and_order_for_update_and_delete())
422 $this->mark_test_skipped('Only MySQL & Sqlite accept limit/order with UPDATE clause');
423
424 $num_affected = Author::update_all(array('set' => 'parent_author_id = 2', 'limit' => 1, 'order' => 'name asc'));
425 $this->assert_equals(1, $num_affected);
426 $this->assert_true(strpos(Author::table()->last_sql, 'ORDER BY name asc LIMIT 1') !== false);
427 }
428};