How to search Elasticsearch-rails with association model conditions? - ruby-on-rails-3

I'm trying to search Doc model has_and_belongs_to_many projects which belongs to specific project.
models/doc.rb
require 'elasticsearch/model'
class Doc < ActiveRecord::Base
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: {
analysis: {
tokenizer: {
ngram_tokenizer: {
type: "nGram",
min_gram: "2",
max_gram: "3",
token_chars: [
"letter",
"digit",
"punctuation"
]
}
},
analyzer: {
ngram_analyzer: {
tokenizer: "ngram_tokenizer"
}
},
},
} do
mappings do
indexes :sourcedb, type: 'string', analyzer: 'ngram_analyzer'
indexes :sourceid, type: 'string', analyzer: 'ngram_analyzer'
indexes :body, type: 'string', analyzer: 'ngram_analyzer'
indexes :docs_projects do
indexes :doc_id
indexes :project_id
indexes :projects do
indexes :id, index: :not_analyzed
end
end
end
end
def as_indexed_json(options={})
as_json(
only: [:id, :sourcedb, :sourceid, :body],
include: { projects: {only: :id} }
)
end
end
search method is below
search_docs = docs.search(
query: {
bool:{
must: [
{match: {
'projects.id' => project_id
}
}
]
}
},
size: 5000,
).records.order('sourcedb ASC, sourceid ASC').paginate(page:params[:page], per_page: 10)
This search method finishes without errors but nothing returned.
Doc Load (4.6ms) SELECT "docs".* FROM "docs" INNER JOIN "docs_projects" ON "docs"."id" = "docs_projects"."doc_id" WHERE "docs_projects"."project_id" = 56
(0.4ms) SELECT COUNT(*) FROM "docs" WHERE 1=0
Doc Load (0.3ms) SELECT "docs".* FROM "docs" WHERE 1=0 ORDER BY sourcedb ASC, sourceid ASC LIMIT 10 OFFSET 0
CACHE (0.0ms) SELECT COUNT(*) FROM "docs" WHERE 1=0
I've tried to search with where([projects.id IN (?)], project_ids), but it cannot search docs belongs to project with max size.
How to search by match with associations?
Thanks in advance.

Now, I found the solution. This is how I solved.
doc.rb
settings index: {
analysis: {
tokenizer: {
ngram_tokenizer: {
type: "nGram",
min_gram: "2",
max_gram: "3",
token_chars: [
"letter",
"digit",
"punctuation"
]
}
},
analyzer: {
ngram_analyzer: {
tokenizer: "ngram_tokenizer"
}
},
},
} do
mappings do
indexes :sourcedb, type: 'string', analyzer: 'ngram_analyzer'
indexes :sourceid, type: 'string', analyzer: 'ngram_analyzer'
indexes :body, type: 'string', analyzer: 'ngram_analyzer'
# indexes :docs_projects, type: 'nested' do
indexes :docs_projects do
indexes :doc_id
indexes :project_id
end
indexes :projects do
indexes :id, index: :not_analyzed
end
end
end
search method
def self.search_docs(attributes = {})
minimum_should_match = 0
minimum_should_match += 1 if attributes[:sourcedb].present?
minimum_should_match += 1 if attributes[:sourceid].present?
minimum_should_match += 1 if attributes[:body].present?
if attributes[:project_id].present?
must_array = [
{match: {
'projects.id' => attributes[:project_id]
}
}
]
end
docs = search(
query: {
bool:{
must: must_array,
should: [
{match: {
sourcedb: {
query: attributes[:sourcedb],
fuzziness: 0
}
}
},
{match: {
sourceid: {
query: attributes[:sourceid],
fuzziness: 0
}
}
},
{match: {
body: {
query: attributes[:body],
fuzziness: 'AUTO'
}
}
},
],
minimum_should_match: minimum_should_match
}
},
size: SEARCH_SIZE,
)
return {
total: docs.results.total,
docs: docs.records.order('sourcedb ASC, sourceid ASC')
}
end

Related

Unknown column in sequelize count

I do such node.js Sequelize query to get rows quantity of included unread_messages, so I can get amount of unread messages of specifi user. But it returns me Unknown column 'unread_messages.id' in 'field list'.
If I remove attributes: {...} error disappears
const result = await Chats.findAndCountAll({
attributes: {
include: [[Sequelize.fn('COUNT', Sequelize.col('unread_messages.id')), 'total_unread_messages']]
},
where: {
...(req.query.filters as WhereOptions),
},
include: [
{ model: Users, as: 'createdBy', required: false },
{ model: ChatTypes, as: 'type', required: false },
{
model: ChatMessages,
as: 'unread_messages',
where: {
id: {[Op.gt]: Sequelize.literal(`(
SELECT last_read_message_id
FROM chats_users
WHERE
user_id = '${req.user?.id}'
AND
chat_id = Chats.id
)`),}
},
required: false,
},
{
model: ChatMessages,
as: 'last_message',
required: false,
include: [
{ model: Users, as: 'to_user' },
{ model: Users, as: 'from_user' },
{ model: Chats, as: 'chat' },
{ model: MessageTypes, as: 'message_type' },
{
model: Users,
as: 'is_mine',
required: false,
where: { id: req.user?.id },
},
],
},
],
group:['chats.id'],
order: req.query.sort as Order,
offset,
limit,
});

