OSDN Git Service

Collectionableプラグイン導入
authorCake <cake_67@users.sourceforge.jp>
Sun, 20 Feb 2011 03:55:10 +0000 (12:55 +0900)
committerCake <cake_67@users.sourceforge.jp>
Sun, 20 Feb 2011 04:01:58 +0000 (13:01 +0900)
17 files changed:
app/app_model.php
app/plugins/collectionable/README.md [new file with mode: 0644]
app/plugins/collectionable/locale/collectionable.pot [new file with mode: 0644]
app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.mo [new file with mode: 0644]
app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.po [new file with mode: 0644]
app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.mo [new file with mode: 0644]
app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.po [new file with mode: 0644]
app/plugins/collectionable/models/behaviors/config_validation.php [new file with mode: 0644]
app/plugins/collectionable/models/behaviors/multi_validation.php [new file with mode: 0644]
app/plugins/collectionable/models/behaviors/options.php [new file with mode: 0644]
app/plugins/collectionable/models/behaviors/virtual_fields.php [new file with mode: 0644]
app/plugins/collectionable/tests/cases/models/behaviors/config_validation.test.php [new file with mode: 0644]
app/plugins/collectionable/tests/cases/models/behaviors/multi_validation.test.php [new file with mode: 0644]
app/plugins/collectionable/tests/cases/models/behaviors/options.test.php [new file with mode: 0644]
app/plugins/collectionable/tests/cases/models/behaviors/virtual_fields.test.php [new file with mode: 0644]
app/plugins/collectionable/tests/fixtures/virtual_fields_post_fixture.php [new file with mode: 0644]
app/plugins/collectionable/tests/fixtures/virtual_fields_user_fixture.php [new file with mode: 0644]

