Skip to content

Instantly share code, notes, and snippets.

@kirel
Last active December 17, 2015 19:39
Show Gist options
  • Select an option

  • Save kirel/5662187 to your computer and use it in GitHub Desktop.

Select an option

Save kirel/5662187 to your computer and use it in GitHub Desktop.
AttributeCasts
require 'active_support/concern'
module AttributeCasts
extend ActiveSupport::Concern
module ClassMethods
# casts method => { writer: type, reader: type}
# or
# casts method => type # equiv to casts method => { writer: type }
# type needs to respond to new or to_proc
# thus primarily Classes, Procs and Symbols can be used for casting
def casts casts = {}
casts.each do |meth, cast|
cast = { writer: cast } unless cast.is_a? Hash
caster = {}
[:reader, :writer].each do |accessor|
caster[accessor] = if cast[accessor].respond_to? :new
lambda { |val| cast[accessor].new val }
elsif cast[accessor].respond_to? :to_proc
cast[accessor].to_proc
elsif cast[accessor].nil?
nil
else
raise ArgumentError, "#{cast} is not a valid caster"
end
end
if caster[:reader]
define_method "#{meth}_with_cast" do
original_value = send("#{meth}_without_cast")
caster[:reader].call original_value
end
alias_method_chain "#{meth}", :cast
end
if caster[:writer]
define_method "#{meth}_with_cast=" do |val|
val = caster[:writer].call(val)
send("#{meth}_without_cast=", val)
end
alias_method_chain "#{meth}=", :cast
end
end
end
end
class Boolean
def self.new val
case val
when nil, false, 0, "0", "", "false"
false
else
true
end
end
end
class WrapArray
def self.new val
case val
when Hash
val.values
else
Array.wrap(val)
end
end
end
end
if __FILE__ == $0
require 'rspec/autorun'
require 'bigdecimal'
describe AttributeCasts do
class Klass
include AttributeCasts
attr_accessor :boolean, :method, :proc, :decimal, :array
casts method: :to_s,
proc: ->(val) { val.to_i },
decimal: BigDecimal,
array: WrapArray,
boolean: Boolean
end
let(:obj) { Klass.new }
describe 'writer' do
it 'casts methods' do
obj.method = 1
obj.method.should == "1"
end
it 'casts procs' do
obj.proc = "1"
obj.proc.should == 1
end
it 'casts classes' do
obj.decimal = "1"
obj.decimal.should == BigDecimal.new('1')
end
it 'casts Boolean' do
obj.boolean = "false"
obj.boolean.should == false
end
it 'casts WrapArray' do
obj.array = 1
obj.array.should == [1]
end
end
describe 'reader' do
pending
end
end
end
module AttributeDefaults
extend ActiveSupport::Concern
module ClassMethods
# sets default values for accessors
# defaults are returned whenever an accessor would return nil
# defaults method => default_value
def defaults defaults = {}
defaults.each do |meth, default|
default_proc = default.respond_to?(:to_proc) ? default.to_proc : proc { default }
define_method "#{meth}_default" do
default_proc.call
end
define_method "#{meth}_with_default" do
res = send("#{meth}_without_default")
!res.nil? ? res : send("#{meth}_default")
end
alias_method_chain meth, :default
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment