like_query
For my clients' newly built applications with Turbo, search queries mostly serve two purposes:
- Index view callable by a url like
/customers?find=müller screw
- javascript component / dropdown on the front, in our case built with svelte, which receives a json and renders a table in a dropdown.
This query generator is built for these two purposes
Modules like one of the svelte components mentioned above have a total response time (from pressing a key until the result is rendered) of about 60 msec, while with turbo the same time is mostly around 140-160 msec. The gem itself, from querying the database to producing a hash, has a time of about 3 msec. These results are for small data sets (e.g. 30 records found).
Installation
add
gem "like_query"
to Gemfile
this adds the methods #like
and #generate_hash
to all models.
Config
You can set a default limit by
config.x.like_query.limit = 20
This can be overriden by calling the methods
Usage
#like
customer = Customer.create(name: 'Ambühl')
Article.create(name: 'first', number: '01', customer: customer)
Article.like('fir', :name, :number)
Article.like('fir ambühl', [:name, :number, customer: :name])
#generate_hash
returns a hash that can easily be transformed by #to_json
for a javascript frontend, for example
customer = Customer.create(name: 'Ambühl')
art1 = Article.create(name: 'first', number: '01', customer: customer)
Article.like('fir', :name).generate_hash(limit: 10)
{
data:
[
{
values: ["Müller"],
attributes: {"customer.name": "Müller"},
id: 1,
model: "Article"
},
...
],
length: 4,
overflow: true,
columns_count: 2,
sub_records_columns_count: 0,
image: false,
time: 18.282996
}
Article.like('fir', :name).generate_hash(:number, limit: 10)
#generate_hash
uses LikeQuery::Collect
, functionality is the same.
Enums
class Article < ApplicationRecord
enum type_enum: { 'Wheel': 0, 'Vehicle': 1, 'Truck': 2 }
end
Article.like('Truck', [:type_enum]).count
Translated enums and enums on associated models (e.g. article.customer) not yet implemented
Numbers
Book.like('Nelson 3.4..8', [:title, :price])
Column specific search
Book.like('title:history', [:title, :price])
Book.like('author.name:marc', [{author: [:name]}])
LikeQuery raises a error if invalid column name is given, for example:
Book.like('title2:history', [:title, :price])
This behaviour can be shut off/on by Rails.configuration.x.like_query.column_search = true/false
And overriden by Book.like(..., column_search: true/false)
There is a helper for allowing the user translating the column names:
LikeQuery::Formatter.translate_columns('one N:two', [['n','name']], case_sensitive: false)
Only upper and lower chars (if case-insensitive), point, underscore, and no German-Umlaute are allowed on the column-key
Class LikeQuery::Collect
cust = Customer.create(name: 'cust')
Article.create(name: 'screw', customer: cust)
c = LikeQuery::Collect.new(4)
c.set_schema(Customer, :name)
c.collect(parent: :customer) { Article.like('screw', :name) }
c.generate_json
c.result
{
"data": [
{
"values": [
"cust"
],
"attributes": {
"name": "cust"
},
"id": 1,
"model": "Customer",
"children": [
{
"values": [
"screw"
],
"attributes": {
"name": "screw"
},
"id": 1,
"model": "Customer.Article",
"parent_id": 1,
}
]
}
],
"length": 2,
"overflow": false,
"columns_count": 1,
"sub_records_columns_count": 0,
"image": false,
"time": 0.0075
}
query schema and result_schema
The resulting hash for a record looks like this:
{
:values => ["abc", "123"],
:id => 456,
:model => "article",
:image => "src:img..."
}
The resulting hash or json is built from the schema, which can look like this:
:number
or
[:number, :name]
or
{
values: [:number, :name],
image: :name_of_a_column_or_method
}
There is a query schema and an output schema.
If no output schema is defined, the query schema is used for both.
Article.like('first', :name).generate_hash
If an output schema is specified, the result may differ from the search scope:
Article.like('first', :name).generate_hash(:number, :name)
The collect class remembers the schema for a model:
c = LikeQuery::Collect.new
c.collect([:name]) { Article.like('x') }
c.collect { Article.like('x') }
c.collect { Article.like('x', :number) }
#set_schema
When a child returns its parent, the schema for the parent must be given.
Otherwise #generate_hash
would not know what values to return.
c = LikeQuery::Collect.new
c.set_schema(Customer, :name)
c.collect(parent: :customer) { Article.like('screw', :name) }
r = c.generate_hash
There is a :no_action
argument on set_schema
. If set to true, it will return a no_action: true
on regarding models on the resulting json. This is meant for omitting javascript actions or mouse hover effects for resulting records that should do nothing.
url
You can also use the handy Rails url generators to avoid having to build urls tediously with javascript on the frontend:
customer = Customer.create(name: 'Ambühl')
proc1 = proc {|cust| customer_path(cust)}
r = Customer.like('ambühl', :name).generate_hash(:name, url: proc1)
r[:data].first[:url]
or
customer = Customer.create(name: 'Ambühl')
c = LikeQuery::Collect.new
proc1 = proc {|cust| customer_path(cust)}
c.collect(url: proc1) { Customer.like('ambühl', :name) }
r = c.generate_hash
r[:data].first[:url]
or
customer = Customer.create(name: 'Ambühl')
art = Article.create(number: 'art-no', customer: customer)
c = LikeQuery::Collect.new
proc1 = proc {|cust| customer_path(cust)}
c.collect(url: proc1) { Customer.like('xyxyxyyxy', :number) }
proc2 = proc {|article| customer_article_path(article.customer, article) }
c.collect(parent: :customer, url: proc2) { Article.like('art', :number) }
r = c.generate_hash
r[:data].first[:url]
r[:data].first[:children].first[:url]
Performance
Values defined by the schema are processed by the #send
method, but recursively.
This means, for example, that for an article
with the given key customer.employees.contact_details.email
in the schema would return the name of the associated customer.
ATTENTION: This can trigger a lot of database queries, depending on your structure or if or which method is behind the called names.
Tests
Tests for this gem, by rspec, are not included in this gem, they can be found in test project
Collaborate with your team
Test and Deploy
Use the built-in continuous integration in GitLab.
Hope this helps
I created this only for one of my customers. If it may help others, i am happy.
License
MIT