Virtus – Attributes For Your Plain Ruby Objects

I’m happy to announce the first release of Virtus gem. It is an extraction of DataMapper Property API with various tweaks and improvements. If you like how properties work in DataMapper and would like to use such functionality in your plain ruby objects then you should give Virtus a try.

It is an early release but I would not expect many API changes before 1.0.0 since the code is based on the stable DataMapper API and I’m quite happy with it.

How to install?

Virtus is just a gem and comes with no dependencies. To install just run this in your shell:

Why?

As some of you know we’re starting to work on DataMapper 2.0. It will be a true implementation of the Data Mapper pattern and will use a certain set of libraries under the hood. Dan Kubb has already finished his absolutely fantastic relational algebra engine called
Veritas along with Veritas SQL Generator – these
gems will be the core part of DataMapper 2.0 Query System.

When we were talking with Dan about the future of Property API in DataMapper we both agreed we need an abstraction for defining your classes that would be
decoupled from any persistence logic. We also agreed that we actually don’t like “property” word in that context and would prefer #8220;attribute”. There you go – Virtus was borned :)

How does it work?

Virtus works in an almost identical way as Property in DataMapper. You can define attributes in your classes and it will create accessors to these attributes along with typecasting abilities. It comes with a set of builtin attribute types but you are free to add your own types too.

The API is dead-simple. Here’s an example class:

This creates 4 attribute objects that are associated with your class. Each of these attributes is responsible for reading and writing values. Values are stored as standard instance variables.

You can easily inspect what attributes a class has:

Every attribute type has the primitive option set. When you are setting a value of an attribute, the corresponding attribute object will check if the value has the correct type. If the type doesn’t match the primitive, then the attribute will typecast the value.

Available Attribute Types

As I mentioned Virtus comes with various attribute types built-in:

  • Array
  • Boolean
  • Date
  • DateTime
  • Decimal
  • Float
  • Hash
  • Integer
  • Object
  • String
  • Time

These classes are organized in a hierarchy where Object inherits from an abstract Attribute class and all other types inherit from Object.

Options For Defining Attributes

When defining an attribute you can provide additional options. Every attribute class has a list of options that it accepts.

At the moment you can only use options for access control:

It is possible to set a default value of an option directly on an attribute class:

Custom Attribute Types and Options

Just like in DataMapper you can implement your own attribute types. Whenever you need some twisted typecasting logic or you need to use extra options you can create a custom attribute class.

Here’s an example use-case – let’s say you want a hash and you need to stringify or symbolize the keys.

Let’s use our custom attribute:

Other ORMs?

It’s a bit early to talk about that but I would love to see other Ruby ORM libraries using a common gem for model attributes. After we integrate DataMapper with Virtus it should be feasible for others like Mongoid, MongoMapper or even ActiveRecord (sic!) to use Virtus too. Well, at least that’s my ultimate goal.

Let’s not reinvent the wheel!

Resources

Here are links related to the project:

Enjoy!

  • Peter Jaros

    So Og was the first to invent the wheel?

    • trans

      Ha. Well, most things tend to be fairly evolutionary. As far as I know, Og was the first to take this approach in Ruby. And I think George Moschovitis deserves a fair bit of credit for his work on Og and Nitro which were in many ways the progenitors of DataMapper, Ramaze  and various other beneficiaries.

      Some good came from this conversation by the way. If you check the issues on Virtus you will see some discussion that led to important improvements to the library.

  • Humberto Marchezi

    I like Virtus API. It is clearer than Active Record but it is not based on Plain Old Ruby Objects (PORO) as the gem creator said. PORO (or POJO, whatever) should not contain ANY reference to the persistence mechanism. This is not my definition. See here: http://en.wikipedia.org/wiki/POJO. However I would be very interested in having a real PORO ORM.

    • http://solnic.eu/ solnic

      Virtus has nothing to do with persistence. What kind of references are you thinking about?

      • Humberto Marchezi

        According to the site, Virtus is a common API for ORMs and ODMs. I meant persistence to talk about “retrieving/saving to database” The references I am talking about is “include Virtus”, “include Virtus:ValueObject”, “attribute”, etc. These expressions are included inside the model classes in the examples of the documentation but they are not part of standard ruby language. However by definition “Plain Old Objects” should not contain any references to external libraries. Instead they should use only standard language types and structures. Please do not think I am diminishing Virtus (great idea!) I am questioning the right use of concepts.

        • http://solnic.eu/ solnic

          I guess that description should be changed as it is a bit confusing. Virtus started as an extraction from DataMapper. It can be used by ORM/ODM libs but it has nothing to do with persistence. It just helps in coercing data types and defining domain models in terms of their attributes. You can use it standalone w/o any ORM/ODM for other things, not-persistence related, like for example Form Objects.

          PORO in this context means that you can include Virtus in a PORO and get extra functionality. So yes, it is based on PORO, it has no dependencies, it doesn’t change your object, it only adds the minimum to make attributes and coercion work. Not sure where’s the problem you’re seeing.

          • Humberto Marchezi

            It is not a problem at all. I am just not sure if this can be called a PORO approach. However after you have explained I think now I can understand better what Virtus can do. It provides attributes with types to ruby classes, nothing more… I think that is what you were trying to say, right ? Well, if I understood correctly while that is not necessary in plain old objects in other languages such as Java or C#, when we work with ruby classes, Virtus provides this meaningful type “meta-data” that can be later used to build a ORMs/ODMs/etc. on top of that. In this case it would be nice to have ORMs/ODMs built for Virtus. If not I may come up with one some day. :-)

          • http://solnic.eu/ solnic

            DataMapper 2 will work with Virtus (initial work is done here: https://github.com/solnic/dm-mapper)