Skip to content

Instantly share code, notes, and snippets.

@tatat
Last active December 18, 2015 11:09
Show Gist options
  • Save tatat/5773417 to your computer and use it in GitHub Desktop.
Save tatat/5773417 to your computer and use it in GitHub Desktop.
require 'active_record'
module ActsAsSplittable
def acts_as_splittable(options = {})
options.reverse_merge!(
callbacks: true,
)
extend ClassMethods
include InstanceMethods
if options[:callbacks]
after_initialize { new_record? or split_column_values! }
before_save { join_column_values! }
end
end
module ClassMethods
SPLITTABLE_DEFAULT_JOIN_PROCESS = Proc.new{|partials| partials.join }
def splittable_columns
@splittable_columns ||= {}
end
def splittable(column, options)
column = column.to_sym
partials = (options[:partials] || options[:pattern].names).map(&:to_sym)
splittable_columns[column] = [options[:pattern], partials, options[:on_split], options[:on_join] || SPLITTABLE_DEFAULT_JOIN_PROCESS]
partials.each do |partial|
define_method partial do
splittable_partials[partial]
end
define_method :"#{partial}=" do |value|
splittable_partials[partial] = value
end
end
end
def inherited(child)
super
child.splittable_columns.merge! splittable_columns.dup
end
end
module InstanceMethods
def split_column_values!(column = nil)
if column.nil?
self.class.splittable_columns.each_key{|key| send __method__, key }
else
column = column.to_sym
pattern, partials, on_split, on_join = self.class.splittable_columns[column]
value = send(column)
values = if on_split
on_split.is_a?(Symbol) ?
send(on_split, value) : on_split.(value)
elsif value
matches = value.match(pattern)
matches[1..(matches.length - 1)]
else
nil
end || []
partials.each_with_index do |partial, index|
send :"#{partial}=", values[index]
end
end
self
end
def join_column_values!(column = nil)
if column.nil?
self.class.splittable_columns.each_key{|key| send __method__, key }
else
pattern, partials, on_split, on_join = self.class.splittable_columns[column.to_sym]
partials = partials.map{|partial| send(partial) }
send :"#{column}=", on_join.is_a?(Symbol) ? send(on_join, partials) : on_join.(partials)
end
self
end
def splittable_partials
@splittable_partials ||= {}
end
end
end
ActiveRecord::Base.extend ActsAsSplittable
EMAIL_SPLIT_PATTERN = /^(?<email_local>[^@]+)@(?<email_domain>[^@]+)$/
EMAIL_JOIN_PROCESS = Proc.new{|partials| partials.join('@') }
class Splittable < ActiveRecord::Base
acts_as_splittable
splittable :email, pattern: /^([^@]+)@([^@]+)$/, partials: [:email_local, :email_domain], on_join: EMAIL_JOIN_PROCESS
splittable :postal_code, pattern: /^(?<postal_code1>[0-9]{3})(?<postal_code2>[0-9]{4})$/
splittable :phone_number, partials: [:phone_number1, :phone_number2, :phone_number3], on_split: :split_phone_number, on_join: :join_phone_number
protected
def split_phone_number(value)
return if value.nil?
matches = value.match(/^([0-9]{3})([0-9]{4})([0-9]{4})$/)
matches[1..(matches.length - 1)]
end
def join_phone_number(partials)
partials.join
end
end
class SplittableInherited < Splittable; end
class SplittableInheritedInherited < SplittableInherited; end
class SplittableWithValidation < ActiveRecord::Base
self.table_name = 'splittables'
acts_as_splittable
splittable :email, pattern: EMAIL_SPLIT_PATTERN, on_join: EMAIL_JOIN_PROCESS
validates :email_local, format: { with: /\A[a-zA-Z0-9_.-]+\Z/ }
validates :email_domain, format: { with: /\A(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4}\Z/ }
end
class SplittableWithValidationForOriginalColumn < SplittableWithValidation
self.table_name = 'splittables'
acts_as_splittable
splittable :email, pattern: EMAIL_SPLIT_PATTERN, on_join: EMAIL_JOIN_PROCESS
validates :email, format: { with: /\A[a-zA-Z0-9_.-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4}\Z/ }
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment