Skip to content

Instantly share code, notes, and snippets.

@wvengen
Created January 7, 2015 12:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wvengen/29ab6096034096011a15 to your computer and use it in GitHub Desktop.
Save wvengen/29ab6096034096011a15 to your computer and use it in GitHub Desktop.
Active Record mixin to also delegate setters
# Also delegate setters with Rails.
#
# If you find yourself wanting to use this, please look at nested attributes first.
# http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
# When that doesn't fit, you may consider the approach shown here.
#
# Example:
#
# class User < ActiveRecord::Base
# include DelegateAssign
#
# has_one :greeter
# delegate :hello, to: :greeter, assign: true
# end
#
# class Greeter < ActiveRecord::Base
# end
#
# u = User.create(name: 'John', hello: 'Howdy')
# u.hello # => 'Howdy'
# u.greeter.hello # => 'Howdy'
#
module DelegateAssign
extend ActiveSupport::Concern
module ClassMethods
# @todo validate delegated attributes (and pass through errors if needed)
# @todo check object is not saved when delegated object has an error
# @todo warn when +assign+ is used with +to: :class+ (or make sure it works)
# @todo handle +prefix+ option
def delegate(*attrs, **options)
assign = options.delete(:assign)
all_attrs = attrs
# delegate writer methods too when asked
all_attrs += attrs.map{|a| "#{a}=".to_sym} if assign
super(*all_attrs, **options)
if assign
to = options[:to]
defs = []
# make sure there is a delegated object before its attribute is set
attrs.each do |attr|
defs += [
"def #{attr}_with_build=(value)", # def hello_with_build=(value)
" self.#{to} ||= build_#{to}", # self.greeter ||= build_greeter
" self.#{attr}_without_build = value", # self.hello_without_build = value
"end", # end
"alias_method_chain '#{attr}=', :build", # alias_method_chain hello=, :build
]
end
# and make sure changes in delegated methods are stored
defs += [
"after_update do", # after_update do
" #{to}.save if #{to} && #{to}.changed?", # greeter.save if greeter && greeter.changed?
"end" # end
]
file, line = caller.first.split(':', 2)
line = line.to_i
module_eval(defs.join(';'), file, line)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment