{"id":13879872,"url":"https://github.com/evilmartians/evil-struct","last_synced_at":"2025-04-12T00:55:43.803Z","repository":{"id":56845045,"uuid":"74290727","full_name":"evilmartians/evil-struct","owner":"evilmartians","description":"Nested structure with type constraints, based on the `dry-initializer` DSL","archived":false,"fork":false,"pushed_at":"2017-01-28T21:14:28.000Z","size":19,"stargazers_count":10,"open_issues_count":0,"forks_count":2,"subscribers_count":40,"default_branch":"master","last_synced_at":"2025-04-12T00:55:37.524Z","etag":null,"topics":["constraints","dsl","ruby"],"latest_commit_sha":null,"homepage":null,"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/evilmartians.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-11-20T17:25:38.000Z","updated_at":"2024-04-27T16:26:36.000Z","dependencies_parsed_at":"2022-09-17T11:02:18.807Z","dependency_job_id":null,"html_url":"https://github.com/evilmartians/evil-struct","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evilmartians%2Fevil-struct","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evilmartians%2Fevil-struct/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evilmartians%2Fevil-struct/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evilmartians%2Fevil-struct/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evilmartians","download_url":"https://codeload.github.com/evilmartians/evil-struct/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248501878,"owners_count":21114683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["constraints","dsl","ruby"],"created_at":"2024-08-06T08:02:36.987Z","updated_at":"2025-04-12T00:55:43.781Z","avatar_url":"https://github.com/evilmartians.png","language":"Ruby","readme":"# Evil::Struct\n\nNested structure with type constraints, based on the [dry-initializer][dry-initializer] DSL.\n\n\u003ca href=\"https://evilmartians.com/\"\u003e\n\u003cimg src=\"https://evilmartians.com/badges/sponsored-by-evil-martians.svg\" alt=\"Sponsored by Evil Martians\" width=\"236\" height=\"54\"\u003e\u003c/a\u003e\n\n[![Gem Version][gem-badger]][gem]\n[![Build Status][travis-badger]][travis]\n[![Dependency Status][gemnasium-badger]][gemnasium]\n[![Inline docs][inch-badger]][inch]\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'evil-struct'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install evil-struct\n\n## Synopsis\n\nThe structure is like [dry-struct][dry-struct] except for it controls optional attributes and default values aside of type constraints.\n\nIts DSL is taken from [dry-initializer][dry-initializer]. Its method `attribute` is just an alias for `option`.\n\n```ruby\nrequire \"evil-struct\"\nrequire \"dry-types\"\n\nclass Product \u003c Evil::Struct\n  attribute :title\n  attribute :price,    Dry::Types[\"coercible.float\"]\n  attribute :quantity, Dry::Types[\"coercible.int\"], default: proc { 0 }\n\n  # shared options\n  attributes optional: true do\n    attribute :subtitle\n    attribute :description\n  end\nend\n\n# Accepts both symbolic and string keys.\n# Class methods `[]`, `call`, and `load` are just aliases for `new`\nproduct = Product[title: \"apple\", \"price\" =\u003e \"10.9\", description: \"a fruit\"]\n\n# Attributes are available via methods or `[]`\nproduct.title       # =\u003e \"apple\"\nproduct[:price]     # =\u003e 10.9\nproduct[\"quantity\"] # =\u003e 0\nproduct.description # =\u003e \"a fruit\"\n\n# unassigned value differs from `nil`\nproduct.subtitle    # =\u003e Dry::Initializer::UNDEFINED\n\n# Raises in case a mandatory value not assigned\nProduct.new # BOOM! because neither title, nor price are assigned\n\n# Hashifies all attributes except for undefined subtitle\n# You can use `dump` as an alias for `to_h`\nproduct.to_h\n# =\u003e { title: \"apple\", price: 10.9, description: \"a fruit\", quantity: 0 }\n\n# The structure is comparable to any object that responds to `#to_h`\nproduct == { title: \"apple\", price: 10.9, description: \"a fruit\", quantity: 0 }\n# =\u003e true\n```\n\nThe structure is designed for immutability. That's why it doesn't contain writers (but you can define them by yourself via `attr_writer`).\n\nInstead of mutating current instance, you can merge another hash to the object via `merge` or `deep_merge`.\n\n```ruby\nnew_product = product.merge(title: \"orange\")\nnew_product.class # =\u003e Product\nnew_product.to_h\n# =\u003e { title: \"orange\", price: 10.9, description: \"a fruit\", quantity: 0 }\n\n# you can merge any object that responds to `to_h` or `to_hash`\nother = Product[title: \"orange\", price: 12]\nnew_product = product.merge(other)\nnew_product.to_h\n# =\u003e { title: \"orange\", price: 12, description: \"a fruit\", quantity: 0 }\n\n# merge_deeply (deep_merge) gracefully merge nested hashes or hashified objects\ngrape = Product.new title: \"grape\",\n                    price: 30,\n                    description: { country: \"FR\", year: 2016, sort: \"Merlot\" }\n\nnew_grape = grape.merge_deeply description: { year: 2017 }\nnew_grape.to_h\n# =\u003e {\n#      title: \"grape\",\n#      price: 30,\n#      description: { country: \"FR\", year: 2017, sort: \"Merlot\" }\n#     }\n```\n\n## Compatibility\n\nTested under rubies [compatible to MRI 2.2+](.travis.yml).\n\n## Contributing\n\n* [Fork the project](https://github.com/dry-rb/dry-initializer)\n* Create your feature branch (`git checkout -b my-new-feature`)\n* Add tests for it\n* Commit your changes (`git commit -am '[UPDATE] Add some feature'`)\n* Push to the branch (`git push origin my-new-feature`)\n* Create a new Pull Request\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](./LICENSE.txt).\n\n[dry-initializer]: https://rom-rb.org/gems/dry-initializer\n[dry-struct]: https://rom-rb.org/gems/dry-struct\n[gem-badger]: https://img.shields.io/gem/v/evil-struct.svg?style=flat\n[gem]: https://rubygems.org/gems/evil-struct\n[gemnasium-badger]: https://img.shields.io/gemnasium/evilmartians/evil-struct.svg?style=flat\n[gemnasium]: https://gemnasium.com/evilmartians/evil-struct\n[inch-badger]: http://inch-ci.org/github/evilmartians/evil-struct.svg\n[inch]: https://inch-ci.org/github/evilmartians/evil-struct\n[travis-badger]: https://img.shields.io/travis/evilmartians/evil-struct/master.svg?style=flat\n[travis]: https://travis-ci.org/evilmartians/evil-struct\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevilmartians%2Fevil-struct","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevilmartians%2Fevil-struct","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevilmartians%2Fevil-struct/lists"}