Overriding Methods in an Activesupport::Concern Module Which Are Defined by a Class Method in the Same Module

Overriding methods in an ActiveSupport::Concern module which are defined by a class method in the same module

I think you're looking for define_method.

module MyModel
module Acceptance

extend ActiveSupport::Concern

included do
enum status: [:declined, :accepted]

define_method :declined! do
self.status = :declined
# some extra logic
self.save!
end

define_method :accepted! do
self.status = :accepted
# some extra logic
self.save!
end

end
end
end

Override ActiveSupport::Concern in model

The problem is that you need to explicitly state that you want bar to be a class method...

module Foo
extend ActiveSupport::Concern

class_methods do
def bar
puts "bar"
end
end
end

Now, you can override it...

class FooFoo < ApplicationRecord
def self.bar
puts "foo bar"
end
end

Rails concern method override another concern method doesn't work like normal modules

The reason for this is that included method block is actually evaluated in the context of the class. That mean, that method defined in it is defined on a class when module is included, and as such takes precedence over included modules.

module Child1
extend ActiveSupport::Concern
included do
def foo
end
end
end

module Child2
def bar
end
end

class A
include Child1
include Child2
end

A.new.method(:foo).owner #=> A
A.new.method(:bar).owner #=> Child2

Method lookup

In ruby, every time you want to call a method, ruby has to find it first (not knowing whether it is method or a variable). It is done with so called method lookup. When no receiver is specified (pure call like puts) it firstly searches the current scope for any variables. When not found it searches for that method on current self. When receiver is specified (foo.bar) it naturally search for the method on given receiver.

Now the lookup - in ruby all the methods always belongs to some module/class. The first in the order is receiver's eigenclass, if it exists. If not, regular receiver's class is first.

If the method is not found on the class, it then searches all the included modules in given class in the reversed order. If nothing is found there, superclass of given class is next. The whole process goes recursively until something is found. When lookup reaches BasicObject and fails to find the method it quit and triggers search for method_missing, with default implementation defined on BasicObject.

Important thing to notice is that methods which belongs to the class always take precedence over module methods:

module M
def foo
:m_foo
end
end

class MyClass
def foo
:class_foo
end

include M
end

MyClass.new.foo #=> :class_foo

About super

Search for a super method is very similar - it is simply trying to find a method with the same name which is further in the method lookup:

module M1
def foo
"M1-" + super
end
end

module M2
def foo
'M2-' + super
end
end

module M3
def foo
'M3-' + super
end
end

class Object
def foo
'Object'
end
end

class A
include M2
include M3
end

class B < A
def foo
'B-' + super
end

include M1
end

B.new.foo #=> 'B-M1-M3-M2-Object'

ActiveSupport::Concern#included

included is a very simple method that takes a block and creates a self.included method on the current module. The block is executed using instance_eval, which means that any code in there is actually executed in the context of the class given module is being included in. Hence, when you define a method in it, this method will be owned by the class including the module, not by the module itself.

Every module can hold only one method with given name, once you tries to define second one with the same name, the previous definition is completely erased and there is no way it can be find using ruby method lookup. Since in your example you included two modules with same method definition in included block, the second definition completely overrides the first one and there is no other definition higher in method lookup, so super is bound to fail.

Rails Concern class_macro with params from inherited class

As described by @mu_is_too_short you can just create a class method which adds the validations to the class when called:

module SlugHelpers
extend ActiveSupport::Concern
class_methods do
def has_slug(name = :slug, scope: :default_value)
validates_uniqueness_of name,
case_sensitive: false,
message: "Slug must be unique",
scope: scope
validates_presence_of name
end
end
end

This is the generally preferred way in Ruby to make the behavior provided by modules configurable.

class Foo
include SlugHelpers
has_slug(scope: [:foo, :bar])
end
class Bar
include SlugHelpers
has_slug(scope: [:baz, :bar])
end

While you could actually call a method on the class when the module is included it would produce very strange results if you want to override the behavior in subclasses since it still would be evaluated just when the module was included.



Related Topics



Leave a reply



Submit