{"id":13879130,"url":"https://github.com/chaadow/active_record-acts_as","last_synced_at":"2025-04-05T00:09:50.992Z","repository":{"id":48119139,"uuid":"84167047","full_name":"chaadow/active_record-acts_as","owner":"chaadow","description":"Simulate multi-table inheritance for ActiveRecord models","archived":false,"fork":false,"pushed_at":"2024-06-18T00:35:15.000Z","size":309,"stargazers_count":99,"open_issues_count":5,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T23:07:39.699Z","etag":null,"topics":["activerecord","inheritance","rails"],"latest_commit_sha":null,"homepage":"https://chaadow.github.io/active_record-acts_as/","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/chaadow.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2017-03-07T07:12:48.000Z","updated_at":"2024-07-10T05:57:00.000Z","dependencies_parsed_at":"2023-02-01T06:15:18.854Z","dependency_job_id":"4faa071e-aea4-49a7-bf69-3b555463a6f5","html_url":"https://github.com/chaadow/active_record-acts_as","commit_stats":{"total_commits":238,"total_committers":31,"mean_commits":7.67741935483871,"dds":0.5168067226890756,"last_synced_commit":"98bff090389e11b5249fa80b05874e16c078d5d3"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaadow%2Factive_record-acts_as","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaadow%2Factive_record-acts_as/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaadow%2Factive_record-acts_as/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaadow%2Factive_record-acts_as/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chaadow","download_url":"https://codeload.github.com/chaadow/active_record-acts_as/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266565,"owners_count":20910836,"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":["activerecord","inheritance","rails"],"created_at":"2024-08-06T08:02:10.722Z","updated_at":"2025-04-05T00:09:50.972Z","avatar_url":"https://github.com/chaadow.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# ActiveRecord::ActsAs\n![Gem](https://img.shields.io/gem/v/active_record-acts_as?style=for-the-badge)\n![Build Status](https://img.shields.io/github/actions/workflow/status/chaadow/active_record-acts_as/ruby.yml?style=for-the-badge)\n\nSimulates multiple-table-inheritance (MTI) for ActiveRecord models.\nBy default, ActiveRecord only supports single-table inheritance (STI).\nMTI gives you the benefits of STI but without having to place dozens of empty fields into a single table.\n\nTake a traditional e-commerce application for example:\nA product has common attributes (`name`, `price`, `image` ...),\nwhile each type of product has its own attributes:\nfor example a `pen` has `color`, a `book` has `author` and `publisher` and so on.\nWith multiple-table-inheritance you can have a `products` table with common columns and\na separate table for each product type, i.e. a `pens` table with `color` column.\n\n## Requirements\n\n* Ruby \u003e= `2.7`\n* ActiveSupport \u003e= `6.0` ( supports `main`/edge branch )\n* ActiveRecord \u003e= `6.0` ( supports `main`/edge branch )\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'active_record-acts_as'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install active_record-acts_as\n\n## Usage\n\nBack to example above, all you have to do is to mark `Product` as `actable` and all product type models as `acts_as :product`:\n\n```ruby\nclass Product \u003c ActiveRecord::Base\n  actable\n  belongs_to :store\n\n  validates_presence_of :name, :price\n\n  def info\n    \"#{name} $#{price}\"\n  end\nend\n\nclass Pen \u003c ActiveRecord::Base\n  acts_as :product\nend\n\nclass Book \u003c ActiveRecord::Base\n  # In case you don't wish to validate\n  # this model against Product\n  acts_as :product, validates_actable: false\nend\n\nclass Store \u003c ActiveRecord::Base\n  has_many :products\nend\n```\n\nand add foreign key and type columns to products table as in a polymorphic relation.\nYou may prefer using a migration:\n\n```ruby\nchange_table :products do |t|\n  t.integer :actable_id\n  t.string  :actable_type\nend\n```\n\nor use shortcut `actable`\n\n```ruby\nchange_table :products do |t|\n  t.actable\nend\n```\n\n**Make sure** that column names do not match on parent and subclass tables,\nthat will make SQL statements ambiguous and invalid!\nSpecially **DO NOT** use timestamps on subclasses, if you need them define them\non parent table and they will be touched after submodel updates (You can use the option `touch: false` to skip this behaviour).\n\nNow `Pen` and `Book` **acts as** `Product`, i.e. they inherit `Product`s **attributes**,\n**methods** and **validations**. Now you can do things like these:\n\n```ruby\nPen.create name: 'Penie!', price: 0.8, color: 'red'\n  # =\u003e #\u003cPen id: 1, color: \"red\"\u003e\nPen.where price: 0.8\n  # =\u003e [#\u003cPen id: 1, color: \"red\"\u003e]\n\n# You can seamlessly query Product attributes\npen = Pen.where(name: 'new pen', color: 'black').first_or_initialize\n  # =\u003e #\u003cPen id: nil, color: \"black\"\u003e\npen.name\n  # =\u003e \"new pen\"\n\n# You can also call `exists?` using Product attributes:\nPen.exists?(name: 'Penie!', price: 0.8)\n  # =\u003e true\n\nProduct.where price: 0.8\n  # =\u003e [#\u003cProduct id: 1, name: \"Penie!\", price: 0.8, store_id: nil, actable_id: 1, actable_type: \"Pen\"\u003e]\npen = Pen.new\npen.valid?\n  # =\u003e false\npen.errors.full_messages\n  # =\u003e [\"Name can't be blank\", \"Price can't be blank\", \"Color can't be blank\"]\nPen.first.info\n  # =\u003e \"Penie! $0.8\"\n```\n\nOn the other hand you can always access a specific object from its parent by calling `specific` method on it:\n\n```ruby\nProduct.first.specific\n  # =\u003e #\u003cPen ...\u003e\n```\n\nIf you have to come back to the parent object from the specific, the `acting_as` returns the parent element:\n\n```ruby\nPen.first.acting_as\n  # =\u003e #\u003cProduct ...\u003e\n```\n\nLikewise, `actables` converts a relation of specific objects to their parent objects:\n```ruby\nPen.where(...).actables\n# =\u003e [#\u003cProduct ...\u003e, ...]\n```\n\nIn `has_many` case you can use subclasses:\n\n```ruby\nstore = Store.create\nstore.products \u003c\u003c Pen.create\nstore.products.first\n  # =\u003e #\u003cProduct: ...\u003e\n```\n\nYou can give a name to all methods in `:as` option:\n\n```ruby\nclass Product \u003c ActiveRecord::Base\n  actable as: :producible\nend\n\nclass Pen \u003c ActiveRecord::Base\n  acts_as :product, as: :producible\nend\n\nchange_table :products do |t|\n  t.actable as: :producible\nend\n```\n\n`acts_as` support all `has_one` options, where defaults are there:\n`as: :actable, dependent: :destroy, validate: false, autosave: true`\n\nMake sure you know what you are doing when overwriting `validate` or `autosave` options.\n\nYou can pass scope to `acts_as` as in `has_one`:\n\n```ruby\nacts_as :person, -\u003e { includes(:friends) }\n```\n\n`actable` support all `belongs_to` options, where defaults are these:\n`polymorphic: true, dependent: :destroy, autosave: true`\n\nMake sure you know what you are doing when overwriting `polymorphic` option.\n\n### Namespaced models\n\nIf your `actable` and `acts_as` models are namespaced, you need to configure them like this:\n\n```ruby\nclass MyApp::Product \u003c ApplicationRecord\n  actable inverse_of: :product\nend\n\nclass MyApp::Pen \u003c ApplicationRecord\n  acts_as :product, class_name: 'MyApp::Product'\nend\n```\n\n## Caveats\n\nMultiple `acts_as` in the same class are not supported!\n## RSpec custom matchers\n\nTo use this library custom RSpec matchers, you must require the `rspec/acts_as_matchers` file.\n\nExamples:\n\n```ruby\nrequire \"active_record/acts_as/matchers\"\n\nRSpec.describe \"Pen acts like a Product\" do\n  it { is_expected.to act_as(:product) }\n  it { is_expected.to act_as(Product) }\n\n  it { expect(Person).to act_as(:product) }\n  it { expect(Person).to act_as(Product) }\nend\n\nRSpec.describe \"Product is actable\" do\n  it { expect(Product).to be_actable }\nend\n```\n\n## Contributing\n\n1. Fork it (https://github.com/chaadow/active_record-acts_as/fork)\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Test changes don't break anything (`rspec`)\n4. Add specs for your new feature\n5. Commit your changes (`git commit -am 'Add some feature'`)\n6. Push to the branch (`git push origin my-new-feature`)\n7. Create a new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchaadow%2Factive_record-acts_as","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchaadow%2Factive_record-acts_as","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchaadow%2Factive_record-acts_as/lists"}