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
endAt 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
#...
endIn 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
#...
endCalling 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
#...
endYou can set multiple filters at once
form do
filters :strip, :downcase
#...
endAll 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
#...
endThis 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
#...
endWhen 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]
#...
endYou can set as just one single symbol, if you don't need multiple filters.
form do
field :field_name, :present => true, filters => :strip
#...
endYou also can set the rule like this.
form do
selection :field_name, :count => 1..3, int => true
#...
endThis 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
endSet 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"]
endYou 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
endAfter 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
endWhen 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
endYou 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
#...
endYou 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
#...
endOr as range
post '/entry' do
form do
field :field01, :present => true, :length => 4..10
end
#...
endCalculate byte size. this constraint use String#bytesize internally You can set integer.
post '/entry' do
form do
field :field01, :present => true, :bytesize => 10
end
#...
endOr as range
post '/entry' do
form do
field :field01, :present => true, :bytesize => 4..10
end
#...
endpost '/entry' do
form do
field :field01, :present => true, :ascii => true
end
#...
endpost '/entry' do
form do
field :field01, :present => true, :regexp => %r{regexp}
end
#...
endpost '/entry' do
form do
field :field01, :present => true, :int => true
end
#...
endFore 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
#...
endpost '/entry' do
form do
field :field01, :present => true, :int => { :gte => 5, :lte => 10 }
end
#...
endpost '/entry' do
form do
field :field01, :present => true, :int => { :between => 5..10 }
end
#...
endUnsined 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
#...
endAlphabet
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
#...
endLimit a scheme as Array
post '/entry' do
form do
field :your_address, :present => true, :uri => [:http, :https], :bytesize => 10..255
end
#...
endpost '/entry' do
form do
field :your_address, :present => true, :uri => [:http], :bytesize => 10..255
end
#...
endIf 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
#...
endrequire 'formkeeper/japanese'
post '/entry' do
form do
filters :zenkaku2hankaku
end
endform_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