How do I search related values in Yii2? - search

I have the following situation in Yii2:
Project model
CustomField, defining a custom field type and whether it should be applied to Projects (other options are employees and companies)
CustomFieldContent, related to both an entity (project in this case) and a custom field type
So, an example:
Project with id 1
CustomField with id 2
CustomFieldContent with entityId = 1, type = 'project', customFieldId = 2 and value = 'test'
Now, displaying custom content for each project in Yii's gridview is no problem. But, I want to make it searchable and sortable. Therefore, I need to add the custom field name as an attribute to ProjectSearch. That, however, can't be done as Yii doesn't allow for dynamic attributes.
Any ideas as to how to go about this?

For searchable and sortable content i suggest you this tutorial where you can find useful sample for build what you need. ( the scenario nuber 2 is the more appropriate to your needs)
In short term, you should extend your base model adding the relation you need, setup in searchModel proper functions adding to the dataProvider->setSort for the field/relation and add the where condition for filtering.
below a short extract
$dataProvider->setSort([
'attributes' => [
....
'yourRelatedField' => [
'asc' => ['field1' => SORT_ASC, ],
'desc' => ['field1' => SORT_DESC,],
'label' => 'your Laber',
'default' => SORT_ASC
],
]
]);
and extending the where condition for filtering.
/* Add your filtering criteria */
// filter CustomFieldContent
$query->joinWith(['table_a' => function ($q) {
$q->where('table_a.CustomFieldContent LIKE "%' . $this->CustomFieldContent . '%" ');
}]);

Related

Convert JavaScript array element from string to object

In React, I am trying to create the order/sort array to send to Nodejs for a situation where there we need to sort by an included table column. So, in React, I have:
sort = `[[Contact, "phone1", "asc"]]`
That is variable depending on which column header they click on in a screen, and whether descending or ascending.
In Nodejs on the backend, this shows up as a string (not an array):
sort = [ [ Contact, 'phone', 'asc' ] ]
I need it to look like this (an array AND with Contact without quotes, so that Sequelize will accept it):
sort = [ [ Contact, "phone", "asc" ] ]
so that it can be passed to Sequelize, such as:
Table.findAll({
where: {
...
},
include [ { model: Contact, ... } ]
order: sort
})
In React I can make the "Contact" have quotes around it, so that in Node I can use JSON.parse, but then Contact has quotes around it, which doesn't work when passing it to Sequelize's sort, as it thinks it is part of the original table that we are querying.
How can this be done?
Thank you very much!
Contact is without quotes because the Contact is reference to the actual class definition which is being used in the frontend. When you send your request to the backend then this class information is lost.
Even if there is a class with the same name on the backend, it cannot be converted to that class definition just by the same name. You should include for example a middleware (if you are using express for example) that replace your stringified "Contact" with an actual definition that Sequelize can use.
Something like this on the server:
const Contact = required('./your-path-to-contact')
// ...some other stuff happening...
sort.forEach(s => {
if(s === 'Contact')
s = Contact
})
This solution is a little bit hacky but can work. For a good solution I would refrain from sending class to the server. I would rather have some kind of query builder which the server understands and can select the appropriate class to use when the time comes.
We solved this by not passing an array from the frontend to the backend, but instead passing an object, with key-value pairs such as table, column, & direction, and then converting to an order array using sequelize.col & literals, such as...
order: [
[sequelize.col(`${table}.${column}`), direction]
]
It's a bit more complicated than just this one line given all of the different possibilities in our app, but that's a good summary.
Some notes:
table = the included table
column = the column in the included table
to sort on
direction = 'asc' or 'desc'
With sequelize.col, you really don't even need to have the included table name, if there are no other column names that are the same.

Usage of TSVECTOR and to_tsquery to filter records in Sequelize

