Include fixture from Tags plugin in app test - cakephp

I'm using the excellent CakeDC Tags plugin on my Solutions model:
class Solution extends AppModel {
public $actsAs = array(
'Tags.Taggable',
'Search.Searchable',
);
}
I have a SolutionsController::search() method:
App::uses('AppController', 'Controller');
class SolutionsController extends AppController {
public $components = array(
'Paginator',
'Search.Prg',
);
public $presetVars = true; // using the model configuration ('Search' plugin)
public function search() {
$this->Prg->commonProcess();
$this->Paginator->settings['conditions'] = $this->Solution->parseCriteria($this->Prg->parsedParams());
$solutions = $this->Paginator->paginate();
if (!empty($solutions)) {
$this->Session->setFlash('Solutions found');
} else {
$this->Session->setFlash('No solutions found');
}
$this->set('solutions', $solutions);
$this->render('index');
}
I'm trying to write a test for this method:
App::uses('SolutionsController', 'Controller');
class SolutionsControllerTest extends ControllerTestCase {
public $fixtures = array(
'app.solution',
'plugin.tags.tag'
);
public function testSearchForOneResultShouldOutputText() {
$data = array('search' => 'fiery');
$result = $this->Solution->search($data);
debug($result);
$expected = array(
'id' => 3,
'name' => 'fiery-colored horse',
'shortdesc' => 'war',
'body' => 'it was granted to the one seated on it..',
'category_id' => 3,
'created_by' => 1,
'modified_by' => 1,
'created' => '2014-02-14 21:28:46',
'modified' => '2014-02-14 21:28:46'
);
$this->assertContains($expected);
}
}
I'm getting this error when running the test:
Missing Database Table
Error: Table tags for model Tag was not found in datasource test.
I've tried copying the plugin Tag fixture to my app Test/fixtures folder and including it as an app fixture. I can't get my test to run. How do I get my test to see the tags fixture from app\Plugin\tags\Test\Fixture\TagFixture.php and run?

Problem turned out to be my SolutionsFixture, which imported the table schema and the records, then also included a $records array (oops). Re-baking this fixture to import neither schema nor records resolved the error.

Related

CakePHP 3 : Unknown method

I am creating a function in model to find all related services.
function in ServiceCategory.php
class ServiceCategory extends Entity
{
public function relatedServices($id)
{
return $this->find('all', [
'conditions' => [
'where' => [
'id !=' => $id
],
'limit' => 5
]
]);
}
}
And calling in ServiceCategoriesController.php
public function view($id = null)
{
$serviceCategory = $this->ServiceCategories->get($id, [
'contain' => ['Services']
]);
$relatedServices = $this->ServiceCategories->relatedServices($id);
$this->set('serviceCategory', $serviceCategory);
$this->set('relatedServices', $relatedServices);
$this->set('_serialize', ['serviceCategory']);
}
But it gives Unknown method 'relatedServices'
Is there any thing wrong I am doing ?
The code is in the wrong class
In the question:
class ServiceCategory extends Entity
This is an entity class
$relatedServices = $this->ServiceCategories->relatedServices($id);
This is making a call on a table object, table objects and entities do not inherit from each other, the method is unavailable to the table class.
Move the code to the table class
The direct solution is to move the code to the table class:
// src/Model/Table/ServiceCategoriesTable.php
namespace App\Model\Table;
class ServiceCategoriesTable extends Table
{
public function relatedServices($id)
{
return $this->find('all', [
'conditions' => [
'where' => [
'id !=' => $id
],
'limit' => 5
]
]);
}
Though the arguably correct/better way to do that is to implement a finder:
// src/Model/Table/ServiceCategoriesTable.php
namespace App\Model\Table;
use Cake\ORM\Query;
use \InvalidArgumentException;
class ServiceCategoriesTable extends Table
{
public function findRelatedServices(Query $query, array $options)
{
if (!isset($options['id'])) {
$message = sprintf('No id in options: %s', json_encode($options));
throw new InvalidArgumentException($message);
}
$query->where(['id !=' => $options['id']);
return $query;
}
Which would be called in exactly the same way as other find calls:
$relatedServices = $this->ServiceCategories->find(
'relatedServices',
['id' => $id]
);

How to join two tables using models in cakephp

I just created 2 models UsersModel and UserpicsModel and try to get both records but it is returning Error: Call to a member function find() on a non-object.
//UsersModel.php
class Users extends AppModel
{
public $hasMany = array(
'Userpics' => array(
'className' => 'Userpics'
)
);
}
//UserpicsMdoel.php
class Userpics extends AppModel
{
public $belongsTo = array(
'Users' => array(
'className' => 'Users',
'foreignKey' => 'uid'
)
);
}
//RecipesController.php
class RecipesController extends AppController {
public $uses =array('Users','Userpics');
public function view() {
$users = $this->Users->Userpics->find('all');
print('<pre>');
print_r($users);
print('<pre>');
exit;
}
}
first: you are not following cake conventions: models should be singular and not plural.
But the actual problem here is that the model files names are wrong: if you still want to use your conventions then the name for the Users model should be Users.php and not UsersModel.php
the same for Userpics
read this useful answer about how to debug this kind of error.
But if you decide to use the cake naming conventions (and I strongly suggest you to do so) consider doing the following:
//User.php (table users)
class User extends AppModel
{
....
}
//UserPic.php (table user_pics)
class UserPic extends AppModel
{
....
}
//RecipesController.php
class RecipesController extends AppController {
public $uses =array('User','UserPic');
public function view() {
$users = $this->User->UserPic->find('all');
print('<pre>');
print_r($users);
print('<pre>');
exit;
}
}
//UsersModel.php
class Users extends AppModel
{
public $hasMany = array(
'Userpics' => array(
'className' => 'Userpics',
'foreignKey' => 'uid'
)
);
}

find('all') doing unexpected (unwanted) SQL join in CakePHP

I've set up two models: city, and country. Below is how I've defined them:
class City extends AppModel { // for "cities" table
public $hasOne = 'Country';
}
class Country extends AppModel { // for "countries" table
public $hasMany = array(
'City' => array(
'className' => 'City'
)
);
}
and in my controller:
public function getCities() {
$this->loadModel('City');
$cities = $this->City->find('all');
}
but it's giving me this error:
Database Error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Country.city_id' in 'on clause'
SQL Query: SELECT `City`.`id`, `City`.`country_id`, `City`.`name`, `City`.`latitude`, `City`.`longitude`, `City`.`time_zone`, `City`.`dma_id`, `City`.`code`, `City`.`created`, `City`.`modified`, `Country`.`id`, `Country`.`country_id`, `Country`.`name`, `Country`.`code`, `Country`.`created`, `Country`.`modified` FROM `rtynbiz_ls`.`cities` AS `City` LEFT JOIN `rtynbiz_ls`.`countries` AS `Country` ON (`Country`.`city_id` = `City`.`id`) WHERE 1 = 1
Notice: If you want to customize this error message, create app/View/Errors/pdo_error.ctp
I can't understand why it's trying to do a join with Country table. I only want to fetch cities. How do I stop this from happening? And, why is it trying to make an association using Country.city_id (which doesn't exists) Also, have I named my classes and tables correctly? Thanks
Following lines of code are making relationship so JOIN is present there. To remove the JOIN from the query just replace :
class City extends AppModel { // for "cities" table
public $hasOne = 'Country';
}
class Country extends AppModel { // for "countries" table
public $hasMany = array(
'City' => array(
'className' => 'City'
)
);
}
With:
class City extends AppModel { }// for "cities" table
class Country extends AppModel {} // for "countries" table
You can make relationships in the tables easily by following this
By default, CakePHP will automatically try to pull additional model's data. To stop that from being the default, in your AppModel, set this variable:
public $recursive = -1;
I would also suggest adding Containable behavior, so your app model looks like this:
<?php
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
Read more about "recursive" and "Containable Behavior" at the CakePHP book.
If you don't want to do join when you want to retrieve your data make it recursive -1
public function get_cities() {
$this->loadModel('City');
$this->City->recursive=-1;
$cities = $this->City->find('all');
//or
$cities = $this->City->find('all', array('recursive'=>-1));
}
any way this would be your model:
class Country extends AppModel {
public $hasMany = array(
'City' => array(
'className' => 'City',
'foreignKey' => 'country_id',
'dependent' => false,
),
);
}
class City extends AppModel {
public $belongsTo = array(
'Country' => array(
'className' => 'Country',
'foreignKey' => 'country_id',
)
);
}
make sure you don't have messy code on these 2 models

Unit testing the Auth Component

I am using the following code to test the login action in UsersController
public function testLogin() {
$data = array('User' => array(
'username' => 'hello',
'password' => '411'
)
);
$this->Users = $this->generate('Users',array('components'=> array('Auth','Session')));
$this->Users->Auth->staticExpects($this->once())
->method('user')
->with('id');
$this->testAction('/users/login', array('data' => $data, 'method' => 'post'));
}
and the fixture is-
class UserFixture extends CakeTestFixture {
public $import = array('model' => 'User', 'records' => true, 'connection' => 'fixture');
}
adn action is-
public function login() {
if($this->request->is('post')) {
if($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
return false;
$this->Session->setFlash(__('Wrong Username Or Password,Please Try Again'));
}
}
}
It always showing
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
What is the problem?Cant find any solution,and the reason also.Please help.
I think your problem mocking the auth component is that in addition to including auth in your components array, you need to specify which methods of Auth to mock, and how to mock them.
The way I deal with AuthComponent in my tests is I create a superclass with a method: _generateMockWithAuthUserId which mocks the Auth component, among other things, the way I need.
I've pasted the code from my superclass below.
class AppControllerTest extends ControllerTestCase {
public function setUp() {
parent::setUp();
$this->User = ClassRegistry::init('User');
}
public function tearDown() {
unset($this->User);
parent::tearDown();
}
public function testPlaceholder(){
// This just here so we don't get "Failed - no tests found in class AppControllerTest"
$this->assertTrue(true);
}
protected function _generateMockWithAuthUserId($contollerName, $UserId){
$this->authUserId = $UserId;
$this->authUser = $this->User->findById($this->authUserId);
$this->controller = $this->generate($contollerName, array(
'methods' => array(
'_tryRememberMeLogin',
'_checkSignUpProgress'
),
'components' => array(
'Auth' => array(
'user',
'loggedIn',
),
'Security' => array(
'_validateCsrf',
),
'Session',
)
));
$this->controller->Auth
->expects($this->any())
->method('loggedIn')
->will($this->returnValue(true));
$this->controller->Auth
->staticExpects($this->any())
->method('user')
->will($this->returnCallback(array($this, 'authUserCallback')));
}
public function authUserCallback($param){
if(empty($param)){
return $this->authUser['User'];
} else {
return $this->authUser['User'][$param];
}
}
}
And then here's a example of a class that inherits from that superclass. Take note of where/how it calls _generateMockWithAuthUserId. Basically, doing that sets up a suitable controller with Auth mocked for the appropriate user id.
<?php
require_once dirname(__FILE__) . DS . 'AppControllerTest.php';
class EmployeeNotesControllerTestCase extends AppControllerTest {
public $fixtures = array(
// your fixtures go here
);
public function setUp() {
parent::setUp();
$this->EmployeeNote = ClassRegistry::init('EmployeeNote');
}
public function tearDown() {
unset($this->EmployeeNote);
parent::tearDown();
}
public function testSupervisorIndexCanNotSeeNotesOnSelf() {
$authUserId = 1;
$this->_generateMockWithAuthUserId('EmployeeNotes', $authUserId);
$this->controller->Session
->expects($this->once())
->method('setFlash');
$result = $this->testAction('supervisor/employee_notes/index/'.$authUserId, array('return' => 'vars', 'method' => 'get'));
$this->assertTrue(empty($result['employeeNotes']));
}
}
Hope that helps.
I have found a solution.it worked.
public function testLogin() {
$data = array('User' => array(
'username' => 'sasa',
'password' => '111'
)
);
$this->Users = $this->generate('Users', array());
$result = $this->testAction('/users/login', array('data' => $data, 'method' => 'post'));
$this->assertEquals($data['User']['username'],$this->Users->Session->read('Auth.User.username'));
$result = $this->testAction('/users/logout');
}

CakePHP: saving file with new record with FileUpload plugin

I'm trying to use the FileUpload plugin (https://github.com/webtechnick/CakePHP-FileUpload-Plugin) in my CakePHP (1.3) app.
I have two models: PendingContract and PendingContractFile. A PendingContract can have many PendingContractFile records. When saving a new PendingContract, I'd also like to save the uploaded PendingContractFile; however, my save method fails because PendingContract does not yet have an ID, and that is used as the foreign key in my PendingContractFile.
For clarity, here are my models:
<?php
class PendingContract extends AppModel {
var $name = 'PendingContract';
var $belongsTo = array(
'Supplier'
);
var $hasMany = array(
'PendingContractFile'
);
}
class PendingContractFile extends AppModel {
var $name = 'PendingContractFile';
var $belongsTo = array(
'PendingContract' => array(
'className' => 'PendingContract',
'foreignKey' => 'pending_contract_id'
),
'Author' => array(
'className' => 'User',
'foreignKey' => 'author_id'
)
);
}
And here is my controller method where I'm saving my PendingContract:
<?php
class PendingContractsController extends AppController {
function add() {
if (!empty($this->data)) {
if ($this->FileUpload->success) {
$this->Session->setFlash('Pending contract successfully created.');
$this->redirect(array('action' => 'index'));
}
else {
$this->Session->setFlash($this->FileUpload->showErrors());
}
}
}
}
Currently the error I'm getting is:
1452: Cannot add or update a child row: a foreign key constraint fails (pending_contract_files, CONSTRAINT pending_contract_files_ibfk_1 FOREIGN KEY (pending_contract_id) REFERENCES pending_contracts (id) ON DELETE CASCADE ON UPDATE CASCADE)
How can I use the FileUpload plugin so that it attaches the uploaded file with my new PendingContract record?
I took a look at the plugin, at it doesn't appear that it will save posted data along with uploaded files. It purposefully separates upload file data from any other input in the form and performs a save for each file.
Personally I would try other plugins such as https://github.com/josegonzalez/upload which do not rely in any controller-level code.
public function beforeSave($options = array()) {
if (!isset($this->data[$this->alias][$this->primaryKey])) {
$this->data[$this->alias][$this->primaryKey] = String::uuid();
}
return parent::beforeSave($options);
}
This will generate a new UUID for the record before save. You should probably only do this if the key is not already set.
i had a similar problem, what i did is unset the validation when adding a new PendingContractFile in your case. So before the saveAll method try adding:
unset($this->PendingContract->PendingContractFile->validate['pending_contract_id']);
so it wont check for the foreign_key.
hope it helps.

Resources