ActiveRecord::AssociatedObject

repository·main·Indexed 18 days ago

https://github.com/kaspth/active_record-associated_object

A gem for breaking down large Active Record models by introducing Associated Objects—namespaced, stateful collaborator POROs. It provides a `has_object` macro for associations, supports callback forwarding, and integrates with Rails' ActiveModel conventions, GlobalID for Active Job, and Kredis. It includes a Rails generator to scaffold associated object classes and automatically inject associations into parent models.

Tokens
3.3K
Snippets
16
Records
20
Agent score
64%

What's inside active_record-associated_object

  1. Integrate with Active Job via GlobalID

    main

    Associated Objects include GlobalID::Identification, allowing you to pass the PORO directly to Active Job. The job will automatically serialize/deserialize the object by finding the parent record and its associated object.

    class Post::Publisher < ActiveRecord::AssociatedObject
      class PublishJob < ApplicationJob
        def perform(publisher) = publisher.publish
      end
    
      def publish_later
        PublishJob.perform_later(self)
      end
    end
  2. What are Associated Objects?

    main
    Associated Objects are Plain Old Ruby Objects (POROs) that act as collaborator objects for Active Record models. Instead of bloating a model with logic or using Service Objects that lack state and proper organization, you create an ActiveRecord::AssociatedObject namespaced under your model. This helps organize code into app/models/<model_name>/ and provides a clear domain concept for collaborators.
  3. Forward callbacks onto the associated object

    main

    The gem allows you to forward callbacks to an associated object. When configuring the association, you can pass true to forward a callback with the same name. For example, passing after_touch: true is equivalent to explicitly defining the callback forwarding.

    Note: The provided documentation snippet ends abruptly, but indicates that true acts as a shorthand for forwarding the same name.

  4. Define and use an Associated Object

    main

    To create an associated object, define a class that inherits from ActiveRecord::AssociatedObject within the namespace of your model. Then, use the has_object macro in your Active Record model to declare the association.

    Note: The associated object's initializer must accept a single argument (the model instance).

    # app/models/post/publisher.rb
    class Post::Publisher < ActiveRecord::AssociatedObject
    end
    
    # app/models/post.rb
    class Post < ApplicationRecord
      has_object :publisher
    end
  5. Install active_record-associated_object

    main

    To add this gem to your Rails application using Bundler, run the following command in your terminal:

    $ bundle add active_record-associated_object

    If you are not using Bundler to manage your dependencies, you can install the gem directly using the gem command:

    $ gem install active_record-associated_object
  6. Use Associated Objects in Controllers and Views

    main

    Associated Objects integrate with Rails' ActiveModel conventions. They support form_with, url_for, and partial rendering.

    Controller usage: Use find on the associated object class. Behind the scenes, this finds the parent record and returns its associated object.

    View usage: You can pass the object directly to form_with or render it as a partial. Partial paths follow the namespacing convention (e.g., Post::Publisher renders app/views/post/publishers/_publisher.html.erb).

    # Controller
    @publisher = Post::Publisher.find(params[:id])
    
    # View
    <%= form_with model: @publisher do |form| %>
      <%= render @publisher %>
    <% end %>
  7. Connect an associated object to a model

    main

    The generator provides a mechanism to automatically connect an associated object to its parent record by injecting the has_object macro into the parent model file.

    This process:

    1. Locates the parent record file (e.g., app/models/post.rb).
    2. Verifies the existence of the record class.
    3. Injects has_object :associated_object_name into the class body with optimized indentation.
    # Example of what the generator injects into the parent model:
    class Post < ApplicationRecord
      has_object :publisher
    end
  8. Use the AssociatedGenerator to create associated object files

    main

    The AssociatedGenerator is a Rails generator designed to automate the creation of associated object files. It generates both the model file and its corresponding test file in the standard Rails directory structure.

    When running the generator, it creates:

    • app/models/[name].rb
    • test/models/[name]_test.rb
    # Note: This is a Rails generator. Usage typically follows the standard rails generate pattern:
    # rails generate associated [name]
  9. Configure `has_object` options

    main

    The has_object macro supports several configurations:

    • Multiple objects: has_object :seats, :entitlements
    • Plural names: has_object :seats will look up Account::Seats.
    • Callback forwarding: You can forward Active Record callbacks to the associated object. Passing true forwards the same name (e.g., after_touch: true forwards after_touch).
    • Specific callback methods: You can map a callback to a specific method on the associated object (e.g., after_create_commit: :publish).
    class Post < ActiveRecord::Base
      # Forwards after_touch and maps after_create_commit to :publish
      has_object :publisher, after_touch: true, after_create_commit: :publish
    end
  10. Use `performs` macro with `active_job-performs`

    main

    If you have the active_job-performs gem installed, you can use the performs macro to remove Active Job boilerplate. This allows you to define method-specific jobs directly within the Associated Object.

    class Post::Publisher < ActiveRecord::AssociatedObject
      performs queue_as: :important
      performs :publish
      performs :retract
    
      def publish
      end
    
      def retract(reason:)
      end
    end
  11. Extend Active Record using `extension`

    main

    Instead of using ActiveSupport::Concern, you can use the extension block within an Associated Object to inject methods, class methods, or associations directly into the parent Active Record model. This keeps the integration logic co-located with the collaborator object.

    class Post::Publisher < ActiveRecord::AssociatedObject
      extension do
        # This code runs in the context of Post
        has_many :contracts, dependent: :destroy
    
        def self.with_contracts = includes(:contracts)
    
        after_create_commit :publish_later
      end
    end