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

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
}
}

Related

Use two completely different classes as one

I have very stupid question about design patterns: let's say we have two classes Post and Product, for each of them we have different table in the DB, and they have nothing in common with each other, so we can't create base class for them. Some Posts even contains Products. And here's what we should do with them:
Somehow store Post and Product instances in the DB, pack them in one array(using C++, if it matters) when user requests news feed from the next item, send it to the client, and receive and unpack on the client side(using Java).
Next, we have to show both Post and Product in the one list(such as news feed on the Facebook).
Also, we can share Post or Product with our friends using chat. So we can send Post or Product as an attachment of the message(consequently, we should to store id of sent Post or Product in the column attached_item of the messages table in the DB on the server side).
So, what design pattern would be best here? How should I implement the Post and Product classes?
It is a very broad question, but here is a skeleton of what you could you, just to give you some ideas:
// An interface containing methods specific to objects you can list
interface Listable {}
// An interface containing methods specific to objects you can share
interface Shareable {}
// An interface containing methods specific to objects you can send
interface Sendable {}
class Post implements Listable, Shareable, Sendable {
List<Product> products;
}
class Product implements Listable, Shareable, Sendable {
}
class ListManager {
public void addToList(Listable element) { }
}
class ShareManager {
public void share(Shareable element) { }
}
class SendManager {
public void send(Sendable element) { }
}
You could then use Post and Product interchangeably this way:
Post post = new Post();
Product product = new Product();
ListManager listManager = new ListManager();
listManager.addToList(post);
listManager.addToList(product);
ShareManager shareManager = new ShareManager();
shareManager.share(post);
shareManager.share(product);
SendManager sendManager = new SendManager();
sendManager.send(post);
sendManager.send(product);
Regarding the database representation, as suggested fusiled in his comment, just stick them in 2 separate tables. With a mapping table in between to link the products to their post.
EDIT
Regarding the issue with the MESSAGES table
You could add a new mapping table MESSAGE_ATTACHED_ITEM with columns messageId, postId, productId. Only set a value to the relevant colum when attaching an item to a message
Or an other option would be to have an ATTACHED_ITEM table with an id only.
And have Post and Product tables to have a foreign key to this table Id.
you can then stick this attachedItemId into your attached_item column
I think the solution could be simpler than you think. Why don't you ust use a common Java-like interface and hide the implementation details?
Just implement a common interface with the methods you need. Supposing this common interface is called EntityInterface:
public class Post implements EntityInterface {};
public class Product implements EntityInterface {};
Then when you want to handle these classes, you treat them as an EntityInterface object:
EntityInterface myNewPost = new Post();
EntityInterface myNewProduct = new Product();
//Now you see myNewProduct and myNewPost as EntityInterface objects
These code fragments are in Java, but use virtual functions in C++ and you get the same.

Using DatastoreIO to save entities of different kinds to datastore in Dataflow

