rubyinheritanceclass-attributesactivesupport-concern

Ruby / concerns / inheritance


Using Ruby 3.1.1.

Trying to implement similar to: https://github.com/mongodb/mongoid/blob/master/lib/mongoid/fields.rb :: class_attribute :fields

When I include a model concern on a parent model the class_attributes seem to get merged on child models. I wanted to maintain a seperate class_attribute hash on each child class, like the mongoid Field concern does.

This only seems to occur when inheriting.

module Dashboardable
  extend ActiveSupport::Concern

  included do
    class_attribute :dashboard_fields

    self.dashboard_fields = {}
  end

  class_methods do
    def dashboard_field(name, options = {})
      dashboard_fields[name] = options.merge(klass: self)
    end
  end
end

class Person
  include Mongoid::Document
  include Dashboardable

  dashboard_field :first_name
end

class Foo < Person
  dashboard_field :foot
  field :foot, type: String
end

class Bar < Person
  dashboard_field :bart
  field :bart, type: String
end

Foo.dashboard_fields: [:first_name, :foot]

Bar.dashboard_fields: [:first_name, :foot, :bart]

Foo.fields: ["_id", "_type", "foot"]

Bar.fields: ["_id", "_type", "bart"]

Solution

  • I took a lot of edits and some digging to figure out the question and whats going on. Sorry.

    Whats happening is that with class_attribute is that all the classes are actually sharing the same object in thier instance attributes. This is an intended behavior.

    You can actually get it to work with class_attribute if you make sure that each subclass gets a duplicate of the "parent hash":

    module Dashboardable
      extend ActiveSupport::Concern
    
      included do
        class_attribute :dashboard_fields, default: {}
      end
    
      class_methods do
        def inherited(subclass)
          subclass.class_eval { self.dashboard_fields = superclass.dashboard_fields.dup } 
        end
    
        def dashboard_field(name, options = {})
          dashboard_fields[name] = options.merge(klass: self)
        end
      end
    end