-
Notifications
You must be signed in to change notification settings - Fork 23
Using Types
The type system for models comes from dry-types and is implemented via dry-struct, which have their own sets of documentation which we highly recommend everyone to read. However, as typing is a core aspect of Valkyrie, we felt it important to include our own little tutorial.
The purpose of types is to allow your models to automatically cast attributes to the appropriate type or even fail to instantiate should you give an inappropriate type. Some examples can be found below.
Consult the dry-types list of built-in types for the list of standard types, including the typical String, boolean, numeric, datetime, etc. types. When using in Valkyrie, prefix the type with Valkyrie::
(e.g., Types::Integer
becomes Valkyrie::Types::Integer
).
Types which are marked as optional will become nil
if not given a value on instantiation.
class Book < Valkyrie::Resource
attribute :id, Valkyrie::Types::ID.optional
end
Book.new.id # => nil
There are a variety of types for single values, many of which can be found here. An important aspect to remember about Valkyrie is that while you can define a singular type, they will be stored in the backend as an array and then re-cast to singular. This is so that if you later change your mind and decide that you have multiple creator
s, you won't have to perform a costly data migration.
class Book < Valkyrie::Resource
attribute :id, Valkyrie::Types::ID.optional
attribute :creator, Valkyrie::Types::String
end
book = Book.new(creator: "Harry Potter")
book.creator # => "Harry Potter"
output = persister.save(resource: book) # This saves in your backend as ["Harry Potter"], but will cast to..
output.creator # => "Harry Potter"
If you set a value to be multiple, insert data into it, and then change it to singular, when it loads out of the database your "singular" field will still be an array. There are a couple options to prevent this:
-
Migrate your data - if you convert all your newly singular fields to only have one value, then it will return just the single value the way you expect.
query_service.find_all.each do |resource| resource.creator = Array(resource.creator).first persister.save(resource: resource) end
-
Use
Strict
singular types. When a multiple value gets populated into it, it'll throw an error. You'll have to migrate your data eventually, but you won't have to worry about the rest of your infrastructure doing inadvertent things with data it doesn't expect.class Book < Valkyrie::Resource attribute :creator, Valkyrie::Types::Strict::String end Book.new(creator: ["Harry", "Potter"]) # => Dry::Struct::Error: [Book.new] ["Harry", "Potter"] (Array) has invalid type for :creator violates constraints (type?(String, ["Harry", "Potter"]) failed)
Valkyrie::Types::Set
and Valkyrie::Types::Array
are provided as options for multi-valued fields. Set
will de-duplicate values, and Array
allows duplicate values. Both will cast singular values to an array. You can define what kind of members are in an Array
or Set
via the of
operator.
The default type for an attribute
declaration is Valkyrie::Types::Set.optional
class Book < Valkyrie::Resource
attribute :creators, Valkyrie::Types::Set
attribute :nil_creators, Valkyrie::Types::Set.optional
attribute :array_creators, Valkyrie::Types::Array
end
book = Book.new(creators: "one", array_creators: ["one", "one"])
book.creators # => ["one"]
book.nil_creators # => nil
book.array_creators # => ["one", "one"]
Since: 1.2.0
Attributes are unordered by default. Adding ordered: true
to an attribute definition will preserve the order of multiple values.
attribute :authors, Valkyrie::Types::Set.meta(ordered: true)