0.0
Low commit activity in last 3 years
A long-lived project that still receives updates
Domain models with typed attributes
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 0.8
~> 5.0, < 5.11
~> 0.2
~> 12.0
~> 0.12

Runtime

 Project Readme

Rasti::Model

Gem Version CI Coverage Status Code Climate

Domain models with typed attributes

Installation

Add this line to your application's Gemfile:

gem 'rasti-model'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rasti-model

Usage

Basic models

class Point < Rasti::Model
  attribute :x
  attribute :y
end

point = Point.new x: 1, y: 2
point.x # => 1
point.y # => 2

# Unexpected attributes
Point.new z: 3 # => Rasti::Model::UnexpectedAttributesError: Unexpected attributes: z

Typed models

T = Rasti::Types

class TypedPoint < Rasti::Model
  attribute :x, T::Integer
  attribute :y, T::Integer
end

point = TypedPoint.new x: '1', y: '2'
point.x # => 1
point.y # => 2

Inline definition

Point = Rasti::Model[:x, :y]

TypedPoint = Rasti::Model[x: T::Integer, y: T::Integer]

Serialization and deserialization

City = Rasti::Model[name: T::String]
Country = Rasti::Model[name: T::String, cities: T::Array[T::Model[City]]]

attributes = {
  name: 'Argentina',
  cities: [
    {name: 'Buenos Aires'},
    {name: 'Córdoba'},
    {name: 'Rosario'}
  ]
}

country = Country.new attributes
country.name # => 'Argentina'
country.cities # => [City[name: "Buenos Aires"], City[name: "Córdoba"], City[name: "Rosario"]]

country.to_h # => attributes

# Attribute filtering
country.to_h(only: [:name]) # => {name: "Argentina"}
country.to_h(except: [:cities]) # => {name: "Argentina"}

Default values

class User < Rasti::Model
  attribute :name, T::String
  attribute :admin, T::Boolean, default: false
  attribute :created_at, T::Time, default: ->(m) { Time.now }
end

user = User.new name: 'John'
user.admin # => false
user.created_at # => 2026-01-02 23:19:15 -0300

Merging models

point_1 = Point.new x: 1, y: 2
point_2 = point_1.merge x: 10
point_2.to_h # => {x: 10, y: 2}

Custom attribute options

You can add custom metadata to attributes that can be used later (e.g., for UI generation):

class User < Rasti::Model
  attribute :name, T::String, description: 'The user full name'
end

attribute = User.attributes.first
attribute.option(:description) # => 'The user full name'

These options are also included in the schema representation.

Equality

point_1 = Point.new x: 1, y: 2
point_2 = Point.new x: 1, y: 2
point_3 = Point.new x: 2, y: 1

point_1 == point_2 # => true
point_1 == point_3 # => false

Error handling

TypedPoint = Rasti::Model[x: T::Integer, y: T::Integer]

point = TypedPoint.new x: true
point.x # => Rasti::Types::CastError: Invalid cast: true -> Rasti::Types::Integer
point.y # => Rasti::Model::NotAssignedAttributeError: Not assigned attribute y

# Bulk validation
point = TypedPoint.new x: 'invalid', y: 'invalid'
point.cast_attributes! # => Rasti::Types::CompoundError: x: ["Invalid cast: \"invalid\" -> Rasti::Types::Integer"], y: ["Invalid cast: \"invalid\" -> Rasti::Types::Integer"]

Model Schema

It is possible to obtain a serializable representation of the model structure (schema).

Point = Rasti::Model[x: T::Integer, y: T::Integer]
Point.to_schema
# => {
#      model: "Point",
#      attributes: [
#        {name: :x, type: :integer},
#        {name: :y, type: :integer}
#      ]
#    }

Custom type serializers

You can register custom serializers for your types to be used in the schema generation:

Rasti::Model::Schema.register_type_serializer(MyCustomType, :custom)

# Or with a block for more details
Rasti::Model::Schema.register_type_serializer(MyCustomType) do |type|
  {type: :custom, details: type.some_info}
end

Also, if a type responds to to_schema, it will be used.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/gabynaiman/rasti-model.

License

The gem is available as open source under the terms of the MIT License.