Skip to content

Instantly share code, notes, and snippets.

@jferris
Created January 9, 2014 16:34
Show Gist options
  • Star 35 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
  • Save jferris/2ed8ecab1ff068a5be3e to your computer and use it in GitHub Desktop.
Save jferris/2ed8ecab1ff068a5be3e to your computer and use it in GitHub Desktop.
Code Show and Tell: PolymorphicFinder
class ApplicationController < ActionController::Base
private
def requested_purchaseable
PolymorphicFinder.
finding(Section, :id, [:section_id]).
finding(TeamPlan, :sku, [:team_plan_id]).
finding(IndividualPlan, :sku, [:individual_plan_id]).
finding(Product, :id, [:product_id, :screencast_id, :book_id, :show_id]).
find(params)
end
end
# Finds one of several possible polymorphic members from params based on a list
# of relations to look in and attributes to look for.
#
# Each polymorphic member will be tried in turn. If an ID is present that
# doesn't correspond to an existing row, or if none of the possible IDs are
# present in the params, an exception will be raised.
class PolymorphicFinder
def initialize(finder)
@finder = finder
end
def self.finding(*args)
new(NullFinder.new).finding(*args)
end
def finding(relation, attribute, param_names)
new_finder = param_names.inject(@finder) do |fallback, param_name|
Finder.new(relation, attribute, param_name, fallback)
end
self.class.new(new_finder)
end
def find(params)
@finder.find(params)
end
private
class Finder
def initialize(relation, attribute, param_name, fallback)
@relation = relation
@attribute = attribute
@param_name = param_name
@fallback = fallback
end
def find(params)
if id = params[@param_name]
@relation.where(@attribute => id).first!
else
@fallback.find(params)
end
end
end
class NullFinder
def find(params)
raise(
ActiveRecord::RecordNotFound,
"Can't find a polymorphic record without an ID: #{params.inspect}"
)
end
end
private_constant :Finder, :NullFinder
end
require 'spec_helper'
describe PolymorphicFinder do
describe '#find' do
it 'finds the first given finder when present' do
individual_plan = create(:individual_plan, sku: 'abc')
result = PolymorphicFinder.
finding(IndividualPlan, :sku, [:individual_plan_id]).
find(individual_plan_id: 'abc')
expect(result).to eq(individual_plan)
end
it 'finds the first of several possible params' do
screencast = create(:screencast)
result = PolymorphicFinder.
finding(Product, :id, [:book_id, :screencast_id, :product_id]).
find(screencast_id: screencast.to_param)
expect(result).to eq(screencast)
end
it 'cascades when the first finder is not present' do
create(:individual_plan, sku: 'abc')
team_plan = create(:team_plan, sku: 'def')
result = PolymorphicFinder.
finding(IndividualPlan, :sku, [:individual_plan_id]).
finding(TeamPlan, :sku, [:team_plan_id]).
find(team_plan_id: 'def')
expect(result).to eq(team_plan)
end
it 'raises an exception for an unknown ID' do
create(:individual_plan, sku: 'abc')
finder = PolymorphicFinder.
finding(IndividualPlan, :sku, [:individual_plan_id])
expect { finder.find(individual_plan_id: 'def') }.
to raise_error(ActiveRecord::RecordNotFound)
end
it 'raises an exception without any ID' do
params = { 'key' => 'value' }
finder = PolymorphicFinder.
finding(IndividualPlan, :sku, [:individual_plan_id])
expect { finder.find(params) }.
to raise_error(ActiveRecord::RecordNotFound, /#{Regexp.escape(params.inspect)}/)
end
end
end
@stevebooks
Copy link

@pithyless, I agree, the first example it took me awhile to understand what it was doing, it seemed like too much abstraction. However, your example I understood exactly what was going on in a matter of seconds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment