I am working on a CakePHP application that has a City / State dropdown box (to prevent user error on input). We have a controller / database table called "Cities" that contains each city in the US along with State, Zip, Lat and Lng. I've included the creation code for the table below for reference.
CREATE TABLE IF NOT EXISTS `cities` (
`zip` int(5) DEFAULT NULL,
`state` varchar(2) DEFAULT NULL,
`city` varchar(16) DEFAULT NULL,
`lat` decimal(8,6) DEFAULT NULL,
`lng` decimal(10,6) DEFAULT NULL
)
For my Cities controller (controllers/CitiesController.php), I have two custom find functions to help populate the dropdown (via JSON)
class CitiesController extends AppController {
public function getstates() {
$this->set('states', $this->City->find('all', array(
'fields' => array('DISTINCT City.state'),
'order' => 'City.state ASC',
'group' => 'City.state',
'recursive' => 0
)));
$this->set('_serialize', array('states'));
}
public function getCitiesInState($state) {
$this->set('cities', $this->City->find('all', array(
'fields' => array('City.city', 'City.zip', 'City.lat', 'City.lng'),
'order' => 'City.city ASC',
'group' => 'City.city',
'conditions' => array('City.state = ' => strtoupper($state)),
'recursive' => 0
)));
$this->set('_serialize', array('cities'));
}
}
Now this is where I am having a problem.
I am trying to implement this on another models "Add" method (views/Facilities/add.ctp). I'm not sure the best way to call the data for the dropdown. I know I will want to make a jQuery onchange event tied to the States dropdown, as suggested in https://stackoverflow.com/a/1872282/722617
The models are associated, the "Facilities" model has a foreign key of "city_id" that is tied to the id of the city from the Cities model.
The current code just lists all the cities in the database, and looks as follows:
<div class="form-group">
<?php echo $this->Form->input('city_id', array('class' => 'form-control', 'placeholder' => 'City Id'));?>
</div>
Any suggestions for properly implementing this code?
Thank you!
Related
I am now having two tables tbl_user and tbl_log, and User and Log ActiveRecord classes respectively.
tbl_log
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`log_date` datetime NOT NULL,
`remarks` varchar(255) NOT NULL,
User class relations
public function relations() {
return array(
'rLog' => array(self::HAS_MANY, 'Log', 'user_id'),
);
}
What I am trying to achieve is to retrieve the latest record on tbl_log, that belongs to a certain user.
I have tried to add the following relation to the User class:
'lastLogDate' => array(self::STAT, 'Log', 'user_id', 'select'=>'log_date', 'order'=>'log_date DESC', 'group'=>'user_id', 'defaultValue'=>'N/A'),
so that I could retrieve the log_date from the latest record by calling something like:
$model = User::model()->findByPk($id);
echo $model->lastLogDate;
But then I realized it was actually not working properly. The log_date returned was always from the record with the smallest id on the tbl_log table, probably due to the behavior of GROUP BY and ORDER BY on a SQL query.
So now, I would like to know how (if possible) to achieve this by using a similar approach (i.e. using relations in the ActiveRecord class)? Thanks in advance.
The idea to go is using 'order' and 'limit', example:
'order'=>'log_date DESC',
'limit'=>1,
But you were wrong when use this type of relationship SELF::STAT, it is used to count the returned of records, not latest record
I don't usually use it that way, instead here is how I will:
In Log model, you should have:
public function relations()
{
return array(
'belongUser' => array(self::BELONGS_TO, 'User', 'user_id'),
}
And it would be simple like below
//get first found Log record of the user by given user_id and sort by log_date DESCENDANT
$lastLogDateRecord = Log::model()->with(array(
'belongUser' => array(
'condition' => 'user_id = :user_id',
'params' => array('user_id'=>$id) //$id is user_id param what user want
)
))->findByAttributes(array(), array('order' => 'log_date DESC'));
I have a table of securities like so:
CREATE TABLE IF NOT EXISTS `securities` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ticker` varchar(36) NOT NULL,
`name` varchar(180) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ticker` (`ticker`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=585 ;
I.e. the primary key is id whilst there is another unique index ticker.
The ticker index refers to my other table, secuity_prices which has this
CREATE TABLE IF NOT EXISTS `security_prices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`price_date` date NOT NULL,
`ticker` varchar(36) NOT NULL,
`price` decimal(10,6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=88340 ;
I want to define a hasMany relationship between them such that security hasMany securityPrice [securityPrice belongsTo security].
The problem I am having is that Cake is using the primary key of security to link to the security_prices table instead of the ticker field. How can I get the join to be made via the ticker?
Here are my relationships:
//Security
public $hasMany = array(
'SecurityPrice' => array(
'className' => 'SecurityPrice',
'foreignKey' => 'ticker',
)
);
//SecurityPrice
public $belongsTo = array(
'Security' =>
array(
'className' => 'Security',
'foreignKey' => 'ticker',
)
);
You can't use $hasMany to do this, because those associations require that you follow Cake's naming conventions for the primary key. You are trying to join two tables via non-primary key columns. That can be done, but not via Cake's automatic associations.
You need to add the join conditions when performing a find operation or pagination operation.
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#joining-tables
$options['joins'] = array(
array('table' => 'security_prices',
'alias' => 'SecurityPrice',
'type' => 'LEFT',
'conditions' => array(
'Security.ticker = SecurityPrice.ticker',
)
)
);
$Security->find('all', $options);
If you have to do this often, then you should create a custom find type.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types
class Security extends AppModel {
public $findMethods = array('ticker' => true);
protected function _findTicker($state, $query, $results = array()) {
if ($state === 'before') {
$query['joins'][] = array(
array('table' => 'security_prices',
'alias' => 'SecurityPrice',
'type' => 'LEFT',
'conditions' => array(
'Security.ticker = SecurityPrice.ticker',
)
)
);
return $query;
}
return $results;
}
}
Then later it's easy to find with the join.
$Security->find('ticker',.....);
I have one model Portfolio in which I have define join like
public $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'category_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
now when I am using code in controller like:
$this->Portfolio->recursive = 0;
$this->paginate = array(
'fields' => array('Portfolio.id', 'Portfolio.application_name','Portfolio.category_id','Portfolio.description','Portfolio.screenshots','Portfolio.icon','Portfolio.bg_color_code','Portfolio.created','Category.title','Category.id'),
'limit' => 10,
'order' => array(
'Portfolio.id' => 'asc'
)
);
so its working fine on my window 7 but its giving me error on linux server like:
Database Error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Category.title' in 'field list'
SQL Query: SELECT `Portfolio`.`id`, `Portfolio`.`application_name`, `Portfolio`.`category_id`, `Portfolio`.`description`, `Portfolio`.`screenshots`, `Portfolio`.`icon`, `Portfolio`.`bg_color_code`, `Portfolio`.`created`, `Category`.`title`, `Category`.`id` FROM `portfolios` AS `Portfolio` WHERE 1 = 1 ORDER BY `Portfolio`.`id` asc LIMIT 10
Notice: If you want to customize this error message, create app/View/Errors/pdo_error.ctp
and my category model contains
var $hasMany = array(
'Portfolio' => array(
'className' => 'Portfolio',
'foreignKey' => 'category_id',
)
);
my table
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`parent_id` int(11) NOT NULL DEFAULT '0',
`status` enum('1','2') NOT NULL COMMENT '''1''=active,''2''=inactive',
PRIMARY KEY (`id`)
)
I have tested in debug result its showing
Included Files
Include Paths
0/home/reviewpr/public_html/imobdevnew/lib
2/usr/lib/php
3/usr/local/lib/php
4-> /home/reviewpr/public_html/imobdevnew/lib/Cake/
Included Files
core
app
Config
Controller
Model
0APP/Model/AppModel.php
Other
0APP/webroot/index.php
plugins
where in local its showing
Included Files
Include Paths
0C
1\wamp\www\imobdevnew\lib;.;C
2\php\pear
3-> C:\wamp\www\imobdevnew\lib\Cake\
Included Files
core
app
Other
0APP/webroot\index.php
1APP/Config\core.php
2APP/Config\bootstrap.php
3APP/Config\config.php
4APP/Config\routes.php
5APP/Controller\PortfoliosController.php
6APP/Controller\AppController.php
7APP/Model\portfolio.php
8APP/Model\AppModel.php
9APP/Config\database.php
10APP/Model\category.php
plugins
that means its not loading models.
Please help me...
Linux is case-sensitive
Ominously showing up in the included-files on your windows install are these files:
7APP/Model\portfolio.php
...
10APP/Model\category.php
These files are the wrong case - so on linux they are not included, instead your models will be AppModel instances.
This will be the direct cause of the problem as without the model files being loaded, there will be no model associations either.
To fix the problem just ensure that all your files follow conventions - this means:
APP/Model/portfolio.php -> APP/Model/Portfolio.php
APP/Model/category.php -> APP/Model/Category.php
The filename, and other conventions, are sumarized in the documentation.
It seems to me that there is something wrong with your database, and not Linux. Is your database properly linked?
$this->Portfolio->recursive = 0;
$this->paginate = array(
'fields' => array('Portfolio.id', 'Portfolio.application_name','Portfolio.category_id','Portfolio.description','Portfolio.screenshots','Portfolio.icon','Portfolio.bg_color_code','Portfolio.created','Category.id'),
'limit' => 10,
'order' => array(
'Portfolio.id' => 'asc'
)
);`
I have a table ...
CREATE TABLE IF NOT EXISTS `messages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`to` int(11) NOT NULL,
`from` int(11) NOT NULL,
`subject` varchar(50) NOT NULL,
`message` varchar(1000) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
To and From is the primary key id from Users Table.
How can I get the user details when I get each message with CodeIgniter DataMapper.
You are missing a couple key points to using the DataMapper for CodeIgniter. First off you need to do some pretty simple and important things. DataMapper (DM) uses normalized db naming to find relationships. What this means is if you query your db. Now it's a little harder to use DM for two columns and I think that you really don't need it.
First if you don't use DM you really only need two queries
SELECT u.*, m.* FROM messages AS m, users AS u WHERE m.from = u.id AND m.id = SOME_ID.
This query will get you all user details and message details for some message ID.
Now this is semi-simple case because I am assuming a message can only be from one user.
For the to field on the other hand I will assume you should use a relational table. To use DM for it you have to name the table something like users_messages but again why do you need to use DM when it really is overkill.
Now for the from field you have a many to many relation because a message can have many users that it was to and a user can have many messages that they sent.
So create a table like this
CREATE TABLE message_to (
user_id BIGINT UNSIGNED NOT NULL,
message_to_id BIGING UNSIGNED NOT NULL,
PRIMARY KEY (user_id, message_to_id),
);
If you want to do it right you will also use foreign keys but that depends on your DB
Now you can query really easily and get all the users a message was sent to.
SELECT u.*, m.* FROM users AS u, m AS messages JOIN messages_to AS m_t ON (u.id = m_t.user_id)
And querying the other way around is just as easy, getting all the messages a user has sent.
Remember just because a tool like DM exists doesn't mean it is the best tool for the job and actually using DM in this case incurs a pretty decent overhead when it is not necessary.
Doing this with DM would require the same things you just cannot name your tables/columns as you see fit and you need a class for every table creating it's relationship with other tables.
Meaning you have a lot of extra work to use DM, and you need to learn their syntax.
What you're looking for is a self-relationship.
If this is a 'has_one' relation, you can do that with in-table foreign keys. You do have to follow the naming convention for keys (to_id and from_id instead of to and from).
Currently (v1.8.0) you can only have one relation between any two models:
$has_one = array(
'to' => array(
'class' => 'messages',
'other_field' => 'messages'
),
'messages' => array(
'other_field' => 'to'
)
);
}
See http://datamapper.wanwizard.eu/pages/advancedrelations.html for more information.
You have to make your models [models/users.php] and
[models/messages.php] like this:
class User extends DataMapper {
var $has_many = array(
'sent_message' => array(
'class' => 'Message',
'other_field' => 'sender',
),
'received_message' => array(
'class' => 'Message',
'other_field' => 'receiver',
),
);
}
class Message extends Datamapper {
var $has_one = array(
'sender' => array(
'class' => 'User',
'other_field' => 'sent_message',
),
'receiver' => array(
'class' => 'User',
'other_field' => 'received_message'
),
);
}
I Only have proviede $has_one and $has_many and you have to include the rest of models.
you have to deifne your tables like this:
Table Name: users fields: id, email, ....
Table Name: messages [this is the important table in this case] field:
id, sender_id, receiver_id, subject, message, created, ....
Now have to fill your database with example messages and then you can test like this:
for example User X is logged in and is now an object. You get users last 10 Messages like this:
$messages = new Message();
$messages->where_related_user('id', $user->id);
$messages->limit(10);
$messages->get();
you can get the reciever and sender of each message like this:
$messages = new Message();
$messages->include_related('sender');
$messages->include_related('receiver');
$messages->get();
Now print the name of each sender and receiver:
foreach($messages as $message):
echo $message->sender->name;
echo $message->receiver->name;
endforeach;
I have an existing web application that I am converting to use CakePHP.
The problem is that the primary keys for most of the tables are in this format "${table_name}_id" (story_id) instead of the CakePHP way of 'id'
When ever I try to update some of the fields for a row in the story table, the Save() function will return false. Is there any way of getting a more detailed error report from the Save() function. ?
When I set Configure::write('debug', 2); in core.php and check the SQL statements I do not see any UPDATE command, only SELECT statements.
I tried to edit the controller adding the following line to manually set the id field for the controller but it did not help.
$this->Story->id = $this->data['Story']['story_id'] ;
I'm running out of ideas. Any suggestions?
I have included the source code that I am using below
Story controller:
function admin_edit($id = null)
{
if (!$id && empty($this->data)) {
$this->Session->setFlash(__('Invalid '. Configure::read('Site.media') , true));
$this->redirect(array('action'=>'index'));
}
$this->layout = 'admin';
if (!empty($this->data)) {
if ($this->Story->save($this->data)) {
$this->Session->setFlash(__('The '. Configure::read('Site.media') .' has been saved', true));
} else {
$this->Session->setFlash(__('The '. Configure::read('Site.media') .' could not be saved. Please, try again.', true));
}
}
$this->data = $this->Story->read(null, $id );
}
Story model:
class Story extends AppModel {
var $name = 'Story';
var $primaryKey = 'story_id';
var $validate = array(
'author_id' => array('numeric'),
'title' => array('notempty'),
'story' => array('notempty'),
'genra' => array('notempty'),
'form' => array('notempty'),
'wordcount' => array('Please enter a number between 1 and 1000' => array(
'rule' => array('range', 1, 1001),
'message' => 'Please enter a number between 1 and 1000' ),
'Required' => array( 'rule' => 'numeric', 'required' => true )
)
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'Author' => array(
'className' => 'Author',
'foreignKey' => 'author_id'
)
);
var $hasMany = array(
'UserNote' => array(
'className' => 'UserNote',
'foreignKey' => 'story_id',
'dependent' => false,
'conditions' => 'UserNote.notes != ""'
)
);
}
Story view:
echo $form->create('Story', array('action' => 'edit' ) );
echo $form->input('story_id',array('type'=>'hidden') );
echo $form->input('title');
echo $form->input('story');
echo $form->input('bio' );
echo $form->end('Update story details');?>
Story table
CREATE TABLE IF NOT EXISTS `stories` (
`story_id` int(11) NOT NULL AUTO_INCREMENT,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`closed` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`author_id` int(11) NOT NULL,
`title` varchar(255) NOT NULL,
`story` text NOT NULL,
`genra` varchar(255) NOT NULL,
`form` varchar(128) DEFAULT NULL,
`wordcount` varchar(255) NOT NULL,
`terms` varchar(255) NOT NULL DEFAULT '0',
`status` varchar(255) NOT NULL DEFAULT 'slush',
`published` date NOT NULL,
`payment` varchar(255) NOT NULL DEFAULT 'none',
`paypal_address` varchar(255) NOT NULL,
`resubmission` tinyint(1) NOT NULL DEFAULT '0',
`bio` text NOT NULL,
`password` varchar(255) NOT NULL DEFAULT 'yyggrrdd',
`comments` text NOT NULL,
PRIMARY KEY (`story_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=10905 ;
You should manually override the primary key field in the model (which is the right place to do this - the name of a primary key field is an attribute of the model, and not something that should be 'fudged' around in the controller.)
class Example extends AppModel { var $primaryKey = 'example_id'; // example_id is the field name in the database}
The above code is from http://book.cakephp.org/view/437/primaryKey
While the suggestion to turn off validation will work, it's not the right way to go about it.
Lastly, if you're setting model variables within a controller, you use $this->Model->set('attributeName',value) rather than $this->Model->attributeName
It looks like the story controller was validating the data, and the data was invalid.
Adding the following line to the controller will stop the validation of the data.
$this->Story->validate = array(); // Stop valadation on the story.
I found this solution on this page
15 essential CakePHP tips