I've been trying to get full search text to work for a while now without any success. The current documentation has this example:
[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // match text search for strings 'fat' and 'rat' (PG only)
So I've built the following query:
Title.findAll({
where: {
keywords: {
[Op.match]: Sequelize.fn('to_tsquery', 'test')
}
}
})
And keywords is defined as a TSVECTOR field.
keywords: {
type: DataTypes.TSVECTOR,
},
It seems like it's generating the query properly, but I'm not getting the expected results. This is the query that it's being generated by Sequelize:
Executing (default): SELECT "id" FROM "Tests" AS "Test" WHERE "Test"."keywords" ## to_tsquery('test');
And I know that there are multiple records in the database that have 'test' in their vector, such as the following one:
{
"id": 3,
"keywords": "'keyword' 'this' 'test' 'is' 'a'",
}
so I'm unsure as to what's going on. What would be the proper way to search for matches based on a TSVECTOR field?
It's funny, but these days I am also working on the same thing and getting the same problem.
I think part of the solution is here (How to implement PostgresQL tsvector for full-text search using Sequelize?), but I haven't been able to get it to work yet.
If you find examples, I'm interested. Otherwise as soon as I find the solution that works 100% I will update this answer.
What I also notice is when I add data (seeds) from sequelize, it doesn't add the lexemes number after the data of the field in question. Do you have the same behavior ?
last thing, did you create the index ?
CREATE INDEX tsv_idx ON data USING gin(column);

Storing and querying PostgreSQL database entities with multiple related entities?

Designing a PostgreSQL database that will be queried by a Node API using Sequelize. Currently, I have a table called recipes that has columns called ingredients and instructions. Those columns are stored for a given as an array of strings like {Tomatoes, Onions}.
That method of storage worked fine for simply fetching and rendering a recipe on the client side. But it wasn't working well for fuzzy search querying because, using Sequelize all I could do was ingredients: { [Op.contains] : [query] }. So if a user typed tomatoes there was no way to write a "fuzzy" search query that would return a recipe with an ingredient Tomatoes.
And then I read this in the PostgreSQL documentation:
Arrays are not sets; searching for specific array elements can be a sign of database misdesign. Consider using a separate table with a row for each item that would be an array element. This will be easier to search, and is likely to scale better for a large number of elements.
Now I'm considering storing ingredients and instructions as separate tables, but I have a couple of questions.
1) As a recipe can have multiple ingredients related to it, should I just use a foreign key for each ingredient and the Sequelize hasMany relationship? That seems correct to me, except that I'm now potentially duplicating common ingredients each time a new recipe is created that uses that ingredient.
2) What would be the best way to write a fuzzy search query so that a user could search the main columns of the recipes table (e.g. title, description) and additionally apply their query to the instructions and ingredients tables?
Essentially I'd like to end up with a fuzzy search query applied to the three tables that looks something like this...
const recipes = await req.context.models.Recipe.findAll({
where: {
[Op.or]: [
{ title: { [Op.iLike]: '%' + query + '%' } },
{ description: { [Op.iLike]: '%' + query + '%' } },
{ ingredients: { ingredient: { [Op.iLike]: '%' + query + '%' } } },
{ instructions: { instruction: { [Op.iLike]: '%' + query + '%' } } }
]
}
});
Thanks!
I have done this, i happen to use graphql in my node layer with sequelize, and i have filter objects that do this type of thing. You'll just need some include statements in your Recipie.findAll.. after your initial where clause where you evaluate whether you are searching title or description or both type thing. i sent my search params in with prefix's i could strip off that told me what sequelize op's i would want to use on them and just ran my args through a utility method to create my where clause, but i know there are many ways to skin that cat. i just did not want to clutter up my resolvers with tonnes of hardcoded ops and conditional clauses was all.... your include might look something like this
include: [{
model: models.Ingredient,
as: 'Ingredients',
through: { some join table specifying keys where necessary since this
is many to many }
where: {some conditional code around your search param},
}, {
model: models.Instruction,
as: 'Instructions',
where: {some conditional code around your search param},
}],
There is good documentation around multiple includes, or nested includes in the sequelize docs, but from what i see above you have a fairly good understanding of what you need to do. To uncomplicate things a bit, i'd start with just searching on your fields from recipie (title, description) before you add the includes and get that working, then it will be a little clearer how you want to form your where clauses.
alternativley.. you can skip the includes and write associations in your models and call them with getters and pass the where clauses to those... i do that as well and again well documented stuff now.. Sequelize has really upped their game
Recipie.associate = function (models) {
models.Recipie.hasMany(models.Ingredient, { as: 'Ingredients', through: "recipie_ingredient" foreignKey: 'recipie_id'});
};
now you have a getter for Ingredients, and if you declare belongsToMany targetting back at Recipie in the Ingredient model then you'll have a getter there as well, and you can pass your search string to that via where clause and get all recipies that have a given ingredient or ingredient list type thing.... Clear as mud?

TYPO3 backend: search custom records

I developed an extension which allows creation of new records.
In List module, under the records list, there is the Search form.
It works with fe users for example, but not with my custom records.
Is there any special configuration that I have to add in my tca to make this form work with my custom records?
EDIT: This seems to be happening after updating to TYPO3 4.6. In the previous version, 4.3.3, it works.
Thanks.
Edit ext_tables.php file in typo3conf/ext/yourext directory, find your table, and add to its ctrl section searchFields property as comma separated list of fields to search in:
$TCA['tx_yourext_table'] = array(
'ctrl' => array(
'title' => 'Title of your table',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
// etc...
'searchFields' => 'title, other_field, yet_other_field',
),
);
Don't forget to clear all caches after that, works at 4.6.3
There's official information when and why it was changed

Kohana 3: Validation rule for has_many through relationship

Is it possible to create a validation rule in Kohana 3 that will validate the has_many through relationship?
Using the example on the guide page, a blog post can have many categories through the categories_posts table. Is there a validation rule that can be setup in the Post model to verify at least one category was added?
I tried the following:
public function rules()
{
return array(
'categories' => array(
array(array($this, 'has'), array('categories'))
)
);
}
because I see that the ORM::has function will return true/false. But I think because 'categories' is a relationship, and not a field, the rule I wrote never gets checked.
Any ideas?
You must save Post before adding has_many relations. You can check Post for categories after saving, and mark it as draft if they were not set.
Woo, good idea.
Focus in MVC design pattern. I think that's C business not the M.
if ($post->categories->find_all())
{
//blablabla
}
Since categories is external to the posts table, you'll want to use external validation. Create a function called Model_Post::rule_has_valid_categories($submitted_categories, $post) that returns a boolean denoting whether or not the submitted categories are valid for this post.
Then, create the extra rule just before you try to save the post:
$extra_rules = Validation::factory(array('categories' => $submitted_categories))
->rule(
'categories',
'Model_Post::rule_has_valid_categories',
array(':value', ':model')
);
try
{
$post->save($extra_rules);
}
catch (ORM_Validation_Exception $e)
{
// if categories rule failed, array will contain _external[categories] field
print_r($e->errors('models'));
}
You store the message in /application/messages/models/post/_external.php:
return array(
'categories' => array(
'Model_Post::rule_has_valid_categories' => 'Invalid categories'
),
);

Resources