Sending an array to index via Algolia's Laravel Search Framework - laravel

I'm having some difficulty sending an array to be indexed in Algolia because I have to encode it to save it to the database first.
$algoliaAgent = AlgoliaAgent::firstOrCreate([
'FirstName' => $item->FirstName,
'LastName' => $item->LastName,
'AgentRecId' => $item->AgentRecId,
'Style' => json_encode(explode(',', $item->Style))
]);
$algoliaAgent->pushToIndex();
The resulting index in Algolia looks like this:
"[\"value1\",\"value2\",\"value3\"]"
Is there a method to decode the value before sending it to Algolia?

I believe you are looking for the json_encode and json_decode methods.
Also, see the Laravel Scout documenation for more information about what is indexed.
By default, the entire toArray form of a given model will be persisted to its search index. If you would like to customize the data that is synchronized to the search index, you may override the toSearchableArray method on the model:

The final solution was to modify the pushToIndex() method to intercept the the loop that sends the object to Algolia.
Something like this:
public function pushToIndex()
{
/** #var \AlgoliaSearch\Laravel\ModelHelper $modelHelper */
$modelHelper = App::make('\AlgoliaSearch\Laravel\ModelHelper');
$indices = $modelHelper->getIndices($this);
/** #var \AlgoliaSearch\Index $index */
foreach ($indices as $index) {
if ($modelHelper->indexOnly($this, $index->indexName)) {
$temp = $this->getAlgoliaRecordDefault($index->indexName);
$temp['Style'] = $this->castArray($temp['Style']);
//$index->addObject($this->getAlgoliaRecordDefault($index->indexName));
$index->addObject($temp);
}
}
}
public function castArray($raw) {
$arrayString = '';
if(is_string($raw)) {
$arrayString = explode(",",$raw);
}
return $arrayString;
}

Related

store and update an array as json in mysql database laravel 9

I have values that I want to store in the database, I have declared the type as json and cast it as an array:
protected $casts = [
'favourites' => 'array'
];
however, I'm not sure how to add and update and read the values inside, any ideas?
controller function:
public function addtofav()
{
$id = request()->get('id');
$user = auth()->user();
json_decode($user->favourites,true);
array_push($user->favourites,$id);
dump('hello');
json_encode($user->favourites);
$user->save();
return response(['id'=> $id],200);
}
Quoting from the Laravel documenations:
adding the array cast to that attribute will automatically deserialize
the attribute to a PHP array when you access it on your Eloquent
model.
therefore, no need to make any encoding or decoding to your values before any update or create, just keep it as an array and Eloquent will take care of that, since you have the $cast array as below:
protected $casts = [
'options' => 'array',
];
Actually you should use many to many but if you want to use array, you don't encode or decode.
public function addtofav()
{
$id = request()->get('id');
$user = auth()->user();
$favourites = $user->favourites;
array_push($favourites,$id);
$user->favourites = $favourites
$user->save();
return response(['id'=> $id],200);
}

Model appends including entire relationship in query

Edit: I was able to see where the relations are being included in my response, but I still don't know why.
On my Customer model, I have:
protected $appends = [
'nps',
'left_feedback',
'full_name',
'url'
];
The accessors are as follows:
/**
* Accessor
*/
public function getNpsAttribute() {
if ($this->reviews->count() > 0) {
return $this->reviews->first()->nps;
} else {
return "n/a";
}
}
/**
* Accessor
*/
public function getLeftFeedbackAttribute() {
if ($this->reviews && $this->reviews->count() > 0 && $this->reviews->first()->feedback != null) {
return "Yes";
} else {
return "No";
}
}
/**
* Accessor
*/
public function getFullNameAttribute() {
return ucwords($this->first_name . ' ' . $this->last_name);
}
/**
* Accessor
*/
public function getUrlAttribute() {
$location = $this->location;
$company = $location->company;
$account_id = $company->account->id;
return route('customers.show', ['account_id' => $account_id, 'company' => $company, 'location' => $location, 'customer' => $this]);
}
So if I comment out the $appends property, I get the response I originally wanted with customer not returning all the relations in my response.
But I do want those appended fields on my Customer object. I don't understand why it would include all relations it's using in the response. I'm returning specific strings.
So is there a way to keep my $appends and not have all the relations it's using in the accessors from being included?
Original Question:
I am querying reviews which belongsTo a customer. I want to include the customer relation as part of the review, but I do not want to include the customer relations.
$reviews = $reviews->with(['customer' => function($query) {
$query->setEagerLoads([]);
$query->select('id', 'location_id', 'first_name', 'last_name');
}]);
$query->setEagerLoads([]); doesn't work in this case.
I've tried $query->without('location'); too, but it still gets included
And I should note I don't have the $with property on the model populated with anything.
Here is the Review model relation:
public function customer() {
return $this->belongsTo('App\Customer');
}
Here is the Customer model relation:
public function reviews() {
return $this->hasMany('App\Review');
}
// I dont want these to be included
public function location() {
return $this->belongsTo('App\Location');
}
public function reviewRequests() {
return $this->hasMany('App\ReviewRequest');
}
In the response, it will look something like:
'review' => [
'id'=> '1'
'customer => [
'somecol' => 'test',
'somecolagain' => 'test',
'relation' => [
'relation' => [
]
],
'relation' => [
'somecol' => 'sdffdssdf'
]
]
]
So a chain of relations ends up being loaded and I don't want them.
As you said in one comment on the main question, you are getting the relations due to the appended accessors.
Let me show you how it should be done (I am going to copy paste your code and simply edit some parts, but you can still copy paste my code and place it in yours and will work the same way but prevent adding the relations) and then let me explain why is this happening:
/**
* Accessor
*/
public function getNpsAttribute() {
if ($this->reviews()->count() > 0) {
return $this->reviews()->first()->nps;
} else {
return "n/a";
}
}
/**
* Accessor
*/
public function getLeftFeedbackAttribute() {
return $this->reviews()->count() > 0 &&
$this->reviews()->first()->feedback != null
? "Yes"
: "No";
}
/**
* Accessor
*/
public function getFullNameAttribute() {
return ucwords($this->first_name . ' ' . $this->last_name);
}
/**
* Accessor
*/
public function getUrlAttribute() {
$location = $this->location()->first();
$company = $location->company;
$account_id = $company->account->id;
return route('customers.show', ['account_id' => $account_id, 'company' => $company, 'location' => $location, 'customer' => $this]);
}
As you can see, I have changed any $this->relation to $this->relation()->first() or $this->relation->get().
If you access any Model's relation as $this->relation it will add it to the eager load (loaded) so it will really get the relation data and store it in the Model's data so next time you do $this->relation again it does not have to go to the DB and query again.
So, to prevent that, you have to access the relation as $this->relation(), that will return a query builder, then you can do ->count() or ->exists() or ->get() or ->first() or any other valid query builder method, but accessing the relation as query builder will prevent on getting the data and store it the model (I know doing ->get() or ->first() will get the data, but you are not directly getting it through the model, you are getting it through the query builder relation, that is different).
This way you will prevent on storing the data on the model, hence giving you problems.
You can also use API Resources, it is used to map a Model or Collection to a desired output.
One last thing, if you can use $this->relation()->exists() instead of $this->relation()->count() > 0 it will help on doing it faster, mostly any DB is faster on looking if data exists (count >= 1) than really counting all the entries it has, so it is faster + more performant on using exists.
Try :
$review->with(‘customer:id,location_id,first_name,last_name’)->get();
Or :
$review->withOnly(‘customer:id,location_id,first_name,last_name’)->get();

Store multiple objects in json column

