Skip to content

Instantly share code, notes, and snippets.

@structuralartistry
Last active August 6, 2016 19:54
Show Gist options
  • Save structuralartistry/6ab5f0d3860dd7392cc9 to your computer and use it in GitHub Desktop.
Save structuralartistry/6ab5f0d3860dd7392cc9 to your computer and use it in GitHub Desktop.
Lookup module mixin
# the point of this is to add simple lookup table-like values to an AR model or really any other rails class.
# it assumes that if you have a lookup named :some_lookup that the model will have a :some_lookup_id field
# EXAMPLE:
class MyModel < ActiveRecord::Base
include Lookup
lookup_for :function_type do
[
{ id: 1, value: :none },
{ id: 2, value: :introduction },
{ id: 3, value: :instruction },
{ id: 4, value: :exercise },
{ id: 5, value: :ending }
]
end
end
# then you get:
# assignment
m.function_type = :ending
# direct accessor
m.function_type
> :ending
# automatic id assignment
m.function_type_id
> 5
# and also if want
MyModel.get_function_type_by(:value, :none)
> { id: 1, value: :none }
# and
MyModel.function_types
> [
{ id: 1, value: :none },
...
{ id: 5, value: :ending }
]
# also a spec helper to check for correct implementation
expect(MyModel).to_implement_lookup_for(:function_type)
# CODE:
module Lookup
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def lookup_for(*args, &block)
if meth == :lookup_for
define_lookup_methods_for(args, &block)
else
super
end
end
def define_lookup_methods_for(args)
lookup_name = args[0]
# class methods
define_singleton_method("#{lookup_name}s") do
yield
end
define_singleton_method("get_#{lookup_name}_by") do |key_to_search_on, value_to_match|
# args[0] is the key in the hash we are seeking on, arbs[1] is the matching val
lookup_names = self.send("#{lookup_name}s")
lookup_names.select { |t| t[key_to_search_on] == value_to_match }.first
end
# instance methods for setter and getter based on the lookup_name + _id field assumption
# getter
define_method(lookup_name) do
lookup_id = send("#{lookup_name}_id")
lookup_data = self.class.send("#{lookup_name}s")
matching_items = lookup_data.select { |item| item[:id] == lookup_id }
matching_items.empty? ? nil : matching_items[0][:value]
end
# setter
define_method("#{lookup_name}=") do |value|
value = value.to_sym if value
lookup_data = self.class.send("#{lookup_name}s")
# should be only one match but we may refactor this later
matching_items = lookup_data.select { |item| item[:value] == value }
matching_items.empty? ? item = nil : item = matching_items[0][:id]
send("#{lookup_name}_id=", item)
end
end
end
end
# SPECS
require 'spec_helper'
describe Lookup do
# in theory this should work with an active record query or whatever code returning an appropriate array
# of hashes in the result in the block passed, but not going this far to test right now
describe 'with an object for lookup data' do
class MyClass
include Lookup
# this could be AR attribute or other
attr_accessor :record_type_id
# the second param could be from anywhere - just an array of hashes with the right values
# in theory this could be an executable AR query
# also note that we dont have an :order attribute, as we leave leeway for the developer to change the
# order of the array of hashes as desired
# also note that the reference in the second param must be coded to the root of app class structure and not
# refer implicitly to self as self can mean different things when the code in this module is executed
# note that the lookup name must be singular
lookup_for :record_type do
[
{ id: 1, value: :first_value, friendly: 'something friendly', notes: 'notes are here' },
{ id: 2, value: :second_value, friendly: 'something friendly', notes: 'notes are here' },
{ id: 3, value: :third_value, friendly: 'something friendly', notes: 'notes are here' }
]
end
end
let(:my_class_instance) { MyClass.new }
describe 'created instance methods' do
describe 'setter attribute' do
it 'creates instance method on class' do
expect(my_class_instance.respond_to?(:record_type=)).to eq true
end
it 'assigns the set value to the [lookup]_id attribute' do
expect(my_class_instance.record_type).to eq nil
my_class_instance.record_type = :second_value
expect(my_class_instance.record_type).to eq :second_value
end
it 'assigns string' do
my_class_instance.record_type = 'third_value'
expect(my_class_instance.record_type).to eq :third_value
end
it 'can clear the value' do
my_class_instance.record_type = :second_value
expect(my_class_instance.record_type_id).to eq 2
my_class_instance.record_type = nil
expect(my_class_instance.record_type).to eq nil
end
end
describe 'getter attribute' do
it 'creates instance method on class' do
expect(my_class_instance.respond_to?(:record_type)).to eq true
end
it 'returns the set value from the [lookup]_id attribute' do
my_class_instance.record_type_id = 3
expect(my_class_instance.record_type).to eq :third_value
end
end
end
describe 'create class methods' do
describe 'lookup list' do
it 'creates class method on class' do
expect(MyClass.respond_to?(:record_types)).to eq true
end
it 'returns the results as an array of hashes' do
record_types = MyClass.record_types
expect(record_types.class).to eq Array
expect(record_types[0].class).to eq Hash
end
end
it 'has lookup shortcut for finding data' do
expect(MyClass.get_record_type_by(:value, :third_value)).to eq MyClass.record_types[2]
end
end
describe 'implements rspec matcher for expect and shoulda' do
describe MyClass do
it { MyClass.should implement_lookup_for(:record_type) }
it 'do it in a bigger block' do
MyClass.should implement_lookup_for(:record_type)
end
end
it { expect(MyClass).to implement_lookup_for(:record_type) }
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment