Formtastic tri-state radio¶ ↑
What is “tri-state”?¶ ↑
— that which has 3 states.
By defenition Boolean values have 2 states: True & False.
However, if you store a Boolean value in a database column with no NOT NULL
restriction, it aquires a 3<sup>d</sup> possible state: null
.
Some may consider this practice questionable — I don’t think so. In real life you always have a case when the answer to your question may be only “yes” or “no”, but you don’t know the answer yet. Using a string type column, storing there "yes"
, "no"
and "unset"
+ using a state machine + validations — feels overkill to me.
What the gem does¶ ↑
-
Provides a custom Formtastic input type
:tristate_radio
which renders 3 radios (“Yes”, “No”, “Unset”) instead of a checkbox (only where you put it). -
Teaches Rails recognize
"null"
and"nil"
param values asnil
. See “How it works” ☟ section for technical details on this. -
Encourages you to add translations for ActiveAdmin “status tag” so that
nil
be correctly translated as “Unset” instead of “False”.
Usage¶ ↑
For a Boolean column with 3 possible states:
f.input :am_i_awake, as: :tristate_radio
You get (HTML is simplified, actually there are more classes etc.):
<fieldset> <legend>Am i awake?</legend> <input name="am_i_awake" type="radio" value="true"> <label>Yes</label> <input name="am_i_awake" type="radio" value="false"> <label>No</label> <input name="am_i_awake" type="radio" value="null"> <label>Unset</label> </fieldset>
Installation¶ ↑
Gem¶ ↑
gem "formtastic_tristate_radio"
Translations¶ ↑
Add translation for the new “unset” option:
ru: formtastic: # :yes: Да # <- these two fall back to translations # :no: Нет # in Formtastic gem but have only English null: Неизвестно # <- this you must provide youself
As noted in Usage, you can override individual translations like so:
f.input :attribute, as: :tristate_radio, null: "Your text"
ActiveAdmin translations¶ ↑
ActiveAdmin will automatically translate nil
as “No”, so if you use ActiveAdmin, add translation like so:
ru: active_admin: status_tag: :yes: Да :no: Нет unset: Неизвестно
Notice that the key ActiveAdmin uses is “unset”, not “null”.
Configuration¶ ↑
It’s difficult to come up with a reasonable use case for that, but you can configure what will be used as inputs value:
# config/initializers/formtastic.rb FormtasticTristateRadio.configure do |config| config.unset_key = "__unset" # default is :null end
which will result in:
<input type="radio" name="am_i_awake" value="true"> <input type="radio" name="am_i_awake" value="false"> <input type="radio" name="am_i_awake" value="__unset">
Mind that for your custom value to work, you also need to configure ActiveModel
to recognize that value as nil
. Currently that is done like so.
Documentation¶ ↑
Low-level methods are properly documented in RubyDoc here.
Dependencies¶ ↑
Now the gem depends on Formtastic (naturally) and Rails. Frankly I am not sure whether I will have time to make it work with other frameworks.
How it works¶ ↑
In Ruby any String is cast to true
:
!!"" #=> true !!"false" #=> true !!"nil" #=> true !!"no" #=> true !!"null" #=> true
Web form params are passed as plain text and are interpreted as String by Rack.
So how are Boolean values transfered as strings if a "no"
or "0"
and even ""
is truthy in Ruby?
Frameworks just have a list of string values to be recognized and mapped to Boolean values:
ActiveModel::Type::Boolean::FALSE_VALUES #=> [ 0, "0", :"0", "f", :f, "F", :F, false, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF, ]
so that
ActiveModel::Type::Boolean.new.cast("0") #=> false ActiveModel::Type::Boolean.new.cast("f") #=> false ActiveModel::Type::Boolean.new.cast(:FALSE) #=> false ActiveModel::Type::Boolean.new.cast("off") #=> false # etc
So what I do in this gem is extend ActiveModel::Type::Boolean
in a consistent way to teach it recognize null-ish values as nil
:
module ActiveModel module Type class Boolean < Value NULL_VALUES = [nil, "", "null", :null, "nil", :nil].to_set.freeze private def cast_value(value) NULL_VALUES.include?(value) ? nil : !FALSE_VALUES.include?(value) end end end end
And voila!
ActiveModel::Type::Boolean.new.cast("") #=> nil ActiveModel::Type::Boolean.new.cast("null") #=> nil ActiveModel::Type::Boolean.new.cast(:null) #=> nil ActiveModel::Type::Boolean.new.cast("nil") #=> nil ActiveModel::Type::Boolean.new.cast(:nil) #=> nil
Warning: as you might have noticed, default Rails behavior is changed. If you rely on Rails’ automatic conversion of strings with value "null"
into true
, this gem might not be for you (and you are definitely doing something weird).
Roadmap¶ ↑
-
[ ] Remove
require_relative "../app/models/active_record/base"
from main file -
[x] Make the gem configurable
-
[x] Pull the key used for “unset” choice value into configuration
-
[x] Add translations into most popular languages
-
[ ] Load translations from gem
-
[ ] Rgister
:tristate_radio
for Boolean columns withnull
-
[ ] Decouple
ActiveModel::Type::Boolean
thing from Formtastic things, maybe into a separate gem -
[ ] Decouple from Rails
License¶ ↑
The gem is available as open source under the terms of the MIT License.