Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

activerecord-exclusive-arc

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

activerecord-exclusive-arc

  • 0.3.1
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

💫 activerecord-exclusive-arc 💫

A RubyGem that allows an ActiveRecord model to exclusively belong to one of any number of different types of ActiveRecord models.

Doesn’t Rails already provide a way to do this?

Yes but here’s a post about why this exists.

So how does this work?

It reduces the boilerplate of managing a Polymorphic Assication modeled as a pattern called an Exclusive Arc, where each potential polymorphic reference has its own foreign key. This maps nicely to a set of optional belongs_to relationships, some polymorphic convenience methods, and a database check constraint with a matching ActiveRecord validation.

How to use

Firstly, add the gem to your Gemfile and bundle install:

gem "activerecord-exclusive-arc"

The feature set of this gem is offered via a Rails generator command:

bin/rails g exclusive_arc <Model> <arc> <belongs_to1> <belongs_to2> ...

This assumes you already have a <Model>. The <arc> is the name of the polymorphic association you want to establish that may either be a <belongs_to1>, <belongs_to2>, etc. Say we ran:

bin/rails g exclusive_arc Comment commentable post comment

This will inject code into your Comment Model:

class Comment < ApplicationRecord
  include ExclusiveArc::Model
  has_exclusive_arc :commentable, [:post, :comment]
end

At a high-level, this essentially transpiles to the following:

class Comment < ApplicationRecord
  belongs_to :post, optional: true
  belongs_to :comment, optional: true
  validate :post_or_comment_present?

  def commentable
    @commentable ||= (post || comment)
  end

  def commentable=(post_or_comment)
    @commentable = post_or_comment
  end
end

It's a bit more involved than that, but it demonstrates the essense of the API as an ActiveRecord user.

If you need to customize a specific belongs_to relationship, you can do so by declaring it before has_exclusive_arc:

class Comment < ApplicationRecord
  include ExclusiveArc::Model
  belongs_to :post, -> { where(comments_enabled: true) }, optional: true
  has_exclusive_arc :commentable, [:post, :comment]
end

Continuing with our example, the generator command would also produce a migration that looks like this:

class CommentCommentableExclusiveArcPostComment < ActiveRecord::Migration[7.0]
  def change
    add_reference :comments, :post, foreign_key: true, index: {where: "post_id IS NOT NULL"}
    add_reference :comments, :comment, foreign_key: true, index: {where: "comment_id IS NOT NULL"}
    add_check_constraint(
      :comments,
      "(CASE WHEN post_id IS NULL THEN 0 ELSE 1 END + CASE WHEN comment_id IS NULL THEN 0 ELSE 1 END) = 1",
      name: "commentable"
    )
  end
end

The check constraint ensures ActiveRecord validations can’t be bypassed to break the fabeled rule - "There Can Only Be One️". Traditional foreign key constraints can be used and the partial indexes provide improved lookup performance for each individual polymorphic assoication.

Exclusive Arc Options

Some options are available to the generator command. You can see them with:

$ bin/rails g exclusive_arc --help
Usage:
  rails generate exclusive_arc NAME [arc belongs_to1 belongs_to2 ...] [options]

Options:
  [--optional], [--no-optional]                                          # Exclusive arc is optional
  [--skip-foreign-key-constraints], [--no-skip-foreign-key-constraints]  # Skip foreign key constraints
  [--skip-foreign-key-indexes], [--no-skip-foreign-key-indexes]          # Skip foreign key partial indexes
  [--skip-check-constraint], [--no-skip-check-constraint]                # Skip check constraint

Adds an Exclusive Arc to an ActiveRecord model and generates the migration for it

Notably, if you want to make an Exclusive Arc optional, you can use the --optional flag. This will adjust the definition in your ActiveRecord model and loosen both the validation and database check constraint so that there can be 0 or 1 foreign keys set for the polymorphic reference.

Updating an existing exclusive arc

If you need to add an additional polymorphic option to an existing exclusive arc, you can simply run the generator command again with the additional target. Existing references will be skipped and the check constraint will be removed and re-added in a reversible manner.

bin/rails g exclusive_arc Comment commentable post comment page
class CommentCommentableExclusiveArcPostCommentPage < ActiveRecord::Migration[7.0]
  def change
    add_reference :comments, :page, foreign_key: true, index: {where: "page_id IS NOT NULL"}
    remove_check_constraint(
      :comments,
      "(CASE WHEN post_id IS NULL THEN 0 ELSE 1 END + CASE WHEN comment_id IS NULL THEN 0 ELSE 1 END) = 1",
      name: "commentable"
    )
    add_check_constraint(
      :comments,
      "(CASE WHEN post_id IS NULL THEN 0 ELSE 1 END + CASE WHEN comment_id IS NULL THEN 0 ELSE 1 END + CASE WHEN page_id IS NULL THEN 0 ELSE 1 END) = 1",
      name: "commentable"
    )
  end
end

The registration in the model will be updated as well.

class Comment < ApplicationRecord
  include ExclusiveArc::Model
  has_exclusive_arc :commentable, [:post, :comment, :page]
end

Compatibility

Currently activerecord-exclusive-arc is tested against a matrix of:

  • Ruby 2.7 and 3.3
  • Rails 6.1, 7.0, 7.1
  • postgresql, sqlite3, and mysql2 database adapters

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/waymondo/activerecord-exclusive-arc.

License

The gem is available as open source under the terms of the MIT License.

FAQs

Package last updated on 20 Jan 2024

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc