Need help with virtus?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

3.8K Stars 240 Forks MIT License 1.6K Commits 74 Opened issues


[DISCONTINUED ] Attributes on Steroids for Plain Old Ruby Objects

Services available


Need anything else?

Contributors list


Working on virtus taught me a lot about handling data in Ruby, which involves coercions, type safety and validation (amongst other things). Even though the project has been successful, and serving well for many people, I decided to build something better. As a result, dry-types, dry-struct and dry-schema were born. These projects should be considered as virtus' successors, with better separation of concerns and better features. If you're interested in a modern take on same problems that virtus tried to solve, please check out these projects!



Gem Version Build Status Code Climate Test Coverage Inline docs

Virtus allows you to define attributes on classes, modules or class instances with optional information about types, reader/writer method visibility and coercion behavior. It supports a lot of coercions and advanced mapping of embedded objects and collections.

You can use it in many different contexts like:

  • Input parameter sanitization and coercion in web applications
  • Mapping JSON to domain objects
  • Encapsulating data-access in Value Objects
  • Domain model prototyping

And probably more.


$ gem install virtus

or in your Gemfile

gem 'virtus'


Using Virtus with Classes

You can create classes extended with Virtus and define attributes:

class User
  include Virtus.model

attribute :name, String attribute :age, Integer attribute :birthday, DateTime end

user = => 'Piotr', :age => 31) user.attributes # => { :name => "Piotr", :age => 31, :birthday => nil } # => "Piotr"

user.age = '31' # => 31 user.age.class # => Fixnum

user.birthday = 'November 18th, 1983' # => #datetime:


user.attributes = { :name => 'Jane', :age => 21 } # => "Jane" user.age # => 21

Cherry-picking extensions

# include attribute DSL + constructor + mass-assignment
class User
  include Virtus.model

attribute :name, String end

user = => 'Piotr') user.attributes = { :name => 'John' } user.attributes

=> {:name => 'John'}

include attribute DSL + constructor

class User include Virtus.model(:mass_assignment => false)

attribute :name, String end => 'Piotr')

include just the attribute DSL

class User include Virtus.model(:constructor => false, :mass_assignment => false)

attribute :name, String end

user = = 'Piotr'

Using Virtus with Modules

You can create modules extended with Virtus and define attributes for later inclusion in your classes:

module Name
  include Virtus.module

attribute :name, String end

module Age include Virtus.module(:coerce => false)

attribute :age, Integer end

class User include Name, Age end

user = => 'John', :age => 30)

Dynamically Extending Instances

It's also possible to dynamically extend an object with Virtus:

class User
  # nothing here

user = user.extend(Virtus.model) user.attribute :name, String = 'John' # => 'John'

Default Values

class Page
  include Virtus.model

attribute :title, String

default from a singleton value (integer in this case)

attribute :views, Integer, :default => 0

default from a singleton value (boolean in this case)

attribute :published, Boolean, :default => false

default from a callable object (proc in this case)

attribute :slug, String, :default => lambda { |page, attribute| page.title.downcase.gsub(' ', '-') }

default from a method name as symbol

attribute :editor_title, String, :default => :default_editor_title

def default_editor_title published? ? title : "UNPUBLISHED: #{title}" end end

page = => 'Virtus README') page.slug # => 'virtus-readme' page.views # => 0 page.published # => false page.editor_title # => "UNPUBLISHED: Virtus README"

page.views = 10 page.views # => 10 page.reset_attribute(:views) # => 0 page.views # => 0

Default values on dynamically extended instances

This requires you to set

option because default values are set in the constructor if it's set to false (which is the default setting):
User =
user =
user.attribute :name, String, default: 'jane', lazy: true # => "jane"

Embedded Value

class City
  include Virtus.model

attribute :name, String end

class Address include Virtus.model

attribute :street, String attribute :zipcode, String attribute :city, City end

class User include Virtus.model

attribute :name, String attribute :address, Address end

user = => { :street => 'Street 1/2', :zipcode => '12345', :city => { :name => 'NYC' } })

user.address.street # => "Street 1/2" # => "NYC"

Collection Member Coercions

# Support "primitive" classes
class Book
  include Virtus.model

attribute :page_numbers, Array[Integer] end

book = => %w[1 2 3]) book.page_numbers # => [1, 2, 3]

Support EmbeddedValues, too!

class Address include Virtus.model

attribute :address, String attribute :locality, String attribute :region, String attribute :postal_code, String end

class PhoneNumber include Virtus.model

attribute :number, String end

class User include Virtus.model

attribute :phone_numbers, Array[PhoneNumber] attribute :addresses, Set[Address] end