I have table with some json column. I want to store multi objects in the json column in Laravel.
My json column look like this "position":[{"lat" : 500, "lon" : 5000, "date":"2020:04:21 16:58:23"}].
this is a function in a service that , i call in my controller
public static function savePositionCamion($id_tournee, $position)
{
$geoTour = GeoTournee::where('geo_tournee.id_tournee', $id_tournee)->first(['position']);
if (!$geoTour) {
$geoTournee = new self();
$geoTournee->id_tournee = $id_tournee;
$geoTournee->position = $position;
return $geoTournee->save() ? $geoTournee : false;
} else {
$arr_pos = json_decode($geoTour->position);
dd($arr_pos);
}
}
in the controller i call the function like this
$geoTourne = GeoTournee::savePositionCamion($request->id_tournee, json_encode($request->position));
anyone can help me to achieve this ! thank's
The array cast type is particularly useful when working with columns that are stored as serialized JSON. You have JSON or TEXT field type that contains serialized JSON, adding the array cast to that attribute will automatically deserialize the attribute to a PHP array when you access it on your Eloquent model:
class GeoTournee extends Model
{
...
protected $casts = [
'position' => 'array',
];
...
}
So you can save this json field like this:
$geoTournee->position = $position;
$geoTournee->save();
Accessing the position attribute and it will automatically be deserialized from JSON into a PHP array. When you set the value of the position attribute, the given array will automatically be serialized back into JSON for storage:
And you don't need to create savePositionCamion function, you can use firstOrCreate instead:
GeoTournee::firstOrCreate(['id_tournee' => $id_tournee], [ 'position' => $position ]);

Call to a member function toBase() on array

I am trying to use resource collection and I am receiving this error " Call to a member function toBase() on array"
The following code is in my resource :
class dataCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection
];
}
}
and here is code from the controller:
$projects = count(Project::all());
$services = count(Service::all());
$users = count(User::all());
$technologies = count(Technology::all());
$customers = count(Customer::all());
$data = [
'projects' => $projects,
'services' => $services,
'users' => $users,
'technologies' => $technologies,
'customers' => $customers
];
return new dataCollection($data);
Can someone help me please?
I'm not sure what your end goal is, but it appears the problem is that, as the error says, you are sending the wrong type of data into a collection. The resource collection method in this case is looking for a set of objects, and you are sending an array.
By the manual, its looking for something like this with a collection of User objects:
UserResource::collection(User::all()); // User collection, not the raw data array
If you are trying to send data back to someone via some API or json, I think you already have what you need - perhaps skip the whole resource collection method if you can? So try this:
$data = [
'projects' => Project::count(),
'services' => Service::count(),
'users' => User::count(),
'technologies' => Technology::count(),
'customers' => Customer::count()
];
return json_encode($data);
Note I changed the code a little to make it more efficient for you - you don't need to assign into variables first - and by calling ::all() plus count(), it is going to make much heavier database queries
The toArray() method on the resource would actually re-convert objects into the type of array you have above - so I suggest you test by skipping the circular process.

codeigniter datamapper get_rules not called after save()

I'm using Datamapper ORM for CodeIgniter I have rules 'serialized' and get_rules 'unserialized' for a field in my model. This field will store serialized data and when I retrieve back, get_rules will unserialize it.
However, after calling save(), I'm trying to re-access the field, but it still return serialized string, instead of array.
Is there any way to re-call or refresh my object so that the get_rules is called again and the field now return array?
Here's my model:
class User extends DataMapper{
public $validation = array(
'password' => array(
'label' => 'Password',
'rules' => array('encrypt')
),
'preferences' => array(
'rules' => array('serialize'),
'get_rules'=> array('unserialize')
)
);
function __construct($id = NULL)
{
parent::__construct($id);
}
function post_model_init($from_cache = FALSE)
{
}
public function _encrypt($field)
{
if (!empty($this->{$field}))
{
$this->{$field} = md5($this->{$field});
}
}
}
Datamapper ORM, afaik, will only use the get_rules when actually performing a get(). A few things you could try:
Given the following
$a = new Fruit();
$a->name = 'grapes';
$a->colors = serialize(array("purple","green"));
$a->save();
1. Create a new datamapper object and re-fetch:
$b = new Fruit();
$b->where('id', $a->id)->get();
$colors = $b->colors;
2. unserialize() the field yourself...
$colors = unserialize($a->colors);
3. You might even be able to use get_clone()
//not tested...
$b = $a->get_clone();
$colors = $b->colors;
This has been fixed here: https://bitbucket.org/wanwizard/datamapper/commits/db6ad5f2e10650b0c00c8ef9b7176d49a8e85163
Get the latest Datamapper library from bitbucket.

Resources