ActiveRecord - possible to delegate nil values to parent model that behaves like a template?

I’m working on an interesting problem. I have a Products model containing a description, price, and sku and so forth. It’s meant to be used in conjunction with another Package model, which itself will have description, price, and sku. I would like to have my Package model delegate these fields to it’s parent Package if these fields are nil/blank/zero, otherwise I want it to assume normal behavior. Essentially I want my Product definition to behave as a template for these fields when they’re not set. I should note that Product also defines some logic and houses data that is shared amongst it’s Package children, so unfortunately I can’t completely eliminate it from the equation.

I tried using delegate, a la:

class Package < ActiveRecord::Base
  belongs_to :product

  delegate :description, :sku, :price, to: :product
end

Which seems to delegate properly, however this makes it impossible to retrieved assigned values once delegated. I also tried define_method only to meet similar results:

class Package < ActiveRecord::Base
  belongs_to :product

  def initialize(*attr)
    super(*attr)
    [:description, :sku, :price].each do |attr|
      if send(attr).nil?
        self.class.instance_eval do
          define_method(attr) do
            product.try(attr)
          end
        end
      end
    end
  end
end

It occurs to me that I can simply write a callback method to set the fields if they’re blank/nil/zero, but I was curious if somebody could think of a more elegant approach or better design alltogether.

A workaround for fixing my first attempt would be to use a prefix for the delegate calls. However, that would require me to do something this anytime I want to make use of the delegate fields:

package.description || package.product_description

Simply calling anywhere I needed it would be more preferable and would remove the need for the extra query.

package.description

What about just overriding the getters?

class Package < ActiveRecord::Base
  belongs_to :product

  def description
    self[:description] || product.description
  end
end

I suppose you could also meta-program this if you wanted to:

class Package < ActiveRecord::Base
  extend ProductDelegatable
  delegates_to_product :description, :sku, :price
end

module ProductDelegatable
  extend ActiveSupport::Concern

  def delegates_to_product(*methods)
    method.each do |method|
      define_method method do
        self[method] || product.send(method)
      end
    end
  end
end

Or you could even make it more generic

module NullDelegatable
  extend ActiveSupport::Concern
  
  def delegate_if_nil(*methods, to:)
    define_method method do
      self[method].nil? ? to.send(method) : self[method]
    end
  end
end

class Package < ActiveRecord::Base
  extend NullDelegatable

  delegate_if_nil :description, :sku, to: :product
end

I’d err on the side of simplicity though

Thanks Jurre, I didn’t know about the [] method in ActiveRecord. I tried overriding the getters earlier, but ran into recursion problems and wrongfully assumed it wouldn’t be possible.