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
@cleytonmessias
Copy link

Hi,

@jferris I've have some case that is something like that

if params[:individual_plan_id] && params[:team_plan_id]
  Class.new(params)
else if params[:individual_plan_id]
  OtherClass.new(params)
else if params[:section_id]
  AnotherClass.new(params)

I've tried to use PolymorphicFinder in this case but I'm not getting it because it only verifies if at least one parameter is present.

I've tried to change but i'm stuck in these line that verifies only one parameter at time.

Do you have some ideas to fit my need?

Thanks!

@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