I'm experimenting with ways to reuse code throughout a Rails web app. I'm interested in feedback concerning one such approach!
Context
A user's request may be scoped to a physical location so that the view shows location-relevant information. In most cases, the location is stored in a cookie. It's possible for location to be overridden via query parameters (?where=
or ?location=
). When location is not in a cookie or in the query string, depending on where the code executes, I'll want to provide some other location.
Here's what I came up with:
class LocationResolver
def initialize(locations)
@locations = locations
end
def call(strategy)
[*strategy].map { |strat| strat.(@locations) }.find(&:itself)
end
class QueryStrategy
def initialize(params)
@params = params
end
def call(locations)
locations.find { |x| x.slug == slug }
end
private
def slug
@slug ||= @params.values_at(:where, :location).find(&:itself)
end
end
class CookieStrategy
def initialize(cookies)
@cookies = cookies
end
def call(locations)
content.presence && locations.find { |x| x.id == content["id"] }
end
private
def content
@content ||= JSON.parse(@cookies.fetch(:location, "{}"))
end
end
Given a simple Location
implementation:
Location = Struct.new(:id, :slug) do
def self.all
[
new(1, "one"),
new(2, "two")
]
end
def self.anywhere
new(nil, "anywhere")
end
end
Usage inside a controller might look like this:
class PropertiesController << ApplicationController
def effective_location
@effective_location ||=
LocationResolver.new(Location.all).([
LocationResolver::QueryStrategy.new(request.query_parameters),
LocationResolver::CookieStrategy.new(request.cookies),
-> (_) { Location.anywhere }
])
end
end
Dislikes
- Feels complex
Likes
- A strategy can be reused.
- Strategies can be reordered or included/excluded depending on your needs.
- Strategies can receive only context they require to do their job.
- Strategies can be created without modifying the resolver
- Use of
.call
allows defining a strategy at runtime. - Easy to stub and test.
What do you think?
- What do you think conceptually?
- What other approaches do you prefer?
- What in particular do you like or dislike about this approach?
- Any glaring issues?
- Are
Resolver
andStrategy
suitable names?
Thank you so much! :)