Undefined model by using Join Query in Laravel - api

I am indexing apples with their specified properties (such as color) using API Laravel. I use join to retrieve apples which are related to a specified brand. but it does not retrieve apples with their own specified properties which are defined in another DB and models.
public function index(Brand $brand)
{
$apples = Apple::join('brands', 'brand_id', 'brands.id')->where('brand_id', $brand->id)->get();
return returnSuccessfulResponse(
trans('api.response.successful.index'),
Resource::collection($apples)
);
}
Apple model:
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function appleProperties()
{
return $this->hasMany(AppleProperty::class);
}
Resource:
return [
'id' => $this->brand->id,
'name' => $this->brand->name,
'apple-properties' => $this->appleProperties,
];
Route:
Route::apiResource('brands/{brand}/apples', 'AppleController');
It is not retrieving appleProperties. I do not understand that reason!

When you use join() method in your queries, it is recommended to use select() as well, so that is no longer ambiguous which table you referenced to. In your code, the query may be something like this:
$apples = Apple::join('brands', 'brand_id', 'brands.id')->select('apples.*')->where('brand_id', $brand->id)->get();

Related

Laravel 9 accessors and mutators - Simple example not working

So I am trying to get my mutators and accessors to work in Laravel 9, in my Tag model I have the following:
protected function name(): Attribute
{
return Attribute::make(
get: fn ($value) => strtolower($value),
set: fn ($value) => strtolower($value),
);
}
When displaying the name in my blade view however, the name is not being displayed in lower cases ({{ $tag->name }}), also not when saving a new model to the database.
The following does work btw:
public function getNameAttribute($value)
{
return strtolower($value);
}
Also when using public it does not work:
public function name(): Attribute
Just trying to understand what I am doing wrong here?
I am using Laravel version 9.44
I dont know if the question's content is exactly your code. I had a similiar problem, get and set not working. But it worked in other model files.
I just found the solution once again, ya, once again.
If the attribute(column name) has two words, you have to make it together, first word should be lowercase.
short_name
protected function shortName(): Attribute
{
return Attribute::make(
get: fn ($value) => strtolower($value),
set: fn ($value) => strtolower($value),
);
}
Inject the class at top of page like below
use Illuminate\Database\Eloquent\Casts\Attribute;

Yii2 REST API relational data return

I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}

How to programmatically filter by property in Orchard in the ApplyFilter method of a Filter implementing IFilterProvider

