This module provides you a easy way for form-validation and fill-in-form on your sinatra application
Add this line to your application's Gemfile:
gem 'sinatra-formkeeper'
And then execute:
$ bundle
Or install it yourself as:
$ gem install sinatra-formkeeper
require 'sinatra/formkeeper'
get '/sign_up' do
form do
filters :strip, :my_filter
field :username, :present => true, :length => 4..8
field :age, :present => true, :int => { :gte => 18 }
field :password01, :present => true, :length => 4..8
field :password02, :present => true, :length => 4..8
same :same_password, [:password01, :password02]
end
if form.failed?
"signup failed"
else
"singup success " + form[:username]
end
end
At your application file's header, add require
line for this library.
require 'sinatra/formkeeper'
And if your application is Sinatra::Base
inheritance type, register Sinatra::FormKeeper
class MyApp < Sinatra::Base
register Sinatra::FormKeeper
#...
end
In your routing block, you should build a form-rule at first, like following
post '/entry' do
form do
filters :strip
field :title, :present => true, :length => 4..20
field :message, :present => true, :length => 0..200
end
#...
end
Calling form
with block which includes rule-setting,
you can build a form-rule.
There are some DSL-method to build rules. In this example, filters
and field
are written.
You can set filters
. All input parameters are filtered by indicated filtering feature
The filtering process is executed before validation.
form do
filters :strip
#...
end
You can set multiple filters at once
form do
filters :strip, :downcase
#...
end
All preset filters are described at 8: Preset Filters
You can add a setting for each field
form do
field :field_name, :present => true, length => 0..10
#...
end
This constraint works for an input form named as field_name
, for instance
<input type="text" name="field_name" />
And key-value pares are following the field name. They are constraints set for the field. You can add your favorite constraints here.
All preset constraints are described at 9: Preset Constraints Read the chapter for more detail.
:present
is a special constraint. if parameter not found for the field which
set :present
constraint, the field will be marked as not present,
and other validation for rest constraints won't be executed.
You also can set :default
form do
field :field_name, :default => 'Default Value', :length => 0..10
#...
end
When it's set, if parameter not found, the indicated value will be set and other validation for rest constraints won't be executed.
You aren't allowed to set both :present and :default at same time.
And you can set filters here,
if you don't want to filter all the parameters included in the request.
This filtering setting only affets on :field_name
.
form do
field :field_name, :present => true, filters => [:strip, :downcase]
#...
end
You can set as just one single symbol, if you don't need multiple filters.
form do
field :field_name, :present => true, filters => :strip
#...
end
You also can set the rule like this.
form do
selection :field_name, :count => 1..3, int => true
#...
end
This is just for field which has multiple values. For instance,
<input type="checkbox" name="field_name[]" value="1" checked>
<label>check1</label>
<input type="checkbox" name="field_name[]" value="2" checked>
<label>check2</label>
<input type="checkbox" name="field_name[]" value="3" checked>
<label>check3</label>
Or
<select name="favorite[]" multiple>
<option value="1" selected="selected">white</option>
<option value="2">black</option>
<option value="3">blue</option>
</select>
Rack request handle such type of name (exp: field_name[]) as Array.
For this type of input, use selection
method.
In this case, you must use :count
constraints instead of :present
.
There is another special rule, Combination
form do
combination :same_address, :fields => ["email01", "email02"], :same => true
combination :favorite_color, :fields => ["white", "black", "blue"], :any => true
end
Set rule-name as a first argument. And you should set multiple target fields. And one constraint like (:same => true), or (:any => true).
:same
and :any
are called as Combination Constraint
For this purpose, formkeeper provides you a simple way to do same things.
form do
same :same_address, ["email01", "email02"]
any :favorite_color, ["white", "black", "blue"]
end
You can call a name of Combination Constraints as a method. Followed by rule-name and target-fields.
All preset constraints are described at 10: Preset Combination Constraints
form.failed?
can be used to judge if user's input is valid for the rule you build.
post '/entry' do
form do
#...
end
if form.failed?
# user's input is invalid
else
# user's input is valid!
end
end
After validation is proccessed without any failure, you can implement your domain logic with valid parameters.
form[:field_name]
can be used to pick up a valid data.
This data you can obtain through this method is a filtered data
according to the rule you build (if you set a filters
rule).
post '/entry' do
form do
#...
end
if form.failed?
#...
else
# do something with valid data
Database.insert( :title => form[:field], :message => form[:message] )
end
end
When validation is failed, you might want to provide user
same form again, with error message that describes what fields was invalid.
For this purpose, use failed_on?
method.
post '/entry' do
form do
#...
end
if form.failed?
erb :entry
else
#...
end
end
__END__
@@ entry
<html>
<head><title>Entry</title></head>
<body>
<% if form.failed? %>
<% if form.failed_on?(:title) %>
<p>Title is invalid</p>
<% end %>
<% if form.failed_on?(:message) %>
<p>Message is invalid</p>
<% end %>
<% end %>
<form action="/entry" method="post">
<label>Title</label><input type="text" name="title"><br />
<label>Message</label><textarea name="message"></textarea>
<input type="submit" value="Post this entry">
</form>
</body>
</html>
You can pass constraint-type to failed_on?
as a second argument.
This provides you a way to show detailed error-messages.
post '/entry' do
form do
#...
end
if form.failed?
erb :entry
else
#...
end
end
__END__
@@ entry
<html>
<head><title>Entry</title></head>
<body>
<% if form.failed? %>
<% if form.failed_on?(:title, :present) %>
<p>Title not found</p>
<% end %>
<% if form.failed_on?(:title, :length) %>
<p>Title's length is invalid </p>
<% end %>
<% if form.failed_on?(:message, :present) %>
<p>Message not found</p>
<% end %>
<% end %>
<form action="/entry" method="post">
<label>Title</label><input type="text" name="title"><br />
<label>Message</label><textarea name="message"></textarea>
<input type="submit" value="Post this entry">
</form>
</body>
</html>
In many case you might want to fill in form with user's last input.
Do like following. fill_in_form
automatically fill the fields with params
post '/entry' do
form do
#...
end
if form.failed?
output = erb :entry
fill_in_form(output)
else
#...
end
end
You can aggregate a error messages into external yaml file.
--- messages.yaml
login:
username:
present: input name!
length: intput name (length should be between 0 and 10)
email:
DEFAULT: input correct email address
post_entry:
title:
present: Title not found
DEFAULT:
username:
present: username not found
-- ...
DEFAULT
is a special type. If it can't find setting for indicated validation-type, it uses message set for DEFAULT
.
After you prepare a yaml file, load it.
form_messages File.expand_path(File.join(File.dirname(__FILE__), 'config', 'form_messages.yaml'))
post '/entry' do
#...
end
You can show messages bound to indicated action-name you set in yaml.
<html>
<head><title>Entry</title></head>
<body>
<% if form.failed? %>
<ul>
<% form.messages(:post_entry).each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
</body>
</html>
If you want to show messages for each field, separately, of course you can.
<html>
<head><title>Entry</title></head>
<body>
<form>
<% if form.failed? %>
<ul>
<% form.messages(:login, :username).each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<label>username</label><input type="text" name="username">
<% if form.failed? %>
<ul>
<% form.messages(:login, :password).each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<label>password</label><input type="text" name="password">
</body>
</html>
calculate length. this constraint use String#length internally You can set integer.
post '/entry' do
form do
field :field01, :present => true, :length => 10
end
#...
end
Or as range
post '/entry' do
form do
field :field01, :present => true, :length => 4..10
end
#...
end
Calculate byte size. this constraint use String#bytesize internally You can set integer.
post '/entry' do
form do
field :field01, :present => true, :bytesize => 10
end
#...
end
Or as range
post '/entry' do
form do
field :field01, :present => true, :bytesize => 4..10
end
#...
end
post '/entry' do
form do
field :field01, :present => true, :ascii => true
end
#...
end
post '/entry' do
form do
field :field01, :present => true, :regexp => %r{regexp}
end
#...
end
post '/entry' do
form do
field :field01, :present => true, :int => true
end
#...
end
Fore more detailed constraint, You can use following options as a hash.
- gt: This means >
- gte: This means >=
- lt: This means <
- lte: This means <=
- between: Or you can set Range object
post '/entry' do
form do
field :field01, :present => true, :int => { :gt => 5, :lt => 10 }
end
#...
end
post '/entry' do
form do
field :field01, :present => true, :int => { :gte => 5, :lte => 10 }
end
#...
end
post '/entry' do
form do
field :field01, :present => true, :int => { :between => 5..10 }
end
#...
end
Unsined integer. This doesn't allow lass than zero. Except for that, it behaves same as integer
post '/entry' do
form do
field :field01, :present => true, :uint => { :between => 5..10 }
end
#...
end
Alphabet
Alphabet and Space
Alphabet and Number
Alphabet, Number and Space
Email-Address
post '/entry' do
form do
field :your_address, :present => true, :email => true, :bytesize => 10..255
end
#...
end
Limit a scheme as Array
post '/entry' do
form do
field :your_address, :present => true, :uri => [:http, :https], :bytesize => 10..255
end
#...
end
post '/entry' do
form do
field :your_address, :present => true, :uri => [:http], :bytesize => 10..255
end
#...
end
If your scheme option is only one. You can set as a String.
post '/entry' do
form do
field :your_address, :present => true, :uri => :http, :bytesize => 10..255
end
#...
end
require 'formkeeper/japanese'
post '/entry' do
form do
filters :zenkaku2hankaku
end
end
form_filter :my_capitalize_filter do |value|
value.capitalize
end
post '/entry' do
form do
filters :my_capitalize_filter
end
end
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request