user = :phone_numbers => [ { :number => '212-555-1212' }, { :number => '919-444-3265' } ], :addresses => [ { :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" } ])

user.phone_numbers # => [#<0x007fdb2d3bef88>, #<0x007fdb2d3beb00>]

user.addresses # => #<set: any st.>}> </0x007fdb2d3beb00></0x007fdb2d3bef88>

Hash attributes coercion

class Package
  include Virtus.model

attribute :dimensions, Hash[Symbol => Float] end

package = => { 'width' => "2.2", :height => 2, "length" => 4.5 }) package.dimensions # => { :width => 2.2, :height => 2.0, :length => 4.5 }

IMPORTANT note about Boolean type

Be aware that some libraries may do a terrible thing and define a global Boolean constant which breaks virtus' constant type lookup, if you see issues with the boolean type you can workaround it like that:

class User
  include Virtus.model

attribute :admin, Axiom::Types::Boolean end

This will be improved in Virtus 2.0.

IMPORTANT note about member coercions

Virtus performs coercions only when a value is being assigned. If you mutate the value later on using its own interfaces then coercion won't be triggered.

Here's an example:

class Book
  include Virtus.model

attribute :title, String end

class Library include Virtus.model

attribute :books, Array[Book] end

library =

This will coerce Hash to a Book instance

library.books = [ { :title => 'Introduction to Virtus' } ]

This WILL NOT COERCE the value because you mutate the books array with Array#<<

library.books << { :title => 'Another Introduction to Virtus' }

A suggested solution to this problem would be to introduce your own class instead of using Array and implement mutation methods that perform coercions. For example:

class Book
  include Virtus.model

attribute :title, String end

class BookCollection < Array def < 'Another Introduction to Virtus' }

Value Objects

class GeoLocation
  include Virtus.value_object

values do attribute :latitude, Float attribute :longitude, Float end end

class Venue include Virtus.value_object

values do attribute :name, String attribute :location, GeoLocation end end

venue = :name => 'Pub', :location => { :latitude => 37.160317, :longitude => -98.437500 })

venue.location.latitude # => 37.160317 venue.location.longitude # => -98.4375

Supports object's equality

venue_other = :name => 'Other Pub', :location => { :latitude => 37.160317, :longitude => -98.437500 })

venue.location === venue_other.location # => true

Custom Coercions

require 'json'

class Json < Virtus::Attribute def coerce(value) value.is_a?(::Hash) ? value : JSON.parse(value) end end

class User include Virtus.model

attribute :info, Json, default: {} end

user = = '{"email":"[email protected]"}' # => {"email"=>"[email protected]"} # => Hash

With a custom attribute encapsulating coercion-specific configuration

class NoisyString < Virtus::Attribute def coerce(value) value.to_s.upcase end end

class User include Virtus.model

attribute :scream, NoisyString end

user = => 'hello world!') user.scream # => "HELLO WORLD!"

Private Attributes

class User
  include Virtus.model

attribute :unique_id, String, :writer => :private

def set_unique_id(id) self.unique_id = id end end

user = => '1234-1234') user.unique_id # => nil

user.unique_id = '1234-1234' # => NoMethodError: private method `unique_id='

user.set_unique_id('1234-1234') user.unique_id # => '1234-1234'

Overriding setters

class User
  include Virtus.model

attribute :name, String

def name=(new_name) custom_name = nil if new_name == "Godzilla" custom_name = "Can't tell" end super custom_name || new_name end end

user = "Frank") # => 'Frank'

user = "Godzilla") # => 'Can't tell'

Strict Coercion Mode

By default Virtus returns the input value even when it couldn't coerce it to the expected type. If you want to catch such cases in a noisy way you can use the strict mode in which Virtus raises an exception when it failed to coerce an input value.

class User
  include Virtus.model(:strict => true)

attribute :admin, Boolean end

this will raise an error :admin => "can't really say if true or false"

Nullify Blank Strings Mode

If you want to replace empty Strings with

values (since they can't be coerced into the expected type), you can use the
class User
  include Virtus.model(:nullify_blank => true)

attribute :birthday, Date end => "").birthday # => nil

Building modules with custom configuration

You can also build Virtus modules that contain their own configuration.

YupNopeBooleans = Virtus.model { |mod|
  mod.coerce = true
  mod.coercer.config.string.boolean_map = { 'nope' => false, 'yup' => true }

class User include YupNopeBooleans

attribute :name, String attribute :admin, Boolean end

Or just include the module straight away ...

class User include Virtus.model(:coerce => false)

attribute :name, String attribute :admin, Boolean end

Attribute Finalization and Circular Dependencies

If a type references another type which happens to not be available yet you need to use lazy-finalization of attributes and finalize virtus manually after all types have been already loaded:

# in blog.rb
class Blog
  include Virtus.model(:finalize => false)

attribute :posts, Array['Post'] end

in post.rb

class Post include Virtus.model(:finalize => false)

attribute :blog, 'Blog' end

after loading both files just do:


constants will be resolved:

Blog.attribute_set[:posts].member_type.primitive # => Post Post.attribute_set[:blog].type.primitive # => Blog

Plugins / Extensions

List of plugins/extensions that add features to Virtus:

Ruby version support

Virtus is known to work correctly with the following rubies:

  • 1.9.3
  • 2.0.0
  • 2.1.2
  • jruby
  • (probably) rbx



  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit, do not mess with Rakefile or version (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
  • Send me a pull request. Bonus points for topic branches.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.