How to swap two indexed fields with laravel migrations? - mysql

In the Models of a many to many relationship I have accidentally identified the foreign key names in reverse. This is done in both related Models so the relationship works. It's in production.
In Articles:
public function categories()
{
return $this->belongsToMany(ArticleCategory::class, 'article_category_article', 'article_category_id', 'article_id');
}
and in ArticleCategory:
public function articles()
{
return $this->belongsToMany(Article::class, 'article_category_article', 'article_id', 'article_category_id');
}
As you can see, both foreign keys are reversed.
It doesn't bother me because it works throughout the project. In the article_category_article table both values are recorded in the 'wrong' column.
But what if I'd like to swap it anyway. The Models are easy, but what about the pivot table? I have tried with a laravel migration:
public function up()
{
Schema::table('article_category_article', function (Blueprint $table) {
$table->renameColumn('article_id', 'temporarily');
$table->renameColumn('article_category_id', 'article_id');
$table->renameColumn('temporarily', 'article_category_id');
});
}
without success, it predictably runs into the error There is no column with name 'temporarily' on table 'article_category_article'
Splitting it up in 2 migration files ran into the same error.
I have the tendency to let it be. The question is: can it be done? I presume swapping the columns inside MySQL (without migrations), re-index the tables and adapt the Models is a possibility. Any ideas? I can test it out on a local server.

Two separate queries work for me:
public function up()
{
Schema::table('article_category_article', function (Blueprint $table) {
$table->renameColumn('article_id', 'temporarily');
$table->renameColumn('article_category_id', 'article_id');
});
Schema::table('article_category_article', function (Blueprint $table) {
$table->renameColumn('temporarily', 'article_category_id');
});
}

Related

Laravel. Connections between tables. Get the record

I used last laravel ver. framework. I can't get access to a certain record. I have two field links to one table.
My migration:
Schema::create('application_forms', function (Blueprint $table) {
$table->unsignedBigInteger('clients_id');
$table->foreign('clients_id')->references('id')->on('clients')->onDelete('cascade');
$table->unsignedBigInteger('buyer_id')->nullable();
$table->foreign('buyer_id')->references('id')->on('clients')->onDelete('cascade');
My model
class Application_form extends Model
{
public function clients()
{
return $this->belongsTo(Client::class);
}
I used on my blade
{{$application_form->clients->FIO_Client}}
But I can't access to record with buyer_id. I get record with clients_id. How I can get access to this
$table->unsignedBigInteger('buyer_id')->nullable();
$table->foreign('buyer_id')->references('id')->on('clients')->onDelete('cascade');

Database columns exclusive or relationship

In my project it would come in handy to put two boolean columns of a table in an 'exclusice or' like relationship, is that somehow possible in laravel? FYI: I use mysql as a driver if that matters.
A CHECK constraint can be used to check specific requirements for a table row. This logic be placed inside the up function of a migration.
public function up ()
{
Schema::create('order', function (Blueprint $table) {
$table->increments('id');
$table->boolean('in_transit');
$table->boolean('is_delivered');
});
// Add the constraint
DB::statement('ALTER TABLE order ADD CONSTRAINT chk_delivery CHECK ((in_transit AND NOT is_delivered) OR (is_delivered AND NOT in_transit));');
}

A crazy database structure - suggestions

I'm working on a project where I need to create three Many-to-Many relationships between 4 models. Here is how it goes:
FAQ Categories can have many FAQ Subcategories and vice versa.
FAQ Groups can have many FAQ Subcategories and vice versa.
FAQs can have many FAQ groups and vice versa.
To all the database experts out there, how should I design this database schema in Laravel? Should I have three different pivot tables? Should I use polymorphic relationships?
I've used polymorphic relationships before, but I'm struggling with implementing it in this scenario.
I would do something like this:
FAQ Categories Table
Schema::create('faq_categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
Schema::table('faq_categories', function (Blueprint $table) {
$table->unsignedInteger('parent_id')->nullable();
$table->foreign('parent_id')->references('id')->on('faq_categories')->onDelete('cascade');
});
FAQ Groups Table
Schema::create('faq_groups', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
FAQs Table
Schema::create('faqs', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
});
As you can see I wouldn't create a FAQ Sub Categories table, because it's cleaner to have a category table referencing itself to a parent category (also important to make that foreign key nullable to be able to create a top level category).
Now to setup the relationships between the tables, we can do this:
FAQ Categories - FAQ Groups (Many to Many)
Schema::create('faq_category_faq_group', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('faq_category_id');
$table->foreign('faq_category_id')->refrences('id')->on('faq_categories')->onDelete('cascade');
$table->unsignedInteger('faq_group_id');
$table->foreign('faq_group_id')->refrences('id')->on('faq_groups')->onDelete('cascade');
});
FAQs - FAQ Groups (Many to Many)
Schema::create('faq_faq_group', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('faq_id');
$table->foreign('faq_id')->refrences('id')->on('faqs')->onDelete('cascade');
$table->unsignedInteger('faq_group_id');
$table->foreign('faq_group_id')->refrences('id')->on('faq_groups')->onDelete('cascade');
});
Should I use polymorphic relationships?
I don't think polymorphic relationship's would make any sense in this scenario. I would stick with standard many to many.
In your model classes you should setup all the relationships like referenced in the docs.
You can do this:
FaqCategory Model
class FaqCategory extends Model
{
/**
* Get the category's parent category.
*/
public function parent()
{
return $this->belongsTo('App\FaqCategory');
}
/**
* Get the category's sub categories.
*/
public function sub_categories()
{
return $this->hasMany('App\FaqCategory', 'parent_id');
}
/**
* Get the category's faq groups.
*/
public function faq_groups()
{
return $this->belongsToMany('App\FaqGroup');
}
}
FaqGroup Model
class FaqGroup extends Model
{
/**
* Get the group's faq categories.
*/
public function faq_categories()
{
return $this->belongsToMany('App\FaqCategory');
}
/**
* Get the group's faqs.
*/
public function faqs()
{
return $this->belongsToMany('App\Faq');
}
}
Faq Model
class Faq extends Model
{
/**
* Get the faq's faq groups.
*/
public function faq_groups()
{
return $this->belongsToMany('App\FaqGroup');
}
}
One concern is whether FAQ Categories and Subcategories should be different tables. Maybe should be the same table in order to give you the option to have more levels in the future.
i.e.
faq_categories(id, parent_id, name etc.)
Then, in my opinion, you need just different pivot tables. I can not see any reason to use polymorphic relationships.

Switching primary key from id to a combination of values in a pivot table

I am trying to switch the primary key of a pivot table from "id" to a combination of two values using a migration inside a laravel project. My up method looks as follows and it works fine:
public function up() {
Schema::table('gallery_image', function (Blueprint $table) {
$table->dropColumn('id');
$table->primary(['image_id', 'gallery_id']);
});
}
However, when I declare the down method in order to undo the above changes like this:
public function down() {
Schema::table('gallery_image', function (Blueprint $table) {
$table->dropPrimary(['image_id', 'gallery_id']);
$table->increments('id');
});
It first gives me an error 1068 Multiple primary key defined, which tells me that the first line in the down method does not work as intended, but when I just run the dropPrimary line, it gives me an error errno: 150 - Foreign key constraint is incorrectly formed.
I am not quite sure as to what I am doing incorrectly.
You could try the following
$table->dropPrimary(); // without the parameters.
Or you could wrap the closure in the down method as following:
DB::statement('SET FOREIGN_KEY_CHECKS=0');
$table->dropPrimary(['image_id', 'gallery_id']);
$table->increments('id');
DB::statement('SET FOREIGN_KEY_CHECKS=1');
or
Schema::disableForeignKeyConstraints();
//code
Schema::enableForeignKeyConstraints();
This will temporarily set the FK Checks off, and turn it back on afterwards.
EDIT
You could look into https://laravel.com/docs/6.0/migrations#dropping-indexes and try out the various functions!

Optimizing Eloquent Relationship Retrieval

I have an interface which displays a list of communities on the platform. Communities have members and in turn members/profiles can befriend one another. On the listing page each community card needs to display the number of members (in the community) and the number of friends (friends of logged in profile) from those members.
Here's an illustration of how a community card looks like
I'm getting the communities with the members first:
$communities = $loggedInProfile->communities->load('members')->take(15);
And then iterating over the communities and then the members to find out which ones are friends with the logged in user.
foreach ($communities as $key => $community) {
$friends = [];
foreach ($community->members as $member) {
if ($loggedInProfile->isFriendWith($member)) {
array_push($friends, $member);
}
}
$community->members_who_are_friends = $friends;
}
My issue is that this is very taxing in terms of the number of queries when the associations get large. Is there a better way of retrieving these relationships without having to use nested for loops? I'm also indexing all data with Elasticsearch. Would a retrieval of this sort be better with Elasticsearch? Also would this be a good use case for hasThrough?
Update
The members relationship:
public function members()
{
return $this->belongsToMany('App\Profile', 'community_members', 'community_id', 'profile_id')->withTimestamps();
}
The isFriendWith relationship:
public function isFriendWith(Model $recipient)
{
return $this->findFriendship($recipient)->where('status', Status::ACCEPTED)->exists();
}
The check is done on a table called friendships. The status column (which can be either 0 or 1) is checked to see if friends or not.
The findFriendship check:
private function findFriendship(Model $recipient)
{
return Friendship::betweenModels($this, $recipient);
}
Database structure:
-Profiles migration
Schema::create('profiles', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
});
-Communities migration (the foreign key is the owner of the community)
Schema::create('communities', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('profile_id');
$table->foreign('profile_id')->references('id')->on('profiles');
$table->string('slug')->unique();
});
-Community_members migration
Schema::create('community_members', function (Blueprint $table) {
$table->primary(['profile_id', 'community_id']);
$table->unsignedInteger('profile_id');
$table->foreign('profile_id')->references('id')->on('profiles');
$table->unsignedInteger('community_id');
$table->foreign('community_id')->references('id')->on('communities');
$table->timestamps();
});
-Friendships migration
Schema::create('friendships'), function (Blueprint $table) {
$table->increments('id');
$table->morphs('sender');
$table->morphs('recipient');
$table->tinyInteger('status')->default(0);
$table->timestamps();
});
In your line:
$communities = $loggedInProfile->communities->load('members')->take(15);
load() is used to perform Lazy Eager loading, i.e. you load the members after the communities have been retrieved, resulting in a different query for every community. You could extract the whole data with a single query using with(). Also, take(15) is performed on the resulting collection and not on the query. Try this:
$communities = $loggedInProfile->communities()->with('members')->take(15)->get();