me108's avatar
Level 1

hasMany always with fixed default entry

I have a Posts model with a hasMany relationship with the User:

class User extends Authenticatable
{
    ...
    public function posts(): HasMany
    {
        return $this->hasMany( Posts::class );
    }
}

When I get the users posts with:

$user = Auth()::user;
$posts = $user->posts()->get()->toArray();

I receive an array like this:

Array
(
    [0] => Array
        (
            [id] => 1
            [user_id] => 1
            [title] => Foo
            [content] => This is a great idea!
        )

    [1] => Array
        (
            [id] => 2
            [user_id] => 1
            [title] => Bar
            [content] => I agree!
        )

    [2] => Array
        (
            [id] => 3
            [user_id] => 1
            [title] => Noodles
            [content] => Yummy! :)
        )
)

What I want is that every call to $user->posts()->get()->toArray() always contains a fixed default entry like this:

Array
(
    [0] => Array
        (
            [id] => 0
            [user_id] => 0
            [title] => Default Post
            [content] => I'm a fixed default post
        )

    [1] => Array
        (
            [id] => 1
            [user_id] => 1
            [title] => Foo
            [content] => This is a great idea!
        )
    ...
)

I could do:

$user = Auth()::user;
$posts = $user->posts()->get()->toArray();
array_unshift( $posts, [
    'id' => 0,
    'user_id' => 0,
    'title' => 'Default Post', 
    'content' => "I'm a fixed default post"
] );

But I feel like there is a more elegant way to do this with Laravel's Eloquent ORM. How could this be done?

0 likes
9 replies
Talinon's avatar

@me108 You could clean it up a bit by doing this instead:

auth()->user()->posts->prepend(0, [
    'id' => 0,
    'user_id' => 0,
    'title' => 'Default Post', 
    'content' => "I'm a fixed default post"
]); // optionally ->toArray(), if you want an array instead of a Collection

But I don't like the idea of passing ids and user_ids that don't exist. I don't know what you're doing with this data, but if you're passing it to the front end, you should probably just separate it into the array of data being passed. For example, if you're passing it to a view:

		return view('my-view', [
				'posts' => auth()->user()->posts,
				'fixed_post' => [
				    'id' => 0,
				    'user_id' => 0,
				    'title' => 'Default Post', 
				    'content' => "I'm a fixed default post"
				],
		]);
1 like
me108's avatar
Level 1

Thank you for your reply and the idea @talinon

I'm passing it via API to an Angular app. The only data thats passed to the front end is the id (uuid) and the title.

In the Angular app, the user can create a new entry or pick from existing entries via a dropdown. And the "default entry" should always be in the dropdown.

Can't I put passing the default entry in the "Posts" model so that I don't have to manually prepend every call?

Talinon's avatar

@me108 Is the "default entry" always the same? or is it determined by something else from the API?

If it's always the same, why not either render the select element with a null value placeholder option, or unshift() it onto the array using javascript?

1 like
me108's avatar
Level 1

@Talinon It is always the same, yes.

I like the idea of rendering it in the front end. I need to think about the effects and implications it has on the backend though, because the entry needs to be processed and handled appropriately by the backend aswell.

I'd really like if it was possible that the HasMany collection returned by has User->posts() has the default entry prepended, like that:

class User extends Authenticatable
{
    ...
    public function posts(): HasMany
    {
		$collection = $this->hasMany( Posts::class );
		$collection = prependDefaultEntryTo( $collection );
        return $collection;
    }
}
Tray2's avatar

So basically you want to pin a post.

I would then do something like this.

DB::select( DB::raw('SELECT *,  1 pos 
									 FROM posts 
									WHERE id = :default_id 
									UNION ALL 
									SELECT *, 2 pos
									FROM posts
									WHERE user_id = :user_id
			'),[  'default_id' => $defaultId,
				  'user_id'  => $user->id ]
);

I added the pos if you need to order the posts, the always start with ->ordeBy('pos')

me108's avatar
Level 1

@Tray2 I want to kind of pin a "virtual" post, it does not - and will not - exist in the database. But it should appear in the collection.

Tray2's avatar

@me108 Then I would create another table to keep it in, and still do a union like I described.

1 like
Snapey's avatar

You can't pretend that its an actual entry when it does not have a valid id

1 like
me108's avatar
Level 1

@tray2 @snapey You are both right. Thank you. I thought about it and decided to create the default entry in the database. Since the entry is connected to users, it will automatically be inserted into the database when a user registered. In that way I can also reference it properly with a foreign key relationship.

Please or to participate in this conversation.