Simplified Model:
public class GolfCourseDetailsPart : ContentPart<GolfCourseDetailsRecord>
{
public bool ShowInHomePage {... //Get and Set using Retrieve and Store methods
}
Simplified Migrations:
ContentDefinitionManager.AlterTypeDefinition("GolfCourse", gc => gc
//...
.WithPart(typeof(GolfCourseDetailsPart).Name)
);
I need to filter all items of type "GolfCourse" to get only the ones that have ShowInHomePage set to true.
Filter:
I have created a filter implementing the IFilterProvider interface and it returns all the GolfCourse content items but I couldn't get to filter by ShowInHomePage yet:
private void ApplyFilter(FilterContext context)
{
context.Query = context.Query.Join(x=>x.ContentPartRecord(typeof(GolfCourseDetailsRecord)));
}
How could I get to filter by the property ShowInHomePage??
You are almost there, the only part missing is the .Where clause. In a HQL query it looks like this:
private void ApplyFilter(FilterContext context)
{
context.Query = context
.Query
.Join(x => x.ContentPartRecord(typeof(GolfCourseDetailsRecord)))
.Where(x => x.ContentPartRecord<GolfCourseDetailsRecord>(), g => g.Eq("ShowInHomePage", true));
}
Is there any reason you want to create an IFilterProvider?
Those will be only useful if you want to have a customized filter available for query projections.
If you simply want to get filtered data programmatically then I would use Query method of ContentManager.
Here is a set of samples on how querying Orchard, I think it will be more useful for you than if I simply put here the query you need: https://orchardtrainingdemo.codeplex.com/SourceControl/latest#Controllers/ContentsAdminController.cs

How to get Phalcon to not reload the relation each time I want to access it

I am using Phalcon and have a model Order that has a one-to-many relationship with model OrderAddress. I access those addresses through the following function:
public function getAddresses($params = null) {
return $this->getRelated("addresses", array(
"conditions" => "[OrderAddress].active = 'Y'"
));
}
The OrderAddress model has a public property errors that I do not want persisted to the database. The problem I am having is that everytime I access the getAddresses function, it reloads the object from MySQL which completely wipes the values that I set against that property.
I really only want the OrderAddress models to be loaded once, so that each call to getAddresses doesn't make another trip to the DB- it just iterates over the collection that was already loaded.
Is this possible?
I suppose there's no such option in phalcon, so it has to be implemented in your code.
You could create an additional object property for cached addresses, and return it if it's already been initialized:
protected $cachedAddresses = null;
public function getAddresses($params = null) {
if ($this->cachedAddresses === null) {
$this->cachedAddresses = $this->getRelated("addresses", array(
"conditions" => "[OrderAddress].active = 'Y'"
));
}
return $this->cachedAddresses;
}
This could be a quick solution, but it will be painful to repeat it if you have other relations in your code. So to keep it DRY, you could redefine a 'getRelated' method in base model so it would try to return cached relations, if they already were initialized.
It may look like this:
protected $cachedRelations = [];
public function getRelated($name, $params = [], $useCache = true) {
//generate unique cache object id for current arguments,
//so different 'getRelated' calls will return different results, as expected
$cacheId = md5(serialize([$name, $params]));
if (isset($this->cachedRelations[$cacheId]) && $useCache)
return $this->cachedRelations[$cacheId];
else {
$this->cachedRelations[$cacheId] = parent::getRelated($name, $params);
return $this->cachedRelations[$cacheId];
}
}
Then, you can leave 'getAddresses' method as is, and it will perform only one database query. In case you need to update cached value, pass false as a third parameter.
And, this is completely untested, but even if there're any minor errors, the general logic should be clear.

Working around Duplicate association path bug in Nhibernate with Query Over

I've got a bit of code that tries to access same association path twice and they're really same aliases but because I'm using query objects, I have them in two different places and I am not really sure how to get the alias.
May be some code can clear the confusion:
var privateBlogQuery = new BlogQuery()
.ByUser(1)
.RemoveHidden()
.FetchUserDetails();
//<-------- In Blog Query object class: ------>
/// Gets all the private blogs the user has permissions to view
public BlogQuery ByUser(int userId)
{
var authorisedUsers = null;
this.Left.JoinQueryOver(r => r.AuthorisedUsers, () => authorisedUsers)
.Where(r => r.User.Id == userId);
return this;
}
/// Removes all private blogs user has marked to be hidden
public BlogQuery RemoveHidden()
{
var authorisedUsers = null;
this.Left.JoinQueryOver(r => r.AuthorisedUsers, () => authorisedUsers)
.Where(r => !r.IsHidden);
return this;
}
/// Loads up details of all users who have permission
/// to view the private blog
public BlogQuery FetchUserDetails()
{
var users = null;
var authorisedUsers = null;
this.Left.JoinQueryOver(r => r.AuthorisedUsers, () => authorisedUsers)
.Left.JoinQueryOver(r => r.User, () => users);
return this;
}
There are times when I'm using all 3 criteria individually and the sql generated is precisely what I need and everything is nice and dandy as long as they are used separately.
Now I need to use them all together, at the same time and nhibernate throws an exception duplicate alias and I changed up the alias on these three functions but then I am greeted with the duplicate association path exeception.
A bit of googling and I learnt that it is a bug in hibernate and I also found a few workarounds on this bug
Trouble is I am using Query objects and hence Query over and I am not really sure how to get the association path / alias here.
So how do I go about this please?
make authorisedUsers a membervariable of BlogQuery and use a marker/flag to know if ByUser and RemoveHidden should do the Join
use JoinAlias
example
AuthorisedUser authorisedUser;
bool authorisedUsersJoined;
public BlogQuery RemoveHidden()
{
if (!authorisedUsersJoined)
this.Left.JoinAlias(r => r.AuthorisedUsers, () => authorisedUser);
this.Where(() => !authorisedUser.IsHidden);
return this;
}
FetchUserDetails is mutual exclusive with the other two because filtering on an association prevents NH from initializing the association. You'll need to subquery with the filter and query the resulting Ids and initialize then.
/// Loads up details of all users who have permission
/// to view the private blog
public BlogQuery FetchUserDetails()
{
this.Query = QueryOver.Of<Blog>()
.WhereRestrictionOn(b => b.Id).IsIn(this.Query.Select(b => b.Id))
.Fetch(r => r.AuthorisedUsers).Eager
.ThenFetch(au => au.User).Eager;
return this;
}