-
-
Save jdtornow/334bcd526a8b1f2ed1f211f354e720cb to your computer and use it in GitHub Desktop.
Customer Facing/Friendly Identifiers for Rails
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
class Cfid | |
attr_reader :prefix | |
attr_reader :id | |
def initialize(prefix:, id:) | |
@prefix = prefix | |
@id = id | |
end | |
def encoded_id | |
sqids.encode([id]) | |
end | |
def model | |
self.class.models[prefix] | |
end | |
def record | |
if model.present? && id.present? | |
model.find_by(id:) | |
end | |
end | |
def to_s | |
"#{ prefix }_#{ encoded_id }" | |
end | |
def self.alphabet | |
# Rails.application.credentials.dig(:cfid, :alphabet) | |
# - or - | |
# ENV["CFID_ALPHABET"] | |
# | |
# This is a hard-coded example for this explanation: | |
"GWkjSVLyXKsoJ1nziMNue8Rafqlgxb4IhQCBtr03HUYAd9v7mP2D6FE5wOpTcZ" | |
end | |
def self.find(cfid) | |
prefix, encoded_id = cfid.to_s.strip.split("_") | |
return nil unless prefix.present? && encoded_id.present? | |
ids = sqids.decode(encoded_id) | |
return nil unless ids.present? | |
new(prefix:, id: ids.last) | |
end | |
# A memoized list of models and prefix for easy lookup later | |
# | |
# If any prefixes are changed, make sure you restart your server or console | |
# to reload this value. | |
def self.models | |
return @models if defined?(@models) | |
@models = {} | |
Dir.glob(Rails.root.join("app/models/**/*.rb")).each do |path| | |
class_name = path.gsub(Rails.root.join("app/models/").to_s, "").gsub(/\.rb$/, "").gsub("concerns/", "").classify | |
klass = class_name.safe_constantize | |
next unless klass&.respond_to?(:cfid_prefix) | |
clean_prefix = klass.cfid_prefix.to_s.gsub(/_$/, "").to_s | |
next unless clean_prefix.present? | |
@models[clean_prefix] = klass | |
end | |
@models | |
end | |
def self.sqids | |
@sqids ||= Sqids.new(alphabet:, min_length: 8) | |
end | |
private | |
def sqids | |
self.class.sqids | |
end | |
end |
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
module CustomerFacingIdentifiable | |
extend ActiveSupport::Concern | |
def cfid | |
return nil if new_record? | |
@cfid ||= Cfid.new(prefix: cfid_prefix, id:) | |
end | |
def cfid_prefix | |
self.class.cfid_prefix | |
end | |
def key | |
[ to_param ] | |
end | |
def to_cfid | |
cfid&.to_s | |
end | |
def to_param | |
to_cfid | |
end | |
class_methods do | |
def cfid_prefix=(prefix) | |
@cfid_prefix = prefix.to_s | |
end | |
def cfid_prefix | |
@cfid_prefix || default_prefix_for_cfid | |
end | |
def customer_facing_prefix(value) | |
self.cfid_prefix = value | |
end | |
# Fallback to a calculated prefix, if none is provided for this model. | |
# | |
# User => "us" | |
# UserAccount => "usac" | |
def default_prefix_for_cfid | |
self.model_name.name.underscore.split("_").map { |s| s[0..1] }.join("") | |
end | |
def find_by_cfid(cfid_str) | |
cfid = Cfid.find(cfid_str) | |
return nil unless cfid.present? | |
return nil unless cfid.prefix == cfid_prefix | |
cfid.record | |
end | |
def find_by_cfid!(cfid) | |
if record = find_by_cfid(cfid) | |
record | |
else | |
raise ActiveRecord::RecordNotFound | |
end | |
end | |
end | |
end |
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
class Project < ApplicationRecord | |
include CustomerFacingIdentifiable | |
customer_facing_prefix :proj | |
end |
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
class ProjectsController < ApplicationController | |
# GET /projects/proj_123 | |
def show | |
@project = Project.find_by_cfid!(params[:id]) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment