Skip to content

Instantly share code, notes, and snippets.

@pda
Created September 12, 2012 07:15
Show Gist options
  • Save pda/3704893 to your computer and use it in GitHub Desktop.
Save pda/3704893 to your computer and use it in GitHub Desktop.
A form model base class for Rails.
require "active_model"
# Base class for Rails forms that aren't backed by a single database model.
# Implement a #save method and treat it like any other model.
#
# class ExampleForm < BaseForm
#
# # Declare attributes.
# required_attribute :email
# attribute :name
# attribute :age
# attribute :language, default: "ruby"
#
# # Standard ActiveModel validations.
# validates :age, numericality: true, allow_nil: true
#
# # Take whatever action you want when the form model is "saved".
# def save
# return false unless valid?
# User.create!(attributes.slice(:email, :name, :age))
# LanguageRegistry.add(user, language)
# LlamaDispatcher.new.dispatch(17)
# true
# end
#
# end
#
class BaseForm
# minimal ActiveModel behaviour to drive form validation.
extend ActiveModel::Naming
include ActiveModel::Validations
def to_model; self; end
def persisted?; false; end
def to_key; nil; end
def to_param; nil; end
# Declare an attribute.
def self.attribute(name, options = {})
define_attribute_reader(name)
define_attribute_writer(name)
defaults[name] = options[:default] if options[:default]
end
# Declare an attribute, and add a validator for its presence.
def self.required_attribute(name, options = {})
attribute(name, options)
validates name, presence: true
end
# Initialize form with the given attributes.
# Unregistered attributes are ignored.
# MassAssignmentSecurity is NOT applied; it is stupid. Use strong_parameters.
def initialize(attributes = nil)
self.attributes = self.class.defaults.merge(attributes || {})
end
# Read all attributes.
attr_reader :attributes
# Overwrite attribute values, including defaults.
def attributes=(attributes)
@attributes = {}
(attributes || {}).each do |key, value|
method = "#{key}="
send(method, value) if respond_to?(method)
end
end
private
def self.define_attribute_reader(name)
define_method(name) do
@attributes[name]
end
end
def self.define_attribute_writer(name)
define_method("#{name}=") do |value|
@attributes[name] = value
end
end
def self.defaults
@defaults ||= {}
end
end
require "spec_helper"
require "base_form"
describe BaseForm do
let(:form_class) do
Class.new(BaseForm) do
attribute :a
attribute :b, default: "the usual"
required_attribute :x, default: 0
required_attribute :y
def self.name; "ExampleForm"; end # only required for anonymous class.
end
end
let(:form) do
form_class.new(a: "A", b: "B", x: "X")
end
it "exposes attributes hash" do
form.attributes.should == {a: "A", b: "B", x: "X"}
end
it "exposes individual attributes" do
form.a.should == "A"
form.b.should == "B"
form.x.should == "X"
end
it "defaults attributes to nil" do
form.y.should == nil
end
it "accepts custom default values" do
form_class.new.b.should == "the usual"
form_class.new.x.should == 0
end
it "is invalid with missing required attribute" do
form.valid?.should == false
form.errors.should be_instance_of(ActiveModel::Errors)
form.errors.size.should == 1
end
describe "#attributes=" do
it "overwrites attributes, including defaults" do
form = form_class.new(a: "initial", b: "attributes")
form.attributes = {x: "updated", y: "attributes"}
form.x.should == "updated"
form.y.should == "attributes"
form.attributes.should == {x: "updated", y: "attributes"}
form.should be_valid
end
it "accepts nil" do
form = form_class.new(a: "initial", b: "attributes")
form.attributes = nil
form.attributes.should == {}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment