module Formality::ClassMethods

Attributes

Working with models

Nesting

Allows forms to have forms nested within them that work nicely with Rails’ :fields_for method.

Validations are called on nested forms, so that if any nested form is invalid, so is the parent.

Public Instance Methods

assign(attrs) click to toggle source

A convenience class method for creating and assigning a Hash to a form.

# File lib/formality.rb, line 77
def assign(attrs)
  new.assign(attrs)
end
attribute(name, options={}) click to toggle source

Declare an attribute.

Defines a reader and writer. Accepts a :default options for the default value of the attribute.

# File lib/formality.rb, line 64
def attribute(name, options={})
  attributes << name.to_s
  define_reader(name, options[:default])
  attr_writer name
end
attributes() click to toggle source

A Set of attribute names, stored on the form class.

# File lib/formality.rb, line 71
def attributes
  @__attributes ||= Set.new
end
from_model(model) click to toggle source

Build a form object from an existing model.

If nested forms were declared with the :from_model_attribute option, it will also build the nested form object(s).

# File lib/formality.rb, line 147
def from_model(model)
  new.tap do |form|
    form.id = model.id
    form.assign(model.attributes)
    nested_forms.each do |nested|
      form.send("#{nested}=", model)
    end
  end
end
model(name_sym) click to toggle source

Declare the model that this form object represents.

Purely a convenience so that you don’t have to specify the :url parameter in your :form_for calls.

# File lib/formality.rb, line 137
def model(name_sym)
  model_klass = name_sym.to_s.capitalize.constantize
  @__model_name = ActiveModel::Name.new(model_klass)
end
model_name() click to toggle source

:model_name must be defined on the class and return a String with various convenience methods. ActiveModel::Name gives us that.

By default, :model_name uses the name of the Form class.

# File lib/formality.rb, line 27
def model_name
  @__model_name ||= ActiveModel::Name.new(self)
end
nest_many(children, options={}) click to toggle source

Plural nesting.

Works just like :nest_one, except it works for an Array of nested forms.

# File lib/formality.rb, line 207
def nest_many(children, options={})
  add_nested_form(children)
  define_nested_form_many(children, options)
end
nest_one(child, options={}) click to toggle source

Singular Nesting.

# File lib/formality.rb, line 198
def nest_one(child, options={})
  add_nested_form(child)
  define_nested_form_one(child, options)
end
nested_forms() click to toggle source

Keep track of what forms we’ve nested.

# File lib/formality.rb, line 213
def nested_forms
  @__nested_forms ||= Set.new
end

Private Instance Methods

add_nested_form(nested) click to toggle source
# File lib/formality.rb, line 219
def add_nested_form(nested)
  attributes << "#{nested}_attributes"
  nested_forms << nested
end
define_nested_form_many(name, options={}) click to toggle source

Define the accessors for a plural nested form.

# File lib/formality.rb, line 247
    def define_nested_form_many(name, options={})
      define_reader(name, [])
      from_model_attribute = options[:from_model_attribute]
      class_eval <<-many
        def #{name}_attributes
          self.#{name}.map { |form| form.attributes }
        end

        def #{name}_attributes=(attrs_array)
          form_klass = "#{name}".classify.constantize
          @#{name} = attrs_array.map do |attrs|
            form_klass.new.assign(attrs)
          end
        end

        def #{name}=(model)
          return unless #{from_model_attribute.inspect}
          nested_models = model.send(#{from_model_attribute.inspect})
          self.#{name}_attributes = nested_models.map { |m| m.attributes }
        end
      many
    end
define_nested_form_one(name, options={}) click to toggle source

Define the accessors for a singular nested form.

# File lib/formality.rb, line 225
    def define_nested_form_one(name, options={})
      define_reader(name)
      from_model_attribute = options[:from_model_attribute]
      class_eval <<-one
        def #{name}_attributes
          self.#{name} ? @#{name}.attributes : nil
        end

        def #{name}_attributes=(attrs)
          form_klass = "#{name}".classify.constantize
          @#{name} = form_klass.new.assign(attrs)
        end

        def #{name}=(model)
          return unless #{from_model_attribute.inspect}
          nested_model = model.send(#{from_model_attribute.inspect})
          self.#{name}_attributes = nested_model.attributes
        end
      one
    end
define_reader(name, default=nil) click to toggle source

Defines an attribute reader with an optional default value.

# File lib/formality.rb, line 85
    def define_reader(name, default=nil)
      class_eval <<-reader
        def #{name}
          @#{name} ||= #{default.inspect}
        end
      reader
    end