module MailForm module Delivery extend ActiveSupport::Concern included do class_attribute :mail_attributes self.mail_attributes = [] class_attribute :mail_captcha self.mail_captcha = [] class_attribute :mail_attachments self.mail_attachments = [] class_attribute :mail_appendable self.mail_appendable = [] if respond_to?(:before_deliver) && respond_to?(:after_deliver) before_deliver :not_spam? after_deliver :deliver! else # For ActiveRecord compatibility before_create :not_spam? after_create :deliver! alias :deliver :save end attr_accessor :request end module ClassMethods # Declare your form attributes. All attributes declared here will be appended # to the e-mail, except the ones captcha is true. # # == Options # # * :validate - A hook to validates_*_of. When true is given, validates the # presence of the attribute. When a regexp, validates format. When array, # validates the inclusion of the attribute in the array. # # Whenever :validate is given, the presence is automatically checked. Give # :allow_blank => true to override. # # Finally, when :validate is a symbol, the method given as symbol will be # called. Then you can add validations as you do in ActiveRecord (errors.add). # # * :attachment - When given, expects a file to be sent and attaches # it to the e-mail. Don't forget to set your form to multitype. # # * :captcha - When true, validates the attributes must be blank # This is a simple way to avoid spam # # == Examples # # class ContactForm < MailForm # attributes :name, :validate => true # attributes :email, :validate => /^([^@]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i # attributes :type, :validate => ["General", "Interface bug"] # attributes :message # attributes :screenshot, :attachment => true, :validate => :interface_bug? # attributes :nickname, :captcha => true # # def interface_bug? # if type == 'Interface bug' && screenshot.nil? # self.errors.add(:screenshot, "can't be blank when you are reporting an interface bug") # end # end # end # def attribute(*accessors) options = accessors.extract_options! # TODO: make this not depend on column_names columns_methods = self.respond_to?(:column_names) ? column_names.map(&:to_sym) : [] attr_accessor(*(accessors - instance_methods.map(&:to_sym) - columns_methods)) if options[:attachment] self.mail_attachments += accessors elsif options[:captcha] self.mail_captcha += accessors else self.mail_attributes += accessors end validation = options.delete(:validate) return unless validation accessors.each do |accessor| case validation when Symbol, Class validate validation break when Regexp validates_format_of accessor, :with => validation, :allow_blank => true when Array validates_inclusion_of accessor, :in => validation, :allow_blank => true when Range validates_length_of accessor, :within => validation, :allow_blank => true end validates_presence_of accessor unless options[:allow_blank] == true end end alias :attributes :attribute # Values from request object to be appended to the contact form. # Whenever used, you have to send the request object when initializing the object: # # @contact_form = ContactForm.new(params[:contact_form], request) # # You can get the values to be appended from the AbstractRequest # documentation (http://api.rubyonrails.org/classes/ActionController/AbstractRequest.html) # # == Examples # # class ContactForm < MailForm # append :remote_ip, :user_agent, :session, :cookies # end # def append(*values) self.mail_appendable += values end end # In development, raises an error if the captcha field is not blank. This is # is good to remember that the field should be hidden with CSS and shown only # to robots. # # In test and in production, it returns true if all captcha fields are blank, # returns false otherwise. # def spam? self.class.mail_captcha.each do |field| next if send(field).blank? if defined?(Rails) && Rails.env.development? raise ScriptError, "The captcha field #{field} was supposed to be blank" else return true end end false end def not_spam? !spam? end # Deliver the resource without running any validation. def deliver! mailer = MailForm::Notifier.contact(self) if mailer.respond_to?(:deliver_now) mailer.deliver_now else mailer.deliver end end # Returns a hash of attributes, according to the attributes existent in # self.class.mail_attributes. def mail_form_attributes self.class.mail_attributes.each_with_object({}) do |attr, hash| hash[attr.to_s] = send(attr) end end end end