From 9c7d2b23c259c5115eabec0ef9b7e32b5e97edc4 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 29 Nov 2013 16:40:57 +0200 Subject: [PATCH] Mongo Active Record and Active Query fixed. --- extensions/mongo/ActiveQuery.php | 72 ++++++++ extensions/mongo/ActiveRecord.php | 19 +- extensions/mongo/Query.php | 6 +- tests/unit/data/ar/mongo/ActiveRecord.php | 16 ++ tests/unit/data/ar/mongo/Customer.php | 27 +++ tests/unit/extensions/mongo/ActiveRecordTest.php | 220 +++++++++++++++++++++++ 6 files changed, 351 insertions(+), 9 deletions(-) create mode 100644 tests/unit/data/ar/mongo/ActiveRecord.php create mode 100644 tests/unit/data/ar/mongo/Customer.php create mode 100644 tests/unit/extensions/mongo/ActiveRecordTest.php diff --git a/extensions/mongo/ActiveQuery.php b/extensions/mongo/ActiveQuery.php index f30eff4..9b7e207 100644 --- a/extensions/mongo/ActiveQuery.php +++ b/extensions/mongo/ActiveQuery.php @@ -19,4 +19,76 @@ use yii\db\ActiveQueryTrait; class ActiveQuery extends Query implements ActiveQueryInterface { use ActiveQueryTrait; + + /** + * Executes query and returns all results as an array. + * @param Connection $db the DB connection used to create the DB command. + * If null, the DB connection returned by [[modelClass]] will be used. + * @return array the query results. If the query results in nothing, an empty array will be returned. + */ + public function all($db = null) + { + $cursor = $this->buildCursor($db); + $rows = []; + foreach ($cursor as $row) { + $rows[] = $row; + } + if (!empty($rows)) { + $models = $this->createModels($rows); + if (!empty($this->with)) { + $this->findWith($this->with, $models); + } + return $models; + } else { + return []; + } + } + + /** + * Executes query and returns a single row of result. + * @param Connection $db the DB connection used to create the DB command. + * If null, the DB connection returned by [[modelClass]] will be used. + * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], + * the query result may be either an array or an ActiveRecord object. Null will be returned + * if the query results in nothing. + */ + public function one($db = null) + { + $row = parent::one($db); + if ($row !== false) { + if ($this->asArray) { + $model = $row; + } else { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $model = $class::create($row); + } + if (!empty($this->with)) { + $models = [$model]; + $this->findWith($this->with, $models); + $model = $models[0]; + } + return $model; + } else { + return null; + } + } + + /** + * Returns the Mongo collection for this query. + * @param Connection $db Mongo connection. + * @return Collection collection instance. + */ + public function getCollection($db = null) + { + /** @var ActiveRecord $modelClass */ + $modelClass = $this->modelClass; + if ($db === null) { + $db = $modelClass::getDb(); + } + if ($this->from === null) { + $this->from = $modelClass::collectionName(); + } + return $db->getCollection($this->from); + } } \ No newline at end of file diff --git a/extensions/mongo/ActiveRecord.php b/extensions/mongo/ActiveRecord.php index beab2ef..d6788fc 100644 --- a/extensions/mongo/ActiveRecord.php +++ b/extensions/mongo/ActiveRecord.php @@ -162,11 +162,7 @@ abstract class ActiveRecord extends Model if (!array_key_exists('multiple', $options)) { $options['multiple'] = true; } - $data = []; - foreach ($counters as $name => $value) { - $data[$name]['$inc'] = $value; - } - return static::getCollection()->update($condition, $data, $options); + return static::getCollection()->update($condition, ['$inc' => $counters], $options); } /** @@ -470,13 +466,20 @@ abstract class ActiveRecord extends Model /** * Returns the list of all attribute names of the model. - * The default implementation will return all column names of the table associated with this AR class. + * This method must be overridden by child classes to define available attributes. + * Note: primary key attribute "_id" should be always present in returned array. + * For example: + * ~~~ + * public function attributes() + * { + * return ['_id', 'name', 'address', 'status']; + * } + * ~~~ * @return array list of attribute names. */ public function attributes() { - // TODO: declare attributes - return []; + throw new InvalidConfigException('The attributes() method of mongo ActiveRecord has to be implemented by child classes.'); } /** diff --git a/extensions/mongo/Query.php b/extensions/mongo/Query.php index e8f3416..42b3403 100644 --- a/extensions/mongo/Query.php +++ b/extensions/mongo/Query.php @@ -182,7 +182,11 @@ class Query extends Component implements QueryInterface public function one($db = null) { $cursor = $this->buildCursor($db); - return $cursor->getNext(); + if ($cursor->hasNext()) { + return $cursor->getNext(); + } else { + return false; + } } /** diff --git a/tests/unit/data/ar/mongo/ActiveRecord.php b/tests/unit/data/ar/mongo/ActiveRecord.php new file mode 100644 index 0000000..6f5bc49 --- /dev/null +++ b/tests/unit/data/ar/mongo/ActiveRecord.php @@ -0,0 +1,16 @@ +andWhere(['status' => 2]); + } +} \ No newline at end of file diff --git a/tests/unit/extensions/mongo/ActiveRecordTest.php b/tests/unit/extensions/mongo/ActiveRecordTest.php new file mode 100644 index 0000000..e96ea30 --- /dev/null +++ b/tests/unit/extensions/mongo/ActiveRecordTest.php @@ -0,0 +1,220 @@ +getConnection(); + $this->setUpTestRows(); + } + + protected function tearDown() + { + $this->dropCollection(Customer::collectionName()); + parent::tearDown(); + } + + /** + * Sets up test rows. + */ + protected function setUpTestRows() + { + $collection = $this->getConnection()->getCollection('customer'); + $rows = []; + for ($i = 1; $i <= 10; $i++) { + $rows[] = [ + 'name' => 'name' . $i, + 'email' => 'email' . $i, + 'address' => 'address' . $i, + 'status' => $i, + ]; + } + $collection->batchInsert($rows); + $this->testRows = $rows; + } + + // Tests : + + public function testFind() + { + // find one + $result = Customer::find(); + $this->assertTrue($result instanceof ActiveQuery); + $customer = $result->one(); + $this->assertTrue($customer instanceof Customer); + + // find all + $customers = Customer::find()->all(); + $this->assertEquals(10, count($customers)); + $this->assertTrue($customers[0] instanceof Customer); + $this->assertTrue($customers[1] instanceof Customer); + + // find by _id + $testId = $this->testRows[0]['_id']; + $customer = Customer::find($testId); + $this->assertTrue($customer instanceof Customer); + $this->assertEquals($testId, $customer->_id); + + // find by column values + $customer = Customer::find(['name' => 'name5']); + $this->assertTrue($customer instanceof Customer); + $this->assertEquals($this->testRows[4]['_id'], $customer->_id); + $this->assertEquals('name5', $customer->name); + $customer = Customer::find(['name' => 'unexisting name']); + $this->assertNull($customer); + + // find by attributes + $customer = Customer::find()->where(['status' => 4])->one(); + $this->assertTrue($customer instanceof Customer); + $this->assertEquals(4, $customer->status); + + // find count, sum, average, min, max, scalar + $this->assertEquals(10, Customer::find()->count()); + $this->assertEquals(1, Customer::find()->where(['status' => 2])->count()); + /*$this->assertEquals((1+10)/2*10, Customer::find()->sum('status')); + $this->assertEquals((1+10)/2, Customer::find()->average('status')); + $this->assertEquals(1, Customer::find()->min('status')); + $this->assertEquals(10, Customer::find()->max('status'));*/ + + // scope + $this->assertEquals(1, Customer::find()->activeOnly()->count()); + + // asArray + $testRow = $this->testRows[2]; + $customer = Customer::find()->where(['_id' => $testRow['_id']])->asArray()->one(); + $this->assertEquals($testRow, $customer); + + // indexBy + $customers = Customer::find()->indexBy('name')->all(); + $this->assertTrue($customers['name1'] instanceof Customer); + $this->assertTrue($customers['name2'] instanceof Customer); + + // indexBy callable + $customers = Customer::find()->indexBy(function ($customer) { + return $customer->status . '-' . $customer->status; + })->all(); + $this->assertTrue($customers['1-1'] instanceof Customer); + $this->assertTrue($customers['2-2'] instanceof Customer); + } + + public function testInsert() + { + $record = new Customer; + $record->name = 'new name'; + $record->email = 'new email'; + $record->address = 'new address'; + $record->status = 7; + + $this->assertTrue($record->isNewRecord); + + $record->save(); + + $this->assertTrue($record->_id instanceof \MongoId); + $this->assertFalse($record->isNewRecord); + } + + /** + * @depends testInsert + */ + public function testUpdate() + { + $record = new Customer; + $record->name = 'new name'; + $record->email = 'new email'; + $record->address = 'new address'; + $record->status = 7; + $record->save(); + + // save + $record = Customer::find($record->_id); + $this->assertTrue($record instanceof Customer); + $this->assertEquals(7, $record->status); + $this->assertFalse($record->isNewRecord); + + $record->status = 9; + $record->save(); + $this->assertEquals(9, $record->status); + $this->assertFalse($record->isNewRecord); + $record2 = Customer::find($record->_id); + $this->assertEquals(9, $record2->status); + + // updateAll + $pk = ['_id' => $record->_id]; + //$ret = Customer::updateAll(['status' => 55], $pk); + $ret = Customer::updateAll(['$set' => ['status' => 55]], $pk); + $this->assertEquals(1, $ret); + $record = Customer::find($pk); + $this->assertEquals(55, $record->status); + } + + /** + * @depends testInsert + */ + public function testDelete() + { + // delete + $record = new Customer; + $record->name = 'new name'; + $record->email = 'new email'; + $record->address = 'new address'; + $record->status = 7; + $record->save(); + + $record = Customer::find($record->_id); + $record->delete(); + $record = Customer::find($record->_id); + $this->assertNull($record); + + // deleteAll + $record = new Customer; + $record->name = 'new name'; + $record->email = 'new email'; + $record->address = 'new address'; + $record->status = 7; + $record->save(); + + $ret = Customer::deleteAll(['name' => 'new name']); + $this->assertEquals(1, $ret); + $records = Customer::find()->where(['name' => 'new name'])->all(); + $this->assertEquals(0, count($records)); + } + + public function testUpdateAllCounters() + { + $this->assertEquals(1, Customer::updateAllCounters(['status' => 10], ['status' => 10])); + + $record = Customer::find(['status' => 10]); + $this->assertNull($record); + } + + /** + * @depends testUpdateAllCounters + */ + public function testUpdateCounters() + { + $record = Customer::find($this->testRows[9]); + + $originalCounter = $record->status; + $counterIncrement = 20; + $record->updateCounters(['status' => $counterIncrement]); + $this->assertEquals($originalCounter + $counterIncrement, $record->status); + + $refreshedRecord = Customer::find($record->_id); + $this->assertEquals($originalCounter + $counterIncrement, $refreshedRecord->status); + } +} \ No newline at end of file