Traversing relationships (simple) - atk4

Bear with me please, I'm still learning.
I have 4 Models as so:
class Model_Users extends Model_Table {
public $table="users";
function init(){
parent::init();
$this->addField('name')->mandatory('Enter Name');
$this->addField('email')->mandatory('Enter E-Mail');
$this->addField('phone')->mandatory('Enter Phone');
$this->addField('password')->type('password')->mandatory('Enter Password');
$this->addField('is_superadmin')->type('boolean');
$this->addField('is_employee')->type('boolean');
$this->addField('is_manager')->type('boolean');
$this->hasMany('companies');
}
}
class Model_areas extends Model_Table {
public $entity_code='areas';
function init(){
parent::init();
$this->addField('name');
$this->addField('description')->type('text');
//$this->addField('companies_id')->refModel('Model_companies');
$this->hasOne('companies','companies_id','name')->mandatory(true);
$this->hasMany('sites');
}
}
class Model_areas extends Model_Table {
public $entity_code='areas';
function init(){
parent::init();
$this->addField('name');
$this->addField('description')->type('text');
//$this->addField('companies_id')->refModel('Model_companies');
$this->hasOne('companies','companies_id','name')->mandatory(true);
$this->hasMany('sites');
}
}
class Model_sites extends Model_Table {
public $entity_code='sites';
function init(){
parent::init();
$this->addField('name');
$this->addField('description')->type('text');
$this->addField('qrcode');
//$this->addField('Company Name','areas_id')->refModel('Model_companies','name');
$this->hasOne('areas','areas_id','name');
}
}
I have the "sites" model in a simple crud. It is successfully pulling the relevant hasOne record from "areas". I have two questions:
1) How do I change the column names for the joined areas column? It just says "Areas", whereas I want it to be "Area Name"
2) And the more complex one: How can I perform something like a grid->addColumn to the resulting CRUD (or would it have to be a grid?) that would pull the company name linked to the area in areas_id? Its all 1 to many relationships down the line. Company has multiple areas. Areas has multiple sites. I want to add the company name to the CRUD view of Sites.
You can see in the commented lines some of my minor attempts at accomplishing this. Then I realized I'm missing something big. I should be able to keep this model simple and simply traverse the relationships..
Thank you for your help.
Back to these tutorial videos.
Edit: OK the column name I figured out. ->caption('Blah'). Still can't figure out the traversal :(

1) Try:
$this->hasOne('areas','areas_id','name')->caption('Area Name');
2) Slightly simplified your models and here it is. I admit - I didn't test this, but it should work:
<?php
class Model_Company extends Model_Table {
public $table = 'company';
function init(){
parent::init();
$this->addField('name');
$this->addField('description');
$this->hasMany('Area');
}
}
class Model_Area extends Model_Table {
public $table = 'area';
function init(){
parent::init();
$this->addField('name');
$this->addField('description');
$this->hasOne('Company', 'company_id', 'name');
$this->hasMany('Site');
}
}
class Model_Site extends Model_Table {
public $table = 'site';
function init(){
parent::init();
$this->addField('name');
$this->addField('description');
$this->hasOne('Area', 'area_id', 'name');
// join area and company tables
$j_area = $this->leftJoin('area', 'area_id');
$j_company = $j_area->leftJoin('company', 'company_id');
// add fields from joined tables to this model
$j_area->addField('area_name', 'name');
$j_company->addField('company_name', 'name');
}
}
Basic idea - use joins. ->leftJoin will not create joined records, but ->join will create them. Just for reporting (grid, crud etc.) you're fine with leftJoin.
P.S.
Define some kind of coding rules for yourself. For example, when you use uppercase, lowercase, camel-case etc for class names, filenames. Otherwise you'll sooner or later run into problems when moving on *NIX systems.
Good example is to name all classnames with first letter in upper case. Don't forget that your respective file names should exactly match your classname - also letter case.
Page classnames I like to name all in lowercase, because their name is used in URLs and then it looks better lowercased. But that's just me :)
One more. If you're working on new project or are just learning ATK, then stick to newest development version (available on github). There are quite many things which have changed since recording of tutorials. For example $entity_code is deprecated - use $table instead. refModel is obsolete too I guess etc.
If you want to be successful with ATK, then you have to look regularly into ATK source code to understand it better. Also there are some useful comments :)
Good luck!

