Using Nested Intermediate Tables - orm

I'm working with Laravel 4 and trying to setup a table structure for handling the following problem.
I have 3 tables: Players, Teams & Seasons
For each season, I will have multiple teams assigned and each team will have multiple players assigned.
I need to maintain historical data for each season, so I can't just connect the tables directly because changing the base player/teams tables would affect all seasons that way.
I connected the Seasons -> Teams table by using an intermediate table teams_in_season as follows:
class Season extends \Eloquent
{
public function teams()
{
return $this->belongsToMany('Team', 'teams_in_season');
}
}
That works as expected. The issue comes when I want to setup the player assignment. Naturally, I want to relate the teams to players so my line of thinking is that I need to create an intermediate table off of another intermediate table. Ex:
seasons -> teams_in_season -> players_in_teams -> players
If I went Seasons -> Players, that would work except that I wouldn't be able to eager load it that way.
seasons -> players_in_season -> players
$season->teams->players->get();
Essentially, the way the user enters data is to create a season, assign teams, assign players to teams, and then eventually add scoring. All data entered needs to be maintained and therefore the intermediate tables are necessary.
So, here's my question(s):
Can I nest/chain intermediate tables like this?
Is there a better way I can setup what I want to achieve?

This is a difficult problem. You will need to associate the players to the pivot table between teams and seasons. For that reason I would probably set that up as it's own model.
Relationships
Season hasMany SeasonTeam
SeasonTeam belongsTo Team
SeasonTeam belongsToMany Player
Eager Loading
Here is how you would list all the teams with their player roster using strictly the relationship methods and eager loading. This is 4 queries.
$season = Season::with('seasonTeams.players', 'seasonTeams.team')->find(1);
foreach ($season->seasonTeams as $seasonTeam)
{
echo $seasonTeam->team->name;
foreach ($seasonTeam->players as $player)
{
echo $player->name;
}
}
Joins
I won't draw out the details here but you could also use joins in the query builder to pull players that were part of a team playing in a given season. Check out the link below.
http://four.laravel.com/docs/queries#joins

