Reputation: 164809
I have a concern with an included
block. In this example it sets a class instance variable.
require 'active_support/concern'
module Mod1
extend ActiveSupport::Concern
included do
p "#{self}: included Mod1"
@foo = "foo is set"
end
class_methods do
def foo
@foo
end
end
end
If I include it I get the method foo
and the included variable is set.
class Parent
include Mod1
end
p Parent.foo # "Parent: included Mod1" "foo is set"
But if I subclass from Parent, I inherit the method foo
, but the included block is not run.
class Subclass < Parent
end
p Subclass.foo # nil
Even if I include the module, the included block is not run. I expect because Subclass.include?(Mod1)
is true.
class Subclass < Parent
include Mod1
end
p Subclass.foo # nil
p Subclass.include?(Mod1) # true
How do I write a concern such that its included
block runs even on subclasses?
Upvotes: 3
Views: 1213
Reputation: 102036
ActiveSupport::Concern#included
is really just syntactic sugar for Module#included
so it only ever fires when the module is included in the parent class. The hook you are looking for is Class#inherited which is invoked whenever a subclass of the current class is created.
require 'active_support/concern'
module Mod1
extend ActiveSupport::Concern
included do
set_foo!
end
class_methods do
def foo
@foo
end
def set_foo!
p "Setting foo"
@foo = "foo is set"
end
def inherited(child_class)
puts "inherited!"
child_class.set_foo!
end
end
end
class Parent
include Mod1
end
class Subclass < Parent; end
require 'minitest/autorun'
class Mod1Test < Minitest::Test
def test_class_ivar_set_in_subclass
assert_equal("foo is set", Subclass.foo) # passes
end
end
"Setting foo"
inherited!
"Setting foo"
Run options: --seed 35149
# Running:
.
Finished in 0.000999s, 1000.7185 runs/s, 1000.7185 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Upvotes: 4
Reputation: 179
It's funny here. Actually the SubClass
has the method foo
, but the return value @foo
is not set. The SubClass
inherits methods from Parent
, but as you are using @foo
which is a variable so it cannot be shared between 2 object Parent
and SubClass
.
You can check by getting the source location of the method foo
in SubClass
SubClass.method(:foo).source_location
And if you move the logic to the method foo
, you can see that SubClass will return the expected value.
For example:
require 'active_support/concern'
module Mod1
extend ActiveSupport::Concern
included do
end
class_methods do
def foo
p "#{self}: included Mod1"
"foo is set"
end
end
end
Then calling foo
from SubClass
will return foo is set
The included do
in active_support/concern
is usually used to declare Rails features such as relationships or validations or scopes.
https://api.rubyonrails.org/classes/ActiveSupport/Concern.html
Upvotes: 0