Fulltext mongodb $text search query in graphql-compose-mongoose

I'm unable to figure out how to construct a graphql query for performing the mongodb fulltext search using the text index. https://docs.mongodb.com/manual/text-search/
I've already created a text index on my string in the mongoose schema but I don't see anything in the schemas that show up in the grapqhl playground.
A bit late, though I was able to implement it like so
const FacilitySchema: Schema = new Schema(
{
name: { type: String, required: true, maxlength: 50, text: true },
short_description: { type: String, required: true, maxlength: 150, text: true },
description: { type: String, maxlength: 1000 },
location: { type: LocationSchema, required: true },
},
{
timestamps: true,
}
);
FacilitySchema.index(
{
name: 'text',
short_description: 'text',
'category.name': 'text',
'location.address': 'text',
'location.city': 'text',
'location.state': 'text',
'location.country': 'text',
},
{
name: 'FacilitiesTextIndex',
default_language: 'english',
weights: {
name: 10,
short_description: 5,
// rest fields get weight equals to 1
},
}
);
After creating your ObjectTypeComposer for the model, add this
const paginationResolver = FacilityTC.getResolver('pagination').addFilterArg({
name: 'search',
type: 'String',
query: (query, value, resolveParams) => {
resolveParams.args.sort = {
score: { $meta: 'textScore' },
};
query.$text = { $search: value, $language: 'en' };
resolveParams.projection.score = { $meta: 'textScore' };
},
});
FacilityTC.setResolver('pagination', paginationResolver);
Then you can assign like so
const schemaComposer = new SchemaComposer();
schemaComposer.Query.addFields({
// ...
facilities: Facility.getResolver('pagination')
// ...
});
On your client side, perform the query like so
{
facilities(filter: { search: "akure" }) {
count
items {
name
}
}
}

Sequelize doesn't use field alias from model

I have model called proxyPool with next fields:
poolId: {
type: DataTypes.INTEGER,
references: {
model: 'pool',
key: 'id',
},
field: 'pool_id',
},
proxyId: {
type: DataTypes.INTEGER,
references: {
model: 'proxy',
key: 'id',
},
field: 'proxy_id',
},
It's a N:M table for two tables which have next associations:
proxy.associate = (models) => {
proxy.belongsToMany(models.pool, {
through: models.proxyPool,
foreignKey: 'proxy_id',
});
};
and
pool.associate = (models) => {
pool.belongsToMany(models.proxy, {
through: models.proxyPool,
foreignKey: 'pool_id',
});
};
When I call proxyPool.findOrCreate({where: {proxyId, poolId}}) it says that column proxyPool.proxyId does not exist, but in raw SQL I see:
SELECT "id", "pool_id" AS "poolId", "createdAt", "updatedAt", "pool_id", "proxy_id"
FROM "portnoi"."proxy_pool" AS "proxyPool"
WHERE "proxyPool"."proxyId" = '3' AND "proxyPool"."pool_id" = '1' LIMIT 1;
Why does it use alias for poolId = pool_id but not use alias described in model for proxyId = proxy_id?
Have you tried to make a query like that?
proxyPool.findOrCreate({
where: {
proxyId: {
[Op.eq]: proxyId,
},
...
}
})

Sequelize: on a subset of model A, sum an integer-attribute of an associated model B

I want to do this:
select sum("quantity") as "sum"
from "orderArticles"
inner join "orders"
on "orderArticles"."orderId"="orders"."id"
and "orderArticles"."discountTagId" = 2
and "orders"."paid" is not null;
which results in on my data base:
sum
-----
151
(1 row)
How can I do it?
My Sequelize solution:
The model definitions:
const order = Conn.define('orders', {
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
primaryKey: true
},
// ...
paid: {
type: Sequelize.DATE,
defaultValue: null
},
// ...
},
// ...
})
const orderArticle = Conn.define('orderArticles',
{
id: {
type: Sequelize.BIGINT,
autoIncrement: true,
primaryKey: true
},
// ...
quantity: {
type: Sequelize.INTEGER,
defaultValue: 1
}
},
{
scopes: {
paidOrders: {
include: [
{ model: order, where: { paid: {$ne: null}} }
]
}
},
// ...
})
Associations:
orderArticle.belongsTo(order)
order.hasMany(orderArticle, {onDelete: 'cascade', hooks: true})
I came up with this after hours of research:
db.models.orderArticles
.scope('paidOrders') // select only orders with paid: {$ne: null}
.sum('quantity', { // sum up all resulting quantities
attributes: ['quantity'], // select only the orderArticles.quantity col
where: {discountTagId: 2}, // where orderArticles.discountTagId = 2
group: ['"order"."id"', '"orderArticles"."quantity"'] // don't know why, but Sequelize told me to
})
.then(sum => sum) // return the sum
leads to this sql:
SELECT "orderArticles"."quantity", sum("quantity") AS "sum",
"order"."id" AS "order.id", "order"."taxRate" AS "order.taxRate",
"order"."shippingCosts" AS "order.shippingCosts", "order"."discount"
AS "order.discount", "order"."paid" AS "order.paid",
"order"."dispatched" AS "order.dispatched", "order"."payday" AS
"order.payday", "order"."billNr" AS "order.billNr",
"order"."createdAt" AS "order.createdAt", "order"."updatedAt" AS
"order.updatedAt", "order"."orderCustomerId" AS
"order.orderCustomerId", "order"."billCustomerId" AS
"order.billCustomerId" FROM "orderArticles" AS "orderArticles" INNER
JOIN "orders" AS "order" ON "orderArticles"."orderId" = "order"."id"
AND "order"."paid" IS NOT NULL WHERE "orderArticles"."discountTagId" =
'4' GROUP BY "order"."id", "orderArticles"."quantity";
which has this result on the same data base: 0 rows
If you know what I got wrong please let me know!
Thank you :)
Found the solution:
in the scopes definition on the orderArticle model:
scopes: {
paidOrders: {
include: [{
model: order,
where: { paid: {$ne: null}},
attributes: [] // don't select additional colums!
}]
}
},
//...
and the algorithm:
db.models.orderArticles
.scope('paidOrders')
.sum('quantity', {
attributes: [], // don't select any further cols
where: {discountTagId: 2}
})
Note: In my case it was sufficient to return the promise. I use GraphQL which resolves the result and sends it to the client.

Filter by belongsToMany relation field

It is impossible to filter data using a linked table. There are two tables Instructor and Club. They related how belongsToMany. I need to get all Instructors which club_id = value.
Instructor model:
sequelize.define('Instructor', {
instance_id: DataTypes.INTEGER,
name: DataTypes.STRING(255)
}, {
tableName: 'instructors',
timestamps: false,
classMethods: {
associate: function (models) {
Instructor.belongsToMany(models.Club, {
through: 'InstructorClub'
});
}
}
});
Club model:
sequelize.define('Club', {
instance_id: DataTypes.INTEGER,
name: DataTypes.STRING
}, {
tableName: 'clubs',
timestamps: false,
classMethods: {
associate: function (models) {
Club.belongsToMany(models.Instructor, {
through: 'InstructorClub'
});
}
}
});
Related table:
sequelize.define('InstructorClub', {
InstructorId: {
type: DataTypes.INTEGER,
field: 'instructor_id'
},
ClubId: {
type: DataTypes.INTEGER,
field: 'club_id'
}
}, {
tableName: 'instructors_clubs'
timestamps: false
});
I am trying to get the data as follows::
models
.Instructor
.findAll({
include: [
{
model: models.Club,
as: 'Clubs',
through: {
attributes: []
}
}
],
# I need to filter by club.id
where: {
'Clubs.id': 10
}
})
Current query generated SQL:
SELECT `Instructor`.`id`,
`Instructor`.`instance_id`,
`Instructor`.`name`,
`Clubs`.`id` AS `Clubs.id`,
`Clubs`.`name` AS `Clubs.name`,
`Clubs.InstructorClub`.`club_id` AS `Clubs.InstructorClub.ClubId`,
`Clubs.InstructorClub`.`instructor_id` AS `Clubs.InstructorClub.InstructorId`
FROM `instructors` AS `Instructor`
LEFT OUTER JOIN (`instructors_clubs` AS `Clubs.InstructorClub` INNER JOIN `clubs` AS `Clubs` ON `Clubs`.`id` = `Clubs.InstructorClub`.`club_id`)
ON `Instructor`.`id` = `Clubs.InstructorClub`.`instructor_id`
WHERE `Instructor`.`Clubs.id` = 10;
Well, I need some kind of this:
SELECT `Instructor`.`id`,
`Instructor`.`instance_id`,
`Instructor`.`name`,
`Clubs`.`id` AS `Clubs.id`,
`Clubs`.`name` AS `Clubs.name`,
`Clubs.InstructorClub`.`club_id` AS `Clubs.InstructorClub.ClubId`,
`Clubs.InstructorClub`.`instructor_id` AS `Clubs.InstructorClub.InstructorId`
FROM `instructors` AS `Instructor`
LEFT OUTER JOIN (`instructors_clubs` AS `Clubs.InstructorClub` INNER JOIN `clubs` AS `Clubs` ON `Clubs`.`id` = `Clubs.InstructorClub`.`club_id`)
ON `Instructor`.`id` = `Clubs.InstructorClub`.`instructor_id`
# It should be like this:
WHERE `Clubs`.`id` = 10;
Move your 'where' up into the include (with model, as, and through).
include: [ {
model: models.Club,
as: 'Clubs',
through: { attributes: [] },
where: { 'Clubs.id': 10 }
} ]