I attempted a few different ideas (including Colin's answer), but decided to go a different route here. The main thing is that I felt that using $season->seasonTeams->teams() was a bit difficult to read. Also, attempting to chain an intermediary table off of another intermediary table felt a bit odd as well. The solution I came up with was this:
For teams, I stayed with my original design of seasons -> teams_in_seasons -> teams which allows me to lookup the instanced teams using $season->teams() and any pivot data necessary therein using the following relationship for the seasons model:
public function teams()
{
return $this->belongsToMany('Team', 'teams_in_season');
}
and inverse on teams:
public function seasons()
{
return $this->belongsToMany('Season', 'teams_in_season');
}
For players, instead of chaining off of the existing pivot table teams_in_season, I decided instead to connect the pivot table players_in_teams to both the seasons and teams table. To connect these together, I decided to use the Query Builder to build my own queries as follows on the Team model:
public function players($season_id, $team_id)
{
return DB::table('players')
->select(DB::raw('players.*'))
->join('players_in_team', 'players.id', '=', 'players_in_team.player_id')
->where('season_id', '=', $season_id)
->where('team_id', '=', $team_id)
->orderBy('last_name');
}
this will allow me to eager load the players for a given season team as follows:
$team->players($season->id, $team->id)->get()
This is a bit unorthodox, perhaps, but it works well for my purposes. I couldn't find another method that made as much sense logically to me. It also has the side effect of allowing me to add a method onto the Seasons model that will allow me to eager load all of the players for a given season such as:
Season::find(1)->players

Related

Laravel - HasManyThrough in a different way

In the laravel docu is the following example for has-many-through relationship: Country has many posts through user.
But in my case i need something like this:
User has many roles through customer.
I would like to have the has-many-through relationship from user to role.
Is there a way to get it working this way?
EDIT
I solved my problem with a collection method:
public function roles() {
$roles = $this->hasMany('SC\Customer')->get()->each(function($item) {
return $item->role();
});
return $roles;
}
It all depends on what you're trying to accomplish.
In most cases where someone needs to define roles, creating a separate pivot table that joins tables would be a good way to do it.
Luckily, Laravel's documentation covers this pretty well, you will likely find the answer in this section: http://laravel.com/docs/5.1/eloquent-relationships#many-to-many
Keep reading through that section and all the way down, Taylor goes deeper into the subject and various solutions for different scenarios..
Let's say you have many users and many roles (admin, user, moderator) etc. Many of these users can have different roles and perhaps other attributes, such as if they are a customer or not. You start by creating the user, roles tables (or whatever else needed).
You can then create a joining pivot table, let's say role_user. In it, you can specify foreign keys like user_id which represents id from the user table and role_id which represents id from the role table.
By specifying the foreign keys, you attach a user to a role and then to whatever attribute you would like in another column. So if you have another column in the pivot table that is "customer", which can 1/0 (is customer / is not customer) or maybe you want to add another foreign key which can be customer_id that represents id column in a customer table.
It all depends on what you're trying to accomplish, but it's easy to get snowed in. Using a UML software to model it all out before actually coding to get a abstract overview, could be a good idea. I would recommend StarUML.
Then in your User model you can just bind them by specifying the relationships, something like this:
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

Eloquent ORM Relationships - is the default "no relationship"?

I'm building an application to keep travel booking information, and wanted to ask about a little thing before I embark on using Laravels ORM.
Say a group of travellers have booked a ticket, that's great. They would also like to buy hotel bookings from us as well. Buying hotel bookings is an option, groups don't have to buy hotel bookings.
So I have the following set up as my database:
Table Group:
Group_ID
Name
etc
Table Hotel_Bookings:
Group_ID
Hotel name
Number of people
Number of rooms
Type of rooms
etc
I know I can define a relationship such as:
class Group extends Eloquent {
public function hotel_bookings()
{
return $this->hasOne('hotel_bookings');
}
}
and
class Hotel_bookings extends Eloquent {
public function group()
{
return $this->belongsTo('group');
}
}
But what if a group doesn't decide to book a hotel with us? In that case there would be no hotel booking row associated with that group in the hotel_bookings table. Would the ORM be aware of there being no such row in the table, and would it spit out an error or do nothing?
Is a one to one relationship where there is one row in the first table related to one row in the second?
Hopefully you can tell that I'm new to ORMs, I'd like to know more about this if it would make my database querying life easier from the beginning.
A "hasOne" or "hasMany" doesn't necessary mean you must have related data, it just means you can.
In this case, entries in your Group table would work fine whether there's a Hotel_booking entry attached to that entry or not.
Hotel_booking, on the other hand, is a "belongsTo". Assuming your database schema reflects this relationship as expected, a Hotel_booking entry would require a related Group entry.
If I understand what you're saying you have a Group that can have an optional Hotel_bookings (check Laravel naming conventions, use HotelBooking or Hotelbooking). The way that you've set up your relationships will not cause any errors. The ORM doesn't force any sort of relationship you should be setting that in your migration.
Schema::table(...function(Blueprint $table)
{
...
$table->integer('group_id')->unsigned();
$table->foreign('group_id')->references('id')->on('groups');
This is what enforces that a HotelBooking must belong to a Group. Eloquent doesn't automatically make the query for you to retrieve a Group's HotelBooking, it requires that you do it using something like:
$group = Group::with('hotel_bookings')->first();
This will not throw errors either, $group->hotel_bookings will simply be empty.

CreateAlias and NHibernate joins on classes w/ no mapped relationship

I'm beginning to wonder if I am in fact too dumb to use NHibernate. I'm currently using FluentNHibernate to create simple db mappings and that's worked well for us. When dealing w/ our various classes in isolation, we're able to read and write, perform updates, etc. to all of those classes. My problem however is in trying to build up a query that involves anything more complex than filtering upon fields of the entity type. A perfect example is illustrated below--
The pertinent portions of the mappings:
public class UserMap : ClassMap<User> {
Id(u => u.Id).Column("UserID").GeneratedBy.Identity();
//other non-pertinent fields
}
public class RoleMap : ClassMap<Role> {
Id(r => r.Id).Column("RoleId").GeneratedByIdentity();
//snip
}
public class RoleMapMap : ClassMap<RoleMap> {
Id(rm => rm.Id).Column("RoleMapId").GeneratedByIdentity();
Map(rm => rm.UserId);
Map(rm => rm.RoleId);
//snip
}
The intent is to generate a query w/ the Criteria API to retrieve all users of a specific role--at a high level, filter rolemap based on a specific role ID, then join to Users, and return only those users.
Attempted with following, but my usage of CreateAlias is obviously flawed, as the runtime exception basically tells me that it has no idea what "RoleMap" in the below is as it relates to the User object.
var criteria = session.CreateCriteria<User>().
CreateAlias("RoleMap", "rm").
Add(Expression.Eq("rm.UserId", "UserId")).
Add(Expression.Eq("rm.RoleId", 99)).
SetResultTransformer(new
DistinctRootEntityResultTransformer());
var users = criteria.List<User>();
Can someone point me in the right direction? I'd prefer not to edit the underlying objects to expose collections--(e.g. a User.Roles[] collection) as there's cases where we specifically have tables used solely for joins but we don't want floating to the middle tier. So learning how to join isolated classes is going to matter to us.
Your mapping contains no way to navigate from User to RoleMap, yet that is what you are trying to do in your Criteria API call. You have multiple options. Here are a couple:
1) Allow User to navigate to RoleMap in your mapping. This is the easiest and how it's normally done.
2) Use two queries, one to get a list of UserIds based on the RoleMap to Role relationship and then a second query to get all the Users for those UserIds.
You say you don't want a User.Roles collection in your middle tier, but NHibernate should exist in your data layer, not necessarily your business layer. You can allow NHibernate to know about User.Roles while effectively hiding it from your business layer.
Joining isolated classes isn't really what ORMs are built for. ORMs are built for joining related classes. Related classes are generally mapped to related tables at the database level. To join isolated classes, you are going to need to do things like option 2 above where you run multiple queries and/or work around the lack of a relationship in custom code.

Accessing the join table in a hql query for a many-to-many relationship in grails

I have 2 domain classes with a many-to-many relationship in grails: decks and cards.
The setup looks like this:
class Deck {
static hasMany = [cards: Card]
}
class Card {
static hasMany = [decks: Deck]
static belongsTo = Deck
}
After I delete a deck, I want to also delete all cards which no longer belong to a deck. The easiest way to accomplish this is to write something like the following sql:
delete from card where card.id not in(select card_id from deck_cards);
However, I can't figure out how to write a HQL query which will resolve to this SQL because the join table, deck_cards, does not have a corresponding grails domain class. I can't write this statement using normal joins because HQL doesn't let you use joins in delete statements, and if I use a subquery to get around this restriction mySQL complains because you're not allowed to refer to the table you're deleting from in the "from" section of the subquery.
I also tried using the hibernate "delete-orphan" cascade option but that results in all cards being deleted when a deck is deleted even if those cards also belong to other decks. I'm going crazy - this seems like it should be a simple task.
edit
There seems to be some confusion about this specific use of "decks" and "cards". In this application, the "cards" are flashcards and there can be tens of thousands of them in a deck. Also, it is sometimes necessary to make a copy of a deck so that users can edit it as they see fit. In this scenario, rather than copying all the cards over, the new deck will just reference the same cards as the old deck, and if a card is changed only then will a new card be created. Also, while I can do this delete in a loop in groovy, it will be very slow and resource-intensive since it will generate tens of thousands of sql delete statements rather than just 1 (using the above sql). Is there no way to access a property of the join table in HQL?
First, I don't see the point in your entities.
It is illogical to make a card belong to more than one deck. And it is illogical to have both belongTo and hasMany.
Anyway, Don't use HQL for delete.
If you actually need a OneToMany, use session.remove(deck) and set the cascade of cards to REMOVE or ALL.
If you really want ManyToMany, do the checks manually on the entities. In pseudocode (since I don't know grails):
for (Card card : deck.cards} {
if (card.decks.size == 0) {
session.remove(card);
}
}
I won't be answering the technical side, but challenging the model. I hope this will also be valuable to you :-)
Functionally, it seems to me that your two objects don't have the same lifecycle:
Decks are changing : they are created, filled with Cards, modified, and deleted. They certainly need to be persisted to your database, because you wouldn't be able to recreate them using code otherwise.
Cards are constant : the set of all cards is known from the beginning, they keep existing. If you delete a Card once in the database, then you will need to recreate the same Card later when someone needs to put it in a Deck, so in all cases you will have a data structure that is responsible for providing the list of possible Cards. If they are not saved in your database, you could recreate them...
In the model you give, the cards have a set of Decks that hold them. But that information has the same lifecycle than the Decks' (changing), so I suggest to hold the association only on the Deck's side (uni-directional Many-To-Many relationship).
Now you've done that, your Cards are really constant information, so they don't even need to be persisted into the database. You would still have a second table (in addition to the Deck), but that Card table would only contain the identifying information for the Card (could be a simple integer 1 to 52, or two values, depending what you need to "select" in your queries), and not other fields (an image, the strength, some points etc...).
In Hibernate, these choices turns the Many-To-Many relationship to a Collection of values (see Hibernate reference).
With a Collection of Values, Card is not an Entity but a Component. And you don't have to delete them, everything is automatically taken care by Hibernate.

Need to assign multiple attributes to records - should I opt for one table or multiple tables?

I am using an MVC pattern to represent a table with many records of music albums. To each music album there are attributes (genre, artist, ratings, etc). Each attribute can have multiple values (so the genre of one album can be both "pop" and "latin", for example). I want to represent those attribute values using table(s).
So there are two basic approaches that I can think of. I wonder which approach is better?
Have a separate table for each attribute (e.g., GENRE, ARTIST). The columns in each table will be album_id and attr_value.
Have a single table ATTRIBUTES which will also include, in addition to the album_id and value, the attribute name ("genre", "artist", ...).
Typically I would opt for method 1 (relational DB and all that), but if I choose method 2, my thought is that when I want to add a new attribute, I don't need to create a new model, controller, and view.
Any opinions?
This isn't so much an MVC issue its a Normalization question.
There is a process for normalizing your database and establishing entities (tables). The two typical forms are 3rd Normal Form or the Boyce-Codd Normal form. Searching for either should provide ample information. Now that said there are a few other designs you can use other than standard normalization. It all depends on how you want to balance errors (update/insert) and performance. Many people have been advocating for non-relational designs (nosql, couchdb, and folks who believe that the old concerns about corruption due to empty columns are unneeded today). Then there's the reality that serialized arrays open up the possibility of hybrid designs. You seem, to be more debating EAV (entity attribute value) vs additional table. EAV has a reputation of being a slower design, but really useful when the input units won't be know ahead of time. So with EAV if I have an artist and I want to add a "column" hometown, I dont have to create a new table of column, simply a new entry in the attributes table. EAV is also notoriously hard to validate and type.
In my products, I play it safe and go relational (Boyce-Codd form). Yes it means more models and more relationships, but its worth a few extra hours. Besides in MVC frameworks like the tagged Cakephp, it couldn't be much easier to make models. Everytime I've used EAV I've wished I just put the extra time into planning it out more.
Option 3: For the obvious and most widely used attributes use dedicated tables, and have another generic table for user-defined attributes (eg. "Annoys Ex-wife How Much")
There ought to be a pretty static set of attributes for the static tables.
I would treat Album and Genre as two distinct models. You can then create tables for each attribute/model in addition to another table mapping the relationship between them. The database structure would look something like this:
=====
Table: Albums
-- id
-- name
-- artist
====
Table: Genres
-- id
-- name
====
Table: Album_Genres
-- id
-- album_id
-- genre_id
Just add all your albums and genres into the relevant table then to state that a album belongs to a specific genre create a new row in the Album_Genres table. If you need to add/remove any attributes in future it's as simple as creating/deleting a couple of tables.
Whenever able - I use one table to represent one 'real thing' and when that isnt enough, such as when you have to relate two different ideas/object/things, I use multiple tables.
That being said we both agree to use your #1 idea - but now you want to be able to add new attributes. This is usually handled easiest with a 'meta' table - a table that abstracts the actual relationship of an object and its attributes - giving you what you want - dynamic attributes.
It could look like this
=======================
Table Name: Albums
----------------AlbumID
----------------GenreID --> foriegn key --> Genre in Genre Table
----------------ArtistID --> foriegn key --> Artist in Artist Table
----------------Name
----------------Etc (attributes that you know you will need)
=======================
Table Name: AlbumMeta
----------------AlbumID
----------------Key
----------------Value
=======================
Now - your albums can have concrete attributes (ie: attributes that all albums have) and if new business rules come up, say for only indie albums - you have a link to your new partner site "indiealbums.com" - you could create an AlbumMeta entry for each album in the genre of indie...
You should do this using 3rd Normal Form
In CakePHP the relation ships you are looking for and required fields are..
<?php
class Album etends AppModel
{
public $name = "Album";
public $hasAndBelongsToMany = array( 'Artist', 'Genre' );
}
?>
<?php
class Artist extends AppModel
{
public $name = "Artist";
public $hasAndBelongsToMany = array( 'Album' );
}
?>
<?php
class Genre extends AppModel
{
public $name = "Genre";
public $hasAndBelongsToMany = array( 'Album' );
}
?>
// fields for the tables with their table names
// only the required ones for the relationships rest is up to you
artists
--id
albums
--id
genres
--id
albums_artists
--id
--album_id
--artist_id
albums_genres
--id
--album_id
--genre_id
This means a bit more work initially entering the information. You have to treat the entry of the Genres and Artists as separate actions in the database. If you decide to treat artists as entire "bands" or as separate individual performers you will be able to with this setup as an album can be associated with an arbitrary group of artists.