Easy way to handle form input without a model class?


#1

I have a couple forms that I’d like to be able to validate and
automatically populate, but it shouldn’t be based on AR. In fact I
often have a bunch of small forms that I can’t really justify writing
a whole new model class for anyway. I’d like to validate the form
input, and then use rails helpers to automatically populate the form
if validations fail. I’ve already found some links on validating
without subclassing from AR, but nothing about auto populating forms
without a model, with or without rails helpers.

If there’s no existing way to do this, I already have an idea for
something simple. I’d write a class that takes a hash at
initialization to populate instance variables and automatically add
accessors, and use method_missing to handle the object before it’s
initialized with the hash. Pretty easy…but I just wanted to see if
something like this exists already.

Thanks,
Pat


#2

Alright here’s what I came up with. It’s a modification of Peter
Donald’s ActiveForm
(http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects).
The only real differences here are the initialize method, which sets
the attributes, and the method_missing, which returns the value of
instance_variable_get (which is nil if the instance variable doesn’t
exist). Because it returns nil, the form helpers don’t complain about
a method not existing.

Hopefully some people can take a look at this and improve it a bit. I
don’t really like how I’ve had to reimplement method_missing…it does
the same exact thing as #. Also some of the stuff in
method_missing may be extraneous - I just hacked up a quick thing that
works for me. I appreciate any comments, and big thanks to Peter
Donald for coming up with the original ActiveForm.

Note “.valid?” method must occur on object for validates_associated

class ActiveForm

def initialize(init_values = {})
init_values.each{ |key, value| instance_variable_set("@#{key}",
value) }
end

def
instance_variable_get("@#{key}")
end

def method_missing( method_id, *args )
if md = /_before_type_cast$/.match(method_id.to_s)
attr_name = md.pre_match
return self[attr_name] if self.respond_to?(attr_name)
end

instance_variable_get("@#{method_id.to_s}")
#super

end

protected
def raise_not_implemented_error(*params)
ValidatingModel.raise_not_implemented_error(*params)
end

def self.human_attribute_name(attribute_key_name)
attribute_key_name.humanize
end

def new_record?
true
end

these methods must be defined before include

alias save raise_not_implemented_error
alias update_attribute raise_not_implemented_error

public
include ActiveRecord::Validations

protected

the following methods must be defined after include so that they

overide

methods previously included

alias save! raise_not_implemented_error

class << self
def raise_not_implemented_error(*params)
raise NotImplementedError
end

alias validates_uniqueness_of raise_not_implemented_error
alias create! raise_not_implemented_error
alias validate_on_create raise_not_implemented_error
alias validate_on_update raise_not_implemented_error
alias save_with_validation raise_not_implemented_error

end
end

require ‘dispatcher’
class Dispatcher
class << self
if ! method_defined?(:form_original_reset_application!)
alias :form_original_reset_application! :reset_application!
def reset_application!
form_original_reset_application!
Dependencies.remove_subclasses_for(ActiveForm) if
defined?(ActiveForm)
end
end
end
end