Created
January 7, 2015 12:08
-
-
Save wvengen/29ab6096034096011a15 to your computer and use it in GitHub Desktop.
Active Record mixin to also delegate setters
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
# 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