Skip to content
Easily create Ruby class hierarchies that support nested attributes, type conversion, serialization, equality, and more.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.


Easily create class hierarchies that support nested attributes, type conversion, equality, and more.

Build Status


class2 :user => [
         :name, :age,
         :addresses => [
           :city, :state, :zip,
           :country => [ :name, :code ]

This creates 3 classes: User, Address, and Country with the following attribute accessors:

  • User: name, age, addresses
  • Address: city, state, zip, country
  • Country: name, code

Each of these classes are created with several additional methods. You can also specify types (or namespaces):

class2 :user => {
         :name => String,
         :age  => Integer,
         :addresses => [
           :city, :state, :zip,  # No explicit types for these
           :country => {
             :name => String,
             :code => String

Attributes without types are treated as is.

After calling either one of the above you can do the following:

user =
  :name => "sshaw",
  :age  => 99,
  :addresses => [
    { :city => "LA",
      :country => { :code => "US" } },
    { :city => "NY Sizzle",
      :country => { :code => "US" } },
    { :city => "São José dos Campos",
      :country => { :code => "BR" } }
)                  # "sshaw"
user.addresses.size        # 3  # "LA"
user.to_h                  # {:name => "sshaw", :age => 99, :addresses => [ { ... } ]}

# keys can be strings too
country ="name" => "America", "code" => "US")
address = => "Da Bay", :state => "CA", :country => country)
user.addresses << address => "sshaw") == => "sshaw")  # true

class2 can create classes with typed attributes from example hashes (with some caveats). This makes it possible to build classes for things like API responses using the API response itself as the specification:

# From JSON.parse
# of
response = [
    "sha" => "f52f1ed9144e1f73346176ab79a61af78df1b6bd",
    "commit" => {
      "author"=> {
    "comment_count": 0

    # snip full response

class2 :commit => response.first do
  include Class2::SnakeCase::JSON

commit =    # "sshaw"
commit.comment_count  # 0

If the JSON uses camelCase but you want your class to use snake_case you can do the following:

class2 "commit" => { "camelCase" => { "someKey" => 123, "anotherKey" => 456 } } do
  include Class2::SnakeCase::Attributes # snake_case accessors
  include Class2::LowerCamelCase::JSON  # but serialize using camelCase

commit = => { :some_key => 55 })
commit.camel_case.some_key # 55

commit = => { :someKey => 55 })
commit.camel_case.some_key # 55

For more info on accessor formats and JSON see:

You can also autoload a definition from a DATA section:

require "class2/autoload"  # builds classes from below JSON
require "pp"

commit = => { :name => "luser1" })
pp commit.to_h

  "response":  {
    "sha": "f52f1ed9144e1f73346176ab79a61af78df1b6bd",
    "commit": {
      "author": {
        "name": "sshaw",
        "email": "",
        "date": "2016-06-30T03:51:00Z"
    "comment_count": 0

class2 API

The are 3 ways to use class2. Pick the one that suites your style and/or requirements:

  • class2()
  • Class2()

They all create classes the same way. They all return nil.

To control the creation of the top-level methods, see the CLASS2_NO_EXPORT environment variable.


class2 uses String#classify to turn keys into class names: :foo will be Foo, :foo_bars will be FooBar.

Plural keys with an array value are always assumed to be accessors for a collection and will default to returning an Array. #classify is used to derive the class names from the plural attribute names. An :addresses key with an Array value will result in a class named Address being created.

Plurality is determined by String#pluralize.


An attempt is made to convert the attribute's type when a value is passed to the constructor or set via its accessor.

You can use any of these classes or their instances in your class definitions:

  • Array
  • Date
  • DateTime
  • Float
  • Hash
  • Integer
  • TrueClass/FalseClass - either one will cause a boolean conversion

Custom conversions are possible, just add the conversion to Class2::CONVERSIONS


class2 can use an exiting namespace or create a new one:

class2 My::Namespace,
       :user => %i[name age] => "sshaw")

class2 "New::Namespace",
       :user => %i[name age] => "sshaw")


Classes created by class2 will have:

  • A constructor that accepts a nested attribute hash
  • Attribute readers and writers
  • #to_h
  • #eql? and #==
  • #hash


To add methods or include modules just open up the class and write or include them:

class2 :user => :name

class User
  include SomeModule

  def first_initial
    name[0] if name
end => "sshaw").first_initial

class2 does accept a block whose contents will be added to every class defined within the call:

class2 :user => :name, :address => :city do
  include ActiveModel::Conversion
  extend ActiveModel::Naming


The default constructor ignores unknown attributes. If you prefer to raise an exception include Class2::StrictConstructor:

class2 :user => %w[id name age] do
  include Class2::StrictConstructor

Now an ArgumentError will be raised if anything but id, name, or age are passed in.

Also see Customizations.

See Also

The Perl modules that served as inspiration:

Surely others I cannot remember...

And these Ruby modules:


Skye Shaw [sshaw AT]


Released under the MIT License:

You can’t perform that action at this time.