I have been trying to figure out how to implement a matching system but have become stuck.
I've managed to build a query within a controller which does exactly the same thing as I want it to do, but I would like to convert it to an Eloquent Model since images are broken and also can access some functions inside my Model.
Here's the query builder within the controller that I wish to convert (if it's possible at all)- I am checking if users have both "liked" each other (similar to Tinder):
class MatchedEmployersController extends Controller
{
public function index()
{
$matches = DB::table('applicant_likes')
->join('employers', 'employers.id', '=', 'applicant_likes.liked_employer_id')
->whereExists(function ($query) {
$query->select(DB::raw(1))
->from('employer_likes')
->whereRaw('employer_likes.employer_id = applicant_likes.liked_employer_id');
})
->get();
return view('applicant.employers.matched', compact('matches'));
}
}
Here's the Applicant model where below I extracted the logic into a usable Traits
App\Models\Applicant
class Applicant extends Authenticatable
{
use Notifiable, LikeableEmployer, MatchableEmployer;
//
public function getAvatarAttribute($value)
{
return asset($value ?: '/images/default-avatar.jpeg');
}
}
App\Trais\LikeableEmployer
trait LikeableEmployer
{
public function likeEmployer(Employer $employer)
{
return $this->likedEmployers()->save($employer);
}
public function unlikeEmployer(Employer $employer)
{
return $this->likedEmployers()->detach($employer);
}
public function toggleLikeEmployer(Employer $employer)
{
if ($this->likingEmployer($employer)) {
return $this->unlikeEmployer($employer);
}
return $this->likeEmployer($employer);
}
public function likingEmployer(Employer $employer)
{
return $this->likedEmployers()->where('liked_employer_id', $employer->id)->exists();
}
public function likedEmployers()
{
return $this->belongsToMany(Employer::class, 'applicant_likes', 'applicant_id', 'liked_employer_id');
}
}
finally, here's where the matched logic should be placed
namespace App\Traits;
use App\Traits\LikeableApplicant;
use App\Traits\LikeableEmployer;
trait MatchableEmployer
{
use LikeableApplicant, LikeableEmployer;
public function matchedEmployers()
{
//
}
}
You need to create a table where you will store the matches. Let's take the following example.
relationships table: id | from | to, It's a match if we have a pair. Example:
id | from | to
1 | 1 | 2
2 | 2 | 1
Now create Relationship Model
class Relationship extends Model
{
public static function getMatch($user_id)
{
return self::leftJoin('relationship reverse', 'relationship.to', '=', 'reverse.from')->where('relationship.from', 'reverse.to')->where('relationship.from', $user_id)->get();
}
}
Now you can simply call User::getMatch('any_user_id');
First of all, create the model for every table you used in this query, then add the following relationship.
In ApplicantLike Model
public function employer(){
return $this->belongsTo('App\Employer','liked_employer_id','id');
}
In Employer Model
public function likes(){
return $this->hasMany('App\EmployerLike','employer_id','id');
}
Final in your MatchedEmployersController
public function index()
{
$matches = ApplicantLike::with('employer','employer.likes')
->has('employer')
->has('employer.likes')
->get();
// dd($matches); // try with this first
return view('applicant.employers.matched', compact('matches'));
}
Try the above code, I converted your given code into ORM, but I think that you are implementing the wrong logic for what you need. If anything will not work fine just reply to me I will help you.
Related
I could not find this in the laravel docs on aggregate relationships
I was able to do something like this
private function refreshUsers()
{
$this->users = User::withSum(['taskTimeSessions'=> function ($query) {
$query->whereMonth('created_at',$this->month)
->where('is_reconciled',1);
}],'session_duration_in_seconds')
->get();
}
But now I am trying to query what is the total time a Sprint has or at the very least what the individual tasks inside a sprint have so that I can just sum the total of those somehow.
Sprint has many SprintTasks (pivot table)
SprintTask belongs to one Task
Task has many TaskTimeSessions
So I am trying to go find the total time of the TaskTimeSessions
Sprint::with([
'sprintTasks.task'=> function ($query) {
$query->withSum('taskTimeSessions','session_duration_in_seconds');
}])
->get();
I am not getting any errors, but not finding the result anywhere when dd
I thought i would get lucky and have something like this work
->withSum('sprintTasks.task.taskTimeSessions', 'session_duration_in_seconds')
But I am getting this error
Call to undefined method App\Models\Sprint::sprintTasks.task()
If anyone can help me out with some guidance on how to go about this, even if it doesn't include withSum it would be much appreciated.
As requested, these are the models.
// Sprint
public function sprintTasks()
{
return $this->hasMany(SprintTask::class, 'sprint_id');
}
// SprintTask
protected $fillable = [
'sprint_id',
'task_id',
'is_completed'
];
public function task()
{
return $this->belongsTo(Task::class,'task_id');
}
public function sprint()
{
return $this->belongsTo(Task::class,'sprint_id');
}
// Task
public function taskTimeSessions()
{
return $this->hasMany(TaskTimeSession::class, 'task_id');
}
// TaskTimeSessions
protected $fillable = [
'task_id',
'session_duration_in_seconds'
];
public function task()
{
return $this->belongsTo(Task::class,'task_id');
}
Is it possible to abstract this into the model as like
public function totalTaskTime() {
// using the relationship stuff to figure out the math and return it?
}
Looking for any advice on what the best approach is to do this.
Right now I am literally doing this in the blade and seems very bad
#php
$timeTracked = 0;
foreach ($sprint->sprintTasks as $sprintTask) {
$timeTracked += $sprintTask->task->time_tracked_in_seconds;
}
#endphp
You have a many to many relation between sprint and task
For that you can setup a direct relation belongsToMany with sprint_tasks as the pivot table
// Sprint
public function sprintTasks()
{
return $this->hasMany(SprintTask::class, 'sprint_id');
}
public function tasks()
{
return $this->belongsToMany(Task::class, 'sprint_tasks', 'sprint_id', 'task_id')->withPivot('is_completed');
}
Now you can use that relation to query your needs
Sprint::with(['tasks'=> function ($query) {
$query->withSum('taskTimeSessions','session_duration_in_seconds');
}])
->get();
There is a good package for Laravel for complex relationships - eloquent-has-many-deep. You can use it to build relationships through an unlimited number of tables.
composer require staudenmeir/eloquent-has-many-deep:"^1.7"
Sprint.php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Staudenmeir\EloquentHasManyDeep\HasManyDeep;
use Staudenmeir\EloquentHasManyDeep\HasRelationships;
class Sprint extends Model
{
use HasRelationships;
public function tasks(): BelongsToMany
{
return $this->belongsToMany(Task::class, 'sprint_tasks');
}
public function taskTimeSessions(): HasManyDeep
{
return $this->hasManyDeepFromRelations($this->tasks(), (new Task())->taskTimeSessions());
}
}
Task.php
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Task extends Model
{
use HasFactory;
public function taskTimeSessions(): HasMany
{
return $this->hasMany(TaskTimeSession::class);
}
}
Result:
$sprints = Sprint::withSum('taskTimeSessions', 'session_duration_in_seconds')->get();
I have the following model:
class EmailAddress extends Model
{
public function scopePrimary($query)
{
return $query->firstWhere('is_primary', true);
}
}
class User extends Model
{
public function emailAddresses()
{
return $this->hasMany(EmailAddress::class);
}
}
echo $user->emailAddresses()->primary()->get();
I would expect Laravel to return a model since firstWhere() essentially does LIMIT 1 in the query but instead I always get a collection with one model. Am I doing something wrong? How to fix that?
Thanks in advance!
Maybe you can utilize the hasOne of many relationship:
class User extends Model
{
public function primaryEmailAddress()
{
$this->hasOne(EmailAddresses::class)->ofMany([], function ($query) {
$query->where('is_primary', true);
});
}
}
I have the following data models:
class Cliente extends Model
{
public function sector()
{
return $this->belongsTo(Sector::class,'sectoresId');
}
}
class Sector extends Model
{
public function sectorLanguage()
{
return $this->hasMany(SectorLanguage::class,'sectoresId');
}
public function cliente()
{
return $this->hasMany(ClienteLanguage::class,'sectoresId');
}
}
class SectorLanguage extends Model
{
public function sector()
{
return $this->belongsTo(Sector::class,'sectoresId');
}
public function idioma()
{
return $this->belongsTo(Idioma::class,'idiomasId');
}
}
I want to recover all the active clients and the name of the sector to which it belongs, if I do something like this
$cliente = Cliente::where('active','1');
When I run $client I can not enter the attribute
foreach($cliente as $cli) {
$cli->sector->sectorLanguage->nombre;
}
Why? It only works for me when I find it by id
$cliente = Cliente::find(1);
echo $cliente->sector->sectorLanguage->nombre;
How can I get what I need without resorting to doing SQL with Query Builder.
Thank you very much, greetings.
According to your defined relations, the Sector has many sectorLanguage, it means you're receiving a collection, so you should work on an object like this:
$cliente->where('active', 1)
->first()
->sector
->sectorLanguage
->first()
->nombre;
Cliente::where('active', 1) and sectorLanguage gives you the collection, so you
should first get the first item from $cliente & sectorLanguage and then apply the
desired relationship
Hope it should work!
I have four Models:
User
Client
Store
Opportunity
The relationships are defined as such:
User hasMany Client
Client hasMany Store
Store hasMany Opportunity
User hasManyThrough Store, Client (this works)
The problem is that I'm attempting to access the User->Opportunity relationship via built-in Laravel relationships, but it doesn't seem as if I can do it without a custom Query or an additional user_id column on the opportunities table to allow direct access (even though one can be inferred from the Store->Client relationship). I'm also not a fan of nested foreach loops if they can be avoided.
My question:
Is there a way to go one level deeper and directly access a User's Opportunities in this scenario? The actual Model code and all relevant relationships are as follows:
User
class User extends Eloquent{
public function clients(){
return $this->hasMany('Client');
}
public function stores(){
return $this->hasManyThrough('Store', 'Client');
}
public function proposals(){
return $this->hasMany('Proposal');
}
public function opportunities(){ //This does the job, but I feel like it could be better
return Opportunity::join('stores', 'stores.id', '=', 'opportunities.store_id')->
join('clients', 'clients.id', '=', 'stores.client_id')->
join('users', 'users.id', '=', 'clients.user_id')->
select('opportunities.*')->
where('users.id', $this->id);
}
public function getOpportunitiesAttribute(){ //This just helps mimic the hasManyThrough shorthand
return $this->opportunities()->get();
}
}
Client
class Client extends Eloquent{
public function stores(){
return $this->hasMany('Store');
}
public function user(){
return $this->belongsTo('User');
}
public function opportunities(){
return $this->hasManyThrough('Opportunity', 'Store');
}
}
Store
class Store extends Eloquent {
public function client(){
return $this->belongsTo('Client');
}
public function opportunities(){
return $this->hasMany('Opportunity');
}
}
Opportunity
class Opportunity extends Eloquent {
public function store(){
return $this->belongsTo('Store');
}
}
I don't think there is such method in Laravel. You have to create your custom query. This custom query can be very expensive since multiple queries will be performed. Thus, the optimum solution for this, according to me, is to relate User and Opportunity with a foreign key.
However, if you don't desire to link User and Opportunity with a foreign key, then you can create a custom query to handle this. Simply add a "hasManyThrough" relation between Opportunity and Client model like,
<?php
class Client extends Eloquent{
public function store(){
return $this->hasMany('Store');
}
public function user(){
return $this->belongsTo('User');
}
public function opportunity(){
return $this->hasManyThrough('Opportunity', 'Store');
}
}
Then create a static function in User model.
<?php
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
public function client(){
return $this->hasMany('Client');
}
public function store(){
return $this->hasManyThrough('Store', 'Client');
}
public static function getOpportunityOfUser($userId)
{
$clients = User::find($userId)->client;
foreach ($clients as $client) {
$opportunities[] = Client::find($client->id)->opportunity;
}
return $opportunities;
}
}
Now you can access Opportunity realted to a User in one go like,
Route::get('/', function()
{
return $usersOpportunities = User::getOpportunityOfUser(1);
});
This will return all opportunity of all clients related to User with id '1'.
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class User extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function opportunities() {
return $this->hasManyDeep(Opportunity::class, [Client::class, Store::class]);
}
}
I have the following models.
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment');
}
}
class Comment extends Eloquent {
public function user() {
return $this->belongsTo('User');
}
}
For the sake of this example, a user could have 1,000s of comments. I am trying to limit them to just the first 10. I have tried doing it in the User model via
class User extends Eloquent {
public function comments() {
return $this->hasMany('Comment')->take(10);
}
}
and via UserController via closures
$users = User::where('post_id', $post_id)->with([
'comments' => function($q) {
$q->take(10);
}
]);
Both methods seem to only work on the first record of the result. Is there a better way to handle this?