I am trying to write entities of different kinds to datastore using DatastoreIO.v1().write().
In my transform function UpsertStoreItemEntityFn extends DoFn<String, Entity I am creating 'Entity' objects conditionally as below:
#Override
public void processElement(ProcessContext processContext) throws Exception {
IngestionJobRecord jobRecord = convertIntoIngestionJobRecord(processContext.element());
String validRecordMsg = isRecordValid(jobRecord);
if (validRecordMsg.isEmpty()) {
processContext.output(makeSuccessRecordEntity(jobRecord));
} else {
processContext.output(makeErrorRecordEntity(validRecordMsg, jobRecord));
}
}
private Entity makeErrorRecordEntity(String errorMessage, IngestionJobRecord jobRecord) {
Entity.Builder entityBuilder = Entity.newBuilder();
// All created entities have the same ancestor Key.
Key.Builder keyBuilder = makeKey("MyService.ErrorRecordEntity", UUID.randomUUID().toString());
keyBuilder.getPartitionIdBuilder().setNamespaceId(tenantId);
entityBuilder.setKey(keyBuilder.build());
..
..
}
private Entity makeSuccessRecordEntity(StoreItemEntity storeItemEntity) {
Entity.Builder entityBuilder = Entity.newBuilder();
// All created entities have the same ancestor Key.
Key.Builder keyBuilder = makeKey("MyService.SuccessRecordEntity", UUID.randomUUID().toString());
keyBuilder.getPartitionIdBuilder().setNamespaceId(tenantId);
entityBuilder.setKey(keyBuilder.build());
..
..
}
As you can see, I am setting different 'kind' for both entities. The problem is that when this line in the pipeline:
.apply(DatastoreIO.v1().write().withProjectId("myProject"));
gets executed, then entities of only one of the kinds are written to the datastore.
I am wondering if I am doing something wrong or this is a limitation of DatastoreIO that doesn't allow entities of different kinds to be allowed to be written together.
If this is not possible, is there some other way in which I can write both entity kinds to datastore in one single step in dataflow?
Any pointers much appreciated!
Thanks
Abhishek

Grails Relation m:n:n

We work with an existing mysql database under grails which contains a m:n relation. No problem here. But now we are in the situation that i have to add a second n relation entry, which links to the same table and has to be associated to the first n entry.
If it were for the database, I would simply create a table which looks like:
field m_id -> links to m table
field n_id1 -> links to n table
field n_id2 -> links to n table
But how can this be represented in a grails domain class?
Possibly the answer can already be found somewhere, but the searches I did were not successful, maybe due to lack of search term creativity.
EDIT:
Trying to clarify the question: we have a many-to-many relation, but with two items on one side, which have to maintain association to each other (and it also must be clear which is for example the original and which is the replacement item), so they can not be seperated into two separate entries into the relation.
Hmm... try to think of racing car drivers nominating for a series of races, and every nomination has to contain the driver and his substitute. Races would be m (left hand), driver would be n1 and substitute would be n2. (It was really hard work to find an example...)
EDIT:
By coincidence I found this question which addresses the same problem but also left off rather unsolved.
Take a look at the documentation, is pretty clear:
http://grails.github.io/grails-doc/2.5.0/guide/GORM.html#gormAssociation
Anyway I'm not sure I understand what you're trying to do, but let's try.
If it's a one-to-one relation, you could simply do something like this:
class DomainA {
DomainB domainB
DomainC firstDomainC
DomainC secondDomainC
}
class DomainB {
}
class DomainC {
}
That would create the following fields in the "domain_a" table:
"domain_b_id"
"first_domain_c_id"
"second_domain_c_id"
If it's a one-to-many relationship from DomainA to both DomainB and DomainC with two differentiated collections of DomainC in DomainA, you must have two different DomainA properties in DomainC to be able to map it.
The example with Airport and Flight in the documentation:
class Airport {
static hasMany = [outboundFlights: Flight, inboundFlights: Flight]
static mappedBy = [outboundFlights: "departureAirport",
inboundFlights: "destinationAirport"]
}
class Flight {
Airport departureAirport
Airport destinationAirport
}
The flight needs to Airport properties in order to be able to distinguish which one is mapping the right hasMany collection in Airport.
EDIT: for the race-driver example
Grails supports many-to-many relations but one of the ends must be the principal one and the other must additionally have a belongsTo to it. Though this is not the case, since neither race belongs to driver nor driver belongs to race.
I would use relation with a property: "drives" with a property "mainDriver".
That cannot be mapped directly, you need to use a domain for the relation:
class Race {
static hasMany = [ participants: DriverRace ]
def mainDrivers() {
getDrivers( false )
}
def substitutes() {
getDrivers( false )
}
private getDrivers( mainDriver ) {
DriverRace.withCriteria {
eq( "race", this )
eq( "mainDriver", mainDriver )
}.collect { it.driver }
}
}
class Driver {
static hasMany = [ races: DriverRace ]
}
class DriverRace {
static belongsTo = [ race: Race, driver: Driver ]
boolean mainDriver
}
We thought we were able to solve the problem by inserting a second reference to domain/table n in the mapping property of (left hand side) domain m. grails then seems to put a second reference to n on the right hand side of the relation. But that turned out to be a hopeful weekend dream.

