avdgaag/conformista

View on GitHub
lib/conformista/presenting.rb

Summary

Maintainability
A
2 hrs
Test Coverage
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