Yii order by another table with has_many relation - yii

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
);
}

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.

Adding a condition to every query in a model

I have a system where all tables in the MySQL database are populated with external data (synchronized with another system every 5 minutes). All tables have a column DELFLAG which is used to mark disabled entries.
So I have about 15 AR models in Yii that are linked to those tables. Whenever I make a query, I need to add something like $criteria->addCondition('DELFLAG=0'). This gets ugly if there are multiple tables present in the query, as every one of them has the flag. Also, there's a potential for error if I forget one of those conditions.
Here's how I do it now:
public function search($showtype = NULL) {
$criteria=new CDbCriteria;
if (isset($showtype)) {
$criteria->with = array(
'TSSSHOW',
'TSSSHOW.TSSSHOWTYPEITEM',
'TSSSHOW.TSSSHOWTYPEITEM.TSSSHOWTYPE'
);
$criteria->compare('TSSSHOWTYPEITEM.TSSSHOWTYPEID', $showtype);
$criteria->addCondition('TSSSHOW.DELFLAG=0');
$criteria->addCondition('TSSSHOWTYPEITEM.DELFLAG=0');
}
$exp = new CDbExpression("`TSSEVENT_START_DATETIME` > NOW()");
$criteria->addCondition($exp);
$criteria->addCondition('t.DELFLAG=0');
$criteria->together = true;
$criteria->order = 't.TSSEVENT_START_DATETIME ASC';
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
'pagination' => array(
'pageSize' => 15,
),
));
}
Is there a convenient way to include this condition into every query that includes these tables? Perhaps a special class which my models shall descend from (as opposed to the default CActiveRecord)?
I'd suggest declaring named scope for these models:
public function scopes()
{
return array(
'disabledEntry'=>array(
'condition'=>'DELFLAG=1',
),
);
}
Using the named scope: Model::model()->disabledEntry()->findAll();
You can provide scope when relaring to model in the with() statement as well: ModelA::model()->with('model:disabledEntry')->findAll();
But if you have to set this condition each time, you may set defaultScope:
public function defaultScope()
{
return array(
'condition' => 'DELFLAG=0',
);
}
Thus, this model by default would have this condition.
Update: Since you have the identically named columns in several models, YII can have some column name ambiguity troubles while building sql-query. If it is the case, use alias in scope declaration or use the following statement to set current table alias explicitly while declaring scope 'condition' => $this->getTableAlias(false, false) . '.DELFLAG=0',
You can create Your own class e.g. CustomActiveRecord extends CActiveRecord
then You should override findAll, findByPk methods with your criteria..
Other solution is to run this query 'DELETE FROM table_name WHERE DELFLAG=1' after every import..

Active Record- How to select a column with relationship?

Projects can have unlimited number of columns (to form a table or something), relationship MANY to MANY. To implement this tbl_project_rel_column is created. It stores project_id, column_id AND pos position of column in Project table.
I am using AC database approach. I have 2 models Project and Column.
Project model's relations method:
public function relations(){
return array(
...
'columns'=>array(self::MANY_MANY,'Column','tbl_project_rel_column('p_id','c_id')
);
}
Now can get all project's columns using something like this:
$model = Project::model()->findbyPk($p_id);
$columns = $model->columns;
But column doesn't store 'pos'(position) value of it's certain project.
How to get 'pos' value of tpl_project_rel_column table of certain project and certain column?
You can use the through feature instead of MANY_MANY. It may also be useful to index the results by the position column. Try something like this:
public function relations()
{
return array(
'projectColumns' => array(self::HAS_MANY, 'ProjectRelColumn', 'p_id', 'index'=>'position'),
'columns' => array(self::HAS_MANY, 'Column', 'c_id', 'through'=>'projectColumns'),
}
Now you can query for projects with columns like this:
$projects = Project::model()->with('columns')->findAll();
foreach($projects as $project) {
// projectColumns are indexed by position. You can sort by this now:
ksort($project->projectColumns)
foreach($project->projectColumns as $pos => $projectColumn)
echo "Pos: $pos Column: {$projectColumn->column->name}";
}

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'
);
//....
),

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

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