Ruby on Rails - Search Query for words within the title - ruby

In my application I am doing a scope/search on :title for a search/filter of my records. The search itself works fine, only thing is that user need to write exactly the title & they can't search word within the :title.
For instance If the title is: This search is cool, user need to start the search and have the complete sentence: This search to search and they can't write is cool and get records that have is cool in the title.
My scope looks like:
class Post < ActiveRecord::Base
scope :search_query, lambda { |query|
return nil if query.blank?
# condition query, parse into individual keywords
terms = query.downcase.split(/\s+/)
# replace "*" with "%" for wildcard searches,
# append '%', remove duplicate '%'s
terms = terms.map { |e|
(e.gsub('*', '%') + '%').gsub(/%+/, '%')
}
# configure number of OR conditions for provision
# of interpolation arguments. Adjust this if you
# change the number of OR conditions.
num_or_conditions = 1
where(
terms.map {
or_clauses = [
"LOWER(posts.title) LIKE ?"
].join(' OR ')
"(#{ or_clauses })"
}.join(' AND '),
*terms.map { |e| [e] * num_or_conditions }.flatten
)
}
How can I make my scope/query so user can search words within the title and get records that has words they have searched for?
I tried with ILIKE, but then the search stop working in development, I think its because of sqlite can't have ILIKE, but in productionthe search worked but still can't search for words within titles.
When I use LIKE, the sql query was:
SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "posts" WHERE ((LOWER(posts.title) LIKE 'rails%')) LIMIT 50 OFFSET 0) subquery_for_count
While when I used ILIKE, the query was:
SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "posts" WHERE ((LOWER(posts.title) ILIKE 'rails%')) LIMIT 50 OFFSET 0) subquery_for_count
SQLite3::SQLException: near "ILIKE": syntax error: SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "posts" WHERE ((LOWER(posts.title) ILIKE 'rails%')) LIMIT 50 OFFSET 0) subquery_for_count
ps: Im using Filterrific gem
I use pg gem for Production ENV & sqlite3 for Development ENV

As its described in this w3schools article, LIKE works as:
WHERE CustomerName LIKE 'a%' => Finds any values that starts with "a"
WHERE CustomerName LIKE '%a' => Finds any values that ends with "a"
WHERE CustomerName LIKE '%or%' => Finds any values that have "or" in any position
WHERE CustomerName LIKE '_r%' => Finds any values that have "r" in the second position
WHERE CustomerName LIKE 'a_%_%' => Finds any values that starts with "a" and are at least 3 characters in length
WHERE ContactName LIKE 'a%o' => Finds any values that starts with "a" and ends with "o"
I needed to change (e.gsub('*', '%') + '%').gsub(/%+/, '%'), to: ('%' + e.gsub('*', '%') + '%').gsub(/%+/, '%') .
When searching with (e.gsub('*', '%') + '%').gsub(/%+/, '%'), result would be (LOWER(posts.title) ILIKE 'keyword%'), where as ('%' + e.gsub('*', '%') + '%').gsub(/%+/, '%'), would give (LOWER(posts.title) ILIKE '%keyword%')

Related

SPARQL filter and union

I am trying to list all entries whose names start with the letter 'R' and are older than 20 but It is not working - Can you please give me a hint ?
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT DISTINCT ?givenname ?Age ?firstName
WHERE {
?Person foaf:givenname ?firstName
{FILTER (?Age > '20')}
UNION
{FILTER regex(?givename, "^(R)")}
}
To find names that start with "R", you can use STRSTARTS():
FILTER( STRSTARTS(?givename, "R") ) .
To filter based on the age, you first have to add/bind this variable in a triple pattern, e.g.:
?Person ex:yourAgeProperty ?Age .
Your age FILTER compares strings (as you use quotation marks and don’t specify a datatype, which defaults to xsd:string). In case the age is given as xsd:integer in your data, you could use:
FILTER( ?Age > "20"^^xsd:integer ) .
And if both filters need to apply, simply list them one after the other (without {} and without UNION). Or you could combine them:
FILTER( STRSTARTS(?givename, "R") && ?Age > "20"^^xsd:integer ) .

implementing oracle query to powerbuilder