Related

ATK CRUD with one:many relationship

I'm a beginner and searched documentation, but can't find this how to do this:
I have two tables, admin and application. Admin can have many applications.
ADMIN:
class Model_Admin extends Model_Table {
public $entity_code='admin';
function init(){
parent::init();
$this->addField('name');
$this->addField('email');
$this->addField('password')->type('password');
$this->addField('active')->type('boolean')->system(true);
$this->addField('super')->type('boolean')->system(true);
$this->addField('created')->type('timestamp')->defaultValue($this->dsql()->expr('now()'))->system(true);
$this->addField('updated')->type('timestamp')->system(true);
$this->hasMany('Application','admin_id');
//$this->hasOne('Application');
$this->addHook('beforeSave',function($m){
$m['updated']=$m->dsql()->expr('now()');
});
}
}
APPLICATION:
class Model_Application extends Model_Table {
public $entity_code='application';
function init(){
parent::init();
$this->addField('name');
$this->addField('fbid');
$this->addField('fbsecret');
$this->addField('active')->type('boolean')->system(true);
$this->addField('created')->type('timestamp')->system(true);
$this->addField('updated')->type('timestamp')->system(true);
}
}
First question, when I generate SQL code (/generate.html) it doesn't produce anything for one to many relationship.
Second, on a page I add CRUD:
$this->add('CRUD')->setModel('Admin');
But there is no hint for any one to many. I would expect it on the add button form, but also there is nothing?
What I want is, that I can add admin, and select which applications belong to it?
in Model_Application
$this->hasOne('Admin');
on Page
$this->add('CRUD')->setModel('Application');
In Edit form of CRUD you will see dropdown with all admins and you'll be able to set admin to each application

Database One-to-Many with two foreign key fields in Laravel

I have been trying to define some Database schema to use the laravel framework. I want to model a Football match. The first step I wanted to do is to define the Entity Relationship diagram, but I found this (which I thought would be quite trivial) to be confusing in some aspects.
First, the obvious approach is to say that a Match is related with two Teams, and a Team is related to any number of Matches. So, we would have a "Many to Many" relationship.
But the implementation of a many to many relation is to have two tables and an intermediate table to relate both entities. I think this would be too much, when I know that a Match will always have two Teams and simply having two columns (local_id and visitant_id) with foreign keys to the Teams table would be enough. Plus, I want to be able to do:
Match::find(1)->local() or Match::find(1)->visitant();
So, thinking on this I am implementing a "One to Many" relation, but with this I have another issue. To retrieve all the matches a Team has played I would like to do:
Team::find(1)->matches();
But I cannot do this because I can only specify one key column when defining the matches() method in eloquent (by default it would be team_id, but it should be visitant_id and local_id).
After some more digging into the source code I found there is a way to actually keep my database schema as it is and achieve what I want (at least in Laravel 4). I posted my problem in github and Taylor Otwell (creator of the framework) gave me the correct answer: https://github.com/laravel/framework/issues/1272
Quoting him, it should be as easy as this:
class Team extends Eloquent {
public function allMatches()
{
return $this->hasMany('Match', 'visitant_id')->orWhere('local_id', $this->id);
}
}
And then...
$team = Team::find(2);
$matches = $team->allMatches;
This is one of those famous database design problems. Friendship relationships, for instance, suffer from that same difficulty. Since you are using Eloquent, I would suggest you to stick with many to many approach and have an extra boolean column local on your intermediate table
class Match extends Eloquent {
public $includes = array('team'); // Always eager load teams
public function teams() {
return $this->has_many_and_belongs_to('team')->with('local');
}
public function get_local() {
foreach ($this->teams as $team) {
if ($team->pivot->local) return $team;
}
}
public function get_visitant() {
foreach ($this->teams as $team) {
if (!$team->pivot->local) return $team;
}
}
}
class Team extends Eloquent {
public function matches() {
return $this->has_many_and_belongs_to('match')->with('local');
}
// I'm doing separate queries here because a team may have
// hundreds of matches and it's not worth looping through
// all of them to retrieve the local ones
public function matches_as_local() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 1);
}
public function matches_as_visitant() {
return $this->has_many_and_belongs_to('match')->with('local')
->where('pivot_local', '=', 0);
}
}
Obs:
The method has_many_and_belongs_to(...)->with('field') has nothing to do with eager loading. It tells Eloquent to load the intermediate table column field and put that in the pivot.
Usage:
$match = Match::find(1);
$match->local; // returns local team
$match->visitant; // returns visitant team
$team = Team::find(1);
$team->matches; // returns all matches
$team->matches_as_local; // ...
$team->matches_as_visitant; // ...
foreach ($team->matches as $match) {
if ($match->pivot->local) {
// put nice local icon here
} else {
// put nice visitant icon here
}
}

