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
Prawn PDF: I Need to Generate Nested Tables
Generic Way to Replace an Object in It's Own Method
How Would I Go About Converting This Time String to Epoch Time in Ruby
Authorizing Namespaced and Nested Controllers Using Cancan
Bundler Could Not Find Compatible Versions for Gem "Bundler": in Gemfile:
Encrypting/Decrypting 3Des in Ruby
Dynamically Create a Class Inherited from Activerecord
Using Watir to Check for Bad Links
Cattr_Accessor Outside of Rails
What Does It Mean to Yield Within a Block
Error:'Incompatible Library Version' SQLite3-1.3.11 in Rails
Rails Not Rolling Back Transaction After Failed Save()
Rails Activeadmin Drop Down Menu on New and Edit Forms
Ruby + Tk Command Binding - Scope Issue
Bundle Install' Fails (Because of Git Protocol)