Laravel Scout search by multiple columns - php

I have a user model with the following fields:
protected $fillable = [
'name',
'email',
'password',
'birthday',
'surname'
];
I created a search bar to search the users. I'm using Laravel scout to query the users like this:
$matchingUsers = User::search($request->search)->get();
If I search with just the name or just the surname, the result is fine. The problem is when introducing the name and the surname. In this case, I get no results.
Expected result
Given the following records in my database:
-[id=>1 , name => "Neil", surname => "Armstrong"]
-[id=>2 , name => "Juan Manuel", surname = "Armstrong"]
If I introduce "Neil Armstrong" I expect to get the user with id = 1.
If I search for "Juan Armstrong", I expect to get user with id = 2.
If I search for "Manuel", I expect to get the user with id = 2.
What I have tried so far
Using raw SQL directly querying in the database manager I got the expected result for situation 1 using the following query:
SELECT id, name, surname, image
FROM users
WHERE CONCAT(name,' ', surname) = "Neil Armstrong";
For the other two cases, I've tried to use the orWhere() method. However, this doesn't seem to work together, and anyway mixing raw SQL with eloquent and Scout seems so dirty.
Is there any way to use Laravel Scout to do something like this? A solution using just Eloquent wold also be useful.

By default, the database engine will execute a "where like" query against every model attribute that you have configured as searchable.
So in your case, name and surname are model attributes, and a query of "Neil Amstrong" will check both for %Neil Armstrong%. Nothing matches, so no results.
You can use an accessor to create a new fullName model attribute, and add that attribute to the toSearchableArray() method on your User model.
Create a fullName attribute for your User model.
UserAttributes.php
trait UserAttributes
{
public function getFullNameAttribute(): string
{
return $this->name." ".$this->surname;
}
}
Then in your User model's toSearchableArray() method, include the fullName attribute.
User.php
...
use UserAttributes;
public function toSearchableArray(): array
{
$searchable = [];
$searchable["fullName"] = $this->fullName;
// add more model attributes to search against...
return $searchable;
}
...
Now when you query "Neil Armstrong", the database engine will search the fullName attribute and find your user.

I've had the same problem, and eventually, I've completely removed Scout from my Laravel project and did it just with Eloquent:
$user_query = User::query();
$user_query->where(DB::raw('concat(name, " ", surname)'), 'like', $request);
$user = $user_query->get();
For the $request, I did:
$request = "%" . str_replace(" ", "%", request("query")) . "%";
With this, all three of the points you mentioned should be achieved.

Related

Get data from multiple sql tables using eloquent with laravel

What i want to achieve?
Show user which pacts he is following.
What I am trying
I have designed two tables which are 'pacts' & 'pacts_follwers'
Table 'pacts' has the details of all the pacts
Table 'pacts_follwers' has details of users following a particular pact.
These links will give you the images of both the tables
For Schema Refer Images
So how to get pacts that user is following.
What I have tried?
Sql Query
SELECT pacts.*, (SELECT pactsid FROM pacts_follwers WHERE pacts.id = pacts_follwers.pactsid
and pacts_follwers.userid = 2 ) as pactID FROM `pacts`
Sql query Result
This query will give pactId some value, where the value is null means the user is not following that pact. If this is the solution then i would need Eloquent for this which i am unable to make.
1st table pacts
id
title
about
created_at
updated_at
pactsImage
2nd table pacts_follwers
id
pactsid
userid
created_at
updated_at
Controller Code
$pacts = DB::select("SELECT pacts.*, (SELECT pactsid FROM pacts_follwers WHERE pacts.id =
pacts_follwers.pactsid and pacts_follwers.userid = ".Auth::id()." ) as pactID FROM `pacts`");
You need to setup hasManyThrough relationship for User and Pact.
class User extends Model {
public function pacts() {
return $this->hasManyThrough(
Pact::class,
PactFollower::class
'userid',
'pactsid'
);
}
}
I don't fully understand if you want to achieve "get user's all pacts" or "if pact is followed by user". Either way, you need to setup related relationships.
Or really simple (and not efficient way)
class Pact extends Model {
public function followers() {
return $this->hasMany(PactFollower::class, 'pactsid')
}
}
Now you can use something like
$userIdsForPact = Pact::followers()->pluck('userid');
if ($userIdsForPact->has($user->id)) {
// your operation
}
Edit: For "if pact is followed by user", you need to setup belongsToThrough relationship. It doesn't come out of the box with Laravel but staudenmeir/belongs-to-through package should serve you well.
After setting the relationship properly, you can use something like this.
Pact::with('user')->get();
Or add some methods in your Pact model:
public function followedByUser($user) {
return $this->users->has($user);
}

how to make fetching from two three model with clause and fetch selected fields in laravel [duplicate]

I have two tables, User and Post. One User can have many posts and one post belongs to only one user.
In my User model I have a hasMany relation...
public function post(){
return $this->hasmany('post');
}
And in my post model I have a belongsTo relation...
public function user(){
return $this->belongsTo('user');
}
Now I want to join these two tables using Eloquent with() but want specific columns from the second table. I know I can use the Query Builder but I don't want to.
When in the Post model I write...
public function getAllPosts() {
return Post::with('user')->get();
}
It runs the following queries...
select * from `posts`
select * from `users` where `users`.`id` in (<1>, <2>)
But what I want is...
select * from `posts`
select id,username from `users` where `users`.`id` in (<1>, <2>)
When I use...
Post::with('user')->get(array('columns'....));
It only returns the column from the first table. I want specific columns using with() from the second table. How can I do that?
Well I found the solution. It can be done one by passing a closure function in with() as second index of array like
Post::query()
->with(['user' => function ($query) {
$query->select('id', 'username');
}])
->get()
It will only select id and username from other table. I hope this will help others.
Remember that the primary key (id in this case) needs to be the first param in the
$query->select() to actually retrieve the necessary results.*
You can do it like this since Laravel 5.5:
Post::with('user:id,username')->get();
Care for the id field and foreign keys as stated in the docs:
When using this feature, you should always include the id column and
any relevant foreign key columns in the list of columns you wish to
retrieve.
For example, if the user belongs to a team and has a team_id as a foreign key column, then $post->user->team is empty if you don't specifiy team_id
Post::with('user:id,username,team_id')->get();
Also, if the user belongs to the post (i.e. there is a column post_id in the users table), then you need to specify it like this:
Post::with('user:id,username,post_id')->get();
Otherwise $post->user will be empty.
For loading models with specific column, though not eager loading, you could:
In your Post model
public function user()
{
return $this->belongsTo('User')->select(['id', 'username']);
}
Original credit goes to Laravel Eager Loading - Load only specific columns
When going the other way (hasMany):
User::with(['post'=>function($query){
$query->select('id','user_id');
}])->get();
Don't forget to include the foreign key (assuming it is user_id in this example) to resolve the relationship, otherwise you'll get zero results for your relation.
In Laravel 5.7 you can call specific field like this
$users = App\Book::with('author:id,name')->get();
It is important to add foreign_key field in the selection.
If you want to get specific columns using with() in laravel eloquent then you can use code as below which is originally answered by #Adam in his answer here in response of this same question, the answer's main code is as below :
Post::with('user:id,username')->get();
So i have used it in my code but it was giving me error of 1052: Column 'id' in field list is ambiguous, so if you guys are also facing same problem
Then for solving it you have to specify table name before the id column in with() method as below code:
Post::with('user:user.id,username')->get();
I came across this issue but with a second layer of related objects. #Awais Qarni's answer holds up with the inclusion of the appropriate foreign key in the nested select statement. Just as an id is required in the first nested select statement to reference the related model, the foreign key is required to reference the second degree of related models; in this example the Company model.
Post::with(['user' => function ($query) {
$query->select('id','company_id', 'username');
}, 'user.company' => function ($query) {
$query->select('id', 'name');
}])->get();
Additionally, if you want to select specific columns from the Post model you would need to include the user_id column in the select statement in order to reference it.
Post::with(['user' => function ($query) {
$query->select('id', 'username');
}])
->select('title', 'content', 'user_id')
->get();
In your Post model:
public function userWithName()
{
return $this->belongsTo('User')->select(array('id', 'first_name', 'last_name'));
}
Now you can use $post->userWithName
There is another alternative you can eager load specific columns
public function show(Post $post)
{
$posts = $post->has('user')->with('user:id,name,email,picture')->findOrFail($post->id);
return view('your_blade_file_path',compact('posts);
}
In your Post model you should have user relationship also
public function user()
{
return $this->belongsTo( User::class, 'user_id')->withDefault();
}
Note: It is mentioned in Laravel docs.
https://laravel.com/docs/8.x/eloquent-relationships#eager-loading-specific-columns
Note that if you only need one column from the table then using 'lists' is quite nice. In my case i am retrieving a user's favourite articles but i only want the article id's:
$favourites = $user->favourites->lists('id');
Returns an array of ids, eg:
Array
(
[0] => 3
[1] => 7
[2] => 8
)
If you use PHP 7.4 or later you can also do it using arrow function so it looks cleaner:
Post::with(['user' => fn ($query) => $query->select('id','username')])->get();
I faced the same issue while using belongsToMany relationship with my user model (Laravel 8.x.x).
After a long search and trial and test method. I found out this answer
You have to make sure you are selecting the id's and any foreign keys that would be needed for the relationship from either side of that relationship. This allows Eloquent to match up parents to their children.
Original credit goes to https://stackoverflow.com/a/64233242/1551102
So I included
Groups::select('groupid')
...
And it worked like a charm. Although now I want to know how to hide the groupid field after fetching.
I know I can simply loop through the array and remove it. But is there any other method? potentially a simpler and better one.
You can also specify columns on related model at the time of accessing it.
Post::first()->user()->get(['columns....']);
You can try this code . It is tested in laravel 6 version.
Controller code
public function getSection(Request $request)
{
Section::with(['sectionType' => function($q) {
$q->select('id', 'name');
}])->where('position',1)->orderBy('serial_no', 'asc')->get(['id','name','','description']);
return response()->json($getSection);
}
Model code
public function sectionType(){
return $this->belongsTo(Section_Type::class, 'type_id');
}
Be careful that if you don't add the key column(s) it won't return anything. If you want to show only the username without the id you could instead define the $visible/$hidden properties within the Model, like so:
app/Models/User.php
protected $visible = ['username'];
Then it will retrieve only username column with:
Post::with('user')->get();
Hiding the key columns:
Alternatively you could hide the key column(s) and then retrieve only the columns you wish.
app/Models/User.php
protected $hidden = ['id'];
Specify which columns you want including the key or else it won't return anything, but this will actually only return the username, because id is $hidden.
Post::with('user:id,username')->get();
Now you can use the pluckmethod on a Collection instance:
This will return only the uuid attribute of the Post model
App\Models\User::find(2)->posts->pluck('uuid')
=> Illuminate\Support\Collection {#983
all: [
"1",
"2",
"3",
],
}
Try with conditions.
$id = 1;
Post::with(array('user'=>function($query) use ($id){
$query->where('id','=',$id);
$query->select('id','username');
}))->get();
So, similar to other solutions here is mine:
// For example you have this relation defined with "user()" method
public function user()
{
return $this->belongsTo('User');
}
// Just make another one defined with "user_frontend()" method
public function user_frontend()
{
return $this->belongsTo('User')->select(array('id', 'username'));
}
// Then use it later like this
$thing = new Thing();
$thing->with('user_frontend');
// This way, you get only id and username,
// and if you want all fields you can do this
$thing = new Thing();
$thing->with('user');
EmployeeGatePassStatus::with('user:id,name')->get();

GroceryCrud set a n-n relation with where clause

I have three tables (simplified) which are:
And I have to display all houses for each user.
In my controller I have a function like this:
public function create_houses_table($usr_id)
{
$crud = new grocery_CRUD();
$crud->set_language("italian");
$crud->set_theme('datatables');
$crud->set_subject('Casette');
$crud->set_table('tbl_houses');
$crud->set_relation_n_n('Casette',
'tbl_users_houses',
'tbl_users',
'house_id',
'user_id',
'usr_name',
NULL,
array('user_id' => $usr_id));
...
}
and what I get is this:
Every time I select a user from the combo I need to refresh my list filtering on usr_id...but I get always all the houses.
What I'm wrong?
This is not the intended usage for set_relation_n_n (it will show all the user houses in one field inside the user row).
What you want can be better done listing from tbl_users_houses, filtering by client with $crud->where() and linking with the other tables with two simple relations.
If I understand correctly you are trying to fetch only the records for the logged in User... and u have multiple users per house, hence the n-n relation.
I also faced this problem and here's what I did.
$myprojects = $this->admin_model->get_employee_projects($this->user_id);
$myprojectids = array_column($myprojects, 'id');
//get only one column from the multi-dimensional array
$crud->where("`projects`.id IN", "(" . implode(",", $myprojectids) . ")", false);
// the false disables escaping
$crud->set_relation_n_n('assigned_employees', 'project_employees', 'employees', 'project', 'employee', 'name');
//Only so it also still shows the name of Users assigned
So basically projects here is like houses, and I am using the WHERE IN clause to filter the records based on the projects I get from my model method...

Mapping field name in model using laravel

Is there a way to map field names to a database to a different attribute name in the model? For example, if the database has a field name of customer_id but I wanted to use eloquent in this way Customer::get(['id']) I've tried using the getAttribute method but that is called after eloquent has attempted to get the value.
You can use accessors to work with such attributes, but there's no way to query them this way with core eloquent.
But fear not! Use this package https://github.com/jarektkaczyk/eloquence and you can easily achieve what you want (Mappable in particular):
// Customer model
protected $maps =[
'id' => 'customer_id',
'name' => 'customer_name',
...
];
// then you can do this:
$customer = Customer::where('name', 'whatever')->first();
// calls WHERE customer_name = ? sql
$customer->id; // customer_id column
$customer->name; // customer_name column
$customer->name = 'different name'; // set mutator works as well
It's in heavy development and currently select is not yet supported, but it's matter of day or two. select support has been pushed already.

Making a join with Eloquent based on a list of ids

I have set up two Eloquent models with a belongsToMany relation in both directions. This works perfectly fine but now I need to make a more detailed query within the relationship. To keep things simple, let's say the tables have the following columns:
wigs:
- id
- name
- type
heads:
- id
- name
heads_wigs:
- head_id
- wig_id
Now I need to fetch a series of wigs with a given type within a list of given head id's. So what I have is:
a wig type
an array with head id's
I am using Eloquent outside of laravel so I want to start building the ORM query on the model. Something like:
Wig::where( 'type', $type )-> ... //here the code to make the join on head id's
This is where my understanding of SQL lacks but I suppose this should not be too hard to achieve.
UPDATE:
To rephrase it in a sentence: get all wigs with type=wig_type that have a belongsToMany relationship with the heads [1,2,3,5,6,8,9]. I want to end up with a collection of wigs by performing a single query.
You could do something like this
Head::whereIn('id', $head_id_array)->load(['wig' => function($query) use ($wig_type) {
$query->where('type', $wig_type);
}])->get();
or
Wig::where('type', $wig_type)->load(['heads' => function($query) use ($head_id_array) {
$query->whereIn('id', $head_id_array);
}])->get();
if I understand your question correctly.
Or
$wig = Wig::where('type', $wig_type)->get();
$heads = $wig->heads()->whereIn('id', $head_id_array)->get();
$matching_head_ids = $heads->lists('id');
$wig->matching_head_ids = $matching_head_ids;
That way, the wig object returned will have an array of matching head ids.
you could put this in a method on your wig model:
class Wig extends Eloquent {
public function heads()
{
return $this->belongsToMany('Head');
}
/**
* #param array $head_ids Provided head id array
* #return array Array of this wigs head id's which match the provided head ids
*/
public function matchingHeadIds($head_ids)
{
return $this->heads()->whereIn('id', $head_ids)->get()->lists('id');
}
}
then use it like so
$wig = Wig::where('type', $wig_type);
$wig->matchingHeadIds($head_ids);
Edit
This is not a simple task for an ORM like eloquent, since it treats each record like a row from the table, so something like this wouldn't work:
$wigs = Head::whereIn('id', $head_ids)->wigs()->where('type', $wig_type)->get();
There is a whereHas method available which you can use like so:
Wig::where('type', $wig_type)->whereHas('heads', function ($query) use ($head_ids) {
$query->whereIn('id', $head_ids);
})->get();
which should give you the results you need.

Categories