How to calculate and store in DB some field values on Model save hook?

What I want to accomplish is the following.
I need to save in database some field which value should be automatically calculated using other values in its Model (or related models). I guess I should do this using one of these model hooks - beforeInsert, beforeModify, afterInsert, afterModify, but how exactly I should do this?
Also, this field should be not changeable, but visible in UI forms/grids.
For example,
class Model_Address extends Model_Table{
public $table='address';
function init(){
parent::init();
$this->hasOne('Territory');
$this->addField('street');
$this->addField('house');
$this->addField('number');
$this->addField('name')->readonly(true); // this should be calculated on save
$this->addHook('beforeModify',$this);
}
// How to write this to set name=street+house+number+territory.name ???
function beforeModify($m){
$ter_name = $m->ref('Territory')->get('name');
$m['name'] = $m['street'].' '.$m['house'].' '.$m['number'].', '.$ter_name;
return $m;
}
}
Edit:
Will this solution will be correct? It looks that it's working, but I'm not sure yet.
class Model_Address extends Model_Table{
public $table='address';
function init(){
parent::init();
$this->hasOne('Territory');
$this->addField('street');
$this->addField('house');
$this->addField('number');
$this->addField('name')->readonly(true); // this should be calculated on save
$this->addHook('beforeSave',$this);
}
function beforeSave($m){
$t=$m->ref('territory_id');
if($t->loaded()){
$m=set('name',$m->get('street').' '.$m->get('house').' '.$m->get('number').', '.$t->get('name'));
}
return $this;
}
}
This looks like a correct solution. At least one of them :)
class Model_Address extends Model_Table{
public $table='address';
function init(){
parent::init();
$this->hasOne('Territory');
$this->addField('street');
$this->addField('house');
$this->addField('number');
$this->addField('name')->readonly(true); // this should be calculated on save
$this->addHook('beforeSave',$this);
}
function beforeSave($m){
$t=$m->ref('territory_id');
if($t->loaded()){
$m=set('name',$m->get('street').' '.$m->get('house').' '.$m->get('number').', '.$t->get('name'));
}
return $this;
}
}
I could use afterLoad hook too, but I have decided to better save this concatenated value in database to minimize load time calculations.
Also when you use afterLoad hook you should be aware that it uses lazy load. That is, it loads only these model fields which are asked and not all of them.
For example, if you have grid with only columns street, house and name, then afterLoad will load only these three fields. Flat, territory_id and number will be empty no matter what. So desired functionality will not fully work that way.
Same applies to using addExpression + callback field.
in the previous version (4.1) the hooks are already in the framework and the functions just do nothing when called unles you override them in the model.
i assume this is the same in 4.2 that the beforeModify, beforeInsert and beforeDelete just need defining in the model and add whatever logic you need in there - this includes adding other models that might have calculated fields or php to do some calcs and dsql statements to populate.

