Looksist
looksist (adj) - forming positive prejudices based on appearances
Use this gem when you have to lookup attributes from a key-value store based on another attribute as key. This supports redis out-of-the-box and it's blazing fast!
Installation
Add this line to your application's Gemfile:
gem 'looksist'
Supported Ruby Versions
ruby 2.1
ruby 1.9.3
jruby 1.7
Dependencies
JsonPath - required by the gem.
ActiveSupport - external dependency, make sure to require it in your project.
Usage
With Object Models (Her, Active Resource or any of your choice)
- Add an initializer to configure looksist
Looksist.configure do |looksist|
looksist.lookup_store = Redis.new(:url => ENV['REDIS_URL'], :driver => :hiredis)
looksist.driver = Looksist::Serializers::Her
end
You need to specify the driver to manage the attributes. In this case, we use HER. You can add support for ActiveResource or ActiveRecord as needed (also refer to specs for free form usage without a driver).
- Please find the sample rspec to understand the usage and internals
it 'should generate declarative attributes on the model with simple lookup value' do
module SimpleLookup
class Employee
include Looksist
attr_accessor :id
lookup :name, using: :id
def initialize(id)
@id = id
end
end
end
expect(Looksist.lookup_store).to receive(:get).with('ids/1').and_return('Employee Name')
e = SimpleLookup::Employee.new(1)
expect(e.name).to eq('Employee Name')
end
lookup can take the following forms:
# will lookup "employees/#{employee_id}" from the store
lookup :name, using: :employee_id
# will lookup "stars/#{employee_id}" from the store
lookup :name, using: :employee_id, bucket_name: "stars"
# will lookup "stars/#{employee_id}" from the store
# for an object with two attributes (name, location)
lookup [:name, :location], using: :employee_id
# will lookup "stars/#{employee_id}" from the store
# for an object with two attributes (name, location) and expose name as nome
lookup [:name, :age], using: :id, as: {name: 'nome'}
With Plain Hashes
Array Of Hashes
it 'should inject single attribute into array of hashes' do
class HashService
include Looksist
def metrics
[
{
employee_id: 5
},
{
employer_id: 3
}
[
end
inject after: :metrics, at: '$',
using: :employee_id, populate: :employee_name
end
# Removed mock expectations, look at the tests for actuals
expect(HashService.new.metrics).to eq([{employeed_id: 5, employee_name: 'Emp 5'},{employeed_id: 3, employee_name: 'Emp 3'}])
end
end
Columnar Hashes
- First Level look ups
it 'should inject multiple attribute to an existing hash' do
class HashService
include Looksist
def metrics
{
table: {
employee_id: [5, 6],
employer_id: [3, 4]
}
}
end
inject after: :metrics, at: :table,
using: :employee_id, populate: :employee_name
inject after: :metrics, at: :table,
using: :employer_id, populate: :employer_name
end
# Removed mock expectations, look at the tests for actuals
expect(HashService.new.metrics).to eq({table: {
employee_id: [5, 6],
employer_id: [3, 4],
employee_name: ['emp 5', 'emp 6'],
employer_name: ['empr 3', 'empr 4']
}})
end
end
Plain Hash
it 'should inject single attribute into a plain hash' do
class FirstLevelHash
include Looksist
def metrics
{employee_id: 5}
end
inject after: :metrics, using: :employee_id, populate: :employee_name
end
# Removed mock expectations, look at the tests for actuals
expect(FirstLevelHash.new.metrics).to eq({employeed_id: 5, employee_name: 'Emp 5'})
end
end
- Inner Lookups using JsonPath
it 'should inject multiple attribute to an existing deep hash' do
class EmployeeHash
include Looksist
def metrics
{
table: {
database: {
employee_id: [15, 16],
employer_id: [13, 14]
}
}
}
end
inject after: :metrics, at: '$.table.database',
using: :employee_id, populate: :employee_name
inject after: :metrics, at: '$.table.database',
using: :employer_id, populate: :employer_name
end
# Mocks removed to keep it simple.
expect(EmployeeHash.new.metrics).to eq({table: {
database: {
employee_id: [15, 16],
employer_id: [13, 14],
employee_name: ['emp 15', 'emp 16'],
employer_name: ['empr 13', 'empr 14']
}
}})
end
- Inner Lookups using JsonPath
it 'should inject multiple attribute to an existing deep hash for class methods' do
class EmployeeHash
include Looksist
def self.metrics
{
table: {
database: {
employee_id: [15, 16],
employer_id: [13, 14]
}
}
}
end
inject after: :metrics, at: '$.table.database',
using: :employee_id, populate: :employee_name
end
# Mocks removed to keep it simple.
expect(EmployeeHash.metrics).to eq({table: {
database: {
employee_id: [15, 16],
employer_id: [13, 14],
employee_name: ['emp 15', 'emp 16'],
employer_name: ['empr 13', 'empr 14']
}
}})
end
Non Columnar Hashes
it 'should be capable to deep lookup and inject' do
class Menu
include Looksist
def metrics
{
table: {
menu: [
{
item_id: 1
},
{
item_id: 2
}
]
}
}
end
inject after: :metrics, at: '$.table.menu',
using: :item_id, populate: :item_name
end
expect(Menu.new.metrics).to eq({
table: {
menu: [{
item_id: 1,
item_name: 'Idly'
},
{
item_id: 2,
item_name: 'Pongal'
}]
}
})
end
Controlling the L2 cache
Looksist has support for an in memory L2 cache which it uses to optimize redis lookups. To disable L2 cache initialize looksists as below.
- Note that in no L2 cache mode, all lookups would go to redis and the gem would not optimize redundant lookups.
- Hash based lookups would still see optimizations which come from performing unique on keys when injecting values.
Looksist.configure do |looksist|
looksist.lookup_store = Redis.new(:url => ENV['REDIS_URL'], :driver => :hiredis)
looksist.driver = Looksist::Serializers::Her
looksist.l2_cache = :no_cache
end