Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.
How to leverage the model to access the database
hgtonight
MVP
This discussion was created from comments split from: New Plugin.
Check out the Documentation! We are always looking for new content and pull requests.
Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.
0
Comments
Hi @R_J (and possibly @hgtonight). I've been following this discussion with interest because I sense that Rangerine is going through the same Vanilla learning journey I was going through, and while I may be in a different point on the road, I am still far away from where I want to be. . Your various howto/Example codes were very useful, but there is one more sample skeleton I would benefit from: a plugin that uses a new model to access a new table. There are many suggestions in replies to use models instead of direct sql, but no comprehensive example that demonstrate all the ways to access the data (create/get/insert/update/delete/count?) and the way to code the model that would support these calls (or is it all already done automatically by Vanilla???).
This would be the next stage for me in Vanilla, but would probably help @rangerine and others as well (if I understand this correctly, accessing sql directly in views [possibly in some hooks as well] is really not encouraged, so in a way I already learned bad habits...)
Basically it is quite easy.
You already know how to create a table, don't you? If not, look at the structure.php files in /applications/(dashboard|conversation|vanilla)/settings/structure.php There are numerous examples and you can directly compare the code and the outcome by looking at your database.
So in your example plugin you know that you have a basic setup like that:
There is a model now for our custom table and that model is an instance of
/library/core/class.model.php
. Open up that file and take a look at the functions there. You can use all of them with your model:There is nothing complicated here. Go through class.model.php and you see all you can do.
If you feel comfortable with that, you should take a look at the validation!
You will even find some other goodies, I think. Take a look at addInsertFields() and saveToSerializedColumn(). They are nothing you can use all the time, but they might come handy in one of your projects some day.
@rbrahmson
You would start by creating a new
class.reviewmodel.php
.Note that it extends Gdn_Model. You call the constructor with the name of the table ("Review"):
And that's it, you have created your first model!
This is how you create a new instance:
Now you can use the various methods it inherits from Gdn_Model.
A few examples:
Get a review by primary key.
Delete a review by primary key.
Insert a new review.
Note that you don't need to specify these fields when calling
insert()
:The model will take care of this.
Update a single row:
Update multiple fields of row
123
Update all rows where
InsertUserID = 5
Delete all rows where
InsertUserID = 5 and Recommended = 0
Get all reviews
Get the latest 30 reviews
Get the latest 30 reviews where
InsertUserID = 5
All of the above will work without writing any code specific to your data model.
Once you define more complex relationships between data, you would start extending these functions in your model.
My themes: pure | minusbaseline - My plugins: CSSedit | HTMLedit | InfiniteScroll | BirthdayModule | [all] - PM me about customizations
VanillaSkins.com - Plugins, Themes and Graphics for Vanillaforums OS
Thanks @R_J , very helpful. Very helpful.
Out of curiosity I got a theoretical question. I understand the MVC separation of data/view, etc. that led my desire to move from direct sql to the model. But how does that work with the hooks? What I am saying is that hooks are called from different places in Vanilla, so wouldn't I eventually be making model calls where vanilla does sql, and sql where Vanilla does model calls, practically breaking the separation?
Wow, @Bleistivt - that's great. much appreciated. But my mind wonders about how this works with a normalized sql approach that adds two tables. For example, let's say I want to create a plugin (I really don't) that allows users add a line of "Observation" to the discussion. So I'd have two tables:
DiscussionObservations - ObservationID, DiscussionID
Observations - ObservationID, Observation
Creating these two is shown in your example above (unless there is a way to create two related tables).
But clearly adding a row to Observations require a more complex process:
One can argue that from a programming convenience it would be nice to be able to have one call that does both add and update (and not having to check whether to create a new observation entry sets in the code layer that handles that).
Andof course, the same two-table handling is true for delete.
Last but not least (that may be answered in the Vanilla code that I've yet to inspect), is there a "filtered" getcount (how many, subject to field values)?
For a simple case, you just define the table via the structure method, then pass the table name to the
Gdn_Model
constructor and use that. The base class provides everything you need for CRUD operations (create, request, update, delete).E.g.
For any methods that aren't defined in the base model, you should extend it. I have a bunch of models in Yaga. I would suggest checking out the badges:
Search first
Check out the Documentation! We are always looking for new content and pull requests.
Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.
You should really do so, since the answer is obvious: https://github.com/vanilla/vanilla/blob/master/library/core/class.model.php#L485
Thanks @hgtonight. Very insightful. I wish there was a way to click on both Insightful and Awesome! ;-)
@R_J wrote:
You are absolutely correct, and I'm planning to once I get to a large screen. I'm on my mobile, so I couldn't contain my curiosity/urge to ask...
Concerning your MVC question: everything starts at your controller. There is no difference if an event is fired in a model or a view. You have to consider, what you are doing. Imagine you are taking care for an event that is fired in a view.
If you like to add e.g. a class name based on some dynamic information, you would handle that completely in your controller.
If you would like to add some information from the database, you might find that some
yourModel()->getWhere('DiscussionID' => $args['Discussion']->DiscussionID)
. And if the information you like to add is no trivial, you might want to get that added information from an external view.Even if you hook into a models "beforeSave..." you might decide to present a very, very verbose error message to the user, and you could use a view for that.
It is not imported where an event is fired. The only thing that is important for your decision is what your code is doing.
Once you have multiple tables, you will need to write your own methods, since the framework can't really guess what their relationship is.
One important property of Gdn_Model is that there is always an
SQL
property available, which is the same as callingGdn::sql()
.Of course Models can also talk to each other: You could create a
DiscussionObservationModel
and call it fromObservationModel::insert()
.Yes,
getCount
will also take a$wheres
array:My themes: pure | minusbaseline - My plugins: CSSedit | HTMLedit | InfiniteScroll | BirthdayModule | [all] - PM me about customizations
VanillaSkins.com - Plugins, Themes and Graphics for Vanillaforums OS
I love when I am writing a lengthy reply and two others have posted before I do.
Feels like the community is doing good when this happens.
Search first
Check out the Documentation! We are always looking for new content and pull requests.
Click on insightful, awesome, and funny reactions to thank community volunteers for their valuable posts.
@Bleistivt wrote:
I understand. Though to be honest, the simple case I jotted with two tables for normalization is so "standard" that it would have made sense to create methods for them (even with enforced table name [for the connecting table] convention).
Thanks for all the guidance!
@Bleistivt wrote in Modifying existing table structure - best practices?
Not sure what arguments you refer to. Basically I want to know how does one uses the model to delete a table in the Ondisable hook of a plugin (in a case where one does want to do so rather than leave data behind)?
I do not know how this could be put into a class that matches all thinkable cases. But you surely are free to try your hands on it. I've made a start (code untested and fugly) just trying to think what the delete() method would have to look like...
Ah!, so it's not implemented internally (suspected so from https://github.com/vanilla/vanilla/blob/master/library/database/class.databasestructure.php#L304-L308)
To keep the information in one place I'm pasting @Bleistivt response from another discussion:
This will add a column to the discussion table:
This will remove every column in Discussion table and add the column:
This will drop/delete the Discussion table and then recreate it with only what is specified below:
But all those set() examples have in common, that at the end of the call there is still a table "Discussion".
Gdn::structure()->table('TableName')->drop();
will kill your table once and for all ("Discussion" wouldn't be wise )Folder structure convention question -- is there one for the plugin (the same way design/xxx.css, locale/en-CA/definitions.php, modules/class.somemodule.php) to name and save the model?
Just put that in the same folder as your plugin file.
Thanks!