Traversing relationships (simple)

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!

Playframework Siena Filtering and Ordering

This is my first question on any of these websites so pardon my unprofessionalism.
I use playframework with SIENA module (with GAE) and I came accross the following problem:
Given 3 entities:
public class Meeting extends Model{
#Id
public Long id;
public String place;
#Owned
Many<MeetingUser> users;
.
.
.
}
public class User extends Model{
#Id
public Long id;
public String firstName;
public String lastName;
#Owned
Many<MeetingUser> meetings;
.
.
.
}
public class MeetingUser extends Model{
#Id
public Long id;
public Meeting meeting;
public User user;
.
.
.
public User getUser(){
return Model.all(User.class).filter("id", user).get();
}
public Meeting getMeeting(){
return Model.all(Meeting.class).filter("id", meeting).get();
}
}
For instance I am listing a meeting and all their users:
public static void meetingInfo(Long meetingId){
Meeting meeting = Models.all(Meeting.class).filter("id",meetingId);
List<MeetingUser> meetingusers = meeting.asList();
List<User> users = new ArrayList<User>();
for(MeetingUser mu: meetingusers){
users.add(mu.getUser());
}
render(users);
}
This is done(is there any better way here?) however when it comes to filtering (especially dynamic filtering for many many fields) I can not use the Query's filter method on the MeetingUser as I need to filter on a MeetingUser's field's field (firstName). The same problem arise for ordering. I need the solution for both problems.
I hope my problem is clear and I appreciate any kind of help here.
Remember that you are in GAE which is a NoSQL DB.
So you can't do Join request as in RDBMS.
Yet, this is not really the pb you have so this was just to be sure you are aware of it ;)
So if you want to find the person having given firstname in a given meeting, can you try the following:
List<MeetingUser> meetingusers = meeting.users.asQuery().filter("firstname", "XXX");
(you can also order)
Nevertheless, knowing that you can't join, remember that you can't write a query searching for a meeting in which there are users whose firstname is XXX as it would require some joins and it doesn't exist in GAE. In this case, you need to change your model following NoSQL philosophy but this is another subject
regards
Let's try to give a way to do what you want...
Your relation is a Many-to-Many which is always the worst case :)
You want to filter Meeting by User's firstname.
It requires a join request which is not possible in GAE. In this case, you must change your model by denormalizing it (sometimes use redundancy also) and manage the join by yourself. Actually, you must do the job of the RDBMS by yourself. It seems overkill but in fact, it's quite easy. The only drawback is that you must perform several requests to the DB. NoSQL means No Schema (& No Join) so there are a few drawbacks but it allows to scale and to manage huge data load... it depends on your needs :)
The choice you did to create the MeetingUser which is a "joined" table and a kind of denormalization is good in GAE because it allows to manage the join yourself.
Solution:
// fetch users by firstname
List<User> users = users.all().filter("firstName", "John").fetch();
// fetch meetingusers associated to these users (verify the "IN" operator works because I didn't use that for a long time and don't remember if it works with this syntax)
List<MeetingUser> meetingusers = MeetingUser.all().filter("user IN", users);
// now you must fetch the whole meeting because in MeetingUser, only the Meeting ID is stored (other fields are Null or O)
List<Meeting> meetings = new ArrayList<Meeting>()
for(MeetingUsers mu:meetingusers) {
meetings.add(meetingusers.meeting);
}
// use the batch feature to fetch all objects
Meeting.batch(Meeting.class).get(meetings);
// you have your meetings
Hope this helps!

Resources