{"id":19684140,"url":"https://github.com/cedarcode/composition","last_synced_at":"2025-11-11T20:01:16.328Z","repository":{"id":62556100,"uuid":"77470323","full_name":"cedarcode/composition","owner":"cedarcode","description":"Alternative ActiveRecord composition","archived":false,"fork":false,"pushed_at":"2018-03-12T20:27:35.000Z","size":27,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-25T04:54:57.474Z","etag":null,"topics":["activerecord","aggregation","aggregations","composition"],"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/cedarcode.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-12-27T16:59:17.000Z","updated_at":"2023-04-05T01:43:36.000Z","dependencies_parsed_at":"2022-11-03T05:45:39.461Z","dependency_job_id":null,"html_url":"https://github.com/cedarcode/composition","commit_stats":null,"previous_names":["marceloeloelo/composition"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/cedarcode/composition","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedarcode%2Fcomposition","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedarcode%2Fcomposition/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedarcode%2Fcomposition/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedarcode%2Fcomposition/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cedarcode","download_url":"https://codeload.github.com/cedarcode/composition/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cedarcode%2Fcomposition/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":283921119,"owners_count":26916743,"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","status":"online","status_checked_at":"2025-11-11T02:00:06.610Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["activerecord","aggregation","aggregations","composition"],"created_at":"2024-11-11T18:16:55.978Z","updated_at":"2025-11-11T20:01:16.263Z","avatar_url":"https://github.com/cedarcode.png","language":"Ruby","readme":"# Composition\n\n[![Build Status](https://travis-ci.org/cedarcode/composition.svg?branch=master)](https://travis-ci.org/cedarcode/composition)\n[![Code Climate](https://codeclimate.com/github/cedarcode/composition/badges/gpa.svg)](https://codeclimate.com/github/cedarcode/composition)\n[![Gem Version](https://badge.fury.io/rb/composition.svg)](https://badge.fury.io/rb/composition)\n\nAlternative composition support for `rails` applications, for when\nActiveRecord's `composed_of` is not enough. This gem adds some behavior\ninto composed objects and ways to interact and send messages between both\nthe one composing and the one being composed.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'composition'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install composition\n\n## Usage\n\nComposition will enable a new way of defining composed objects into an\nActiveRecord class. You should have available a `compose` macro for your\nuse in your application models.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  compose :credit_card,\n          mapping: {\n            credit_card_name: :name,\n            credit_card_brand: :brand,\n            credit_card_expiration: :expiration\n          }\nend\n```\n\nThe `User` class has now available the following methods to manipulate\nthe `credit_card` object:\n* `User#credit_card`\n* `User#credit_card=(credit_card)`\n\nThese methods will operate with a credit_card value object like the one\ndescribed below:\n```ruby\nclass CreditCard \u003c Composition::Base\n  composed_from :user\n\n  def expired?\n    Date.today \u003e expiration\n  end\nend\n```\n\nNotice that `CreditCard` inherits from `Composition::Base` and that the\n`composed_from` macro is set to `:user`. This is necessary in order to gain\nfull access to the `user` object from the `credit_card`.\n \n### How to interact with the value object\nWith the previous setup in place, now it should be possible to access attributes from\nthe database through the value objects instead. You can think of the `CreditCard`\nas a normal `ActiveModel::Model` class with the attributes that you already\nspecified in the `mapping` option.\n\nYou would interact with the credit_card object like the following:\n```ruby\nuser.credit_card_name  = 'Jon Snow'          # Set the ActiveRecord attribute\nuser.credit_card_brand = 'Visa'              # Set the ActiveRecord attribute\nuser.credit_card_expiration = Date.yesterday # Set the ActiveRecord attribute\n\nuser.credit_card                    # =\u003e CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 11 May 2017)\nuser.credit_card.name               # =\u003e 'Jon Snow'\nuser.credit_card.brand              # =\u003e 'Visa'\nuser.credit_card.expiration         # =\u003e Thu, 11 May 2017\nuser.credit_card.user == user       # =\u003e true\nuser.credit_card.attributes         # =\u003e { name: 'Jon Snow', brand: 'Visa', expiration: Thu, 11 May 2017 }\n\nuser.credit_card.expired?           # =\u003e true\n```\n\nModifying the credit_card attributes:\n```ruby\nuser.credit_card.name                # =\u003e 'Jon Snow'\nuser.credit_card.name = 'Arya Stark' # =\u003e 'Arya Stark'\nuser.credit_card_name                # =\u003e 'Arya Stark'\nuser.save                            # =\u003e true\n```\n\n### Writing to value objects\nThe value object can be set by either setting attributes individually, by\nassigning a new value object, or by using `assign_attributes` on the parent.\n\n```ruby\nuser.credit_card.name = 'Jon Snow'\nuser.credit_card.brand = 'Visa'\nuser.credit_card.expiration = Date.today\nuser.credit_card # =\u003e CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)\n\nuser.credit_card = CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Date.today)\nuser.credit_card # =\u003e CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)\n\nuser.assign_attributes(credit_card: { name: 'Jon Snow', brand: 'Visa', expiration: Date.today })\nuser.credit_card # =\u003e CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)\n\nuser.update_attributes(credit_card: { name: 'Jon Snow', brand: 'Visa', expiration: Date.today })\nuser.credit_card # =\u003e CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: Thu, 12 May 2017)\n```\n\n### Validations\nIf you need to add validations to your value object that should just work.\n\n```ruby\nclass CreditCard \u003c Composition::Base\n  composed_from :user\n\n  validates :expiration, presence: true\n\n  def expired?\n    Date.today \u003e expiration\n  end\nend\n\nuser.credit_card = CreditCard.new(name: 'Jon Snow', brand: 'Visa', expiration: nil)\nuser.credit_card.valid? # =\u003e false\n```\n\n### Detailed macro documentation\nComposition will assume some things and use some defaults based on naming\nconventions for when you define `compose` and `composed_from` macros. However,\nthere will be cases where you will have to override the naming convention with\nsomething custom. Following you will find the complete reference for the provided\nmacros.\n\n#### Options for compose\nThe `compose` method will accept the following options:\n\n##### :mapping \nThis is required. It will accept a hash of mappings between the attributes\nin the parent object and their mapping to the new value object being defined.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  compose :credit_card,\n          mapping: {\n            credit_card_name: :name,\n            credit_card_brand: :brand,\n            credit_card_expiration: :expiration\n          }\nend\n```\n\n##### :class_name\nOptional. If the name of the value object cannot be derived from the composition\nname, you can use the `:class_name` option to supply the class name. If a `user` has\na `credit_card` but the name of the class is something like `CCard`, then you can use:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  compose :credit_card,\n          mapping: {\n            credit_card_name: :name,\n            credit_card_brand: :brand,\n            credit_card_expiration: :expiration\n          }, class_name: 'CCard'\nend\n```\n\n#### Options for composed_from\nThe `composed_from` method will accept the following options:\n\n##### :class_name\nOptional. If the name of the value object cannot be derived from the composition\nname, you can use the `:class_name` option to supply the class name. If a `user` has\na `credit_card` but the name of the user class is something like `AdminUser`, then\nyou can use:\n\n```ruby\nclass CreditCard \u003c Composition::Base\n  compose_from :user, class_name: 'AdminUser'\nend\n```\n\n\n## Contributing\n\n1. Fork it ( https://github.com/cedarcode/composition/ )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\nSee the [Running Tests](RUNNING_TESTS.md) guide for details on how to run the test suite.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedarcode%2Fcomposition","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcedarcode%2Fcomposition","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcedarcode%2Fcomposition/lists"}