Types
NoBrainer uses a field type mechanism to automatically cast and validates field values. Using the type mechanism improves the integrity and security of your application.
Specifying Field Types
The following example demonstrates how to specify field types:
The following types are currently supported:
String
Text
Integer
Float
Boolean
Symbol
Enum
Time
Date
Binary
Array
Set
Hash
Geo::Point
Geo::Circle
Geo::Polygon
Geo::LineString
Model Behavior
The behavior is the following:
- The default field type is
Object
, meaning that anything will do and values will be delivered to the database as is. - Declaring a
Boolean
field adds anattr?
getter for convenience. - When assigning an attribute to a value, NoBrainer will attempt to cast the given value
to the correct type in a safe manner if the value does not match the specified type
as described below. If the casting operation fails, then NoBrainer leaves the
value as is, meaning that reading the attribute back will return the uncasted
value. This should be taken in consideration when writing
before_save
callbacks since incorrectly typed values may be read. - When performing validations, NoBrainer will check that attribute values match the specified type. If some values do not match their types, validation errors will be added to prevent the model to be persisted.
belongs_to
foreign key associations are not type checked.- When data is read back from the database, no type casting is performed.
For example, when reading back a field from the database with a
value of
"1"
(a string), the field value read from the model API will always be"1"
and not1
, even if the field type is declared to be anInteger
. You must perform a database migration to convert all the strings into integers.
Note that the nil
value is always valid and never casted. If you wish to
prevent this, you may add a not_null
or presence
validation.
Query Behavior
NoBrainer validates and cast values passed in where()
queries. When a bad value is
used, a NoBrainer::Error::InvalidType
exception will be raised. If left
uncaught in a Rails controller, a 400 status code will be returned. For example:
Types
String
- Strings with less than 255 characters are accepted. This length limit is
configurable with
config.max_string_length
. - Symbols are accepted.
Text
- Strings are accepted.
Integer
- Integers are accepted.
- Strings are converted to integers only when the resulting integer can be
converted back to the original stripped string. For example,
" -4 "
and"+3"
are valid, but"4f"
or""
are not. - Floats are accepted when their values matches exactly an integer.
Float
- Floats are accepted.
- Integers are accepted.
- Strings are converted to floats only when the resulting integer can be
converted back to the original stripped string, excluding leading
0
’s. Be aware that the current mechanism assume that the decimal separator is"."'
. No localization is performed, meaning that using","
as a decimal separator will not work.
Boolean
true
andfalse
are accepted.- Strings are accepted with the following rules: the lowercase stripped value
must either be
true
,yes
,t
,1
orfalse
,no
,f
,0
. 1
and0
integers are accepted.
Symbol
- Symbols are accepted.
- Non empty strings are accepted. The cast operation is
value.strip.to_sym
.
Enum
Enum is similar to the Symbol
type, except it adds additional methods.
- First, the
:in
option is mandatory when declaring an Enum field to specify the possible values. - Each of the values specified in the
:in
option generates two methods. For each allowedvalue
, a methodvalue?
returns whether the defined field is set tovalue
; and a methodvalue!
changes the field tovalue
. Note thatsave
must still be invoked to persist the changes to the database. - These method names can be prefixed or suffixed by specifying a
:prefix
or:suffix
option to avoid naming conflicts.
Example:
Time
- Times are accepted.
- Dates are not accepted.
- Strings in the ISO 8601 combined date and time format are accepted.
For example
"2007-04-05T14:30Z"
or"2007-04-05T12:30-02:00"
.
Note that NoBrainer can be configured with user_timezone
and db_timezone
to
specify how timezones should be handled. Read more in the
Installation section to learn more.
Read more about Time
at the bottom of this page.
Date
- Dates are accepted.
- Times are not accepted.
- Strings in the ISO 8601 date format are accepted. For example
"2007-04-05"
. - Any other value is ignored, and a validation error is added.
Note that Dates are persisted in the database as UTC times. This is an important consideration when querying dates due to time millisecond precision.
Read more about Date
at the bottom of this page.
Binary
- Binaries are accepted.
- Strings are accepted.
Array
- Arrays containing any types are accepted.
- Arrays containing a specific type may be specified with the
of
method or array literal, for example:Array.of(String)
or[String]
. (Starting with version 0.36.0)
Set
- Sets and Arrays containing any types are accepted.
Hash
- Hashes containing any types are accepted.
Geo::Point
- Geo::Point are accepted.
- Pairs of floats:
[longitude, latitude]
. - Hashes
{:longitude => long, :latitude => lat}
or{:long => long, :lat => lat}
.
Geo::Circle
- Geo::Circle are accepted.
- Pairs of
[center, radius]
wherecenter
can coerce to aGeo::Point
andradius
to a Float. - Hash
{:center => center, :radius => radius}
.
Additionally, you may pass options as specified in the
r.circle
documentation.
Geo::Polygon
- Geo::Polygon are accepted
- Accepts an array of values coercible to a
Geo::Point
.
Geo::LineString
- Geo::LineString are accepted
- Accepts an array of values coercible to a
Geo::Point
.
DateTime
- Use the
Time
type instead. Read more below.
Custom Types
NoBrainer supports custom types. The following shows an example to define a Point
type.
Overriding Default Behavior
If you wish to override some of the default behavior of an existing type, for example, to cast integers in an unsafe manner, you may use override the attribute setter. For example:
Another way to override a type behavior is to define custom casting behavior similarly to custom types. For example, to parse all times with chronic, the following code will do:
Note that calling super()
is important as it will take care of the timezone
converstion if needed.
You can also subclass the Time
class, add the casting method, and call it
ChronicTime
. The chronic time casting will only be performed if you use the
ChronicTime
type instead of the Time
type.
Date/Time Notes
Regarding date/time types, here is what you need to know:
- The RethinkDB driver only supports
Time
serialization/deserialization at this moment. In Ruby 1.9+, there is no longer the need to use theDateTime
type as theTime
type no longer has restrictive bounds. Nevertheless, the RethinkDB database have some limitations and are described in their documentation. Essentially, you can start to worry when you start to deal with times which year is outside of the range[1400, 10000]
. See also this post. - Times are serialized by the driver by passing to the database a special hash
containing
time.to_f
and its timezone. The database takes this value and truncates it to get a precision of a millisecond. - When writing your application tests, you have to keep this loss of precision
in mind when using
==
on times. Applyingto_i
before comparing times is a good workaround to millisecond rounding issues.
If this behavior does not match your expectations, please open an issue on GitHub.