Last active
August 6, 2016 19:54
-
-
Save structuralartistry/6ab5f0d3860dd7392cc9 to your computer and use it in GitHub Desktop.
Lookup module mixin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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