cakephp behavior afterFind not called on related models

I am using an afterFind function to modify data from a find function. It works fine. If I move the afterFind function into a behavior (in a plugin) it still works, but only when the model of interest is the primary model, i.e. it isn't called when the model belongsTo another model. Is there any way round this? I'm using cake 1.3.4. This is a simplified version of the behavior:
class ChemicalStructureBehavior extends ModelBehavior {
function afterFind(&$model, $results, $primary) {
foreach ($results as &$unit) {
// format chemical formula (with subscripts)
$unit[$model->alias]['chemical_formula_formatted'] = preg_replace('/([0-9]+)/i', '<sub>$1</sub>', $unit[$model->alias]['chemical_formula']);
}
return $results;
}
}
I guess I'd do one of 2 things depending on how generically the code block applies:
Universal version: not use a behavior, but include your method block in AppModel::afterFind
Surgical version: use a behavior and attach it to each model that needs to share the functionality.
A behavior isn't supposed to work on related models, for example, if you have this two models:
app/models/product.php
<?php
class Product extends AppModel{
var $belongsTo = array('Category');
var $actsAs = array('SomeBehavior');
}
?>
app/models/category.php
<?php
class Category extends AppModel {
var $hasMany = array('Product');
}
?>
SomeBehavior will only be executed when calling methods for Product, because the behavior isn't associated with Category
http://github.com/m3nt0r/eventful-cakephp
Set up an event that does the formatting - trigger that event however you need to. Easy as Cake.

Generating pages from a database

I'm looking for some help understanding how to generate pages from a database to create a catalog of items, each with different URLs. All I can seem to find through google are products that will do this for me, or full e-commerce solutions. I don't want a shopping cart! Just an inventory.
Also, perhaps someone could recommend their favorite/the best simple login solution.
Thank you so much for your time and any help, suggestions, comments, solutions.
I just posted a thorough solution to another question that is very closely-related to this question. I'll re-post it here for your convenience:
I would suggest using some of the MVC (Model, View, Controller) frameworks out there like KohanaPHP. It is essentially this. You're working in a strictly Object-Oriented environment. A simple page in Kohana, build entirely from a class would look like this:
class Home_Controller extends Controller
{
public function index()
{
echo "Hello World";
}
}
You would then access that page by visiting youur url, the class name, and the method name:
http://www.mysite.com/home/ (index() can be called after home/, but it's implicit)
When you start wanting to bring in database-activity, you'll start working with another Class called a Model. This will contain methods to interact with your database, like the following:
class Users_Model extends Model
{
public function count_users()
{
return $this->db->count_records('users');
}
}
Note here that I didn't write my own query. Kohana comes with an intuitive Query Builder.
That method would be called from within your Controller, the first class that we mentioned at the beginning of this solution. That would look like this:
class Home_Controller extends Controller
{
public function index()
{
$usersModel = new Users_Model;
$userCount = $usersModel->count_users();
echo "We have " . $userCount . " users!";
}
}
Eventually, you'll want more complicated layouts, which will involve HTML/CSS/Javascript. At this point, you would introduce the "Views," which are just presentation layers. Rather than calling echo or print from within the Controller, you would load up a view (an HTML page, essentially) and pass it some variables:
class Home_Controller extends Controller
{
public function index()
{
$myView = new View("index");
$usersModel = new Users_Model;
$userCount = $usersModel->count_users();
$myView->userCount = $userCount;
$myView->render(TRUE);
}
}
Which would load the following "View"
<p>We have <?php print $userCount; ?> users!</p>
That should be enough to get you started. Using the MVC-style is really clean, and very fun to work with.
There's a lot of tools out there for generating a web interface around a data model. I find Django pretty easy to use. Based on its popularity, I'm sure that Ruby on Rails is another viable option.

Resources