module Pod4::TypeCasting
A mixin to give you some more options to control how Pod4
deals with data types.
Example
class Foo < Pod4::Model include Pod4::TypeCasting class Interface < Pod4::SomeInterface # blah blah blah end set_interface Interface.new($stuff) attr_columns :name, :issue, :created, :due, :last_update, :completed, :thing # Now the meat force_encoding Encoding::UTF-8 typecast :issue, as: Integer, strict: true typecast :created, :due, as: Date typecast :last_update, as: Time typecast :completed, as: BigDecimal, ot_as: Float typecast :thing, use: mymethod end
So this adds two commands to the model DSL: force_encoding, and typecast. Both are optional.
Force Encoding
Pass this a Ruby encoding, and it will call force the encoding of each incoming value from the database to match. It is to work around problems with some data sources like MSSQL, which may deal with encoding poorly.
Typecasting
This has the syntax: `typecast <attr> [,…], <options>`.
Options are `as:`, `ot_as:`, `strict:` and `use:`. You must specify either `as:` or `use:`.
Valid types are BigDecimal, Float, Integer, Date, Time, and :boolean.
Changes to Behaviour of Model
General: Any attributes named using `typecast` are set `attr_reader` if they are not already so.
`map_to_model`: incoming data from the data source is coerced to the given encoding if `force_encoding` has been used. Typecast attributes are cast as per their settings, or if they cannot be cast, are left alone. (Unless you have specified strict: true, in which case they are set to nil.)
`set()`: typecast attributes are cast as per their settings, or if they cannot be cast, are left alone. (Unless you have specified `strict: true`, in which case they are set to nil.)
`to_ot()`: any typecast attributes with `ot_as` are cast that way in the outgoing OT, and set guard that way too (see Octothorpe#guard) to give a reasonable default value instead of nil.
`map_to_interface()`: typecast attributes are cast as per their settings, or if they cannot be cast, are set to nil.
Note: Typecasting does not prevent you from setting any value you please on a model attibute @foo by using `model.foo = value`.
Additional methods
The following are provided:
-
`typecast?(:columnname, value)` returns true if the value can be cast; value defaults to the column value if not given.
-
`typecast(type, value, options)` returns a typecast value, or either the original value, or
nil if options is true.
-
`guard(octothorpe)` sets guard conditions on the given octothorpe, based on the attributes typecast knows about. If the value was nil, it will be a reasonable default for the type instead.
Custom Typecasting Methods
By specifying `use: my_method` you are telling Pod4
that you have a method that will return the typecast value for the type. This method will be called as `my_method(value, options)`, where value is the value to be typecast, and options is the hash of options you specified for that column. Pod4
will set the column to whatever your method returns.
The options hash will have an additional key :mode in case you need to cast differently in different circumstances. Mode will be one of :set, :map_to_interface, :map_to_model, or :typecast? (if you call `typecast?` yourself).
What you don't get
None of this has any direct effect on validation, although of course we do provide methods such as `typecast?()` to specifically help you with validation.
Naming an attribute using `typecast` does not automatically make it a Pod4
column; you need to use `attr_column`, just as in plain Pod4
. Furthermore, only Pod4
columns can be named in the typecast command, although you can use the `typecast` instance method, etc., to help you roll your own typecasting for non-column attributes.
Loss of information. If your column is typecast to Integer, then setting it to 12.34 will not round it to 12. Likewise, I know that Time.to_date is a thing, but we don't support it.
Protection from nil, except when using `ot_as:`. A column is always allowed to be nil, regardless of how it is typecast. (On the contrary: by forcing strict columns to nil if they fail typecasting, we help you validate.)
It's theoretically possible that you could typecast a column into something that the interface cannot cast back onto the database. We don't cover you in that case. If it happens, you will have to deal with it yourself in `map_to_interface`.
Constants
- TYPES
Public Class Methods
A little bit of magic, for which I apologise.
When you include this module it actually adds the methods in ClassMethods
to the class as if you had called `extend TypeCasting:ClassMethds` AND adds the methods in InstanceMethods
as if you had written `prepend TypeCasting::InstanceMethods`.
In my defence: I didn't want to have to make you remember to do that…
# File lib/pod4/typecasting.rb, line 138 def self.included(base) base.extend ClassMethods base.send(:prepend, InstanceMethods) end