I have difficulty in implementing Oracle query to powerbuilder. I have 3 singeline edit the name is :
1. sle_merk
2. sle_tipe
3. sle_jns
and I have oracle query
select b.nm_merk
, c.keterangan
, c.tipe
, b.keterangan
, c.usrid
, c.otr
, c.dupd
, c.dotr
, c.status
from tipe_kend c
left
outer
join jns_kendaraan b
on c.kd_jenis = b.kd_jenis
left
outer
join merk_kend b
on c.kd_merk = b.kd_merk
where b.kd_jenis like '%%'
AND b.kd_merk like '%%'
AND c.tipe like '%%'
what I want is if all singeline edit is null then the data is appears, but when one of singeline edit is filled then the data is appears where data like %singelineedit%. I have difficulty in implementing the query into Powerbuilder.
Since you don't say you are using a datawindow control I will assume you are not. With that being said then this must be a piece of embedded SQL inside some method. As such you need to declare variables for each of the columns in your retrieve. You also need variables to hold the contents of the SLE controls.
You would end up with something like this:
string ls_nm // make sure datatypes are correct
string ls_sle1, ls_sle2, ls_sle3
// get values of the sle controls
ls_sle1 = sle_merk.text
IF IsNull(ls_sle1) THEN ls_sle1 = ''
ls_sle2 = sle_tipe.text
IF IsNull(ls_sle2) THEN ls_sle2 = ''
ls_sle3 = sle_jns.text
IF IsNull(ls_sle3) THEN ls_sle3 = ''
select b.nm_merk, ...
INTO :ls_nm, ...
from tipe_kend c
left outer join ns_kendaraan b on c.kd_jenis = b.kd_jenis
left outer join merk_kend b on c.kd_merk = b.kd_merk
where b.kd_jenis like '%' + ls_sle1 + '%'
AND b.kd_merk like '%' + ls_sle2 + '%'
AND c.tipe like '% + ls_sle3 + '%';
Note this will FAIL if more than a single row is returned. If you need multiple rows you need to use a datawindow/datastore control (or cursors but they are very inefficient).

Sparql multi lang data compression to one row

I'd like to select data property values using sparql with some restrictions on their languages:
I have an ordered set of preferred languages ("ru", "en", ... etc )
If an item have more than one language for value, I'd like to have only one value restricted by my set of languages (if ru is available - I want to see ru value, else if en available I want to see en else if ... etc if no lang available - no lang value).
Current query is:
select distinct ?dataProperty ?dpropertyValue where {
<http://dbpedia.org/resource/Blackmore's_Night> ?dataProperty ?dpropertyValue.
?dataProperty a owl:DatatypeProperty.
FILTER ( langmatches(lang(?dpropertyValue),"ru") || langmatches(lang(? dpropertyValue),"en") || lang(?dpropertyValue)="" )
}
The problem with it: results contain two rows for abstract (ru+en). I want only one row, which should contain ru. In case when ru is not available I'd like to get en etc.
How?
Suppose you have data like this:
#prefix : <http://stackoverflow.com/q/21531063/1281433/> .
:a a :resource;
:p "a in english"#en, "a in russian"#ru .
:b a :resource ;
:p "b in english"#en .
Then you're hoping to get results like this:
--------------------------------
| resource | label |
================================
| :b | "b in english"#en |
| :a | "a in russian"#ru |
--------------------------------
Here are two ways of doing this.
Associate language tags with ranks, find the rank of the best label, then find the label with that rank
This way uses SPARQL 1.1 subqueries, aggregates, and data provided with values. The idea is to use values to associate each language tag with a rank. Then you use a subquery to pull out the optimal rank over all the labels that the resource has). Then in the outer query, you have access to the optimal rank, and you just retrieve the label with the language corresponding to that rank.
prefix : <http://stackoverflow.com/q/21531063/1281433/>
select ?resource ?label where {
# for each resource, find the rank of the
# language of the most preferred label.
{
select ?resource (min(?rank) as ?langRank) where {
values (?lang ?rank) { ("ru" 1) ("en" 2) }
?resource :p ?label .
filter(langMatches(lang(?label),?lang))
}
group by ?resource
}
# ?langRank from the subquery is, for each
# resource, the best preference. With the
# values clause, we get just the language
# that we want.
values (?lang ?langRank) { ("ru" 1) ("en" 2) }
?resource a :resource ; :p ?label .
filter(langMatches(lang(?label),?lang))
}
Select the labels separately and coalesce in the order that you want
You can select an optional label for each of the languages you're considering, and then coalesce them into (so you get the first one that's bound) in the order of your preference. This is kind of verbose, but if you need to do anything else with the labels in various languages other than the most preferred, you'll have access to them.
prefix : <http://stackoverflow.com/q/21531063/1281433/>
select ?resource ?label where {
# find resources
?resource a :resource .
# grab a russian label, if available
optional {
?resource :p ?rulabel .
filter( langMatches(lang(?rulabel),"ru") )
}
# grab an english label, if available
optional {
?resource :p ?enlabel .
filter( langMatches(lang(?enlabel),"en") )
}
# take either as the label, but russian over english
bind( coalesce( ?rulabel, ?enlabel ) as ?label )
}

