I'm in the process of building a plugin that includes a behavior and several related models. My goal is to make this as easy as possible for the developer using the behavior. My perfect world has the dev simply attaching the behavior to any relevant models and configuring it.
The behavior interacts directly with one of the models and a hasOne association is being created on the fly, but the other models contain supporting data that is important. What I'd like to do is to have that model pull in its related data by modifying the Containable models.
In short:
MyModel (which actsAs the behavior) gets bound to top level model during the behavior's setup method.
The supporting models are directly associated to the top level model
In MyBehavior::beforeFind, I'd like to ensure that supporting model data is returned without the user having to know to ask for it when calling MyModel::find( ... ).
I haven't found the right keys that will allow me to modify these things at runtime. Maybe it's not even possible given that I want to essentially interact with another behavior (Containable).
Any thoughts would be appreciated.
This code automatically adds some contains to the find before it is run, you just have to make sure that your behavior is attached before the containable behavior or it will not work. The beforeFind callback for a behavior is only run once, so once containable has been called adding something like this does nothing. Took me a while to get it going because of that.
https://github.com/infinitas/infinitas/blob/dev/core/contents/models/behaviors/contentable.php#L65
Related
New to backbone/marionette, but I believe that I understand how to use backbone when dealing with CRUD/REST; however, consider something like results from a search query. How should one model this? Of course the results likely relate to some model(s), but they are not meant to be tied to said model(s).
Part of me thinks that I should use a collection using a model that doesn't actually sync with a data store through the server, but instead just exists as a means of a modeling a search result object.
Another solution could be to have a collection with no models and just override parse.
I assume that the former is preferred, but again I have no experience with the framework. If there's an alternative/better solution than those listed above, please advise.
I prefer having one object which is responsible for both request and response parsing. It can parse the response to appropriate models and nothing more. I mean - if some of those parsed models are required somewhere in your page, there is something that keeps reference to this wrapper object and takes models from response it requires via wrapper methods.
Another option is to have Radio (https://github.com/marionettejs/backbone.radio) in this wrapper - you will not have to keep wrapper object in different places but call for data via Radio.
I have models with the the following relations, defining a situation where users can belong to many groups, and multiple groups can be granted access to a project.
User HABTM Group HABTM Project
I would like to set things up so that any find() done on the Project model will only return results to which the current user has access, based on her group membership.
My first thought is to use the beforeFind() callback to modify the query. However, the two-level association has me stumped. I solved a similar problem (see this question) by rebinding models. However, that was for a custom find method—I don't think that approach will work in a general situation like this where I need to modify arbitrary queries.
Using afterFind() to filter results isn't a good idea because it will confuse pagination (for example) when it doesn't return the right number of records.
Finally, I have a nagging suspicion that I'm trying to re-invent the wheel. The access control I've seen in CakePHP (e.g. Cake ACLs) has been at the controller/action level rather than at the model/record level, but I feel like this should be a solved problem.
Edit: I eventually decided that this was over-complicated and just added a getAccessibleByUser($id) method to my Project model. However, I'm still curious whether it's possible to globally add this kind of restriction to all find() operations. It seems like exactly the sort of thing you'd want to do in beforeFind(), and I suspect (as DavidYell suggests below) that the answer may lie with the Containable behavior.
You should look at the Containable behaviour. If you are using CakePHP 2.x then it comes in the box.
This behaviour allows you to manage the model relations and the data which is returned by them, along with allowing you to pass conditions, such as a group_id into your contain.
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Is there a function like unloadModel in cakePHP that should be called to unload a model that was loaded using loadModel() function?
I found an unload method,
http://api20.cakephp.org/file/Cake/Model/BehaviorCollection.php#method-BehaviorCollectionunload
But it seems to be used for Behavior. Im new to cake. Is there a function like that or does it get automatically unloaded when the called action loses scope?
One more doubt; is using loadModel against MVC's normal conventions? Does it have any adverse effects?
You do not need to unload your model. If you're going to use the model throughout the entire Controller, then use the $uses variable:
public $uses = array('MyModel', 'AnotherModel');
If you're going to just use it in a specific action(s), use loadModel:
$this->loadModel('MyModel');
That's it - no unloading necessary.
And no, it's not against MVC imo and I have seen no adverse effects.
It's VERY common to load a model. Example - most of my projects require a few "homepages" that have greatly-varying data from nearly ever model. In that case, I create a "DashboardsController", which doesn't even have a table - then I load each model when I need to access it's data. (Or with $uses if I'm going to use it's data in all the actions).
no, behaviors and models are two different things.
behaviors add functionality through hooks. Meaning: they alter the way other methods in models work. So if you want to geocode your data automatically, you use a geocoder behavior. Or if you want your results to be decrypted upon find, you add the decrypt behavior.
So there you NEED the option to detach/unload behaviors because you might not want this functionality there at some point.
Models are just access to the database or provide wrapper methods. They don't have to be removed in order for the rest of the site to function as they do not alter the way other methods work.
loadModel is just a way to dynamically load models that are not automatically related. its totally fine to do that from controller actions where you need those models.
I'm writing a plugin which includes a behavior that has a dependency on the ContainableBehavior. In my behavior, I'd like to tweak any query conditions in its beforeFind() callback, but I'm finding that ContainableBehavior::beforeFind() has already been executed so my changes of course are falling on deaf ears, so to speak.
The only solution, as far as I can tell, is to manually change the behavior execution order so that my behavior's beforeFind() method is called before ContainableBehavior::beforeFind(), but I'm having some trouble making that happen.
I don't want to make any assumptions about the apps that may use my plugin so I don't want to create an arbitrary dependency that defines how users have to configure any behaviors they're using. I'd like to just make the necessary adjustment on the fly where I need to make it. What I thought made the most sense is this:
In MyBehavior::setup(), I'm simply trying to detach and reattach the ContainableBehavior so that in the collection of behaviors attached to the parent model, it falls after MyBehavior:
# Assume that a condition checking existence is in place
$model->Behaviors->detach( 'Containable' );
$model->Behaviors->attach( 'Containable' );
If I then dump the list of attached behaviors, I get an array with the attached behaviors ordered the way I want them, but ContainableBehavior::beforeFind() still fires before MyBehavior::beforeFind().
Is there any way to force the execution order of behavior callbacks that isn't a total hack job or that doesn't impose an unrealistic dependency/standard on any apps that may deploy the plugin?
Thanks.
Say I got two Controllers like this Table1sController, and Table2sController.
With corresponding models: Table1sModel, Table2sModel.
In the Table1sController, I got this:
$this->Table1sModel->action();
Say I want to access some data in Table2sModel.
How is it possible to do something like this in Table1sController?
I have tried this in Table1sController:
$this->Table2sModel->action();
But I received an error message like this:
Undefined property: Table1sController::$Table2sModel
There are a few ways to go here.
If your models have defined associations (hasMany, etc.), then you can access that model's methods (assuming you're in Model1Controller) with:
$this->Model1->Model2->method();
If there is no model association between the two models, but you want to be able to use the Model2's methods, you can add an entry in the $uses attribute of model1Controller. See http://book.cakephp.org/2.0/en/controllers.html#components-helpers-and-uses
Finally, if it's a transitory connection (you don't want the overhead of loading other models every time, because you're only rarely going to access model2), check out the manual's section on creating / destroying associations on the fly, at http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Something is inherently wrong with what you are doing.
In any controller, you can specify $uses = array('Table1sModel', 'Table2sModel', 'LolModel') and use each Model you need in your controller. You are not calling another controller to access a Model. Models are for data access, you access the needed ones directly from any controller.
I understand, that many MVC examples are almost always show you one page of one controller with one model which is horribly wrong as 99% of the cases you have one site from one controller using many different parts of different models.
(If you really need to call an action, use $this-requestAction())