简体   繁体   中英

How to design database in order to store “default” items

I'm developing an app in Laravel. I've models called Account and ShippingAddress. An Account may have one or more ShippingAddress, but a ShippingAddress can only have one Account. So that I'm using a one-to-many relation.

Now I want to implement a functionality that marks a ShippingAddress as "default", and there's can be only one "default Shipping Address" per "Account". I've identified two possible solutions:

  1. Add a "is_default" column on ShippingAddress table.
  2. Create a "default_shipping_address" pivot table where I'll store the "account_id" "and shipping_address_id".

Which one is the best solution up to you and why? In case of the second one: how can I implement a one-to-one relation in Laravel through a pivot table?

IMHO using a pivot table seems a bit redundant considering that one ShippingAddress belongs to only one Account , the first solution looks cleaner to me.

BTW you can design the Models in this way or something like:

class Account extends Model
{
    public function shipping_addresses()
    {
        return $this->hasMany('App\ShippingAddress');
    }
    public function default_shipping_address()
    {
        return $this->hasOne('App\ShippingAddress')->where('is_default', true);
    }
}

class ShippingAddress extends Model
{
    public function account()
    {
        return $this->belongsTo('App\Account');
    }
}

In this way you can eager load 'shipping_addresses' or only 'default_shipping_address' when you retrieve the Account model, eg:

$account = Account::with('shipping_addresses')->find($id);
$account = Account::with('default_shipping_address')->find($id);

Obviously you should not need to eager load both the relationships togheter because shipping_addresses already include the defaut shipping address .

You have only to check that there is only one default shipping address in your code either with validation ( unique rule ) or database constraints or both.

I would go with option 1 because it is simpler and has no downsides. In order to make sure you have only one default ShippingAddress per Account you listen for the saving event and do some validation like:

class ShippingAddress extends Model
{
    // ... other code

    public function boot()
    {
        parent::boot();

        static::saving(function (ShippingAddress $shippingAddress) {
            if (! $shippingAddress->is_default) {
                return;
            }

            $defaultAlreadyExists = static::where([
                ['account_id', '=', $shippingAddress->account_id],
                ['id', '!=', $shippingAddress->id],
                ['is_default', '=', true],
            ])->exists();

            if ($defaultAlreadyExists) {
                throw new YourCustomException("Account with id {$shippingAddress->account_id} already has a default shipping address");
            }
        });
    }

    // ... other code

}

my personal point of view, is that in the case of a single favorite address, using a pivot table, although it may be more correct from the formal, would only be adding a complication to the query. Instead by adding a flag column to the address to determine when it is favorite, it would make the query easier.

Now, if you want to use a pivot table, you should establish a relationship in Account model using a function such as:

public function defaultShippingAddress()
    {
      return $this->hasOneThrough('App\Models\AccountShippingAddress', 'App\Models\ShippingAddress');
    }

Here you can read the whole documentation about it https://laravel.com/docs/5.8/eloquent-relationships#has-one-through

Regards.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM