Kohana ORM: Get results based on value of a foreign table - orm

taxonomies
-id
-name
taxonomy_type
-taxonomy_id
-type_id
I've configured two models:
class Model_Taxonomy{
protected $_has_many = array('types'=>array());
}
class Model_Taxonomy_Type{
protected $_belongs_to = array('taxonomy' => array());
}
*Please note that taxonomy_type is not a pivot table.*
A taxonomy can have multiple types associated.
Then, what I'm trying to do is get all taxonomies that belong to a given type id.
This is would be the SQL query I would execute:
SELECT * FROM taxonomies, taxonomy_type WHERE taxonomy_type.type_id='X' AND taxonomies.id=taxonomy_type.taxonomy_id
I've tried this:
$taxonomies = ORM::factory('taxonomy')
->where('type_id','=',$type_id)
->find_all();
Obviously this doesn't work, but I can't find info about how execute this kind of queries so I have no clue.

class Model_Taxonomy{
protected $_belongs_to = array(
'types' => array(
'model' => 'Taxonomy_Type',
'foreign_key' => 'taxonomy_id'
)
);
}
class Model_Taxonomy_Type{
protected $_has_many = array(
'taxonomies' => array(
'model' => 'Taxonomy',
'foreign_key' => 'taxonomy_id'
)
);
}
And use some like that:
$type = ORM::factory('taxonomy_type')
->where('type_id', '=', $type_id)
->find();
if( ! $type->taxonomies->loaded())
{
types->taxonomies->find_all();
}

type_id column is a PK of taxonomy_type table, am I right?
So, you have one (unique) taxonomy_type record, and only one related taxonomy object (because of belongs_to relationship). Instead of your:
get all taxonomies that belong to a
given type id
it will be a
get taxonomy for a given type id

Related

SilverStripe duplicate entries even with unique index

I'm trying to prevent duplicate records when adding customer records in my CRM with the following index:
private static $indexes = array(
'IndexFirstSurName' => array(
'type' => 'unique',
'value' => '"FirstName","Surname"'
)
);
Note that I extended Customer from Member where FirstName and Surname came from:
class Customer extends Member
But SilverStripe is still allowing duplicate entries of FirstName and Surname combination? Has anyone experienced the same problem?
The Man, in my experience a validate() is still needed even when using indexing:
public function validate() {
$result = parent::validate();
if(Member::get()->filter(array('FirstName' => $this->FirstName, 'Surname' => $this->Surname))->first()) {
$result->error('First and Surname must be unique for each member.');
}
return $result;
}
Alternately for a more robust breakout:
public function validate() {
$result = parent::validate();
if($member = Member::get()->filter(array('FirstName' => $this->FirstName, 'Surname' => $this->Surname))->first()) {
if($member->FirstName == $this->FirstName){
$result->error('Your Surname is fine, please change your First Name.');
}
if($member->Surname == $this->Surname){
$result->error('Your First Name is fine, please change your Surname.');
}
}
return $result;
}
note that I extended Customer from Member were FirstName and Surname came from
I wonder if SilverStripe is attempting to set indexes on the non-existent fields Customer.FirstName and Customer.Surname. Maybe try qualifying the columns by prepending the table that is actually having the indexes added to it like this:
private static $indexes = array(
'IndexFirstSurName' => array(
'type' => 'unique',
'value' => '"Member"."FirstName","Member"."Surname"'
)
);
You might also consider decorating Member instead of subclassing it. That way you wouldn't need to qualify the query fragments in this way.
The way to extend Member on SilverStripe is by extending DataExtension. As theruss is saying, you are trying to create a unique index on the table Customer, where you prabably do not have the fields FirstName and Surname.
Try this instead
class Customer extends DataExtension
{
private static $indexes = array(
'IndexFirstSurName' => array(
'type' => 'unique',
'value' => '"FirstName","Surname"'
)
);
}
And then let SilverStripe know about your extension in config.yml
Member:
extensions:
- Customer
Now run /dev/build?flush and you should see your index being created.
Check here for more information about extensions.

yii showing data on cgridview from others table

