Skip to content

Instantly share code, notes, and snippets.

@duleorlovic
Last active April 1, 2020 19:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save duleorlovic/724b8ab1eb44d7f847ee to your computer and use it in GitHub Desktop.
Save duleorlovic/724b8ab1eb44d7f847ee to your computer and use it in GitHub Desktop.
Linkable concern instead of friendly_id gem
# app/models/concerns/linkable.rb
#
# Generate link that you can use to find resources
# Similar to friendly_id gem but much simpler (one file) and you can easily
# update instead of reading DSL for friendly_id
# You just need to define where to store link and which fields to use
# to generate link
#
# For example:
# rails g migration add_link_to_users link:string
# puts also: add_index :users, :link, unique: true
# rake db:migrate
# You can generate links for exists users
# User.all.map { |u| u.generate_link; u.save! }
#
# class User < ActiveRecord::Base
# include Linkable
# linkable :link, # where to store generated url
# [:business_name, :city], # try first with business name and city
# [:first_name, :last_name, :city], # if business_name is nil, use first_name, last_name
# [:business_name], # fall back if city is nil
# [:first_name, :last_name] # fallback if business_name is nil
# end
#
# You can find users by link
# User.find_by link: link
# you can change resources param name:
# resources :users, param: :link
# but it's better to create another route since link could change, but id won't
# resources :users do member do get 'by-link' end end
module Linkable
extend ActiveSupport::Concern
included do
def self.linkable(link_field, *candidate_field)
@@linkable_link_field = link_field
@@linkable_candidate_field = candidate_field
before_validation :generate_link, if: ->(obj) do
# TODO: just in case is changed, for example address_changed?
true
end
end
end
def generate_link
candidate_text = find_candidate_text
if candidate_text.blank?
errors.add(:base, "Can't generate link")
return
end
link = prepare_link candidate_text
link = check_duplication link
send "#{@@linkable_link_field}=", link
end
private
def find_candidate_text
candidate_text = nil
if @@linkable_candidate_field.class == Array
@@linkable_candidate_field.each do |candidate_field|
if candidate_field.class == Array
candidate_text_array = candidate_field.map { |f| send f }
next if candidate_text_array.select(&:blank?).present?
candidate_text = candidate_text_array.join('-')
else
candidate_text = send candidate_field
end
break if candidate_text.present?
end
else
candidate_text = send @@linkable_candidate_field
end
candidate_text
end
def check_duplication(link)
new_link = link
num = 1
while self.class.where(@@linkable_link_field => new_link).where.not(id: id).exists?
num += 1
new_link = "#{link}-#{num}"
end
new_link
end
def prepare_string(input)
# http://stackoverflow.com/questions/20224915/iconv-will-be-deprecated-in-the-future-transliterate
result_ascii = ActiveSupport::Multibyte::Unicode
.normalize(input, :kd)
.chars.grep(/\p{^Mn}/)
.join('')
result = result_ascii.strip.downcase.gsub(/\s+/, ".")
result
end
def prepare_link(input)
temp = prepare_string input
temp = temp.tr(".", "-") # dot . conflicts with rails routes request type
temp = temp.tr(",", "-") # comma , conflicts with joining customers website maps with ,
temp = temp.gsub(/-+/, '-') # replace one or more --- with -
temp
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment