DuncanOgle's avatar

Eager loading works, but returns no data (Lumen)

So I've got a recipe manager, and each recipe has many instructions (One-to-many). When I request the instructions of a Recipe model, it just returns an empty array. I believe the issue is because I have custom column names.

So, Recipe with PK of recipeID Instruction with PK of instructionID and FK of recipeID

Relationship as defined in Recipe

public function instructions() {
    return $this->hasMany('App\Instruction', 'recipeID', 'recipeID');
}

Relationship as defined in Instruction

public function recipe() {
    return $this->belongsTo('App\Recipe', 'recipeID', 'recipeID');
}

Any clues?

Edit:

Also, I am calling the model like this (which does return the Recipe data, but an empty array for instructions):

    return Recipe::where('recipeID', $id)
        ->with('instructions')
        ->get()
        ->first();
0 likes
16 replies
bobbybouwmann's avatar

Are you sure your column names are correct?

return $this->hasMany('App\Instruction', 'foreign_key', 'local_key');

return $this->belongsTo('App\Recipe', 'foreign_key', 'other_key');
DuncanOgle's avatar

Yes, the naming and constraints are set up consistently. I have also tried various variants of the foreign/local key values but it's still not working.

I have a Many-to-Many relationship set up using custom columns, but one to many appears to be an issue.

jimmck's avatar

Is this an existing database or did you create it as a laravel migration? Can you post the full model and the 'Actual' database schemas. Not the Laravel schema, but what the database has.

pmall's avatar

Did you specified the primary key of recipe is 'RecipeId' by setting the $primaryKey attribute on the model ?

DuncanOgle's avatar

Sorry guys, work has been busy. Heres the information you want.

$primaryKey value has been set in ALL models (due to Laravel/Lumen expecting 'id' to be the primary key name)

Tables were not created with migrations, heres the Schema for relevant tables.

CREATE TABLE `recipe` (
  `recipeID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `userID` int(10) unsigned NOT NULL,
  `title` varchar(191) DEFAULT NULL,
  `description` text,
  `original` varchar(191) DEFAULT NULL,
  `yield` varchar(255) DEFAULT NULL,
  `activeTime` varchar(255) DEFAULT NULL,
  `totalTime` varchar(255) DEFAULT NULL,
  `notes` text,
  `image` varchar(255) DEFAULT NULL,
  `createdAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `updatedAt` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`recipeID`),
  KEY `userID` (`userID`),
  FULLTEXT KEY `title` (`title`,`description`),
  CONSTRAINT `recipe_ibfk_1` FOREIGN KEY (`userID`) REFERENCES `user` (`userID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `instruction` (
  `instructionID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `recipeID` int(10) unsigned NOT NULL,
  `step` tinyint(3) unsigned DEFAULT NULL,
  `instruction` text,
  PRIMARY KEY (`instructionID`),
  KEY `recipeID` (`recipeID`),
  CONSTRAINT `instruction_ibfk_1` FOREIGN KEY (`recipeID`) REFERENCES `recipe` (`recipeID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

And to be complete, here are the models for Recipe and Instruction respectively

class Recipe extends Model
{
    public $recipeID, $userID, $title, $description, $original, $yield, $activeTime, $totalTime, $notes, $image, $createdAt, $updatedAt;

    protected $table = 'recipe';
    public $timestamps = false;
    protected $primaryKey = 'recipeID';

    public function instructions() {
        return $this->hasMany('App\Instruction', 'recipeID', 'recipeID');
    }

    // Other (working) relations ommited
}
class Instruction extends Model
{
    public $instructionID, $recipeID, $step, $instruction;

    protected $table = 'instruction';
    public $timestamps = false;
    protected $primaryKey = 'instructionID';

    public function recipe() {
        return $this->belongsTo('App\Recipe', 'recipeID', 'recipeID');
    }
}
DuncanOgle's avatar

Nope, that didn't work - also, I don't think its correct. The foreign key field in the instruction table is 'recipeID' (for the second parameter in hasMany)

bobbybouwmann's avatar

Foreign key always comes first

public function comments()
{
    return $this->hasMany('App\Comment', 'foreign_key', 'local_key');
}

public function post()
{
    return $this->belongsTo('App\Post', 'foreign_key', 'other_key');
}
DuncanOgle's avatar

Sorry, was a typo. I meant second.

Also, to be clear, the foreign keys share the same name as the primary keys it relates to.

So, a link to a recipe in another table would use the name 'recipeID' - just like in Recipe which has 'recipeID' as its primary key

This is mainly due to being able to do something like left join ingredients using(recipeID) - much cleaner MySQL syntax.

bobbybouwmann's avatar

Oke my brain just broke.... I have no clue here on what is correct and what is incorrect...

DuncanOgle's avatar

Basically, they share the same key name. The relations are set up correctly in Lumen and in the DB, and the related models are being called correctly, however it is only returning an empty array.

http://i.imgur.com/t436AMw.png Here is a diagram of the relationship if it helps.

I'm not sure if custom key names are the issue, but this was working before I changed the naming of the keys. Custom key names work with Many-to-Many relationships, but not with One-to-Many.

DuncanOgle's avatar

Just checked the database logs - the queries that Eloquent is crafting are correct and work (pulling out the data they are meant to)

Somewhere in the framework it is losing the 'instructions' it has just pulled out..

micbenner's avatar

Thinking logically, you say Eloquent is building the correct queries.

I will guarantee you that Laravel is not broken.

That leads me to believe you haven't set your data up correctly.

Check your instruction's actually have the correct recipe IDs set.

Try die and dumping this:

$recipes = Recipe::where('recipeID', $id)
        ->with('instructions')
        ->get();

dd($recipes);

Does any other recipe have the relation loaded correctly?

DuncanOgle's avatar

Actually, I think it could be something in Laravel/Lumen..

I've rebuild the database structure manually from scratch, the relationships are set up and correctly named, I've followed all the documentation. It isn't pulling out instructions for ANY recipe, regardless of if they have them or not.

Either theres some simple, not-obvious mistake in how I've set up the relations in Lumen, or its an issue lower down in the code. After 3 days of looking over it, I might raise an issue on GitHub for Taylor to look at, as from what I can tell, it should work

andrewtweber's avatar

If you are doing a join, followed by an eager load:

$recipes = Recipe::join('instructions', 'recipe.id', '=', 'instructions.recipe_id')->get();

$recipes->load(['instructions']);

This could cause issues if the tables have the same column names. Fix by only selecting the columns from the first table:

$recipes = Recipe::join('instructions', 'recipe.id', '=', 'instructions.recipe_id')
    ->select('recipes.*')
    ->get();
EnthusiasticLearner's avatar

Sorry if this revive a zombie thread. I too have this problem, assuming that the relationship is correctly defined. Have you try use get() as to debug it?

For my case, it also returned an empty results if I use select(), if the select() doesn't consist of columns of the id between the two relationship. i.e.: in your case, the "recipeID" (both is the same in your recipe table and instruction table). However, I tested by removing the select() and replace with get() and it does return.

To get your select(), simply include additional "recipeID" as one of the eager loading.

source: http://stackoverflow.com/questions/43122127/eager-loading-relationship-returns-empty-using-select-in-with-clause

Please or to participate in this conversation.