Security News
Weekly Downloads Now Available in npm Package Search Results
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
A declarative interface for building Elasticsearch queries.
Building valid Elasticsearch queries by hand is difficult, especially as search criteria and logic become more complex.
Chewie aims to reduce the cognitive complexity of building queries, so you can focus on the search experience instead of grappling Elasticsearch syntax.
NOTE: Chewie currently supports Elasticsearch 7.x.
Add this line to your application's Gemfile:
gem 'chewie'
And then execute:
$ bundle
Or install it yourself as:
$ gem install chewie
Define a Chewie
class:
# app/chewies/school_chewie.rb
class SchoolChewie
extend Chewie
term :name
range :age
match :description
filter_by :governances, with: :terms
end
Pass filter parameters to the #build
method:
# app/**/*.rb
params = {
query: "Park School"
filters: {
age: { 'gte': 20, 'lte': 10 },
governances: ['Charter', 'Alop']
}
}
query = params[:query]
filters = params[:filters]
query = SchoolChewie.build(query: query, filters: filters)
puts query
# =>
# {
# query: {
# term: {
# name: { value: 'Park School' }
# },
# range: {
# age: { 'gte': 20, 'lte': 10 }
# },
# match: {
# message: { query: 'Park School' }
# },
# bool: {
# filter: {
# terms: {
# governances: [ 'Charter', 'Alop' ]
# }
# }
# }
# }
# }
Chewie expects incoming parameter attributes to match the attributes defined in your Chewie class, in order to pull the correct value and build the query.
# definition
filter_by :governances, with: :terms
# parameters
{ governances: ['ALOP'] }
# output
{ filter: { terms: { governances: ['ALOP'] } }
Some queries simply take a string value, which is pulled from :query
.
:query
is typically a user search value (search bar).
# definition
term :name
# parameters
{ query: 'A search value' }
# output
{ query: { term: { name: { value: 'A search value' } } } }
Depending on how you build your index, some fields might store values from multiple tables.
A simple case is if you'd like to filter records through an association.
class School
has_many :school_disciplines
has_many :disciplines, through: :school_disciplines
end
class Discipline
has_many :school_disciplines
has_many :schools, through: :school_disciplines
end
class SchoolDiscipline
belongs_to :school
belongs_to :discipline
end
We can imagine a search engine that helps users find schools in their area and allow them to filter schools by various criteria.
Some schools might offer discipline specific programs, therefore a school will have many disciplines.
Disciplines is a non-user populated collection that schools can associate with in the application.
In the search UI, we might provide a disciplines
filter and allow users to filter by disciplines via dropdown.
We provide the search UI with ids
of disciplines we'd like to filter by.
{
filters: {
disciplines: [1, 2, 3, 4]
}
}
The idex consists of school records, therefore we won't have access to every discipline each school is associated to by default.
Instead, we need to define custom index attributes for school records to capture those relationships.
We can do that by defining model methods on School
that collects associated id values and returns a collection of strings to be indexed.
class School
def disciplines_index
discipline_ids = disciplines.pluck(:id)
discipline_ids.map do |discipline_id|
"discipline_#{discipline_id}"
end
end
# Method Elasticsearch can use to populate the index
def search_data
{
name: name,
disciplines: disciplines_index
}
end
end
When Elasticsearch indexes School
records, each record will now have knowledge of which disciplines it is associated to.
{
name: 'Park School',
disciplines: [
"discipline_1",
"discipline_2",
"discipline_3"
]
}
At this point, the index is ready to return associated School
records when given a collection of Discipline
ids.
The caveat is the stored values of :disciplines
is in a format that contains both the School
and Discipline
id.
We'll need to do a little extra work at search time to ensure the id
filter values are transformed into the appropriate string format.
To address this, bool
query methods have a :format
option that takes a lambda and exposes attribute values given.
class SchoolChewie
disciplines_format = lambda do |id|
"discipline_#{id}"
end
filter_by :disciplines, with: :terms, format: disciplines_format
end
params = {
query: '',
filters: {
disciplines: [1, 4]
}
}
result = SchoolChewie.build(query: params[:query], filters: params[:filters])
puts result
# =>
# {
# query: {
# bool: {
# filter: {
# terms: {
# disciplines: [
# "discipline_1",
# "discipline_4",
# ]
# }
# }
# }
# }
# }
Now that the query for disciplines
matches values stored in the index, Elasticsearch will find School
records where disciplines
match to either "discipline_1"
or "discipline_4"
; allowing us to find schools by their associated disciplines.
Sometimes there are additional criteria we'd like to leverage when filtering against associated records.
Continuing with the previous example, let's say we want to filter schools by disciplines where the discipline programs are "active"
.
"active"
might be a boolean attribute found on SchoolDiscipline
.
We can re-write #discipline_index
to pull the discipline id
and active
attributes from SchoolDiscipline
join records.
class School
def disciplines_index
school_disciplines.map do |school_discipline|
discipline_id = school_discipline.id
active = school_discipline.active
"discipline_#{discipline_id}_active_#{active}"
end
end
# Method Elasticsearch can use to populate the index
def search_data
{
name: name,
disciplines: disciplines_index
}
end
end
Which changes the index to:
{
name: 'Park School',
disciplines: [
"discipline_1_active_true",
"discipline_2_active_false",
"discipline_3_active_false"
]
}
We can now imagine there is a active
toggle in the search UI, which expands the filter parameters.
params = {
query: '',
filters: {
disciplines: [1, 4],
active: true
}
}
At search time we not only need to format with the disciplines
collection, but combine those values with the active
attribute.
Let's update SchoolChewie
to take this new criteria into account.
class SchoolChewie
disciplines_format = lambda do |id, combine|
"discipline_#{id}_active_#{combine.first}"
end
filter_by :disciplines, with: :terms, combine: [:active], format: disciplines_format
end
:combine
takes a collection of attribute symbols, which Chewie uses to access and pass parameter values to the format lambda at search time; the value collection is exposed as the second argument in the lambda block.
The order of the values matches the order defined in the method call.
combine: [:active, :governances, :age]
lambda do |id, combine|
combine[0] #=> :active value
combine[1] #=> :governances value
combine[2] #=> :age value
end
The new output:
result = SchoolChewie.build(query: params[:query], filters: params[:filters])
puts result
# =>
# {
# query: {
# bool: {
# filter: {
# terms: {
# disciplines: [
# "discipline_1_active_true",
# "discipline_4_active_true",
# ]
# }
# }
# }
# }
# }
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/chewie. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Chewie project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
FAQs
Unknown package
We found that chewie demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
Security News
A Stanford study reveals 9.5% of engineers contribute almost nothing, costing tech $90B annually, with remote work fueling the rise of "ghost engineers."
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.