I'm having problem to show data on cgridview using foreign keys.
This is my case, i have table employee(id, username), client(id, username), and transaction(id, employeeId, clientId). employeeId foreign key to employee.id, and clientId is foreign key to client.id. Now, I want to show employee's name and client's name instead of their id on transaction admin.php.
This is my code:
class Transaction extends CActiveRecord
{
public $client_search;
public $employee_search;
public function rules()
{
return array(
.
.
.
array('id, employeeId, clientId, balance, status, date, client_search, employee_search', 'safe', 'on'=>'search'),
);
}
public function relations()
{
return array(
'employee' => array(self::BELONGS_TO, 'Employee', 'employeeId'),
'client' => array(self::BELONGS_TO, 'Client', 'clientId'),
);
}
public function search()
{
$criteria=new CDbCriteria;
$criteria->with = array( 'client', 'employee' );
$criteria->together = true;
$criteria->compare('t.id',$this->id,true);
$criteria->compare('employee.username', $this->employee_search, true );
$criteria->compare('client.username', $this->client_search, true );
$criteria->compare('t.balance',$this->balance,true);
$criteria->compare('t.status',$this->status);
$criteria->compare('t.date',$this->date,true);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
//the other functions are there, i don't edit it.
}
that is my model/Transaction.php
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'transaction-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'columns'=>array(
'id',
array(
'header' => 'Employee',
'name' => 'employee_search',
'value' => '$data->employee->username',
),
array(
'header' => 'Client',
'name' => 'client_search',
'value' => '$data->client->username',
),
array(
'class'=>'CButtonColumn',
),
),
));
that is my views/transaction/admin.php.
and this code gave me error Trying to get property of non-object ($data->employee->id marked).
Actually I have succed to show the employee's name instead of employee's id, but, after that I use the same method for the client, and the error appear.
anyone can help me? My method is making public employee_search, add the rules, add the relation, adding $creiteria->with, then change the admin.php. Anyone please help me.
//UPDATE
SOLVED. Its actually my fault. There is an error in the database about the relation (foreign key). My coding is fine.
It's hard to guess without being able to debug your code but here are some things that could help (I'll add more as I can think of them).
It might be because both the default joinType for relations is LEFT OUTER JOIN and maybe you have a Transaction where one of them is null? If you try to access attributes on null object (as opposed to an actual ActiveRecord object), that's precisely the error message you would be seeing.
You could change it by doing:
public function relations()
{
return array(
'employee' => array(self::BELONGS_TO, 'Employee', 'employeeId',array('joinType'=>'INNER JOIN')),
'client' => array(self::BELONGS_TO, 'Client', 'clientId',array('joinType'=>'INNER JOIN')),
);
}
P.S.: INNER JOIN is the same as just JOIN
Not sure if it will help you, but it's worth trying.
More information at: http://www.yiiframework.com/doc/api/1.1/CActiveRecord#relations-detail

Yii order by another table with has_many relation

I have this Object model with 'has_many' relation to Variables
public function relations()
{
return array(
'variables' => array(self::HAS_MANY, 'Variables', 'variable_id')
);
}
Now i want to use the variables to order my data like
$criteria->with = array('variables');
$criteria->order = 'variables.id DESC';
But it doesn't work.
Is there a way to do something like this? Thanks.
You can define the relation directly with an order if you want, in this case you can do.
public function relations()
{
return array(
'variables' => array(self::HAS_MANY, 'Variables', 'object_id', 'order'=>'variables.id DESC')
);
}
What you wrote it is not working because you have a 1 to many relation. The criteria will run 2 queries, 1 to get the main record, the second time to get the relations. That is why your order is not working.
If you want it to work like you said you should do a ->join instead of ->with.
There is quite a difference between the 2 so take care how you are writing the criteria.
I think the issue is with foreign key binding, if you are adding a relationship in object table (Object model) which has many variables having object_id as foreign key in variable table (Variable model) then you need to define relationship as follows:
public function relations()
{
return array(
'variables' => array(self::HAS_MANY, 'Variables', 'object_id') // check the change in foreign key column
);
}

CGridview and Yii Active Record Relation

I have two tables tbl_business and business_contacts of the following structure:
tbl_business
---
business_id (PK)
othercolumns
and
business_contacts
---
contact_id (PK)
business_id
othercolumns
The scenario is that one business row has many contacts. I am using cGridview using gii's CRUD generator and needed to display firstname and lastname from business_contacts (one of multiple possible rows in the table) for each tbl_business record.
As far as I understand, I've updated the relation function in tbl_business's model as:
'businesscontacts' => array(self::HAS_MANY,'BusinessContact','business_id','select' => 'contact_firstname, contact_lastname')
and for the same, a contact relation is defined in the business_contacts' model as:
'contactbusiness' => array(self::BELONGS_TO,'BusinessContact','business_id')
I expected that would work for pulling related records so that I can have something in the grid like, business_id, contact_firstname, contact_lastname , ... otherbusinesstablecolumns .. but I'm only getting blank values under firstname and lastname .. could someone please help me understand the error? :(
So you are trying to display a table of Businesses (tbl_business) using CGridView? And in each Business's row you want to list multiple Contacts (business_contacts)?
CGridView does not support displaying HAS_MANY relations by default. CGridView makes it easy to list which Business a Contact BELONGS_TO (i.e. you can use a column name like contactbusiness.business_id), but not all of the Contacts that are in a business.
You can do it yourself though, by customizing a CDataColumn. (Note: this will not allow you to sort and filter the column, just view. You'll have to do a lot more work in to get those working.)
First, in your Business model, add a method like this to print out all of the contacts:
public function contactsToString() {
$return = '';
foreach ($this->businesscontacts as $contact) {
$return .= $contact->contact_firstname.' '.$contact->contact_firstname.'<br />';
}
return $return;
}
(EDIT: Or do this to print out just the first contact):
public function contactsToString() {
if($firstContact = array_shift($this->businesscontacts)) {
return $firstContact->contact_firstname.' '.$firstContact->contact_firstname;
}
return '';
}
Then make a new column in your grid and fill it with this data like so:
<?php $this->widget('zii.widgets.grid.CGridView', array(
'id'=>'business-grid',
'dataProvider'=>$model->yourDataProviderFunction(),
'columns'=>
'business_id',
array(
'header'=>'Business Contacts', // give new column a header
'type'=>'HTML', // set it to manual HTML
'value'=>'$data->contactsToString()' // here is where you call the new function
),
// other columns
)); ?>
EDIT2: Yet another way of doing this, if you just want to print out ONE of a HAS_MANY relation, would be to set up a new (additional) HAS_ONE relation for the same table:
public function relations()
{
return array(
'businesscontacts' => array(self::HAS_MANY,'BusinessContact','business_id','select' => 'contact_firstname, contact_lastname') // original
'firstBusinesscontact' => array(self::HAS_ONE, 'BusinessContact', 'business_id'), // the new relation
);
}
Then, in your CGridView you can just set up a column like so:
'columns'=>array(
'firstBusinesscontact.contact_firstname',
),
Getting only the first contact could be achieved like this also:
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'business-grid',
'dataProvider'=>$model->yourDataProviderFunction(),
'columns'=>
'business_id',
//....
array(
'name' => 'contacts.contact_firstname',
'value' => '$data->contacts[0]->contact_firstname', // <------------------------
'type' => 'raw'
);
//....
),

