Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
An implementation of the Conductor Pattern
module GoldPlating
extend ActiveSupport::Concern
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
included do
extend ActiveModel::Callbacks
define_model_callbacks :validation
validate :wrapped_objects_are_valid
end
module ClassMethods
def wrapped_classes
@wrapped_classes ||= []
end
def wrap(klass, *attributes)
wrapped_classes << klass
object_name = klass.model_name.singular
attr_writer object_name
class_eval <<-CODE, __FILE__, __LINE__
def #{object_name}
@#{object_name} ||= #{klass}.new
end
CODE
Array.wrap(attributes).flatten.each do |attribute|
method_name = "#{object_name}_#{attribute}"
class_eval <<-CODE, __FILE__, __LINE__
def #{method_name}
#{object_name}.#{attribute}
end
def #{method_name}=(value)
#{object_name}.#{attribute} = value
end
CODE
end
end
# Strip Conductor off the model name, eg PreferencesConductor -> Preferences
# This makes the normal rails form builder params pretty, params[:preferences]
# instead of params[:preferences_conductor]
def model_name
@_model_name ||= ActiveModel::Name.new(self, nil, self.to_s.gsub(/Conductor$/, ''))
end
end
def initialize(args = {})
merge(args)
end
def merge(args = {})
args.each do |name, value|
self.send(:"#{name}=", value)
end unless args.blank?
end
def objects
self.class.wrapped_classes.map { |klass| self.send(klass.model_name.singular) }
end
def persisted?
objects.map(&:persisted?).all?
end
def save
valid? && objects.each(&:save).all?
end
def save!
valid? && objects.each(&:save!).all?
end
def run_validations!
run_callbacks :validation do
super
end
end
def wrapped_objects_are_valid
objects.each do |object|
object.valid? || object.errors.each do |field, error|
local_field = "#{object.class.model_name.singular}_#{field}".to_sym
errors.add(local_field, error)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment