add a common scope to all models in cakephp - cakephp-2.2

I am building an online accounts application where each business has its own data and therefore each table has a field business_id.
Is there a way to automatically add to each Model the condition
Model.business_id => x
For example if a user did a search for all Transactions containing Project the conditions added would be:
Transaction.business_id => x
Project.business_id => x
I am guessing that this would be best placed in a behavior as it applies to all but two models
Thanks

you can modify query data in beforeFind(), which can be within a behavior as well
in your models:
$actsAs = array('BusinessCondition');
The behavior would be something like:
<?php
App::uses('AuthComponent', 'Controller/Component');
class BusinessCondition extends ModelBehavior {
public function beforeFind(Model $Model, $query) {
$query['conditions'][$Model->alias . '.business_id'] = AuthComponent::user('business_id');
return $query;
}
}

Related

laravel scout add additional attribute (only in meilisearch)

I try to migrate a SQL based search to meilisearch using laravel scout.
At the moment the whole search should be migrated to meilisearch, including all filter and sorting options.
A product has a relation to feedbacks (product model):
//returns all feedbacks for the product
public function allFeedbacks()
{
return $this->hasMany('App\Models\Feedback');
}
I would like to include the amount of feedbacks to meilisearch, but not the whole relation, since it's not required for sorting.
How can I add additional fields to be index by meilisearch, without including a field into the mySQL database (feedback_amount f.e.)?
In Product.php:
public function toSearchableArray() {
$fields = [
'feedback_amount' => $this->allFeedbacks()->count(),
'price' => $this->price,
'other_stuff' => $this->other_stuff
];
return $fields;
}
Then flush and import the model again.
This will override the parent toSearchableArray() so you will want to include any other fields you want searchable.

How to use existing data from the database in Codeception FactoryMuffin?

I'm trying to set up easy test data in my Acceptance tests:
public function shouldUseAFakeAccountHolder(AcceptanceTester $I) {
$I->have(AccountHolder::class);
// ...
}
I've copied the example code from the Codeception documentation and modified it with my entity names (as well as fixing the bugs).
<?php
public function _beforeSuite()
{
$factory = $this->getModule('DataFactory');
// let us get EntityManager from Doctrine
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(AccountHolder::class, [
'firstName' => Faker::firstName(),
// Comment out one of the below 'accountRole' lines before running:
// get existing data from the database
'accountRole' => $em->getRepository(AccountRole::class)->find(1),
// create a new row in the database
'accountRole' => 'entity|' . AccountRole::class,
]);
}
The relationship using existing data 'accountRole' => $em->getRepository(AccountRole::class)->find(1) always fails:
[Doctrine\ORM\ORMInvalidArgumentException] A new entity was found through the relationship 'HMRX\CoreBundle\Entity\AccountHolder#accountRole' that was not configured to cascade persist operations for entity: HMRX\CoreBundle\Entity\AccountRole#0000000062481e3f000000009cd58cbd. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'HMRX\CoreBundle\Entity\AccountRole#__toString()' to get a clue.
If I tell it to create a new entry in the related table 'accountRole' => 'entity|' . AccountRole::class, it works, but then it adds rows to the table when it should be using an existing row. All the role types are known beforehand, and a new random role type makes no sense because there's nothing in the code it could match to. Creating a duplicate role works, but again it makes so sense to have a separate role type for each user since roles should be shared by users.
I've had this error before in Unit tests, not Acceptance tests, when not using Faker / FactoryMuffin, and it's been to do with accessing each entity of the relationship with a different instance of EntityManager. As soon as I got both parts using the same instance, it worked. I don't see how to override the native behaviour here though.
It works (at least in Codeception 4.x) by using a callback for the existing relation:
<?php
public function _beforeSuite()
{
$factory = $this->getModule('DataFactory');
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(AccountHolder::class, [
'firstName' => Faker::firstName(),
'accountRole' => function($entity) use ($em) {
$em->getReference(AccountRole::class)->find(1);
},
]);
}
I've found it here: https://github.com/Codeception/Codeception/issues/5134#issuecomment-417453633

Yii 1.x how to change model name in POST (CActiveForm)

Its posible change model name in post/get?
I have model with large names, like "VerLargeModelName" and many parameters.
It does not fit in GET (query string limit).
Update:
i need just change generated inputs from CActiveForm (change LongModelName[a] to short[a])
You can just change the name. You can do this like this echo $form->textFieldBlock($model,'name',array('name' => 'x["name"]') or whatever you want. You could also create a class (widget) with does this for your.
class MyActiveForm extends CActiveForm {
public function hiddenField($model, $attribute, $htmlOptions = array()) {
if(isset($htmlOptions['shortName'])) {
$htmlOptions['name'] = $htmlOptions['shortName'] . "[".$attribute."]";
unset($htmlOptions['shortName']);
}
return parent::hiddenField($model, $attribute, $htmlOptions);
}
}
You change CActiveFrom from the widget to MyActiveForm. Then use $form->textFieldBlock($model,'name',array('shortName' => 'x'). You could also change the above code to always change to a shortname without the htmlOptions. So that it is always x. However you could not have two form at once in this case. Benifit is that you would not need to add array('shortName' => 'x') to all of them, but just change CActiveFrom to MyActiveForm. So that would save you time, but cost your flexibility (with you might need later on maybe).
You have to create a function offcourse for every input field you want to use from ActiveRecord. The name of the element would become x['name']
In the controller you could simply do $model->attributes = $_POST['x'].

Yii Many to Many Relational Query

Using a many many relational query with users having many clients and clients having many users. Trying to view a record of a particular client for a particular user. And if that client is not associated with that user, redirect to a different page.
// the relation in the client model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'owners'=>array(self::MANY_MANY, 'User','owner_client(owner_id, client_id)'),
);
}
//the relation in the user model
public function relations()
{
return array(
'clients'=>array(self::MANY_MANY, 'Clients','owner_client(owner_id, client_id)'),
);
}
//determine if user can view this client
//client record
$client_record = Clients::model()->findByPk($id);
//many query to find users
$users = $client_record->owners;
//if user id is not found in array, redirect
if (!in_array(Yii::app()->user->id, $users))
{
$this->redirect(array('/site/dashboard'));
}
The above code redirects, even though I know the client is related to the user logged in
When you call $users = $client_record->owners;, what you're getting back is an array of all your user models that are associated with the current client. As a result, you're comparing integers to objects, which means your in_array() condition will always fail.
What I recommend is that you build a conditional query to do your verification check. Something like this should work:
$model = Clients::model()->with(
array(
'owners'=>array(
'select'=>'owner_id',
'condition'=>'user.id = '.Yii::app()->user->id,
),
)
)->findByPk($id);
if ($model === null) {
$this->redirect(array('/site/dashboard'));
}

Yii form and model for key-value table

I have a table which has only two column key-value. I want to create a form which allow user insert 3 pair of key-value settings.
Do I need pass 3 different models to the view? Or is there any possible way to do this?
Check out this link:
http://www.yiiframework.com/doc/guide/1.1/en/form.table
This is considered best form in Yii for updating for creating multiple models.
In essence, for creation you can create a for loop generate as many inputs a you wish to have visible, and in your controller loop over the inputs to create new models.
View File:
for ( $settings as $i=>$setting ) //Settings would be an array of Models (new or otherwise)
{
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::error($setting, "[$i]key");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::error($setting, "[$i]value");
}
Controller actionCreate:
$settings = array(new Setting, new Setting, new Setting);
if ( isset( $_POST['Settings'] ) )
foreach ( $settings as $i=>$setting )
if ( isset( $_POST['Setttings'][$i] ) )
{
$setting->attributes = $_POST['Settings'][$i];
$setting->save();
}
//Render View
To update existing models you can use the same method but instead of creating new models you can load models based on the keys in the $_POST['Settings'] array.
To answer your question about passing 3 models to the view, it can be done without passing them, but to validate data and have the correct error messages sent to the view you should pass the three models placed in the array to the view in the array.
Note: The example above should work as is, but does not provide any verification that the models are valid or that they saved correctly
I'm going to give you a heads up and let you know you could potentially make your life very complicated with this.
I'm currently using an EAV patterned table similar to this key-value and here's a list of things you may find difficult or impossible:
use CDbCriteria mergeWith() to filter related elements on "value"s in the event of a search() (or other)
Filtering CGridView or CListView
If this is just very straight forward key-value with no related entity aspect ( which I'm guessing it is since it looks like settings) then one way of doing it would be:
create a normal "Setting" CActiveRecord for your settings table (you will use this to save entries to your settings table)
create a Form model by extending CFormModel and use this as the $model in your form.
Add a save() method to your Form model that would individually insert key-value pairs using the "Setting" model. Preferably using a transaction incase a key-value pair doesn't pass Settings->validate() (if applicable)
optionally you may want to override the Form model's getAttributes() to return db data in the event of a user wanting to edit an entry.
I hope that was clear enough.
Let me give you some basic code setup. Please note that I have not tested this. It should give you a rough idea though.:
Setting Model:
class Setting extends CActiveRecord
{
public function tableName()
{
return 'settings';
}
}
SettingsForm Model:
class SettingsForm extends CFormModel
{
/**
* Load attributes from DB
*/
public function loadAttributes()
{
$settings = Setting::model()->findAll();
$this->setAttributes(CHtml::listData($settings,'key','value'));
}
/*
* Save to database
*/
public function save()
{
foreach($this->attributes as $key => $value)
{
$setting = Setting::model()->find(array('condition'=>'key = :key',
'params'=>array(':key'=>$key)));
if($setting==null)
{
$setting = new Setting;
$setting->key = $key;
}
$setting->value = $value;
if(!$setting->save(false))
return false;
}
return true;
}
}
Controller:
public function actionSettingsForm()
{
$model = new Setting;
$model->loadAttributes();
if(isset($_POST['SettingsForm']))
{
$model->attributes = $_POST['SettingsForm'];
if($model->validate() && $model->save())
{
//success code here, with redirect etc..
}
}
$this->render('form',array('model'=>$model));
}
form view :
$form=$this->beginWidget('CActiveForm', array(
'id'=>'SettingsForm'));
//all your form element here + submit
//(you could loop on model attributes but lets set it up static for now)
//ex:
echo $form->textField($model,'fieldName'); //fieldName = db key
$this->endWidget($form);
If you want further clarification on a point (code etc.) let me know.
PS: for posterity, if other people are wondering about this and EAV they can check the EAV behavior extention or choose a more appropriate DB system such as MongoDb (there are a few extentions out there) or HyperDex