index 4e9ff9e..c691b52 100644 (file)
@@ -14,6 +14,7 @@
 app::import('Sanitize');
 class AppModel extends Model {
        var $actsAs = array(
+               'Collectionable.options',
                'Cakeplus.ValidationErrorI18n',
                'Cakeplus.AddValidationRule',
                'SanitizePlus',
diff --git a/app/plugins/collectionable/README.md b/app/plugins/collectionable/README.md
new file mode 100644 (file)
index 0000000..2a4a911
--- /dev/null
@@ -0,0 +1,276 @@
+# Collectionable Plugin #
+
+## Introduction ##
+This is a utility plugin for CakePHP. This helps managing find options, virtualFields and validations.
+
+## Setup ##
+- Define $options(such a property name can be modified by configure) for Options Behavior
+- Define $virtualFieldsCollection(such a property name can be modified by configure) for VirtualFields Behavior
+- Define 'Validation'(such a config name can be modified by configure) section into Configure for ConfigValidationBehavior
+- Define $validate{PatternName}, like $validateAdd, same structure with $validate, for MultiValidationBehavior
+
+## Sample code ##
+
+### OptionsBehavior ###
+
+Here is a simple Post Model.
+       class Post extends AppModel {
+               var $hasMany = array('Comment');
+               var $hasOne = array('Status');
+
+               var $acsAs = array('Collectionable.options');
+               var $defaultOption = true; // or string like 'default'
+
+               var $options =array(
+                       'default' => array(
+                               'contain' => array(
+                                       'Comment',
+                                       'Status',
+                               ),
+                       'limit' => 10,
+                       ),
+                       'published' => array(
+                               'condtiions' => array('Status.published' => true),
+                       ),
+                       'recent' => array(
+                               'order' => ('Post.updated DESC'),
+                       ),
+                       'rss' => array(
+                               'limit' => 15,
+                       ),
+                       'unlimited' => array(
+                               'limit' => null,
+                       ),
+                       'index' => array(
+                               // You can do sub merging
+                               'options' => array(
+                                       'published',
+                                       'recent',
+                               ),
+                       ),
+               );
+       }
+
+You can use them by like:
+       class PostsController extends AppController {
+               function index() {
+                       $this->paginate = $this->Post->options('index');
+                       $this->set('posts', $this->paginate());
+               }
+
+               function rss() {
+                       $this->paginate = $this->Post->options('index', 'rss'); // multiple merging at run time;
+                       $this->set('posts', $this->paginate());
+               }
+
+               function all_in_one_page() {
+                       // you can use "options" attribute wihtin finding options
+                       $posts = $this->Post->find('all', array('options' => array('index', 'unlimited')));
+                       $this->set(compact('posts'));
+               }
+       }
+
+To see more syntax, you would look at [the test case](http://github.com/hiromi2424/Collectionable/blob/master/tests/cases/behaviors/options.test.php) or [the code](http://github.com/hiromi2424/Collectionable/blob/master/models/behaviors/options.php).
+
+### VirtualFieldsBehavior ###
+
+This sample uses [MatchableBehavior](http://github.com/hiromi2424/MatchableBehavior).
+
+       class User extends AppModel {
+               var $hasMany = array('Post');
+               var $actsAs = array('Collectionable.VirtualFields', 'Matchable');
+
+               var $virtualFields = array(
+                       'full_name' => "CONCAT(User.first_name, ' ', User.last_name)",
+               );
+               var $virtualFieldsCollection = array(
+                       'posts_count' => 'COUNT(Post.id)',
+                       'amount_used' => 'SUM(Post.volume)',
+               );
+       }
+
+You can use them by like:
+
+
+       class UsersController extends AppController {
+               function admin_index() {
+                       // Hey, you may feel like using OptionsBehavior :P
+                       $jointo = array('Post');
+                       $group = 'User.id';
+                       $virtualFields = array('posts_count', 'amount_used'); // key of collections
+                       $this->paginate = compact('jointo', 'group', 'virtualFields');
+                       $this->set('users', $this->paginate());
+               }
+
+               function profile() {
+                       $virtualFields = array('full_name' => false); // disable virtualFields
+                       $user = $this->User->find('first', compact('virtualFields'));
+                       $this->set(compact('user'));
+               }
+       }
+
+### ConfigValidationBehavior ###
+
+
+       class User extends AppModel {
+               var $actsAs = array('Collectionable.ConfigValidation');
+
+               var $validate = array(
+                       'nickname' => array(
+                               'required' => array(
+                                       'rule' => array('notempty'),
+                               ),
+                               'min' => array(
+                                       'rule' => array('minlength'),
+                                       'message' => 'I said more than %s!!',
+                               ),
+                       ),
+                       'email' => array(
+                               'required' => array(
+                                       'rule' => array('notempty'),
+                               ),
+                               'among' => array(
+                                       'rule' => array('between'),
+                               ),
+                       ),
+               );
+       }
+
+You can set validation parameters, messages from Configuration:
+
+
+       Configure::write('Validation', array(
+               'parameters' => array(
+                       'User' => array(
+                               'nickname' => array(
+                                       'min' => 3,
+                               ),
+                               'email' => array(
+                                       'among' => array(16, 256)
+                               ),
+                       ),
+               ),
+               'messages' => array(
+                       'default' => array(
+                               'required' => 'you need to enter.',
+                               'min' => '%s characters needed',
+                       ),
+                       'User' => array(
+                               'email' => array(
+                                       'required' => 'are you kidding me or misreading?',
+                               ),
+                       ),
+               ),
+       ));
+
+
+Note that priority is "hard coded on your model" > "specifying Model and field" > "default".
+But if you turn $overwrite property on, "specifying Model and field" forces to overwrite("default" does not).
+
+
+### MultiValidationBehavior ###
+
+
+       class User extends AppModel {
+
+               var $actsAs = array('Collectionable.MultiValidation');
+
+               var $validate = array(
+                       'password_raw' => array(
+                               'required' => array(
+                                       'rule' => array('notempty'),
+                               ),
+                               'minlength' => array(
+                                       'rule' => array('minlength', 6),
+                               ),
+                       ),
+               );
+
+               // note that $validateprofile is invalid with 'profile'
+               var $validateProfile = array(
+                       'nickname' => array(
+                               'required' => array(
+                                       'rule' => array('notempty'),
+                               ),
+                               'maxlength' => array(
+                                       'rule' => array('maxlength', 40),
+                               ),
+                       ),
+               );
+
+               var $validateRequireEmail = array(
+                       'email' => array(
+                               'required' => array(
+                                       'rule' => array('notempty'),
+                               ),
+                               'email' => array(
+                                       'rule' => array('email'),
+                               ),
+                       ),
+               );
+
+               var $validatePasswordConfirm = array(
+                       'password_confirm' => array(
+                               'required' => array(
+                                       'rule' => array('notempty'),
+                               ),
+                               'confirm_password' => array(
+                                       'rule' => array('confirm_password'),
+                               ),
+                       ),
+               );
+
+               // You can set validation pattern on demand:
+               function add($data, $validate = true, $options = array()) {
+                       $this->useValidationSet('requireEmail');
+                       $this->create();
+                       return $this->save($data, $validate, $options);
+               }
+
+               // You can dsiable default $validate with second argument as false:
+               function edit($data, $validate = true, $options = array()) {
+                       $this->useValidationSet('profile', false);
+                       return $this->save($data, $validate, $options);
+               }
+
+               // You can specify two and more rule sets. these will be merged
+               function resetEmail($data) {
+                       $this->useValidationSet(array('requireEmail', 'passwordConfirm'));
+               }
+
+               function confirm_password() {
+                       // confirm password
+               }
+
+       }
+
+
+## Thanks ##
+- [nojimage](http://github.com/nojimage) created [base of this plugin](http://github.com/nojimage/paging)
+
+
+## License
+
+Licensed under The MIT License.
+Redistributions of files must retain the above copyright notice.
+
+
+Copyright 2010 hiromi, https://github.com/hiromi2424
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/app/plugins/collectionable/locale/collectionable.pot b/app/plugins/collectionable/locale/collectionable.pot
new file mode 100644 (file)
index 0000000..889acca
--- /dev/null
@@ -0,0 +1,29 @@
+# LANGUAGE translation of CakePHP Application
+# Copyright hiromi <tanaka2424@gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Collectionable Plugin for CakePHP v1.0\n"
+"POT-Creation-Date: 2011-02-10 06:00+0900\n"
+"PO-Revision-Date: 2011-02-10 06:11+0900\n"
+"Last-Translator: hiromi <tanaka2424@gmail.com>\n"
+"Language-Team: hiromi <tanaka2424@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: models\behaviors\config_validation.php:118
+#: tests\cases\behaviors\config_validation.test.php:358
+msgid "getValidationParameter() requires 2 arguments as $field and $rule"
+msgstr ""
+
+#: models\behaviors\config_validation.php:143
+#: tests\cases\behaviors\config_validation.test.php:376
+msgid "getValidationMessage() requires a argument as $rule"
+msgstr ""
+
+#: models\behaviors\multi_validation.php:97
+msgid "Unexpected property name: Model::$%s was not found."
+msgstr ""
+
diff --git a/app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.mo b/app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.mo
new file mode 100644 (file)
index 0000000..c821a1d
Binary files /dev/null and b/app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.mo differ
diff --git a/app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.po b/app/plugins/collectionable/locale/eng/LC_MESSAGES/collectionable.po
new file mode 100644 (file)
index 0000000..652e04d
--- /dev/null
@@ -0,0 +1,29 @@
+# LANGUAGE translation of CakePHP Application
+# Copyright hiromi <tanaka2424@gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Collectionable Plugin for CakePHP v1.0\n"
+"POT-Creation-Date: 2011-02-10 06:00+0900\n"
+"PO-Revision-Date: 2011-02-10 06:11+0900\n"
+"Last-Translator: hiromi <tanaka2424@gmail.com>\n"
+"Language-Team: hiromi <tanaka2424@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: models\behaviors\config_validation.php:118
+#: tests\cases\behaviors\config_validation.test.php:358
+msgid "getValidationParameter() requires 2 arguments as $field and $rule"
+msgstr "getValidationParameter() requires 2 arguments as $field and $rule"
+
+#: models\behaviors\config_validation.php:143
+#: tests\cases\behaviors\config_validation.test.php:376
+msgid "getValidationMessage() requires a argument as $rule"
+msgstr "getValidationMessage() requires a argument as $rule"
+
+#: models\behaviors\multi_validation.php:97
+msgid "Unexpected property name: Model::$%s was not found."
+msgstr "Unexpected property name: Model::$%s was not found."
+
diff --git a/app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.mo b/app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.mo
new file mode 100644 (file)
index 0000000..5b00124
Binary files /dev/null and b/app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.mo differ
diff --git a/app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.po b/app/plugins/collectionable/locale/jpn/LC_MESSAGES/collectionable.po
new file mode 100644 (file)
index 0000000..dd9d005
--- /dev/null
@@ -0,0 +1,29 @@
+# LANGUAGE translation of CakePHP Application
+# Copyright hiromi <tanaka2424@gmail.com>
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Collectionable Plugin for CakePHP v1.0\n"
+"POT-Creation-Date: 2011-02-10 06:00+0900\n"
+"PO-Revision-Date: 2011-02-10 06:11+0900\n"
+"Last-Translator: hiromi <tanaka2424@gmail.com>\n"
+"Language-Team: hiromi <tanaka2424@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: models\behaviors\config_validation.php:118
+#: tests\cases\behaviors\config_validation.test.php:358
+msgid "getValidationParameter() requires 2 arguments as $field and $rule"
+msgstr "getValidationParameter() は$fieldと$ruleの二つの引数が必要です"
+
+#: models\behaviors\config_validation.php:143
+#: tests\cases\behaviors\config_validation.test.php:376
+msgid "getValidationMessage() requires a argument as $rule"
+msgstr "getValidationParameter() は$rule引数が必要です"
+
+#: models\behaviors\multi_validation.php:97
+msgid "Unexpected property name: Model::$%s was not found."
+msgstr "予期しないプロパティ名:Model::$%sが見つかりませんでした。"
+
diff --git a/app/plugins/collectionable/models/behaviors/config_validation.php b/app/plugins/collectionable/models/behaviors/config_validation.php
new file mode 100644 (file)
index 0000000..ed640f4
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+
+class ConfigValidationBehavior extends ModelBehavior {
+       var $configName = 'Validation';
+       var $overwrite = false; // true or $parameterName or $messageName
+       var $convertFormat = true;
+
+       var $parametersName = 'parameters';
+       var $messagesName = 'messages';
+       function beforeValidate(&$model) {
+               if (!$model->validate || !is_array($model->validate)) {
+                       return true;
+               }
+
+               $this->_setParameters($model);
+               $this->_setMessages($model);
+               $this->_convertFormat($model);
+               return true;
+       }
+
+       function _setParameters(&$model) {
+               $overwrite = $this->overwrite === true || $this->overwrite == $this->parametersName;
+               foreach ($model->validate as $field => $elements) {
+                       foreach ($elements as $name => $element) {
+                               $parameters = $this->_config($this->parametersName, $model->name, $field, $name);
+                               if ($parameters === null || !isset($element['rule'])) {
+                                       continue;
+                               }
+
+                               if (count($element['rule']) > 1 && !$overwrite) {
+                                       continue;
+                               }
+
+                               $model->validate[$field][$name]['rule'] = array_merge(array(current($element['rule'])), (array)$parameters);
+                       }
+               }
+       }
+
+       function _setMessages(&$model) {
+               $overwrite = $this->overwrite === true || $this->overwrite == $this->messagesName;
+               foreach ($model->validate as $field => $elements) {
+                       foreach ($elements as $name => $element) {
+                               $default = $this->_config($this->messagesName, 'default', $name);
+                               $desire = $this->_config($this->messagesName, $model->name, $field, $name);
+                               if ($default === null && $desire === null) {
+                                       continue;
+                               }
+
+                               if (isset($element['message']) && !$overwrite) {
+                                       continue;
+                               }
+                               if ($desire !== null) {
+                                       $model->validate[$field][$name]['message'] = $desire;
+                                       continue;
+                               }
+                               if (!isset($element['message'])) {
+                                       $model->validate[$field][$name]['message'] = $default;
+                               }
+                       }
+               }
+       }
+
+       function _convertFormat(&$model) {
+               if (!$this->convertFormat) {
+                       return;
+               }
+               foreach ($model->validate as $field => $elements) {
+                       foreach ($elements as $name => $element) {
+                               if (!isset($element['message']) || !isset($element['rule']) || !is_array($element['rule'])) {
+                                       continue;
+                               }
+                               if (count($element['rule']) > 1) {
+                                       array_shift($element['rule']);
+                                       array_unshift($element['rule'], $element['message']);
+                                       $model->validate[$field][$name]['message'] = call_user_func_array('sprintf', $element['rule']);
+                               }
+                       }
+               }
+       }
+
+       function _config() {
+               $args = func_get_args();
+               array_unshift($args, $this->configName);
+               return Configure::read(implode('.', $args));
+       }
+
+       function getValidationParameter(&$model, $field, $rule) {
+               if (is_array($field) || is_array($rule) || $field === null || $rule === null) {
+                       trigger_error(__d('collectionable', 'getValidationParameter() requires 2 arguments as $field and $rule', true));
+                       return null;
+               }
+
+               $this->beforeValidate($model);
+               if (empty($model->validate[$field][$rule]['rule'])) {
+                       return null;
+               }
+               $rule = $model->validate[$field][$rule]['rule'];
+               array_shift($rule);
+               if (empty($rule)) {
+                       return null;
+               }
+               $parameters = count($rule) === 1 ? current($rule) : $rule;
+               return $parameters;
+       }
+
+       function getValidationMessage(&$model, $rule, $field = null) {
+               if (is_array($rule) || $rule === null) {
+                       trigger_error(__d('collectionable', 'getValidationMessage() requires a argument as $rule', true));
+                       return null;
+               }
+               if (is_array($field)) {
+                       $field = null;
+               }
+               if ($field !== null) {
+                       $swap = $rule;
+                       $rule = $field;
+                       $field = $swap;
+               }
+               if ($field === null) {
+                       $default = $this->_config($this->messagesName, 'default', $rule);
+                       return $default;
+               }
+
+               $this->beforeValidate($model);
+               if (empty($model->validate[$field][$rule]['message'])) {
+                       return null;
+               }
+               return $model->validate[$field][$rule]['message'];
+       }
+}
\ No newline at end of file
diff --git a/app/plugins/collectionable/models/behaviors/multi_validation.php b/app/plugins/collectionable/models/behaviors/multi_validation.php
new file mode 100644 (file)
index 0000000..fa48cfe
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+class MultiValidationBehavior extends ModelBehavior {
+
+       var $settings = array();
+       var $defaultSettings = array(
+               'restore' => true,
+       );
+
+       var $_backupValidate = array();
+
+       function setup($Model, $settings = array()) {
+
+               $this->settings[$Model->alias] = array_merge($this->defaultSettings, (array)$settings);
+               return true;
+
+       }
+
+       function restoreValidate($Model) {
+
+               if (isset($this->_backupValidate[$Model->alias])) {
+                       $Model->validate = $this->_backupValidate[$Model->alias];
+                       unset($this->_backupValidate[$Model->alias]);
+               }
+
+       }
+
+       function afterSave($Model, $created = true, $options = array()) {
+
+               if ($this->settings[$Model->alias]['restore']) {
+                       $this->restoreValidate($Model);
+               }
+
+               return true;
+
+       }
+
+       function useValidationSet($Model, $method, $useBase = true) {
+
+               $validates = array_map('ucfirst', (array)$method);
+
+               $result = array();
+               foreach ($validates as $validate) {
+
+                       $property = 'validate' . $validate;
+                       if (!isset($Model->{$property})) {
+                               trigger_error(sprintf(__d('collectionable', 'Unexpected property name: Model::$%s was not found.', true), $property));
+                               return false;
+                       }
+                       $result = $this->mergeValidationSet($Model, $result, $Model->{$property});
+
+               }
+
+               if ($useBase) {
+                       $result = $this->mergeValidationSet($Model, $Model->validate, $result);
+               }
+
+               $this->_backupValidate[$Model->alias] = $Model->validate;
+               $Model->validate = $result;
+
+               return true;
+
+       }
+
+       function mergeValidationSet($Model) {
+
+               $validationSets = func_get_args();
+               /* $Model = */ array_shift($validationSets);
+
+               $result = array();
+               foreach ($validationSets as $validationSet) {
+                       foreach ($validationSet as $field => $ruleSet) {
+                               foreach ($ruleSet as $name => $rules) {
+                                       if (isset($result[$field][$name])) {
+                                               $result[$field][$name] = array_merge($result[$field][$name], $rules);
+                                       } else {
+                                               $result[$field][$name] = $rules;
+                                       }
+                               }
+                       }
+               }
+
+               return $result;
+
+       }
+
+}
diff --git a/app/plugins/collectionable/models/behaviors/options.php b/app/plugins/collectionable/models/behaviors/options.php
new file mode 100644 (file)
index 0000000..89557b2
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+class OptionsBehavior extends ModelBehavior {
+       
+       var $settings = array();
+       var $defaultSettings = array(
+               'setupProperty' => true,
+               'defaultOption' => false,
+               'optionName' => 'options',
+       );
+
+       var $defaultQuery = array(
+               'conditions' => null, 'fields' => null, 'joins' => array(), 'limit' => null,
+               'offset' => null, 'order' => null, 'page' => null, 'group' => null, 'callbacks' => true
+       );
+
+       function setup(&$Model, $settings = array()) {
+               $this->settings = array_merge($this->defaultSettings, (array)$settings);
+               $optionName = $this->settings['optionName'];
+               if ($this->settings['setupProperty']) {
+                       if (empty($Model->{$optionName})) {
+                               $Model->{$optionName} = array();
+                       }
+                       if (empty($Model->defaultOption)) {
+                               $Model->defaultOption = $this->settings['defaultOption'];
+                       }
+               }
+               return true;
+       }
+
+       function beforeFind(&$Model, $query = array()) {
+               if (isset($query['options'])) {
+                       $options = $query['options'];
+                       unset($query['options']);
+
+                       $query = Set::merge($this->defaultQuery, $this->options($Model, $options), Set::filter($query));
+               }
+               return $query;
+       }
+
+       function options(&$Model, $type = null){
+               $args = func_get_args();
+               if (func_num_args() > 2) {
+                       array_shift($args);
+                       $type = $args;
+               }
+
+               $option = array();
+               if (is_array($type)) {
+                       foreach ($type as $t) {
+                               $option = Set::merge($option, $this->options($Model, $t));
+                       }
+               } else {
+                       $optionName = $this->settings['optionName'];
+                       $option = isset($Model->{$optionName}[$type]) ? $Model->{$optionName}[$type] : array();
+                       $default = array();
+                       if ($Model->defaultOption) {
+                               $default = $this->_getDefault($Model->defaultOption, $Model->{$optionName});
+                       }
+                       $options = array();
+                       if (isset($option[$optionName]) && !empty($option[$optionName])) {
+                               $options = $this->_intelligentlyMerge(array(), $option[$optionName], $Model->{$optionName});
+                               unset($option['options']);
+                       }
+                       $option = Set::merge($default, $options, $option);
+               }
+               return $option;
+       }
+
+       function _getDefault($defaultOption, $options) {
+               $default = array();
+               if ($defaultOption === true && !empty($options['default'])) {
+                       $default = $options['default'];
+               } elseif (is_array($defaultOption)) {
+                       $default = $this->_intelligentlyMerge($default, $defaultOption, $options);
+               } elseif (!empty($options[$defaultOption])) {
+                       $default = $this->_intelligentlyMerge($default, $options[$defaultOption], $options);
+               }
+               return $default;
+       }
+
+       function _intelligentlyMerge($data, $merges, $options) {
+               $merges = (array)$merges;
+               if (Set::numeric(array_keys($merges))) {
+                       foreach($merges as $merge) {
+                               if (!empty($options[$merge])) {
+                                       $data = $this->_intelligentlyMerge($data, $options[$merge], $options);
+                               }
+                       }
+               } else {
+                       $optionName = $this->settings['optionName'];
+                       if (array_key_exists($optionName, $merges)) {
+                               $data = $this->_intelligentlyMerge($data, $merges[$optionName], $options);
+                               unset($merges[$optionName]);
+                       }
+                       $data = Set::merge($data, $merges);
+               }
+               return $data;
+       }
+}
\ No newline at end of file
diff --git a/app/plugins/collectionable/models/behaviors/virtual_fields.php b/app/plugins/collectionable/models/behaviors/virtual_fields.php
new file mode 100644 (file)
index 0000000..d121d3e
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+class VirtualFieldsBehavior extends ModelBehavior {
+       var $settins = array();
+       var $defaultSettings = array(
+               'setupProperty' => true,
+               'collectionName' => 'virtualFieldsCollection',
+       );
+       var $__virtualFieldsBackup = array();
+
+       function setup(&$Model, $settings = array()) {
+               $this->settings = Set::merge($this->defaultSettings, $settings);
+               extract($this->settings);
+               if (!isset($Model->{$collectionName}) && $this->settings['setupProperty']) {
+                       $Model->{$collectionName} = array();
+               }
+               return true;
+       }
+
+       function beforeFind(&$Model, $query = array()){
+               extract($this->settings);
+               if (!isset($query['virtualFields'])) {
+                       return true;
+               }
+               $virtualFields = Set::normalize($query['virtualFields']);
+               unset($query['virtualFields']);
+
+               $blackList = array();
+               foreach ($virtualFields as $key => $sql) {
+                       if (($sql !== false && empty($sql)) || $sql === true) {
+                               if (isset($Model->{$collectionName}[$key])) {
+                                       $virtualFields[$key] = $Model->{$collectionName}[$key];
+                               } else {
+                                       unset($virtualFields[$key]);
+                               }
+                       } else {
+                               $blackList[] = $key;
+                               unset($virtualFields[$key]);
+                       }
+               }
+
+               if (!empty($virtualFields) || !empty($blackList)){
+                       $this->__virtualFieldsBackup[$Model->alias] = $Model->virtualFields;
+                       $Model->virtualFields = array_merge($Model->virtualFields, $virtualFields);
+                       if (!empty($blackList)) {
+                               foreach ($blackList as $key) {
+                                       if (isset($Model->virtualFields[$key])) {
+                                               unset($Model->virtualFields[$key]);
+                                       }
+                               }
+                       }
+               }
+               return $query;
+       }
+
+       function afterFind(&$Model, $results = array(), $primary = false) {
+               if (isset($this->__virtualFieldsBackup[$Model->alias])) {
+                       $Model->virtualFields = $this->__virtualFieldsBackup[$Model->alias];
+                       unset($this->__virtualFieldsBackup[$Model->alias]);
+               }
+               return true;
+       }
+}
\ No newline at end of file
diff --git a/app/plugins/collectionable/tests/cases/models/behaviors/config_validation.test.php b/app/plugins/collectionable/tests/cases/models/behaviors/config_validation.test.php
new file mode 100644 (file)
index 0000000..e202078
--- /dev/null
@@ -0,0 +1,344 @@
+<?php\r
+App::import('Behavior', 'Collectionable.ConfigValidation');\r
+\r
+class ConfigValidaitonMockModel extends Model {\r
+       var $useTable = false;\r
+       var $validate = array(\r
+               'nickname' => array(\r
+                       'required' => array(\r
+                               'rule' => array('notempty'),\r
+                       ),\r
+                       'min' => array(\r
+                               'rule' => array('minlength'),\r
+                               'message' => '%s文字ください',\r
+                       ),\r
+                       'max' => array(\r
+                               'rule' => array('maxlength', 32),\r
+                               'message' => '32文字',\r
+                       ),\r
+                       'hoge' => array(\r
+                               'rule' => array('userdefined', 2000),\r
+                       ),\r
+                       'piyo' => array(\r
+                               'rule' => array('userdefined'),\r
+                       ),\r
+                       'fuga' => array(\r
+                               'rule' => array('userdefined'),\r
+                               'message' => 'ふがー',\r
+                       ),\r
+                       'moge' => array(\r
+                               'rule' => array('userdefined', 5000, 'hoge'),\r
+                               'message' => '%s %s',\r
+                       ),\r
+                       'multi' => array(\r
+                               'rule' => array('userdefined'),\r
+                       ),\r
+               ),\r
+       );\r
+}\r
+\r
+class ConfigValidationBehaviorTestCase extends CakeTestCase {\r
+       var $data = array(\r
+               'ConfigValidaitonMockModel' => array(\r
+                       'nickname' => '0123456789012',\r
+               ),\r
+       );\r
+\r
+       function start() {\r
+               Configure::write('TestValidation', array(\r
+                       'parameters' => array(\r
+                               'ConfigValidaitonMockModel' => array(\r
+                                       'nickname' => array(\r
+                                               'min' => 10,\r
+                                               'max' => 100,\r
+                                               'multi' => array(-100, 900),\r
+                                       ),\r
+                               ),\r
+                       ),\r
+                       'messages' => array(\r
+                               'default' => array(\r
+                                       'required' => '必須項目です',\r
+                                       'max' => '%s文字以内で入力してください。',\r
+                                       'hoge' => '%sです',\r
+                                       'fuga' => 'でふぉると',\r
+                               ),\r
+                               'ConfigValidaitonMockModel' => array(\r
+                                       'nickname' => array(\r
+                                               'required' => '必ず入力してください。',\r
+                                               'piyo' => 'うりりりー',\r
+                                               'fuga' => 'してい',\r
+                                       ),\r
+                               ),\r
+                       ),\r
+               ));\r
+               parent::start();\r
+       }\r
+\r
+       function startTest() {\r
+               $this->_attach();\r
+       }\r
+\r
+       function endTest() {\r
+               $this->_clear();\r
+       }\r
+\r
+       function _attach() {\r
+               $this->Behavior = new ConfigValidationBehavior;\r
+               $this->Behavior->configName = 'TestValidation';\r
+               $this->Behavior->convertFormat = false;\r
+               $this->Model =& ClassRegistry::init('ConfigValidaitonMockModel');\r
+       }\r
+\r
+       function _clear() {\r
+               unset($this->Behavior);\r
+               unset($this->Model);\r
+               ClassRegistry::flush();\r
+       }\r
+\r
+       function _reattach() {\r
+               $this->_clear();\r
+               $this->_attach();\r
+       }\r
+\r
+       function testAll() {\r
+               $this->Behavior->convertFormat = true;\r
+               $this->Behavior->beforeValidate($this->Model);\r
+\r
+               $result = $this->Model->validate;\r
+               $expects = array(\r
+                       'nickname' => array(\r
+                               'required' => array(\r
+                                       'rule' => array('notempty'),\r
+                                       'message' => '必ず入力してください。',\r
+                               ),\r
+                               'min' => array(\r
+                                       'rule' => array('minlength', 10),\r
+                                       'message' => '10文字ください',\r
+                               ),\r
+                               'max' => array(\r
+                                       'rule' => array('maxlength', 32),\r
+                                       'message' => '32文字',\r
+                               ),\r
+                               'hoge' => array(\r
+                                       'rule' => array('userdefined', 2000),\r
+                                       'message' => '2000です',\r
+                               ),\r
+                               'piyo' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'うりりりー',\r
+                               ),\r
+                               'fuga' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'ふがー',\r
+                               ),\r
+                               'moge' => array(\r
+                                       'rule' => array('userdefined', 5000, 'hoge'),\r
+                                       'message' => '5000 hoge',\r
+                               ),\r
+                               'multi' => array(\r
+                                       'rule' => array('userdefined', -100, 900),\r
+                               ),\r
+                       ),\r
+               );\r
+\r
+               $this->_reattach();\r
+               $this->Behavior->convertFormat = true;\r
+               $this->Behavior->overwrite = 'parameters';\r
+               $this->Behavior->beforeValidate($this->Model);\r
+\r
+               $result = $this->Model->validate;\r
+               $expects = array(\r
+                       'nickname' => array(\r
+                               'required' => array(\r
+                                       'rule' => array('notempty'),\r
+                                       'message' => '必ず入力してください。',\r
+                               ),\r
+                               'min' => array(\r
+                                       'rule' => array('minlength', 10),\r
+                                       'message' => '10文字ください',\r
+                               ),\r
+                               'max' => array(\r
+                                       'rule' => array('maxlength', 100),\r
+                                       'message' => '32文字',\r
+                               ),\r
+                               'hoge' => array(\r
+                                       'rule' => array('userdefined', 2000),\r
+                                       'message' => '2000です',\r
+                               ),\r
+                               'piyo' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'うりりりー',\r
+                               ),\r
+                               'fuga' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'ふがー',\r
+                               ),\r
+                               'moge' => array(\r
+                                       'rule' => array('userdefined', 5000, 'hoge'),\r
+                                       'message' => '5000 hoge',\r
+                               ),\r
+                               'multi' => array(\r
+                                       'rule' => array('userdefined', -100, 900),\r
+                               ),\r
+                       ),\r
+               );\r
+\r
+               $this->_reattach();\r
+               $this->Behavior->convertFormat = true;\r
+               $this->Behavior->overwrite = 'messages';\r
+               $this->Behavior->beforeValidate($this->Model);\r
+\r
+               $result = $this->Model->validate;\r
+               $expected = array(\r
+                       'nickname' => array(\r
+                               'required' => array(\r
+                                       'rule' => array('notempty'),\r
+                                       'message' => '必ず入力してください。',\r
+                               ),\r
+                               'min' => array(\r
+                                       'rule' => array('minlength', 10),\r
+                                       'message' => '10文字ください',\r
+                               ),\r
+                               'max' => array(\r
+                                       'rule' => array('maxlength', 32),\r
+                                       'message' => '32文字',\r
+                               ),\r
+                               'hoge' => array(\r
+                                       'rule' => array('userdefined', 2000),\r
+                                       'message' => '2000です',\r
+                               ),\r
+                               'piyo' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'うりりりー',\r
+                               ),\r
+                               'fuga' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'してい',\r
+                               ),\r
+                               'moge' => array(\r
+                                       'rule' => array('userdefined', 5000, 'hoge'),\r
+                                       'message' => '5000 hoge',\r
+                               ),\r
+                               'multi' => array(\r
+                                       'rule' => array('userdefined', -100, 900),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertEqual($result, $expected);\r
+\r
+               $this->_reattach();\r
+               $this->Behavior->convertFormat = true;\r
+               $this->Behavior->overwrite = true;\r
+               $this->Behavior->beforeValidate($this->Model);\r
+\r
+               $result = $this->Model->validate;\r
+               $expected = array(\r
+                       'nickname' => array(\r
+                               'required' => array(\r
+                                       'rule' => array('notempty'),\r
+                                       'message' => '必ず入力してください。',\r
+                               ),\r
+                               'min' => array(\r
+                                       'rule' => array('minlength', 10),\r
+                                       'message' => '10文字ください',\r
+                               ),\r
+                               'max' => array(\r
+                                       'rule' => array('maxlength', 100),\r
+                                       'message' => '32文字',\r
+                               ),\r
+                               'hoge' => array(\r
+                                       'rule' => array('userdefined', 2000),\r
+                                       'message' => '2000です',\r
+                               ),\r
+                               'piyo' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'うりりりー',\r
+                               ),\r
+                               'fuga' => array(\r
+                                       'rule' => array('userdefined'),\r
+                                       'message' => 'してい',\r
+                               ),\r
+                               'moge' => array(\r
+                                       'rule' => array('userdefined', 5000, 'hoge'),\r
+                                       'message' => '5000 hoge',\r
+                               ),\r
+                               'multi' => array(\r
+                                       'rule' => array('userdefined', -100, 900),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertEqual($result, $expected);\r
+\r
+               $this->_reattach();\r
+               $this->Model->validate = true;\r
+               $this->assertTrue($this->Behavior->beforeValidate($this->Model));\r
+               $this->assertTrue($this->Model->validate);\r
+\r
+               $this->_reattach();\r
+               $this->Model->validate = array();\r
+               $this->assertTrue($this->Behavior->beforeValidate($this->Model));\r
+               $this->assertEqual($this->Model->validate, array());\r
+\r
+               $this->_reattach();\r
+               $this->Model->validate = array('hoge' => array('monyomonyo' => array('hoge' => '%s')));\r
+               $this->assertTrue($this->Behavior->beforeValidate($this->Model));\r
+               $this->assertEqual($this->Model->validate, array('hoge' => array('monyomonyo' => array('hoge' => '%s'))));\r
+       }\r
+\r
+       function testSetParameters() {\r
+               $this->Behavior->_setParameters($this->Model);\r
+\r
+               $this->assertEqual($this->Model->validate['nickname']['min']['rule'], array('minlength', 10));\r
+               $this->assertEqual($this->Model->validate['nickname']['max']['rule'], array('maxlength', 32));\r
+               $this->assertEqual($this->Model->validate['nickname']['multi']['rule'], array('userdefined', -100, 900));\r
+\r
+               $this->_reattach();\r
+               $this->Behavior->overwrite = true;\r
+               $this->Behavior->_setParameters($this->Model);\r
+\r
+               $this->assertEqual($this->Model->validate['nickname']['min']['rule'], array('minlength', 10));\r
+               $this->assertEqual($this->Model->validate['nickname']['max']['rule'], array('maxlength', 100));\r
+               $this->assertEqual($this->Model->validate['nickname']['multi']['rule'], array('userdefined', -100, 900));\r
+       }\r
+\r
+       function testSetMessages() {\r
+               $this->Behavior->_setMessages($this->Model);\r
+               $this->assertEqual($this->Model->validate['nickname']['required']['message'], '必ず入力してください。');\r
+               $this->assertEqual($this->Model->validate['nickname']['max']['message'], '32文字');\r
+               $this->assertEqual($this->Model->validate['nickname']['hoge']['message'], '%sです');\r
+               $this->assertEqual($this->Model->validate['nickname']['piyo']['message'], 'うりりりー');\r
+               $this->assertEqual($this->Model->validate['nickname']['fuga']['message'], 'ふがー');\r
+\r
+               $this->_reattach();\r
+               $this->Behavior->overwrite = true;\r
+               $this->Behavior->_setMessages($this->Model);\r
+\r
+               $this->assertEqual($this->Model->validate['nickname']['fuga']['message'], 'してい');\r
+       }\r
+\r
+       function testConvertFormat() {\r
+               $this->Behavior->convertFormat = true;\r
+               $this->Behavior->_convertFormat($this->Model);\r
+\r
+               $this->assertEqual($this->Model->validate['nickname']['moge']['message'], '5000 hoge');\r
+       }\r
+\r
+       function testGetValidationParameter() {\r
+               $this->expectError();\r
+               $this->assertNull($this->Behavior->getValidationParameter($this->Model, 'nickname', null));\r
+               $this->assertNull($this->Behavior->getValidationParameter($this->Model, 'nickname', 'undefined'));\r
+               $this->assertEqual($this->Behavior->getValidationParameter($this->Model, 'nickname', 'max'), 32);\r
+               $this->assertEqual($this->Behavior->getValidationParameter($this->Model, 'nickname', 'multi'), array(-100, 900));\r
+       }\r
+\r
+       function testGetConfigMessage() {\r
+               $this->Behavior->convertFormat = true;\r
+\r
+               $this->expectError();\r
+               $this->assertNull($this->Behavior->getValidationMessage($this->Model, null));\r
+               $this->assertNull($this->Behavior->getValidationMessage($this->Model, 'not defined'));\r
+               $this->assertEqual($this->Behavior->getValidationMessage($this->Model, 'fuga'), 'でふぉると');\r
+               $this->assertEqual($this->Behavior->getValidationMessage($this->Model, 'max'), '%s文字以内で入力してください。');\r
+               $this->assertEqual($this->Behavior->getValidationMessage($this->Model, 'nickname', 'min'), '10文字ください');\r
+       }\r
+}\r
diff --git a/app/plugins/collectionable/tests/cases/models/behaviors/multi_validation.test.php b/app/plugins/collectionable/tests/cases/models/behaviors/multi_validation.test.php
new file mode 100644 (file)
index 0000000..def8ae6
--- /dev/null
@@ -0,0 +1,175 @@
+<?php\r
+\r
+class MultiValidaitonMockModel extends CakeTestModel {\r
+\r
+       var $useTable = false;\r
+       var $validate = array(\r
+               'email' => array(\r
+                       'email' => array(\r
+                               'rule' => array('email'),\r
+                       ),\r
+               ),\r
+               'title' => array(\r
+                       'notempty' => array(\r
+                               'rule' => array('notempty'),\r
+                       ),\r
+               ),\r
+       );\r
+\r
+       var $validateBestAnswer = array(\r
+               'best_answer_id' => array(\r
+                       'notempty' => array(\r
+                               'rule' => array('notempty'),\r
+                       ),\r
+               ),\r
+       );\r
+\r
+       var $validateEdit = array(\r
+               'email' => array(\r
+                       'email' => array(\r
+                               'rule' => array('email', true),\r
+                       ),\r
+               ),\r
+       );\r
+\r
+}\r
+\r
+class MultiValidationBehaviorTestCase extends CakeTestCase {\r
+\r
+       var $data = array(\r
+               'MultiValidaitonMockModel' => array(\r
+                       'nickname' => '0123456789012',\r
+               ),\r
+       );\r
+\r
+       function startTest() {\r
+               $this->_attach();\r
+       }\r
+\r
+       function endTest() {\r
+               $this->_clear();\r
+       }\r
+\r
+       function _attach($settings = array()) {\r
+\r
+               $this->Model = ClassRegistry::init('MultiValidaitonMockModel');\r
+               $this->Model->Behaviors->detach('Collectionable.MultiValidation');\r
+               $this->Model->Behaviors->attach('Collectionable.MultiValidation', $settings);\r
+\r
+       }\r
+\r
+       function _clear() {\r
+\r
+               unset($this->Model);\r
+               ClassRegistry::flush();\r
+\r
+       }\r
+\r
+       function _reattach($settings = array()) {\r
+\r
+               $this->_clear();\r
+               $this->_attach($settings);\r
+\r
+       }\r
+\r
+       function testUseValidationSet() {\r
+\r
+               $this->Model->useValidationSet('BestAnswer');\r
+               $result = $this->Model->validate;\r
+               $expected = array(\r
+                       'email' => array(\r
+                               'email' => array(\r
+                                       'rule' => array('email'),\r
+                               ),\r
+                       ),\r
+                       'title' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+                       'best_answer_id' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertIdentical($result, $expected);\r
+\r
+               $this->Model->restoreValidate();\r
+               $this->Model->useValidationSet('bestAnswer');\r
+               $result = $this->Model->validate;\r
+               $this->assertIdentical($result, $expected);\r
+\r
+               $this->Model->restoreValidate();\r
+               $this->Model->useValidationSet('BestAnswer', false);\r
+               $result = $this->Model->validate;\r
+               $expected = array(\r
+                       'best_answer_id' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertIdentical($result, $expected);\r
+\r
+               $this->Model->restoreValidate();\r
+               $this->Model->useValidationSet(array('bestAnswer', 'edit'));\r
+               $result = $this->Model->validate;\r
+               $expected = array(\r
+                       'email' => array(\r
+                               'email' => array(\r
+                                       'rule' => array('email', true),\r
+                               ),\r
+                       ),\r
+                       'title' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+                       'best_answer_id' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertIdentical($result, $expected);\r
+\r
+               $this->expectError();\r
+               $this->assertFalse($this->Model->useValidationSet('invalidPropertyName'));\r
+\r
+       }\r
+\r
+       function testAfterSave() {\r
+\r
+               $this->Model->useValidationSet('bestAnswer', false);\r
+               $this->Model->Behaviors->MultiValidation->afterSave($this->Model);\r
+               $expected = array(\r
+                       'email' => array(\r
+                               'email' => array(\r
+                                       'rule' => array('email'),\r
+                               ),\r
+                       ),\r
+                       'title' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertIdentical($this->Model->validate, $expected);\r
+\r
+               $this->_reattach(array('restore' => false));\r
+\r
+               $this->Model->useValidationSet('bestAnswer', false);\r
+               $this->Model->Behaviors->MultiValidation->afterSave($this->Model);\r
+               $expected = array(\r
+                       'best_answer_id' => array(\r
+                               'notempty' => array(\r
+                                       'rule' => array('notempty'),\r
+                               ),\r
+                       ),\r
+               );\r
+               $this->assertIdentical($this->Model->validate, $expected);\r
+\r
+       }\r
+\r
+}\r
diff --git a/app/plugins/collectionable/tests/cases/models/behaviors/options.test.php b/app/plugins/collectionable/tests/cases/models/behaviors/options.test.php
new file mode 100644 (file)
index 0000000..63ebfc5
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+
+class OptionsBehaviorMockModel extends CakeTestModel {
+       var $useTable = false;
+}
+
+if (!class_exists('VirtualFieldsUser')) {
+       class VirtualFieldsUser extends CakeTestModel {
+               var $name = 'VirtualFieldsUser';
+               var $alias = 'User';
+               var $hasMany = array(
+                       'Post' => array(
+                               'className' => 'VirtualFieldsPost',
+                               'table' => 'Virtual_fields_posts',
+                               'foreignKey' => 'user_id',
+                       ),
+               );
+       }
+}
+
+class OptionsBehaviorTest extends CakeTestCase {
+       var $Model;
+       var $fixtures = array('plugin.collectionable.virtual_fields_user', 'plugin.collectionable.virtual_fields_post');
+       var $autoFixtures = false;
+
+       function startCase() {
+               $this->Model =& ClassRegistry::init('OptionsBehaviorMockModel');
+               $this->_reset();
+       }
+
+       function _reset($settings = array()) {
+               unset($this->Model->defaultOption);
+               unset($this->Model->options);
+               $this->Model->Behaviors->attach('Collectionable.Options', $settings);
+       }
+
+       function startTest($method) {
+               $this->_reset(false);
+       }
+
+       function testArguments() {
+               $result = $this->Model->options();
+               $expects = array();
+               $this->assertIdentical($result, $expects);
+
+               $result = $this->Model->options('no sence');
+               $expects = array();
+               $this->assertIdentical($result, $expects);
+
+               $result = $this->Model->options(array('one', 'two', 'three'));
+               $expects = array();
+               $this->assertIdentical($result, $expects);
+
+               $this->_reset(array('defaultOption' => true));
+               $result = $this->Model->defaultOption;
+               $expects = true;
+               $this->assertIdentical($result, $expects);
+       }
+
+       function testDefaults() {
+               $this->Model->options = array(
+                       'default' => array('order' => 'default'),
+                       'one' => array('conditions' => array('one')),
+                       'two' => array('order' => 'two'),
+                       'three' => array('order' => 'three'),
+               );
+
+               $result = $this->Model->options('one');
+               $expects = array('conditions' => array('one'));
+               $this->assertEqual($result, $expects);
+
+               $this->Model->defaultOption = true;
+               $result = $this->Model->options('one');
+               $expects = array('conditions' => array('one'), 'order' => 'default');
+               $this->assertEqual($result, $expects);
+
+               $this->Model->defaultOption = 'three';
+
+               $result = $this->Model->options('one');
+               $expects = array('conditions' => array('one'), 'order' => 'three');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('two');
+               $expects = array('order' => 'two');
+               $this->assertEqual($result, $expects);
+
+               $this->Model->defaultOption = array('one', 'three');
+
+               $result = $this->Model->options('two');
+               $expects = array('conditions' => array('one'), 'order' => 'two');
+               $this->assertEqual($result, $expects);
+
+       }
+
+       function testMerge() {
+               $this->Model->options = array(
+                       'one' => array('conditions' => array('one')),
+                       'two' => array('order' => 'two', 'group' => 'two'),
+                       'three' => array('order' => 'three'),
+                       'four' => array('conditions' => array('four'), 'group' => 'four'),
+               );
+
+               $result = $this->Model->options('one', 'two', 'three');
+               $expects = array('conditions' => array('one'), 'group' => 'two', 'order' => 'three');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('three', 'four', 'one');
+               $expects = array('conditions' => array('four', 'one'), 'order' => 'three', 'group' => 'four');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('one', 'two', 'four');
+               $expects = array('conditions' => array('one', 'four'), 'order' => 'two', 'group' => 'four');
+               $this->assertEqual($result, $expects);
+       }
+
+       function testOptions() {
+               $this->Model->options = array(
+                       'one' => array('conditions' => array('one')),
+                       'two' => array('order' => 'two', 'group' => 'two'),
+                       'three' => array('order' => 'three'),
+                       'four' => array('conditions' => array('four'), 'group' => 'four'),
+                       'string' => array(
+                               'conditions' => array('merging'),
+                               'order' => 'merging',
+                               'options' => 'one',
+                       ),
+                       'single_array' => array(
+                               'conditions' => array('merging'),
+                               'order' => 'merging',
+                               'options' => array('one'),
+                       ),
+                       'multi_array' => array(
+                               'conditions' => array('merging'),
+                               'order' => 'merging',
+                               'options' => array('one', 'two'),
+                       ),
+                       'extra_options' => array(
+                               'conditions' => array('merging'),
+                               'order' => 'merging',
+                               'options' => array(
+                                       'conditions' => array('extra'),
+                               ),
+                       ),
+                       'chaos' => array(
+                               'conditions' => array('merging'),
+                               'order' => 'merging',
+                               'options' => array(
+                                       'one',
+                                       'four',
+                                       'conditions' => array('chaos'),
+                               ),
+                       ),
+               );
+
+               $result = $this->Model->options('string');
+               $expects = array('conditions' => array('one', 'merging'), 'order' => 'merging');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('single_array');
+               $expects = array('conditions' => array('one', 'merging'), 'order' => 'merging');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('multi_array');
+               $expects = array('conditions' => array('one', 'merging'), 'order' => 'merging', 'group' => 'two');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('extra_options');
+               $expects = array('conditions' => array('extra', 'merging'), 'order' => 'merging');
+               $this->assertEqual($result, $expects);
+
+               $result = $this->Model->options('chaos');
+               $expects = array('one', 'four', 'conditions' => array('chaos', 'merging'), 'order' => 'merging');
+               $this->assertEqual($result, $expects);
+       }
+
+       function testFindOptions() {
+               $this->loadFixtures('VirtualFieldsUser');
+               $User =& ClassRegistry::init(array('alias' => 'User', 'class' => 'VirtualFieldsUser'));
+               $User->recursive = -1;
+               $User->options = array(
+                       'limitation' => array(
+                               'limit' => 1,
+                               'fields' => array('User.id', 'User.first_name')
+                       ),
+               );
+               $User->Behaviors->attach('Collectionable.options');
+
+               $result = $User->find('all', array('options' => 'limitation', 'limit' => 2, 'order' => 'User.id'));
+               $expects = array(
+                       array('User' => array('id' => 1, 'first_name' => 'yamada')),
+                       array('User' => array('id' => 2, 'first_name' => 'tanaka')),
+               );
+               $this->assertEqual($result, $expects);
+       }
+
+       function testRecursiveMerge() {
+               $this->Model->options = array(
+                       'one' => array('conditions' => array('one')),
+                       'two' => array('conditions' => array('two'), 'options' => 'one'),
+                       'three' => array('conditions' => array('three'), 'options' => 'two'),
+                       'four' => array('conditions' => array('four')),
+               );
+
+               $result = $this->Model->options('three');
+               $expects = array('one', 'two', 'three');
+               $this->assertEqual($result['conditions'], $expects);
+
+               $this->Model->defaultOption = 'three';
+               $result = $this->Model->options('four');
+               $expects = array('one', 'two', 'three', 'four');
+               $this->assertEqual($result['conditions'], $expects);
+       }
+}
\ No newline at end of file
diff --git a/app/plugins/collectionable/tests/cases/models/behaviors/virtual_fields.test.php b/app/plugins/collectionable/tests/cases/models/behaviors/virtual_fields.test.php
new file mode 100644 (file)
index 0000000..52ac608
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+class VirtualFieldsBehaviorMockModel extends CakeTestModel {
+       var $useTable = false;
+}
+
+if (!class_exists('VirtualFieldsUser')) {
+       class VirtualFieldsUser extends CakeTestModel {
+               var $name = 'VirtualFieldsUser';
+               var $alias = 'User';
+               var $hasMany = array(
+                       'Post' => array(
+                               'className' => 'VirtualFieldsPost',
+                               'table' => 'Virtual_fields_posts',
+                               'foreignKey' => 'user_id',
+                       ),
+               );
+       }
+}
+
+class VirualFieldsBehaviorTest extends CakeTestCase {
+       var $Behavior;
+       var $Model;
+       var $db;
+
+       var $fixtures = array(
+               'plugin.collectionable.virtual_fields_post',
+               'plugin.collectionable.virtual_fields_user',
+       );
+
+       function startCase() {
+               parent::startCase();
+               $this->_reset();
+       }
+
+       function _reset($settings = array(), $model = null) {
+               $model = $model === null ? 'VirtualFieldsBehaviorMockModel' : $model;
+               $this->Model = ClassRegistry::init($model);
+               $this->Model->Behaviors->attach('Collectionable.VirtualFields', $settings);
+               $this->Behavior =& ClassRegistry::getObject('VirtualFieldsBehavior');
+       }
+
+       function testUndefined() {
+               $virtualFields = array('not_be_defined');
+               $this->Behavior->beforeFind($this->Model, compact('virtualFields'));
+               $result = $this->Model->virtualFields;
+               $expects = array();
+               $this->assertEqual($result, $expects);
+
+               $this->Behavior->afterFind($this->Model);
+               $this->Model->virtualFields = array(
+                       'full_name' => "CONCAT(User.first_name, ' ', User.last_name)",
+               );
+               $virtualFields = array('not_be_defined');
+               $this->Behavior->beforeFind($this->Model, compact('virtualFields'));
+               $result = $this->Model->virtualFields;
+               $expects = array('full_name' => "CONCAT(User.first_name, ' ', User.last_name)");
+               $this->assertEqual($result, $expects);
+
+               $this->Behavior->afterFind($this->Model);
+               $this->Model->virtualFields = array();
+               $this->Model->virtualFieldsCollection = array(
+                       'posts_count' => 'COUNT(Post.id)',
+               );
+               $virtualFields = array('not_be_defined');
+               $this->Behavior->beforeFind($this->Model, compact('virtualFields'));
+               $result = $this->Model->virtualFields;
+               $expects = array();
+               $this->assertEqual($result, $expects);
+       }
+
+       function testFind() {
+               $this->_reset(false, 'VirtualFieldsUser');
+               $this->skipIf($this->db->config['driver'] !== 'mysql', "%s This tests belonges to MySQL('s SQL expression)");
+
+               $this->Model->virtualFields = array(
+                       'full_name' => "CONCAT(User.first_name, ' ', User.last_name)",
+               );
+               $this->Model->virtualFieldsCollection = array(
+                       'posts_count' => 'COUNT(Post.id)',
+               );
+               $this->Model->recursive = -1;
+
+               $result = $this->Model->find('first', array('fields' => array('User.full_name')));
+               $expected = array('User' => array('full_name' => 'yamada ichirou'));
+               $this->assertEqual($result, $expected);
+
+               $virtualFields = array('posts_count');
+               $joins = array(
+                       array(
+                               'table' => $this->Model->Post->table,
+                               'alias' => 'Post',
+                               'type' => 'LEFT',
+                               'conditions' => array('User.id = Post.user_id')
+                       )
+               );
+               $group = 'User.id';
+               $fields = array('User.full_name', 'User.posts_count');
+               $result = $this->Model->find('first', compact('fields', 'virtualFields', 'joins', 'group'));
+               $expected = array('User' => array('full_name' => 'yamada ichirou', 'posts_count' => 2));
+               $this->assertEqual($result, $expected);
+
+               // ensure virtualFields of model was reset
+               $result = $this->Model->find('first', array('fields' => array('User.full_name')));
+               $expected = array('User' => array('full_name' => 'yamada ichirou'));
+               $this->assertEqual($result, $expected);
+
+               $this->Model->virtualFieldsCollection['full_name'] = "CONCAT(User.first_name, '.', User.last_name)";
+               $result = $this->Model->find('first', array('fields' => array('User.full_name'), 'virtualFields' => 'full_name'));
+               $expected = array('User' => array('full_name' => 'yamada.ichirou'));
+               $this->assertEqual($result, $expected);
+
+               $result = $this->Model->find('first', array('fields' => array('User.full_name')));
+               $expected = array('User' => array('full_name' => 'yamada ichirou'));
+               $this->assertEqual($result, $expected);
+       }
+
+       function testBlackList() {
+               $this->Model->virtualFields = array(
+                       'full_name' => "CONCAT(User.first_name, ' ', User.last_name)",
+                       'posts_count' => 'COUNT(Post.id)',
+               );
+
+               $this->Behavior->beforeFind($this->Model);
+               $result = $this->Model->virtualFields;
+               $expected = array('full_name' => "CONCAT(User.first_name, ' ', User.last_name)", 'posts_count' => 'COUNT(Post.id)');
+               $this->assertEqual($result, $expected);
+
+               $this->Behavior->afterFind($this->Model);
+               $virtualFields = array('posts_count' => false);
+               $this->Behavior->beforeFind($this->Model, compact('virtualFields'));
+               $result = $this->Model->virtualFields;
+               $expected = array('full_name' => "CONCAT(User.first_name, ' ', User.last_name)");
+               $this->assertEqual($result, $expected);
+
+               $this->Behavior->afterFind($this->Model);
+               $result = $this->Model->virtualFields;
+               $expected = array('full_name' => "CONCAT(User.first_name, ' ', User.last_name)", 'posts_count' => 'COUNT(Post.id)');
+               $this->assertEqual($result, $expected);
+       }
+
+       function testSettings() {
+               
+       }
+}
\ No newline at end of file
diff --git a/app/plugins/collectionable/tests/fixtures/virtual_fields_post_fixture.php b/app/plugins/collectionable/tests/fixtures/virtual_fields_post_fixture.php
new file mode 100644 (file)
index 0000000..e4e2f67
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+class VirtualFieldsPostFixture extends CakeTestFixture {
+       var $name = 'VirtualFieldsPost';
+
+       var $fields = array(
+               'id' => array('type' => 'integer', 'key' => 'primary'),
+               'user_id' => array('type' => 'integer', 'key' => 'index'),
+               'title' => array('type' => 'string', 'length' => 255, 'null' => false),
+               'body' => 'text',
+       );
+
+       var $records = array(
+               array('id' => 1, 'title' => 'title1', 'body' => 'article 1', 'user_id' => 1),
+               array('id' => 2, 'title' => 'title2', 'body' => 'article 2', 'user_id' => 2),
+               array('id' => 3, 'title' => 'title3', 'body' => 'article 3', 'user_id' => 1),
+               array('id' => 4, 'title' => 'title4', 'body' => 'article 4', 'user_id' => 3),
+       );
+}
\ No newline at end of file
diff --git a/app/plugins/collectionable/tests/fixtures/virtual_fields_user_fixture.php b/app/plugins/collectionable/tests/fixtures/virtual_fields_user_fixture.php
new file mode 100644 (file)
index 0000000..126c30a
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+class VirtualFieldsUserFixture extends CakeTestFixture {
+       var $name = 'VirtualFieldsUser';
+
+       var $fields = array(
+               'id' => array('type' => 'integer', 'key' => 'primary'),
+               'first_name' => array('type' => 'string', 'length' => 255, 'null' => false),
+               'last_name' => array('type' => 'string', 'length' => 255, 'null' => false),
+       );
+
+       var $records = array(
+               array('id' => 1, 'first_name' => 'yamada', 'last_name' => 'ichirou'),
+               array('id' => 2, 'first_name' => 'tanaka', 'last_name' => 'jirou'),
+               array('id' => 3, 'first_name' => 'kaneda', 'last_name' => 'saburou'),
+               array('id' => 4, 'first_name' => 'shimizu', 'last_name' => 'shinou'),
+       );
+}
\ No newline at end of file