lib/conformista/presenting.rb
module Conformista
# Presenting provides the macro to extend a form object with the methods to
# present models and to build, persist and validate them.
#
# Given a model `User`, you can override the following methods to customise
# the default behaviour:
#
# * `build_user`: should return a new instance of the model
# * `user`: should return the currently presented model instance
# * `user=`: set the currently presented model instance
# * `persist_user`: persist the current model instance
# * `load_user_attributes`: get model attributes and store it in the form
# object
# * `delegate_user_attributes`: set model attributes from the form object
# attributes
# * `validate_user`: test if the model instance is valid
#
# @example Present a single model
# class SignupForm < Conformista::FormObject
# presents User, :email, :password
# end
#
# @example Present multiple models
# class SignupForm < Conformista::FormObject
# presents User => %i[email password],
# Profile => %i[twitter github bio]
# end
#
# @see FormObject
module Presenting
# Convenience method to use either `present_models` or `present_model`,
# based on the number of arguments passed in.
#
# @overload presents(model, *attributes)
# Present a single model and its attributes
# @param [Class] model
# @param [Symbol] method name
# @overload presents(models)
# Present multiple models using a Hash of classes and attributes
# @param [Hash] models as keys, array of attributes as value
def presents(*args)
if args.size == 1
present_models *args
else
present_model *args
end
end
# Present multiple models using a Hash of classes and attributes
#
# @param [Hash] options models as keys, array of attributes as value
def present_models(options = {})
options.each do |model, attributes|
present_model model, *attributes
end
end
# Present a single model and its attributes
#
# @param [Class] model the class of object to present
# @param [Symbol] attributes one or more attribute names
def present_model(model, *attributes)
model_name = model.model_name.singular
ivar = :"@#{model_name}".to_sym
mod = Module.new do
attr_accessor *attributes
define_method :presented_models do
super().tap do |orig|
orig << model unless orig.include? model
end
end
define_method model_name do
if instance_variable_get(ivar).nil?
instance_variable_set ivar, send(:"build_#{model_name}")
else
instance_variable_get ivar
end
end
define_method :"build_#{model_name}" do
model.new
end
define_method :"#{model_name}=" do |obj|
instance_variable_set(ivar, obj).tap do |obj|
send :"load_#{model_name}_attributes"
end
end
define_method :"load_#{model_name}_attributes" do
attributes.each do |attribute|
send("#{attribute}=", send(model_name).send("#{attribute}"))
end
end
define_method :"delegate_#{model_name}_attributes" do
attributes.each do |attribute|
send(model_name).send("#{attribute}=", send(attribute))
end
end
define_method :"validate_#{model_name}" do
send(model_name).valid?
end
define_method :"persist_#{model_name}" do
send(model_name).save
end
end
include mod
end
end
end