Proper Ubercard search using Drupal 7 Views

Overview
I wanted to create a product search in Drupal 7. I created a view and exposed the fields that I wanted to have exposed for searching (filtering). The search works perfect but only when you search for items using the Item Title.
The Question:
I want a user to be able to type in a more "descriptive search" using all filters:
Title
Type
Variant
Size
SKU, etc
View SQL Code Sample
SELECT node.nid AS nid, node.type AS node_type, node.language AS node_language, node.title AS node_title, uc_products.model AS uc_products_model, node.created AS node_created, 'node' AS field_data_uc_product_image_node_entity_type, 'node' AS field_data_field_product_variant_node_entity_type, 'node' AS field_data_body_node_entity_type, 'node' AS field_data_field_product_tags_node_entity_type
FROM
{node} node
LEFT JOIN {field_data_body} field_data_body ON node.nid = field_data_body.entity_id AND (field_data_body.entity_type = 'node' AND field_data_body.deleted = '0')
LEFT JOIN {field_data_field_product_variant} field_data_field_product_variant ON node.nid = field_data_field_product_variant.entity_id AND (field_data_field_product_variant.entity_type = 'node' AND field_data_field_product_variant.deleted = '0')
LEFT JOIN {uc_products} uc_products ON node.vid = uc_products.vid
WHERE (( (node.status = '1') AND (node.type IN ('product')) AND (field_data_body.body_value LIKE '%%' ESCAPE '\\') AND (field_data_field_product_variant.field_product_variant_value LIKE '%%' ESCAPE '\\') ))
ORDER BY node_created DESC, node_title ASC
LIMIT 6 OFFSET 0
I wish to find out what I am doing wrong. I wish to merge all the exposed filter entities into one so that they can be searched / filtered from the one search box.
You response is highly appreciated.
You can take a look at this module http://drupal.org/project/views_filters_populate
But it only works with text filters (no combos etc.).

how to find number of tag matches in acts as taggable on

I have two entries in my database
Obj1 is tagged with "hello, world, planet"
Obj2 is tagged with "hello"
if I do modelName.tagged_with(["hello", "world", "planet", "earth"], :any=>true)
I want to sort the returned objects in order of highest to lowest number of tags matched.
so in this case i'd like the order to be Obj1, Obj2
how can I do this? is there a way to get number of tags matched for each of the returned results?
You can call tag_list on the objects and use that to figure out how many tags there are:
tags = %w{hello world planet earth}
objs = ModelName.taggedWith(tags, :any => true)
objs.sort_by! { |o| -(tags & o.tag_list).length }
The tags & o.tag_list yields the intersection of the tags you're looking for and the tags found, then we negate the size of the intersection to tell sort_by (which sorts in ascending order) to put larger intersections at the front, negating the result is an easy way to reverse the usual sort order.
Posting this here if someone else is looking for a way to query a model by tags and order by the number of matches. This solution also allows for the usage of any "equality" operator like the % from pg_trgm.
query = <<-SQL
SELECT users.*, COUNT(DISTINCT taggings.id) AS ct
FROM users
INNER JOIN taggings ON taggings.taggable_type = 'User'
AND taggings.context = 'skills'
AND taggings.taggable_id = users.id
AND taggings.tag_id IN
(SELECT tags.id FROM tags
WHERE (LOWER(tags.name) % 'ruby'
OR LOWER(tags.name) % 'java'
OR LOWER(tags.name) % 'sa-c'
OR LOWER(tags.name) % 'c--'
OR LOWER(tags.name) % 'gnu e'
OR LOWER(tags.name) % 'lite-c'
))
GROUP BY users.id
ORDER BY ct DESC;
SQL
User.find_by_sql(query)
Note that the code above will only work if you have pg_trgm enabled. You can also simply replace % with ILIKE.
EDIT: With ActiveRecord and eager loading:
This could be in a scope or class method and can be chained with other ActiveRecord methods.
ActiveRecord::Base.connection
.execute('SET pg_trgm.similarity_threshold = 0.5')
matches = skills.map do
'LOWER(tags.name) % ?'
end.join(' OR ')
select('users.*, COUNT(DISTINCT taggings.id) AS ct')
.joins(sanitize_sql_array(["INNER JOIN taggings
ON taggings.taggable_type = 'User'
AND taggings.context = 'skills'
AND taggings.taggable_id = users.id
AND taggings.tag_id IN
(SELECT tags.id FROM tags WHERE (#{matches}))", *skills]))
.group('users.id')
.order('ct DESC')
.includes(:skills)
Override skill_list from acts-as-taggable-on in the model:
def skill_list
skills.collect(&:name)
end
and proceed normally.

Resources