Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

moved magic to separate method, added config parameters, improved test isolation #14

Merged
merged 4 commits into from

3 participants

@phishy

moved view detection/creation logic into autoView() method
added 'createViews' and 'autoViews' Connection config toggle
tests now require li3-test bucket
test connection now requires username/password
tests now get flushed once before all tests
added preliminary code for bucket creation/deletion
native $viewOptions are now handled separately of $conditions

@phishy phishy refactored magic to separate method and added config params
moved view detection/creation logic into autoView() method
added 'createViews' and 'autoViews' Connection config toggle
tests now require li3-test bucket
test connection now requires username/password
tests now get flushed once before all tests
added preliminary code for bucket creation/deletion
native $viewOptions are now handled separately of $conditions
816d677
extensions/data/source/Couchbase.php
((7 lines not shown))
);
- $this->prefix = (Environment::get() == 'production') ? '' : 'dev_';
+ if (Environment::get() == 'production') {
@nateabele Owner

It breaks separation for adapters to reference the environment directly. The way to do this would be to either have an 'environment' flag that you'd configure in connections.php, or just expose the flags you're using directly so that people can explicitly manage their configurations across different environments.

@phishy
phishy added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@gwoo gwoo merged commit 60cb3af into UnionOfRAD:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 12, 2013
  1. @phishy

    refactored magic to separate method and added config params

    phishy authored
    moved view detection/creation logic into autoView() method
    added 'createViews' and 'autoViews' Connection config toggle
    tests now require li3-test bucket
    test connection now requires username/password
    tests now get flushed once before all tests
    added preliminary code for bucket creation/deletion
    native $viewOptions are now handled separately of $conditions
  2. @phishy

    added couchbase design doc importer/exporter

    phishy authored
    li3 couchbase export - exports design docs
    li3 couchbase import - imports design docs
Commits on Feb 13, 2013
  1. @phishy
  2. @phishy
This page is out of date. Refresh to see the latest.
View
87 extensions/command/Couchbase.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace li3_couchbase\extensions\command;
+
+use lithium\data\Connections;
+
+class Couchbase extends \lithium\console\Command {
+
+ /**
+ * Reads data from an endpoint and decodes it
+ *
+ * @param $url
+ * @return mixed
+ */
+ protected function _get($url) {
+ return json_decode(file_get_contents("{$this->hostname}{$url}"));
+ }
+
+ public function _init() {
+ parent::_init();
+ $this->db = Connections::get('default');
+ $this->cluster = new \CouchbaseClusterManager(
+ $this->db->_config['host'],
+ $this->db->_config['login'],
+ $this->db->_config['password']
+ );
+ $this->info = json_decode($this->cluster->getInfo());
+ $this->hostname = "http://{$this->info->nodes[0]->hostname}";
+ $this->viewPath = LITHIUM_APP_PATH . '/extensions/data/source/couchbase/views';
+ }
+
+ /**
+ * Loads design docs into Couchbase
+ *
+ */
+ public function import() {
+ $data = array();
+ $dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->viewPath));
+ foreach ($dir as $k => $v) {
+ if (!in_array($v->getFilename(), array('.', '..'))) {
+ $pieces = explode('/', $k);
+ $file = array_pop($pieces);
+ $view = array_pop($pieces);
+ $design = array_pop($pieces);
+ $file = str_replace('.js', '', $file);
+ $data[$design]['views'][$view][$file] = file_get_contents($v->getPathname());
+ }
+ }
+ foreach ($data as $design => $v) {
+ $this->db->setDesignDoc($design, json_encode($v));
+ $this->out("wrote _design/{$design}");
+ }
+ }
+
+ /**
+ * Reads design docs from the cluster and writes them to disk
+ */
+ public function export() {
+ if (!file_exists($this->viewPath)) {
+ mkdir($this->viewPath, 0755, true);
+ $this->stop();
+ }
+ $buckets = $this->_get($this->info->buckets->uri);
+ $default = $buckets[0];
+ $ddocs = $this->_get($default->ddocs->uri);
+ foreach ($ddocs->rows as $doc) {
+ $design = str_replace('_design', '', $doc->doc->meta->id);
+ $views = $doc->doc->json->views;
+ foreach ($views as $name => $scripts) {
+ $out = "{$this->viewPath}{$design}/{$name}";
+ if (!file_exists($out)) {
+ mkdir($out, 0755, true);
+ }
+ if (!empty($scripts->map)) {
+ file_put_contents("{$out}/map.js", $scripts->map);
+ $this->out("wrote map.js to {$out}/map.js");
+ }
+ if (!empty($scripts->reduce)) {
+ file_put_contents("{$out}/reduce.js", $scripts->reduce);
+ $this->out("wrote reduce.js to {$out}/map.js");
+ }
+ }
+ }
+ $this->stop();
+ }
+}
+
View
151 extensions/data/source/Couchbase.php
@@ -94,14 +94,17 @@ class Couchbase extends \lithium\data\Source {
*/
public function __construct(array $config = array()) {
$defaults = array(
- 'host' => 'localhost:8091',
- 'login' => null,
- 'password' => null,
- 'database' => 'default',
- 'persistent' => true
+ 'host' => 'localhost:8091',
+ 'login' => null,
+ 'password' => null,
+ 'database' => 'default',
+ 'persistent' => true,
+ 'autoViews' => true,
+ 'createViews' => true,
+ 'prefix' => 'dev_'
);
- $this->prefix = (Environment::get() == 'production') ? '' : 'dev_';
parent::__construct($config + $defaults);
+ $this->prefix = $this->_config['prefix'];
}
/**
@@ -303,12 +306,19 @@ public function views($source = '') {
* @param string $source
* @param string $field
*/
- public function createView($source, $field) {
+ public function createView($source, $field = '') {
$views = json_decode($this->getDesignDoc("{$this->prefix}{$source}"), true);
- $views['views']["by_{$field}"] = array(
- 'map' =>
- "function (doc, meta) { if(doc._source == '{$source}') { emit(doc.{$field}, doc) }}",
- );
+ if ($field == '') {
+ $views['views']['all'] = array(
+ 'map' =>
+ "function (doc, meta) { if(doc._source == '{$source}') { emit(meta.id, doc) }}"
+ );
+ } else{
+ $views['views']["by_{$field}"] = array(
+ 'map' =>
+ "function (doc, meta) { if(doc._source == '{$source}') { emit(doc.{$field}, doc) }}",
+ );
+ }
$this->setDesignDoc("{$this->prefix}{$source}", json_encode($views));
$this->registerViews($source);
}
@@ -321,13 +331,101 @@ public function createView($source, $field) {
* @return array
*/
public function conditions($conditions, $context) {
+ return $conditions;
+ }
+
+ /**
+ * Desperately tries to determine what view should be used and/or created
+ *
+ * @param $source
+ * @param $conditions
+ * @param $options
+ * @return array
+ */
+ public function autoView($params = array()) {
+ extract($params);
+ $viewOptionsDefaults = array(
+ 'debug' => null,
+ 'descending' => null,
+ 'endkey' => null,
+ 'endkey_docid' => null,
+ 'full_set' => null,
+ 'group' => null,
+ 'group_level' => null,
+ 'inclusive_end' => null,
+ 'key' => null,
+ 'keys' => null,
+ 'limit' => null,
+ 'on_error' => null,
+ 'reduce' => null,
+ 'skip' => null,
+ 'stale' => false,
+ 'startkey' => null,
+ 'startkey_docid' => null
+ );
+ $viewOptions = array_intersect_key($options, $viewOptionsDefaults);
+ $viewOptions = array_filter($viewOptions + $viewOptionsDefaults, function ($v) {
+ return !is_null($v);
+ });
+
+ /**
+ * If filtering by the key in the 'all' view, prepend the _source name until this lib
+ * can be refactored to not need such a hack
+ */
+ if (isset($conditions['view']) && $conditions['view'] == 'all') {
+ if (isset($viewOptions['key'])) {
+ $viewOptions['key'] = "{$source}:{$viewOptions['key']}";
+ }
+ }
+
+ $viewName = '';
+ $field = '';
+
+ /**
+ * Removes 'all' view from conditions until it can be determined if there are filterable
+ * conditions and the 'all' view is appropriate. All 'all' view becomes irrelevant in
+ * situations like findAllBy$Field, since that it now the actual view that will be
+ * queried.
+ */
if (isset($conditions['view']) && $conditions['view'] == 'all') {
unset($conditions['view']);
}
+
+ /**
+ * If there are no other conditions, then clearly the user is looking for all records
+ * and no filtering. Here's we'll set the view to 'all' and it will be caught below.
+ */
if (empty($conditions)) {
$conditions['view'] = 'all';
}
- return $conditions;
+
+ /**
+ * Matches Model::find($id);
+ */
+ if (!empty($conditions[$key])) {
+ /**
+ * Matches Model::find('by_field'), in that lithium believes this to be an id
+ * because the view has not been created or registered as a finder.
+ */
+ if (strstr($conditions[$key], 'by_')) {
+ $field = str_replace('by_', '', $conditions[$key]);
+ $viewName = $conditions[$key];
+ unset($conditions[$key]);
+ }
+ } elseif (empty($conditions['view'])) {
+ $field = key($conditions);
+ $viewName = "by_{$field}";
+ $viewOptions['key'] = array_shift($conditions);
+ } else {
+ $viewName = $conditions['view'];
+ }
+
+ $viewExists = array_key_exists($viewName, $this->views($source));
+ if ($_config['createViews'] && $viewName && !$viewExists) {
+ $this->createView($source, $field);
+ }
+
+ return compact('conditions', 'viewName', 'viewOptions');
}
/**
@@ -340,7 +438,9 @@ public function conditions($conditions, $context) {
*/
public function read($query, array $options = array()) {
$this->_checkConnection();
- $defaults = array('expiry' => 0);
+ $defaults = array(
+ 'expiry' => 0,
+ );
$options += $defaults;
$params = compact('query', 'options');
@@ -352,32 +452,18 @@ public function read($query, array $options = array()) {
extract($query->export($self, array('keys' => array(
'source', 'model', 'conditions'
))));
+
$key = $model::key();
$viewName = '';
- $viewOptions = array('stale' => false);
-
- if (!empty($conditions[$key])) {
- // nada
- } elseif (empty($conditions['view'])) {
- $field = key($conditions);
- $viewName = "by_{$field}";
- $viewOptions['key'] = array_shift($conditions);
- } else {
- $viewName = $conditions['view'];
- }
-
- if ($viewName && !array_key_exists($viewName, $self->views($source))) {
- $self->createView($source, $field);
- }
+ $viewOptions = '';
- if (isset($conditions['key'])) {
- $viewOptions['key'] = $conditions['key'];
+ if ($_config['autoViews']) {
+ extract($self->autoView(compact('_config', 'source', 'key', 'conditions', 'options')));
}
if ($viewName) {
- $view = $self->connection->view("{$self->prefix}{$source}", $viewName,
- $viewOptions);
+ $view = $self->connection->view("{$self->prefix}{$source}", $viewName, $viewOptions);
$records = array();
if (!empty($view['rows'])) {
foreach ($view['rows'] as $r) {
@@ -423,6 +509,7 @@ public function update($query, array $options = array()) {
$data = $query->data();
$entity = $query->entity();
$id = "{$source}:{$data[$key]}";
+ $data['_source'] = $source;
$result = $self->connection->set($id, json_encode($data), $options['expiry']);
if ($result) {
View
5 tests/cases/extensions/data/source/CouchbaseTest.php
@@ -51,7 +51,10 @@ public function testDefaults() {
'database' => 'default',
'persistent' => true,
'autoConnect' => true,
- 'init' => false
+ 'init' => false,
+ 'autoViews' => true,
+ 'createViews' => true,
+ 'prefix' => 'dev_'
);
$cb = new Couchbase(array('init' => false));
View
83 tests/integration/data/CrudExtendedTest.php
@@ -9,6 +9,7 @@
namespace li3_couchbase\tests\integration\data;
use lithium\data\Connections;
+use lithium\util\Set;
use li3_couchbase\extensions\data\source\Couchbase;
use li3_couchbase\tests\mocks\models\Companies;
use li3_couchbase\tests\mocks\models\Custom;
@@ -22,8 +23,9 @@ class CrudExtendedTest extends \lithium\test\Integration {
protected $_key = null;
public $data = array(
- array('name' => 'Marine Store', 'active' => true),
- array('name' => 'Bait Shop', 'active' => false)
+ array('name' => 'Marine Store', 'active' => true, 'founded' => 2012),
+ array('name' => 'Bait Shop', 'active' => false, 'founded' => 2013),
+ array('name' => 'Tackle Shack', 'active' => true, 'founded' => 2013)
);
/**
@@ -43,6 +45,7 @@ public function skip() {
$this->skipIf(!$isAvailable, "No {$connection} connection available.");
$this->db = Connections::get($connection);
+ $this->config = $this->db->_config;
$this->_database = $this->config['database'];
$this->_key = Companies::key();
@@ -52,7 +55,44 @@ public function skip() {
* Creating the test database
*/
public function setUp() {
- //$this->db->connection->put($this->_database);
+ static $flushed = false;
+ if (!$flushed) {
+ $this->cluster = new \CouchbaseClusterManager($this->config['host'],
+ $this->config['login'],
+ $this->config['password']
+ );
+ if ($this->config['database'] !== 'li3-test') {
+ throw new \Exception('Create a new bucket `li3-test` and edit `database` li3_couchbase/config/bootstrap.php');
+ }
+ if (!in_array('li3-test', $this->_getBuckets())) {
+ throw new \Exception('Create a new bucket `li3-test` and edit `database` li3_couchbase/config/bootstrap.php');
+ }
+ if ($this->config['database'] == 'default') {
+ throw new \Exception('Refusing to flush default bucket. Create a different one.');
+ }
+ $this->db->flush();
+ /**
+ * @todo Work out errors in automatic bucket creation/deletion
+ */
+ /**
+ $buckets = $this->_getBuckets();
+ if (in_array('li3-test', $buckets)) {
+ $this->cluster->deleteBucket('li3-test');
+ }
+ $this->cluster->createBucket('li3-test',
+ array(
+ "type" => "couchbase",
+ "quota" => 100,
+ "replicas" => 0,
+ "enable_flush" => 1,
+ "parallel_compaction" => true,
+ "auth" => "sasl",
+ //"password" => "foobar"
+ )
+ );
+ **/
+ $flushed = true;
+ }
}
/**
@@ -62,6 +102,19 @@ public function tearDown() {
//$this->db->connection->delete($this->_database);
}
+ /**
+ * Returns an array of bucket names retrieved from cluster
+ *
+ * @return array
+ */
+ protected function _getBuckets() {
+ $info = json_decode($this->cluster->getInfo());
+ $bucketUrl = "http://{$info->nodes[0]->hostname}{$info->buckets->uri}";
+ $bucketData = json_decode(file_get_contents($bucketUrl), true);
+ $buckets = Set::extract($bucketData, '/name');
+ return $buckets;
+ }
+
public function testCreateType() {
$company = Companies::create($this->data[0]);
$this->assertTrue($company->save());
@@ -142,6 +195,23 @@ public function testFindByStaticFinder() {
$company2->delete();
}
+ public function testFindByNativeParameters() {
+ $company1 = Companies::create($this->data[0]);
+ $company1->save();
+ $company2 = Companies::create($this->data[1]);
+ $company2->save();
+ $company3 = Companies::create($this->data[2]);
+ $company3->save();
+
+ // when using findAll and filtering by key, special key prepending happens internally
+ $companies = Companies::find('all', array('key' => $company3->id));
+ $this->assertEqual(1, count($companies));
+
+ $company1->delete();
+ $company2->delete();
+ $company3->delete();
+ }
+
public function testFindByView() {
$company1 = Companies::create($this->data[0]);
$company1->save();
@@ -157,9 +227,6 @@ public function testFindByView() {
// $companies = Companies::find('first', array('conditions' => array('view' => 'by_active')));
// $this->assertEqual(2, count($companies->data()));
- $companies = Companies::find('by_active', array('conditions' => array('key' => false)));
- $this->assertEqual(1, count($companies->data()));
-
$company1->delete();
$company2->delete();
}
@@ -170,8 +237,8 @@ public function testFindByViewAutocreate() {
$company2 = Companies::create($this->data[1]);
$company2->save();
- $company = Companies::findAllByName('Marine Store');
- $this->assertEqual(1, count($company->data()));
+ $companies = Companies::findAllByName('Marine Store');
+ $this->assertEqual(1, count($companies->data()));
$company = Companies::findByName('Marine Store');
$this->assertEqual('Marine Store', $company['name']);
Something went wrong with that request. Please try again.