No commit activity in last 3 years
No release in over 3 years
Makes it easy to have specific concern modules included into all of your models after the model has connected to the database
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.17
>= 0
>= 0
~> 10.0
~> 3.0

Runtime

< 5.3, >= 4.2
< 5.3, >= 4.2
 Project Readme

ActiveRecordInclude

Have you ever wanted to make a module get included into all of your models so that you don't have to remember to include in each individual model?

Sure, if all you need is for some methods to be available in all your models, then you can just include the module in your ApplicationRecord and all subclasses of ApplicationRecord will have access to those methods by inheritance.

But if it's a ActiveSupport::Concern with a included blocks or similar that needs to do something specific for each individual model subclass, then that approach doesn't work.

You can do something like this to automatically include MyConcern into all subclasses of ApplicationRecord:

module MyExtensions
  module ClassMethods

  private

    # These extensions aren't on (or can't be on) Base itself but on every model (every subclass of
    # Base)
    def inherited(subclass)
      super
      subclass.class_eval do
        include MyConcern
      end
    end
  end
end

class ApplicationRecord < ActiveRecord::Base
  include MyExtensions
end

Or you could use this gem and just write:

class ApplicationRecord < ActiveRecord::Base
  include_when_inherited MyConcern
end

However, even that doesn't always work. If we try to connect to a table from MyConcern that way, we'll end up getting an error like this:

  PG::UndefinedTable: ERROR:  relation "application_records" does not exist

Model extensions/concerns that require an database connection (such as those that get a list of columns for the model) can't be automatically included from inherited() because that's too early (inherited gets called the moment it hits the class Model < ApplicationRecord line), before the body of the class definition has been evaluated, and thus before the model (or its ancestors) has had a chance to configure itself with self.abstract_class= or self.table_name= yet, for example.

Do this to have it include the given module in all (non-abstract) model subclasses as soon as that model has connected to its database:

  class ApplicationRecord < ActiveRecord::Base
    self.abstract_class = true
    include_when_connected NormalizeTextColumns
  end

By default, it includes the module into all subclasses. If you don't want it to include the module into subclasses, pass recursive: false.

  class SomeModel < ActiveRecord::Base
    include_when_connected MyConcern, recursive: false
  end

Limitations

ActiveSupport::Concern modules are only included if the module is not already an ancestor

This means that the order you include concerns matters.

If you have AA < A < ApplicationRecord and you include the concern into A and then AA, when you include it into AA the included block will not be run for AA, because it has already been run for an ancestor of AA, namely A.

If on the other hand you somehow managed to include the concern into AA before it got included into A, you can get it include the included block of the concern for both AA and A. But this is easier said then done, because A needs to be defined first before you can subclass it, and usually they are in different files so it would get included into A as soon as the file for A was loaded. You would have to be very, very intentional about it, and reopen A, like this:

class A < ApplicationRecord
end
class AA < A
  include MyConcern
end
class A
  include MyConcern
end

This is a limitation of ActiveSupport::Concern, not of this gem. (include_when_inherited calls include to include the concern, but it has no effect.)

Often this limitation isn't a problem, because all of your models are direct descendents of ApplicationRecord and you don't need to also include the concern into ApplicationRecord, for example.

But if you really need a module's features to be appended (its included code) into both a model and its superclass, one way to work around this limitation is to define your own included callback, like we did in spec/support/models/concerns/creature_self_identification.rb:

  def self.included(base)
    base.class_eval do
      puts "#{self}: included CreatureSelfIdentification"
      extend ClassMethods
      define_identity_methods
    end
  end

Then you can call include_recursively from the base class of the class hierarchy you want to include the module in, and it will append its features to all of its descendents:

class Creature < ApplicationRecord
  include CreatureConcern
  include_recursively CreatureSelfIdentification
end

Installation

Add this line to your application's Gemfile:

gem 'active_record_include'

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/TylerRick/active_record_include.