Pagination with hasMany association cakePHP

I have two tables:
Contestant and Votes
Contestant hasMany Votes
I've tried doing a count(Vote.id) as Votes so I can place it on the
recordset and just paginate them but I have no idea where to place it.
I did it on the fields array but it gave me the total count of votes
regardless of the contestant they belong to.
The votes are linked together in the Contestant recordset so what I
did was on my view I did a count($contestant[Vote]) but this can't be
paginated and my client wants to be able to sort the contestants by
votes.
Is there a way I can do something like this on my view?:
sort('Votes', 'count(Vote)'); ?>
Or do I have to create a query which does a count for all the votes
where Contestant.id = Votes.contestant_id ?
Controller Contestant:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$this->paginate =
array(
'order' => 'id ASC',
'contain' => array(
'Vote' => array(
'fields' => array("Vote.contestant_id",'Vote.id')
)
)
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate('Contestant',
$conditions));
}
Check out deceze's response in this question: CakePHP mathematic-calculation field?
Essentially you want to do something like this I'm guessing:
'contain' => array(
'Vote' => array(
'fields' => array('SUM(Vote.id) AS Contestant__votes'),
'group' => array('Vote.contestant_id'),
)
)
Since cakephp doesn't support group by in containable behavior I tried a different approach. Create the paginate var for the vote model instead (All of this is done in the Contestants Controller):
var $paginate = array(
'Vote'=>array(
'limit'=>5,
'fields' => array(
'Contestant.*, count(Vote.contestant_id) as Contestant_votes, Vote.id'
),
'group' => array(
'Vote.contestant_id'
),
'order' => array(
'Contestant_votes Desc'
)
),
'Contestant'=>array(
'limit'=>5,
'order' => array(
'Contestant.id Desc'
)
)
);
And now in my controller I do the following:
function index() {
$page = 'Contestants';
$this->set('page', $page);
$conditions ["Contestant.active"] = 1;
$this->set('contestants', $this->paginate($this->Contestant->Vote,$conditions));
}
Now the contestants are ordered by their total vote tally, although I still can't figure how to place the Contestant_votes as a paginator variable since in the record set it's in a array of it's own and not in any of the model arrays used to paginate.
Thanks Matt Huggins your approach was the one that led me to this solution.
Addition: Do you also want to sort by Votes (total votes) ascending or descending? If yes, you can not do it easily by the default pagination method of cakephp.
For that you need a little tweak. Here is the details about this trick: CakePHP Advanced Pagination – sort by derived field
Hope you'd find it helpful.
Thanks
Adnan
For the specific relationship you define, your needs are well-served by counter-caching.
You will need to define a new field in your contestants table: vote_count. Then, in your Votes model, you'll need to update the $belongsTo definition slightly:
class Votes extends AppModel
{
var $belongsTo = array(
'Contestant' => array( 'counterCache' => true )
);
}
Now, anytime a Vote record is saved, the vote_count field of the parent Contestant will be updated. Now you can simply sort by Contestant.vote_count as you would any other Contestant field:
class ContestantsController extends AppController
{
// Controller stuff that comes before...
function index()
{
$this->paginate = array( 'Contestant' => array(
'conditions' => array( 'Contestant.active' => 1 ),
'order' => array( 'Contestant.vote_count' => 'DESC' ),
));
$contestants = $this->paginate('Contestants');
$this->set( compact('contestants'));
}
// Controller stuff that comes after...
}