EOTS
EOTS stands for Email Of The Species. It is a mountable Rails engine that lets you define a hierarchy of kinds (species) of fillable forms to be emailed to you. EOTS will take care of generating views, accepting posted values, and sending the email.
API
There are two main calls you need to be familiar with:
EOTS::email_kind(name, options={}, &block)
Use EOTS::email_kind
to declare a new kind of email. The name may be any
type, but will be converted to a string, so that when the form is asked for or
submitted, it will match the param.
Recognized options are:
-
header
: a string that will be displayed above the form -
footer
: a string that will be displayed below the form - Any of the following standard email fields:
from
to
reply-to
cc
bcc
subject
You can also specify any of them within the block supplied to
EOTS::email_kind
, using methods such as EOTS::header
, EOTS::from
,
etc. These will overwrite any such options already defined on the current
email kind being defined. Standard email fields will be inherited by any
subtypes of the current email type. Headers will be printed in forward order
in the hierarchy, and footers in reverse; see the "Form Order" section for an
example.
Attempting to redefine an existing email kind will cause an
EOTS::EmailKind::AlreadyDefinedError
error.
You generally will want to supply a block to this, because that's where you'll define the fields on this kind of email, using:
EOTS::field(name, label, options={})
Again, the name may be a string or symbol or whatever, but will be stored as a string, for param matching. Options are:
-
caption
: anything you want to appear in small text underneath the field -
section
: to move this to the top, middle, or bottom section of the form. Valid values are:header
,:body
(default), and:footer
. As with the headers and footers on the email as a whole,:header
fields are shown from general to specific (as are:body
fields), and:footer
fields are the other way around. -
any others you wish to put in the HTML, such as a type, id, class, default value, maximum length, etc. If you use
required: true
, not only will the user be required to put a value in the field (or check the box; no, I don't have a required-UNchecked at this time), but also its label will be preceded by an asterisk (*). You may want to mention that fact in a footer.
The fields are shown on the form in this order:
-
:header
fields, from most general to most specific -
:body
fields, from most general to most specific -
:footer
fields, from most specific to most general
Within each section, they are shown in the order in which they were defined. Again, see the "Form Order" section for an example.
I have tested it so far with these kinds of fields:
- checkbox
- email (default size and maxlength of 60)
- text (ditto)
- textarea (default 60 cols by 5 rows and maxlength 300)
Attempting to redefine an existing field on the same email kind will cause an
EOTS::Field::AlreadyDefinedError
error.
Showing the Form
EOTS is a "mountable engine". That means that in your config/routes.rb
, you
must specify a "mount point" for it, such as: mount EOTS::Engine => "/contact"
. Each type of email you define (regardless of the level of
hierarchy) will be at a URL beneath that, corresponding to its name. For
instance, with the route and hierarchy above, they will be at
/contact/general
and /contact/specific
.
At this time, those are not actual route table entries. EOTS uses the URL
/:kind
to get Rails to stick the "kind" in the params
. So, if you wanted
to construct the URL, such as for use in a link_to
, you'd have to tack the
email kind name onto eots_path
, such as "#{eots_path}/general"
.
The form will be shown in your default layout. You can change that by copying the EOTS controller and tweaking it there. If there is demand, maybe I can add an option to say what layout a form should be shown on.
Form Order
Suppose you defined the following email kinds:
EOTS::email_kind("general",
to: "general@example.com",
bcc: "records@example.com",
header: "General Header",
footer: "Aaaaaaaaaaaaargh!!") do
EOTS::field("name", "What is your name?",
section: :header, required: true)
EOTS::field("email", "What is your email address?",
type: :email, section: :header, required: true)
EOTS::field("else", "Anything else to tell us?",
type: :textarea, section: :footer)
EOTS::field("human", "Are you a real human?",
type: :checkbox, section: :footer, required: true)
EOTS::email_kind("bridge",
header: "Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.") do
EOTS::field("quest", "What is your quest?", required: true)
EOTS::email_kind("for-lancelot",
to: "holy-grail@example.com",
footer: "I think you're gonna get this right.") do
EOTS::field("color", "What is your favorite color?",
required: true)
end
EOTS::email_kind("for-robin",
to: "pit@example.com",
footer: "Ha, good luck, sucker!") do
EOTS::field("capital", "What is the capital of Assyria?", required: true)
end
EOTS::email_kind("for-arthur",
to: "holy-grail@example.com",
footer: "Right this way, my liege!") do
EOTS::field("capital", "What is the air-speed velocity of an unladen swallow?",
required: true)
end
end
end
and ask for EOTS to show the form for a for-robin
email. The form will have these parts:
- General Header
- Who would cross the Bridge of Death must answer me these questions three, ere the other side he see.
- * What is your name?
- text entry box for that, marked required
- * What is your email address?
- text entry box for that, marked required, hinting mobile devices to use a keyboard layout appropriate for email addresses, and with basic email validation applied before submission
- * What is the capital of Assyria?
- text entry box for that, marked required
- Anything else to tell us?
- textarea for that, NOT marked required
- * Are you a real human?
- checkbox for that, marked required
- Send button
- Ha, good luck, sucker!
- Aaaaaaaaaaaaargh!!
When this form is submitted, the email will be sent to specific@example.com
,
since that email kind's definition overrode the general
kind's to
option.
However, since the bcc
was not overridden, records@example.com
will still
be bcc'ed.
You can see some real-life examples linked to from
my contact page --
the config file to create them is here.
(Please don't send me emails just to test it!)
I've put the email destination in
an environment variable called EMAIL_DESTINATION
so that later I can open-source the whole site
without revealing that,
but you don't have to do that;
you can just use a literal address there.
Accepting the Filled-In Form
Don't worry, EOTS has that covered. There is a controller, and routes.rb entries.
It even has some degree of spam protection. It's up to you to describe your form, including what fields may be required (such as an "I am not a robot" checkbox). Some spammers are using your own forms to spam you, by using tools that bypass the fact that the HTML says certain fields are required. (That is, they will submit the form directly to the endpoint, not using any standard browser or other tool that will refuse to do so if a field marked "required" is not filled in.) But now, as of version 0.0.3, EOTS will check the required fields on submission, so the spammers will have to make sure that any required fields are filled in, and any required checkbox checked.
However, at this time it does not set up your credentials and email processor
and such. You must still do that yourself, in the
ActionMailer::Base.smtp_settings
section of config/environment.rb
(or
whatever you use).
Recommendations
- Start with a type called
:all
or some such thing, that contains the fields (as:header
) that you're going to want on all emails, like the sender's name, email address, and maybe a Subject line. Then get specialized, dividing into divergent kinds where needed. For instance, to have the name, email address, and subject fields at the top of all forms, and a checkbox about them not being a spambot at the bottom of all forms, and a freeform textbox just above that with slightly different wording if that's the only other field, and always a note at the bottom about required fields, you could do something like this:
EOTS::email_kind("all",
footer: "Required fields are marked with an asterisk (*)") do
EOTS::field("name", "What is your name?",
section: :header, required: true, autofocus: true)
EOTS::field("email", "What is your email address?",
type: :email, section: :header, required: true)
EOTS::field("subject", "What are you contacting us about?",
section: :header, required: true)
EOTS::field("not_bot", "You are not a spambot",
type: :checkbox, required: true)
EOTS::email_kind "general" do
EOTS::field("info", "What do you want to say to us?",
type: :textarea, required: true)
end
EOTS::email_kind "specific" do
EOTS::field("info", "Do you have anything else to say to us?",
type: :textarea, section: :footer required: true)
EOTS::email_kind "compliment" do
EOTS::header "Someone really made your day? We're glad to hear it!"
EOTS::field "person", "Who should we heap praises upon?", required: true
EOTS::field("why", "What did they do to make you so happy?",
type: :textarea, required: true)
end
EOTS::email_kind "complaint" do
EOTS::header "We're sorry you're not satisfied. Please give us a chance to set things right."
EOTS::field("object", "What product, service, person, etc. are you having problems with?",
required: true)
EOTS::field("problem", "What problem are you experiencing?",
type: :textarea, required: true, rows: 10)
EOTS::field("followup", "Would you like us to contact you about this?",
type: :checkbox)
end
end
end
- The obvious place to put this setup is in
config/initializers/eots.rb
.
Ideas
Things I have in mind to do eventually:
-
Generate an index of forms, including descriptions specified in the config
-
Let you specify what layout a form should be shown on
-
Let you specify what URL to redirect to upon submitting a form
-
Let you specify a nil name for types you just want to subtype but never use directly, and exempt that nil from name conflict checking
-
Meta: improve the test suite, add installation instructions and contribution guidelines, maybe a Code of Conduct
-
If you've got other ideas (or find a bug), you can open an Issue or contact me