# Looksist [![Build Status](https://travis-ci.org/jpsimonroy/looksist.png?branch=master)](https://travis-ci.org/jpsimonroy/looksist) [![Coverage Status](https://img.shields.io/coveralls/jpsimonroy/looksist.svg)](https://coveralls.io/r/jpsimonroy/looksist?branch=master) [![Code Climate](https://codeclimate.com/github/jpsimonroy/looksist/badges/gpa.svg)](https://codeclimate.com/github/jpsimonroy/looksist) [![Gem Version](https://badge.fury.io/rb/looksist.svg)](http://badge.fury.io/rb/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 ``` ruby 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](https://github.com/remiprev/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 ``` ruby 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: ``` ruby # 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 ```ruby 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 ```ruby 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 ```ruby 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](https://github.com/joshbuddy/jsonpath) ```ruby 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](https://github.com/joshbuddy/jsonpath) ```ruby 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 ```ruby 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. ```ruby 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 ```