Add row to Eloquent Collection by default based on raw query - sql

In my Eloquent collections, I'd like to add an extra column called "editable". "Editable" should be included in each query I run on some models. "Editable" show either be true or false, based on a raw query.
So I have a query that should be runned in each query on my models. Adding an extra column to my collection. The value of "editable" is determined by a raw query.
What is the best way to do this?

You could add an addSelect() method to your query chain to include the custom attribute..
Something like
$results = YourModelClass::select("*")
->addSelect(DB::raw("IF(condition,1,0) AS editable"))
->get();
In the above case, you would replace condition with your relevant SQL statement that would be evaluated per-row as part of the query. If the statement is true, then editable = 1 and if false then editable = 0 for each row returned to your Collection.
EDIT: I just saw that you want this on every query, so you probably would need a global scope/trait for your models, but the above technique for including the extra attribute should be the correct one.
I won't copy/paste the documentation on adding global scopes, that's in the core Laravel docs and I'm sure you can find it.

You can add a custom getter to your model:
public function getEditableAttribute()
{
/* return result from your raw query here */;
}

Related

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

How do I implement, for instance, "group membership" many-to-many in Parse.com REST Cloud Code?

A user can create groups
A group had to have created by a user
A user can belong to multiple groups
A group can have multiple users
I have something like the following:
Parse.Cloud.afterSave('Group', function(request) {
var creator = request.user;
var group = request.object;
var wasGroupCreated = group.existed;
if(wasGroupCreated) {
var hasCreatedRelation = creator.relation('hasCreated');
hasCreatedRelation.add(group);
var isAMemberOfRelation = creator.relation('isMemberOf');
isAMemberOfRelation.add(group);
creator.save();
}
});
Now when I GET user/me with include=isMemberOf,hasCreated, it returns me the user object but with the following:
hasCreated: {
__type: "Relation"
className: "Group"
},
isMemberOf: {
__type: "Relation"
className: "Group"
}
I'd like to have the group objects included in say, 'hasCreated' and 'isMemberOf' arrays. How do I pull that using the REST API?
More in general though, am I approaching this the right way? Thoughts? Help is much appreciated!
First off, existed is a function that returns true or false (in your case the wasGroupCreated variable is always going to be a reference to the function and will tis always evaluate to true). It probably isn't going to return what you expect anyway if you were using it correctly.
I think what you want is the isNew() function, though I would test if this works in the Parse.Cloud.afterSave() method as I haven't tried it there.
As for the second part of your question, you seem to want to use your Relations like Arrays. If you used an array instead (and the size was small enough), then you could just include the Group objects in the query (add include parameter set to isMemberOf for example in your REST query).
If you do want to stick to Relations, realise that you'll need to read up more in the documentation. In particular you'll need to query the Group object using a where expression that has a $relatedTo pointer for the user. To query in this manner, you will probably need a members property on the Group that is a relation to Users.
Something like this in your REST query might work (replace the objectId with the right User of course):
where={"$relatedTo":{"object":{"__type":"Pointer","className":"_User","objectId":"8TOXdXf3tz"},"key":"members"}}

Filter RavenDB Search Results

I have a query that returns valid search results using the IRavenQueryable.Search method. However, I want to further filter those results via a .Where method call such that the search results are then filtered to only include those that have the matching ProjectId.
My object structure is a set of Project entities each containing a collection of Issue entities.
My index creates a projection of Issue Search Results that looks like:
{Id, Key, Summary, Description, ProjectId, ProjectKey, Query}
The Query property is an object[] that is used by the keyword search.
When I run the keyword search:
var results = session.Query().AsProjection().Search(x => x.Query, "some key word");
I get the right results back. But when I try to also apply the Where method:
results = results.Where(i => i.ProjectId == SelectedProject.Id);
It does not filter the results, but instead includes all other results with matching Project Id's.
What is the correct way to force Linq or RavenDB's IRavenQueryable to apply an AND instead of an OR in this scenario?
After posting this question I managed to find the answer elsewhere on stackoverflow.
Here is the solution:
ravendb combining Search with Where
In a nutshell, the Search method provides an extra optional parameter [options] to allow you to specify how the search is combined with other where clauses in the query. It defaults to SearchOptions.Or so you need to explicitly set it to options: SearchOptions.And.

Can anyone explain how CDbCriteria->scopes works?

I've just checked the man page of CDbCriteria, but there is not enough info about it.
This property is available since v1.1.7 and I couldn't find any help for it.
Is it for dynamically changing Model->scopes "on-the-fly"?
Scopes are an easy way to create simple filters by default. With a scope you can sort your results by specific columns automatically, limit the results, apply conditions, etc. In the links provided by #ldg there's a big example of how cool they are:
$posts=Post::model()->published()->recently()->findAll();
Somebody is retrieving all the recently published posts in one single line. They are easier to maintain than inline conditions (for example Post::model()->findAll('status=1')) and are encapsulated inside each model, which means big transparency and ease of use.
Plus, you can create your own parameter based scopes like this:
public function last($amount)
{
$this->getDbCriteria()->mergeWith(array(
'order' => 't.create_time DESC',
'limit' => $amount,
));
return $this;
}
Adding something like this into a Model will let you choose the amount of objects you want to retrieve from the database (sorted by its create time).
By returning the object itself you allow method chaining.
Here's an example:
$last3posts=Post::model()->last(3)->findAll();
Gets the last 3 items. Of course you can expand the example to almost any property in the database. Cheers
Yes, scopes can be used to change the attributes of CDbCriteria with pre-built conditions and can also be passed parameters. Before 1.1.7 you could use them in a model() query and can be chained together. See:
http://www.yiiframework.com/doc/guide/1.1/en/database.ar#named-scopes
Since 1.1.7, you can also use scopes as a CDbCriteria property.
See: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#relational-query-with-named-scopes

NHibernate CreateCriteria and CreateQuery generates different sql?

I'm new to NHibernate and can't figure out why these two statements generates different sql.
the first one only get the ClientInformation (with Information and Client being Proxies) which is what i want.
return repository
.CreateQuery("from ClientInformation ci where ci.Information.IsMandatory = true and ci.Client.Id = :clientId")
.SetParameter("clientId", clientId)
.List<ClientInformation>();
The second one generates everything. All data is returned for the 3 entities, which is not what i want
return repository.CreateCriteria()
.CreateAlias("Information", "inf")
.CreateAlias("Client", "cli")
.Add(Expression.Eq("cli.Id", clientId))
.Add(Expression.Eq("inf.IsMandatory", true))
.List<ClientInformation>();
What i'm i doing wrong ?
thanks
Actually it all boils down to what you want to do.
First of all the Criteria queries honor the mapping definitions (lazy/eager joins etc) where in constrast HQL queries unless defined otherwise everything is lazy (excluding value properties of course)
Secondly the CreateAlias method defines which entities to join and default behaviour is to also select them.
Note that you are calling
repository.CreateCriteria()
and if that wraps directly to nhSession.CreateCriteria() then you haven't defined exactly what you want to select.
So, try to make this
nhSession.CreateCriteria(typeof(ClientInformation));
which will be translated as 'select only ClientInformation'...