{"id":16102384,"url":"https://github.com/sshaw/class2","last_synced_at":"2026-04-10T10:02:57.314Z","repository":{"id":62555712,"uuid":"99437219","full_name":"sshaw/class2","owner":"sshaw","description":"Easily create Ruby class hierarchies that support nested attributes, type conversion, serialization, equality, and more.","archived":false,"fork":false,"pushed_at":"2025-07-16T01:50:10.000Z","size":69,"stargazers_count":16,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-31T19:07:24.789Z","etag":null,"topics":["api","class-builder","class-generator","data-transfer-object","dsl","forms","json","nested-attributes","nested-objects","parameters","poro","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sshaw.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-08-05T17:17:56.000Z","updated_at":"2025-10-06T09:01:48.000Z","dependencies_parsed_at":"2025-07-16T00:32:06.113Z","dependency_job_id":"cb652058-0c88-4637-b8a4-d5f683fc9413","html_url":"https://github.com/sshaw/class2","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/sshaw/class2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2Fclass2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2Fclass2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2Fclass2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2Fclass2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sshaw","download_url":"https://codeload.github.com/sshaw/class2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshaw%2Fclass2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31637748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T07:40:12.752Z","status":"ssl_error","status_checked_at":"2026-04-10T07:40:11.664Z","response_time":98,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","class-builder","class-generator","data-transfer-object","dsl","forms","json","nested-attributes","nested-objects","parameters","poro","ruby"],"created_at":"2024-10-09T18:53:41.935Z","updated_at":"2026-04-10T10:02:57.299Z","avatar_url":"https://github.com/sshaw.png","language":"Ruby","readme":"# class2\n\nEasily create class hierarchies that support nested attributes, type conversion, equality, and more.\n\n## Usage\n\n```rb\nclass2 :user =\u003e [\n         :name, :age,\n         :addresses =\u003e [\n           :city, :state, :zip,\n           :country =\u003e [ :name, :code ]\n         ]\n       ]\n```\n\nThis creates 3 classes: `User`, `Address`, and `Country` with the following attribute accessors:\n\n* `User`: name, age, addresses\n* `Address`: city, state, zip, country\n* `Country`: name, code\n\n\nEach of these classes are created with\n[several additional methods](#methods).  You can also specify types\n(or [namespaces](#namespaces)):\n\n```rb\nclass2 :user =\u003e {\n         :name =\u003e String,\n         :age  =\u003e Integer,\n         :addresses =\u003e [\n           :city, :state, :zip,  # No explicit types for these\n           :country =\u003e {\n             :name =\u003e String,\n             :code =\u003e String\n           }\n         ]\n       }\n```\n\nAttributes without types are treated as is.\n\nAfter calling either one of the above you can do the following:\n\n```rb\nuser = User.new(\n  :name =\u003e \"sshaw\",\n  :age  =\u003e 99,\n  :addresses =\u003e [\n    { :city =\u003e \"LA\",\n      :country =\u003e { :code =\u003e \"US\" } },\n    { :city =\u003e \"NY Sizzle\",\n      :country =\u003e { :code =\u003e \"US\" } },\n    { :city =\u003e \"São José dos Campos\",\n      :country =\u003e { :code =\u003e \"BR\" } }\n  ]\n)\n\nuser.name                  # \"sshaw\"\nuser.addresses.size        # 3\nuser.addresses.first.city  # \"LA\"\nuser.to_h                  # {:name =\u003e \"sshaw\", :age =\u003e 99, :addresses =\u003e [ { ... } ]}\n\n# keys can be strings too\ncountry = Country.new(\"name\" =\u003e \"America\", \"code\" =\u003e \"US\")\naddress = Address.new(:city =\u003e \"Da Bay\", :state =\u003e \"CA\", :country =\u003e country)\nuser.addresses \u003c\u003c address\n\nUser.new(:name =\u003e \"sshaw\") == User.new(:name =\u003e \"sshaw\")  # true\n```\n\n`class2` can create classes with typed attributes from example hashes (with some caveats).\nThis makes it possible to build classes for things like API responses using the API response\nitself as the specification:\n\n```rb\n# From JSON.parse\n# of https://api.github.com/repos/sshaw/selfie_formatter/commits\nresponse = [\n  {\n    \"sha\" =\u003e \"f52f1ed9144e1f73346176ab79a61af78df1b6bd\",\n    \"commit\" =\u003e {\n      \"author\"=\u003e {\n        \"name\"=\u003e\"sshaw\",\n        \"email\"=\u003e\"skye.shaw@gmail.com\",\n        \"date\"=\u003e\"2016-06-30T03:51:00Z\"\n      }\n    },\n    \"comment_count\": 0\n\n    # snip full response\n  }\n]\n\nclass2 :commit =\u003e response.first do\n  include Class2::SnakeCase::JSON\nend\n\ncommit = Commit.new(response.first)\ncommit.author.name    # \"sshaw\"\ncommit.comment_count  # 0\nJSON.dump(commit)\n```\n\nIf the JSON uses `camelCase` but you want your class to use `snake_case` you can do the following:\n\n```rb\nclass2 \"commit\" =\u003e { \"camelCase\" =\u003e { \"someKey\" =\u003e 123, \"anotherKey\" =\u003e 456 } } do\n  include Class2::SnakeCase::Attributes # snake_case accessors\n  include Class2::LowerCamelCase::JSON  # but serialize using camelCase\nend\n\ncommit = Commit.new(:camel_case =\u003e { :some_key =\u003e 55 })\ncommit.camel_case.some_key # 55\n\ncommit = Commit.new(:camelCase =\u003e { :someKey =\u003e 55 })\ncommit.camel_case.some_key # 55\n```\n\nFor more info on accessor formats and JSON see:\n\n* [`Class2::SnakeCase`](https://www.rubydoc.info/gems/class2/Class2/SnakeCase)\n* [`Class2::UpperCamelCase`](https://www.rubydoc.info/gems/class2/Class2/UpperCamelCase)\n* [`Class2::LowerCamelCase`](https://www.rubydoc.info/gems/class2/Class2/LowerCamelCase)\n\n[Using Ruby-specific JSON extensions](https://github.com/ruby/json?tab=readme-ov-file#usage) you can define\nRuby types in the JSON class2 will use for type conversion:\n\n```json\n{\n  \"your_class\": {\n    \"id\": 0,\n    \"name\": \"string\",\n    \"updated_at\": {\"json_class\":\"Time\",\"s\":0,\"n\":0},\n  }\n}\n```\n\nThen require the appropriate JSON conversion class:\n\n```rb\nrequire \"json/add/time\"\n```\n\nThis will result in class2 creating a `MyClass` class with the following:\n\n- `#id` returning an `Integer` instance\n- `#name` returning a `String` instance\n- `#updated_at` returning a `Time` instance\n\nThis will work for any [supported conversions](#conversions).\n\nYou can also autoload a definition from a DATA section:\n\n```rb\nrequire \"class2/autoload\"  # builds classes from below JSON\nrequire \"pp\"\n\ncommit = Commit.new(:author =\u003e { :name =\u003e \"luser1\" })\npp commit.to_h\n\n__END__\n{\n  \"response\":  {\n    \"sha\": \"f52f1ed9144e1f73346176ab79a61af78df1b6bd\",\n    \"commit\": {\n      \"author\": {\n        \"name\": \"sshaw\",\n        \"email\": \"skye.shaw@gmail.com\",\n        \"date\": \"2016-06-30T03:51:00Z\"\n      }\n    },\n    \"comment_count\": 0\n  }\n}\n```\n\n### class2 API\n\nThe are 3 ways to use class2. Pick the one that suites your style and/or requirements:\n\n* `class2()`\n* `Class2()`\n* `Class2.new`\n\nThey all create classes the same way. They all return `nil`.\n\nTo control the creation of the top-level methods, see the\n[`CLASS2_NO_EXPORT` environment variable](https://github.com/sshaw/class2/blob/a7ebe022b48db33d532cc483b0e036e4ec7d2e66/lib/class2.rb#L9-L23).\n\n#### Naming\n\n`class2` uses\n[`String#classify`](http://api.rubyonrails.org/classes/String.html#method-i-classify)\nto turn keys into class names: `:foo` will be `Foo`, `:foo_bars` will\nbe `FooBar`.\n\nPlural keys with an array value are always assumed to be accessors for\na collection and will default to returning an `Array`. `#classify` is\nused to derive the class names from the plural attribute names. An\n`:addresses` key with an `Array` value will result in a class named\n`Address` being created.\n\nPlurality is determined by [`String#pluralize`](http://api.rubyonrails.org/classes/String.html#method-i-pluralize).\n\n#### Conversions\n\nAn attempt is made to convert the attribute's type when a value is passed to the constructor\nor set via its accessor.\n\nYou can use any of these classes or their instances in your class definitions:\n\n* `Array`\n* `Date`\n* `DateTime`\n* `Float`\n* `Hash`\n* `Integer`\n* `Time`\n* `TrueClass`/`FalseClass` - either one will cause a boolean conversion\n\nCustom conversions are possible, just add the conversion to\n[`Class2::CONVERSIONS`](https://github.com/sshaw/class2/blob/517239afc76a4d80677e169958a1dc7836726659/lib/class2.rb#L14-L29)\n\n#### Namespaces\n\n`class2` can use an exiting namespace or create a new one:\n\n```rb\nclass2 My::Namespace,\n       :user =\u003e %i[name age]\n\nMy::Namespace::User.new(:name =\u003e \"sshaw\")\n\nclass2 \"New::Namespace\",\n       :user =\u003e %i[name age]\n\nNew::Namespace::User.new(:name =\u003e \"sshaw\")\n```\n\n#### Methods\n\nClasses created by `class2` will have:\n\n* A constructor that accepts a nested attribute hash\n* Attribute readers and writers\n* `#to_h`\n* `#eql?` and `#==`\n* `#hash`\n\n#### Customizations\n\nTo add methods or include modules just open up the class and write or include them:\n\n```rb\nclass2 :user =\u003e :name\n\nclass User\n  include SomeModule\n\n  def first_initial\n    name[0] if name\n  end\nend\n\nUser.new(:name =\u003e \"sshaw\").first_initial\n```\n\n`class2` does accept a block whose contents will be added to\n*every* class defined within the call:\n\n```rb\nclass2 :user =\u003e :name, :address =\u003e :city do\n  include ActiveModel::Conversion\n  extend ActiveModel::Naming\nend\n\nUser.new.model_name.route_key\nAddress.new.model_name.route_key\n```\n\n#### Constructor\n\nThe default constructor ignores unknown attributes.\nIf you prefer to raise an exception include `Class2::StrictConstructor`:\n\n```rb\nclass2 :user =\u003e %w[id name age] do\n  include Class2::StrictConstructor\nend\n```\n\nNow an `ArgumentError` will be raised if anything but `id`, `name`, or\n`age` are passed in.\n\nAlso see [Customizations](#customizations).\n\n## See Also\n\nThe Perl modules that served as inspiration:\n\n* [`MooseX::NestedAttributesConstructor`](https://github.com/sshaw/MooseX-NestedAttributesConstructor)\n* [`Class::Tiny`](https://metacpan.org/pod/Class::Tiny)\n* [`Moose`](https://metacpan.org/pod/Moose), [`Moo`](https://metacpan.org/pod/Moo), and [`Mouse`](https://metacpan.org/pod/Mouse)\n* [`Type::Tiny`](https://metacpan.org/pod/Type::Tiny)\n* [`MooseX::Types`](https://metacpan.org/pod/MooseX::Types)\n* [`Rubyish`](https://metacpan.org/pod/Rubyish)\n\nSurely others I cannot remember...\n\nAnd these Ruby modules:\n\n* [`require3`](https://github.com/sshaw/require3)\n* [`alias2`](https://github.com/sshaw/alias2)\n\n## Author\n\nSkye Shaw [sshaw AT gmail.com]\n\n## License\n\nReleased under the MIT License: www.opensource.org/licenses/MIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshaw%2Fclass2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsshaw%2Fclass2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshaw%2Fclass2/lists"}