I'm developing a plugin to the Rails (6.1.7) project Foreman (v3.5.1; based on the Foreman Plugin Template) and face the issue that one of my modules (DnsInterfaceExtensions
) ought to be prepended to a Foreman module. Problem is, even after prepend
, DnsInterfaceExtensions
does not show up as an ancestor to Foreman's DnsInterface
(in the Rails console):
irb(main):001:0> DnsInterface.ancestors
=> [DnsInterface]
irb(main):002:0> DnsInterface.prepend ForemanCnames::Concerns::DnsInterfaceExtensions
=> DnsInterface
irb(main):003:0> DnsInterface.ancestors
=> [DnsInterface]
irb(main):005:0> DnsInterface.prepend Module.new
=> DnsInterface
irb(main):006:0> DnsInterface.ancestors
=> [#<Module:0x0000558ef346d9d0>, DnsInterface]
I've tried defining DnsInterfaceExtensions#prepended
, which evidently is executed, but the new class methods are not added to DnsInterface
, nor is the module's constant :RECORD_TYPE
overridden. Without any errors from Ruby, I'm clueless what the reason for this outcome is. Any ideas?
Thank you for your time,
Xavier.
EDIT-1
I just found that the same phenomenon occurs with any hollow module that extends ActiveSupport::Concern
?
irb(main):001:0> (DnsInterface.prepend Module.new { extend ActiveSupport::Concern }).ancestors
=> [DnsInterface]
EDIT-2
However, in this SO answer, @Marian13 says that Rails 6.1.7 should support prepend. It doesn't work however, when both modules extend ActiveSupport::Concern
.
irb(main):001:1* module M
irb(main):002:1* extend ActiveSupport::Concern
irb(main):003:0> end
=> M
irb(main):004:1* module N
irb(main):005:1* extend ActiveSupport::Concern
irb(main):006:0> end
=> N
irb(main):007:0> (M.prepend N).ancestors
=> [M]
Yet, without extending ActiveSupport::Concern
in my module, I cannot use either prepended
or class_methods
blocks.
EDIT-3
I guess this whole affair goes wrong because Concern's class_methods
defines the Module :ClassMethods
. So by extending ActiveSupport::Concern
in both modules, each have their own :ClassMethods
, somehow preventing establishing ancestry?
Seems like my hunch regarding both modules extending ActiveSupport::Concern
was correct. And by mimicking the syntactic sugar of Concern, prepending with a nested module not called ClassMethods
, things turn out better.
module ForemanCnames::DnsInterfaceExtensions
def self.prepended(base)
class << base
prepend CnameClassMethods
end
end
module CnameClassMethods
def dns_cname_record_attrs
end
end
end
irb(main):001:0> DnsInterface.ancestors
=> [ForemanCnames::DnsInterfaceExtensions, DnsInterface]
irb(main):002:0> DnsInterface.methods.filter { |m| m.to_s =~ /cname/ }
=> [:dns_cname_record_attrs]
Though I failed to overwrite the constant.
irb(main):003:0> DnsInterface::RECORD_TYPES
=> [:a, :aaaa, :ptr4, :ptr6]
... but that is a different question.
EDIT-1
Guess I have to retract this answer, since it doesn't achieve what I'm looking for...
irb(main):001:0> DnsInterface.method(:dns_feasible?).source_location
=> ["/usr/local/share/gems/gems/foreman_cnames-0.0.1/app/models/foreman_cnames/dns_interface_extensions.rb", 15]
irb(main):002:0> DnsInterface.instance_method(:dns_feasible?).source_location
=> ["/usr/share/foreman/app/models/concerns/dns_interface.rb", 22]
While the methods of DnsInterfaceExtension
are added, they don't overlay what DnsInterface
already had.
EDIT-2
With a slight modification, overriding the methods does work after all:
module ForemanCnames::DnsInterfaceExtensions
def self.prepended(base)
base.prepend CnameClassMethods
end
(...)
end
And the effect on DnsInterface
in the rails console...
irb(main):001:0> DnsInterface.instance_method(:dns_feasible?).source_location
=> ["/usr/local/share/gems/gems/foreman_cnames-0.0.1/app/models/foreman_cnames/dns_interface_extensions.rb", 13]
irb(main):002:0> DnsInterface.ancestors
=> [ForemanCnames::DnsInterfaceExtensions::CnameClassMethods, ForemanCnames::DnsInterfaceExtensions, DnsInterface]
Not as clean as with ActiveSupport::Concern
and I have to admit that I don't fully understand the difference.
EDIT-3
Failing forward one step at a time. :-)
Yet, without extending ActiveSupport::Concern in my module, I cannot use either prepended or class_methods blocks.
That is true, but I don't have to use class_methods
, since I want instance methods (which I had not realized before). Those don't need special treatment in modules when they are included. That leaves prepended
and for that Ruby has the original def self.prepended
callback.
In fewer words, I don't need the nested module CnameClassMethods
.