Basically, I've implemented the HABTM successfully in CakePHP, but the trouble is, I don't understand why it works.
The thing I hate about the CakePHP cookbook is that is tells you what to do but make very little effort to explain the underlying segments of their code.
Essentially, my data model is like this.
Task HABTM Question
I don't understand this code fragment.
$this->set('questions', $this->Task->Question->find('list'))
In particular, what is $this->Task->Question supposed to accomplish?
Also how is the above code link to this code fragment in the view?
echo $this->Form->input('Question');
One thing that is very peculiar is that with the above code fragment, I get a multiple select option.
However, if I change the code to this,
echo $this->Form->input('question');
I get a single select drop down list.
I scoured the entire documentation and still cannot find a satisfactory explanation to my doubts.
Would really appreciate if anyone can clarify this issue for me.
1. Model chaining
When a model has an association to another model (like in your example an HABTM one) then you can call methods of the associated model by chaining it to the current model. This is explained early in Associations and an example of exactly how it works is given at the end of the first section.
When you are someplace in your TasksController normally you would expect that only your Task model would be available. Instead any association described in the Task model is chained to that model in the form of $this->Model1->Model2.
So $this->set('questions', $this->Task->Question->find('list')) means:
From current model Task that you know about, access the associated model Question and then call its find('list') method. Then $this->set the results to the view as variable questions.
2. FormHelper Conventions
When you use a CamelCased single name for field input, like in $this->Form->input('Question'); you are saying to FormHelper that the data contained in the questions variable come from a model named Question with a HABTM association, therefore they should be handled as a multiple select (as HABTM points to such an association).
With a field name of model_id, like in this example question_id, you're asking for a single select (select a single id of the connected model).
With anything else, FormHelper looks at the field definition and takes the decision itself, but of course your can override any default behavior you want using options.
This is explained in detail and I'm surprised you missed both. CakePHP has one of the best documentations available, almost everything you need is there.
Related
I'm struggling to understand the different types of data bindings in ExtJS and I couldn't figure this out:
What is the difference between "hasMany" and "field.reference" when defining associations on two models?
When should I use "hasMany" and when is "reference" better?
For example, if I want to define multiple email addresses to one user, what is the best practice so I can use the email model elsewhere too?
I'm aware that I have 3 questions, but these seem to belong together.
Thanks!
One of the best breakdowns on this I've seen is here:
https://moduscreate.com/blog/associations-in-ext-js-5/
It goes into a lot of detail, and specifically addresses your third question regarding email addresses - because the association is now defined on the child model rather than parent model, you have to have a different email model if you want to attach it to a different parent, i.e. a CustomerEmail class to attach to Customer and an AdminEmail class to attach to Admin.
There's a bit of detail for the reason for the change here:
http://www.sencha.com/blog/deep-dive-into-ext-js-5-data
Declaring associations is another area in Ext JS 5 where we have
reduced boilerplate code requirements. In previous releases, the
hasMany, hasOne and belongsTo configs required that you manually
maintain symmetric declarations on both “sides” of an association.
This is no longer the case. You can declare an association in either
of the associated classes (though typically on the “many” side).
I'm using CakePHP2.3 and my app has many associations between models. It's very common that a controller action will involve manipulating data from another model. So I start to write a method in the model class to keep the controllers skinny... But in these situations, I'm never sure which model the method should go in?
Here's an example. Say I have two models: Book and Author. Author hasMany Book. In the /books/add view I might want to show a drop-down list of popular authors for the user to select as associated with that book. So I need to write a method in one of the two models. Should I...
A. Write a method in the Author model class and call that method from inside the BooksController::add() action...
$this->Author->get_popular_authors()
B. Write a method in the Book model class that instantiates the other model and uses it's find functions... Ex:
//Inside Book::get_popular_authors()
$Author = new Author();
$populars = $Author->find('all', $options);
return $populars;
I think my question is the same as asking "what is the best practice for writing model methods that primarily deal with associations between another model?" How best to decide which model that method should belong to? Thanks in advance.
PS: I'm not interested in hearing whether you thinking CakePHP sucks or isn't "true" MVC. This question is about MVC design pattern, not framework(s).
IMHO the function should be in the model that most closely matches the data you're trying to retrieve. Models are the "data layer".
So if you're fetching "popular authors", the function should be in the Author model, and so on.
Sometimes a function won't fit any model "cleanly", so you just pick one and continue. There are much more productive design decisions to concern yourself with. :)
BTW, in Cake, related models can be accessed without fetching "other" the model object. So if Book is related to Author:
//BooksController
$this->Book->Author->get_popular_authors();
//Book Model
$this->Author->get_popular_authors();
ref: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#relationship-types
Follow the coding standards: get_popular_authors() this should be camel cased getPopularAuthors().
My guess is further that you want to display a list of popular authors. I would implement this using an element and cache that element and fetching the data in that element using requestAction() to fetch the data from the Authors controller (the action calls the model method).
This way the code is in the "right" place, your element is cached (performance bonus) and reuseable within any place.
That brings me back to
"what is the best practice for writing model methods that primarily
deal with associations between another model?"
In theory you can stuff your code into any model and call it through the assocs. I would say common sense applies here: Your method should be implement in the model/controller it matches the most. Is it user related? User model/controller. Is it a book that belongs to an user? Book model/controller.
I would always try to keep the coupling low and put the code into a specific domain. See also separation of concerns.
I think the key point to answer your question is defined by your specifications: "... popular authors for the user to select as associated with that book.".
That, in addition to the fact that you fetch all the authors, makes me ask:
What is the criteria that you will use to determine which authors are popular?
I doubt it, but if that depends on the current book being added, or some previous fields the user entered, there's some sense in adopting solution B and write the logic inside the Book model.
More likely solution A is the correct one because your case needs the code to find popular authors only in the add action of the Book controller. It is a "feature" of the add action only and so it should be coded inside the Author model to retrieve the list and called by the add action when preparing the "empty" form to pass the list to the view.
Furthermore, it would make sense to write some similar code inside the Book model if you wanted, e.g., to display all the other books from the same author.
In this case you seem to want popular authors (those with more books ?), so this clearly is an "extra feature" of the Author model (That you could even code as a custom find method).
In any case, as stated by others as well, there's no need to re-load the Author model as it is automatically loaded via its association with Books.
Look out for Premature Optimization. Just build your project till it works. You can always optimize your code or mvc patterns after you do a review of your code. And most important after your project is done most of the time you will see a more clear or better way to do it faster/smarter and better than you did before.
You can't and never will build a perfect mvc or project in one time. You need to find yourself a way of working you like or prefer and in time you'll learn how to improve your coding.
See for more information about Premature Optimization
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
I have a model Fix with a relationship HABTM Device model.
Device model has a belongsTo to Device_type model, like this, for only getting the device type name:
var $belongsTo = array('Device_type'=>array('fields'=>'name'));
So, I need every Fix, its devices and its Device_types. When I make a Fix->find('all', array('recursive' => 2))
I expect to get every Device related to Fix (this works ok) and ALSO for every device, its Device_type.name (which is not working).
This is what I get instead for every Device in the result (an empty array):
["Device_type"]=>
array(0) {
}
Besides this, when I make this query for testing: Fix->Device->find('all'), it returns the current Device_type.names for every device related to fixes, which means models are related propertly.
Any help? Thanks.
First thing I notice, is your naming conventions should be lower case under_score for your multi-word table names.
And its also apparent your relationships most likely are not set up correctly if you are not getting the data on a recursive 2.
It's kind of hard to make more judgement with your limited code.
If you are new to CakePHP and MVC, it would be really best to follow the blog tutorial on the CakePHP web site. From that, you will learn the basics of building a CakePHP app and in the end have working application which you can "play" with and modify to learn how MVC ticks. You can experiment and learn a lot from this : )
There is a question very similar to this but I wanted to ask it in a different way.
I am a very customized guy, but I do like to take shortcuts at times. So here it goes.
I do find these two classes very similar although one "helps" the programmer to write code faster or have less code/repeating code. Connecting Models to Forms sounds like an obvious thing to do. One thing that is not particularly clear in the docs using a ModelForm. What happens if you need to add extra fields that are not in the Model or some way connected to another Model?
I guess you could subclass that out and make it work, but does that really help you save time than just manually doing it with a Form?
So next question may not have a definite answer if I do subclass it out, and use ModelForm. Is ModelForm particularly faster than Form? Does it still use the same Update techniques or is binding significantly faster in one or the other?
If you want a form across two models, you got a couple options:
1) create two modelforms, save each individually when posted, and if one of the two depends on the other (i.e. foreignkey), set that in your view before saving.
2) try Django's inline formset: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view
3) Add non-model fields to your modelform. On a ModelForm, you can add fields that are not tied to your model. They are available in cleaned_data as any other field would, be but are simply ignored when the model is saved.
One advantage that ModelForm's have over Form's is you can specify the ordering of fields (searching for how to order Form fields brought to your post incidentally). Obvious other advantages are you don't have to rewrite your model saving code