Differencebetween 'Include' and 'Prepend' in Ruby

What is the difference between include and extend in Ruby?

What you have said is correct. However, there is more to it than that.

If you have a class Klazz and module Mod, including Mod in Klazz gives instances of Klazz access to Mod's methods. Or you can extend Klazz with Mod giving the class Klazz access to Mod's methods. But you can also extend an arbitrary object with o.extend Mod. In this case the individual object gets Mod's methods even though all other objects with the same class as o do not.

Ruby module prepend vs derivation

No, it is not. B can only inherit from one class, but Mod can be prepended to many classes. If you were to call super inside B#here, it would always refer to A#here, but inside of Mod#here, it will refer to the #here instance method of whatever class Mod was prepended to:

module Mod   
def here
super + ' Mod'
end
end

class A
prepend Mod
def here
'A'
end
end

class B
prepend Mod
def here
'B'
end
end

A.new.here
# => 'A Mod'

B.new.here
# => 'B Mod'

and

class A
def here
'A'
end
end

class B
def here
'B'
end
end

class C < A
def here
super + ' C'
end
end

C.new.here
# => 'A C'

class C < B
def here
super + ' C'
end
end
# TypeError: superclass mismatch for class C

Why do people use `Module.send(:prepend, …)`?

Module#prepend was added to Ruby version 2.0.0.

It was originally added as a private method, with the intended use case being in the following format:

module Foo
# ...
end

class Bar
prepend Foo

# ... The rest of the class definition ...
end

However, it soon became apparent that in many cases people wanted to prepend a module to a class without defining any other aspects of the class (in that section of code). Hence, the following pattern became common:

Bar.send(:prepend, Foo)

In Ruby version 2.1.0, this issue was addressed by making Module#prepend a public method - so you can now simply write this as:

Bar.prepend(Foo)

However, note that if you are writing a library that is required to support Ruby 2.0.0 (even though official support ended on 24th Feb 2016), then you must unfortunately stick to the old .send(:prepend, ...) approach.

Module#include (which has been in the Ruby language since its inception) was also a private method in version <= 2.0.0, and was made public in 2.1.0.

Is there a reason why Ruby's prepend behaves differently when used with modules versus classes?

https://bugs.ruby-lang.org/issues/9573 shows a similar behavior concerning classes and modules. The bug report was posted on 2014 and was only closed 2020. Based on that report, I've manually confirmed that

  • This behavior still appears in ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux], but
  • This behavior no longer appears in ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]

Why does order of `Object.include` and `Fixnum.prepend` matter?

See the documentation of Module#prepend_features:

When this module is prepended in another, Ruby calls prepend_features in this module, passing it the receiving module in mod. Ruby’s default implementation is to overlay the constants, methods, and module variables of this module to mod if this module has not already been added to mod or one of its ancestors. See also Module#prepend.

So, prepend only does anything if its argument hasn't already been added to the receiver or one of its ancestors. Since Object is an ancestor of Fixnum, Fixnum.prepend(MyMod) when called after Object.include(MyMod) doesn't do anything.

Elegant way to prepend to a module which is already included?

TL;DR – you can't in general, but Base1.include Patch may be good enough.


For your example code, the ancestors of Base1 and Base2 are: (aligned for clarity)

Base1.ancestors #=> [Base1,        Feature, Object, Kernel, BasicObject]
Base2.ancestors #=> [Base2, Patch, Feature, Object, Kernel, BasicObject]

Base2 has an additional ancestor Patch before Feature – the result of Feature.prepend Patch.

Ruby doesn't allow us to freely modify a module's ancestors chain, so we can't just prepend Patch to Feature retroactively.

But fortunately, Patch is the first module after the Base class, so we can resort to include to append Patch to Base1 instead:

Base1.include Patch
Base1.ancestors #=> [Base1, Patch, Feature, Object, Kernel, BasicObject]

Obviously, this only works for very specific cases and not in general.

Here's a counter example:

module Feature
def action ; 'Feature' ; end
end

module Foo
def action ; "#{super} overridden" ; end
end

module Patch
def action ; 'Patch' ; end
end

class Base1
include Feature
include Foo
end

Feature.prepend(Patch)

class Base2
include Feature
include Foo
end

Base1.new.action #=> "Feature overridden"
Base2.new.action #=> "Patch overridden"

Base1.include Patch

Base1.new.action #=> "Patch"

Looking at the ancestors reveals the problem:

Base1.ancestors #=> [Base1, Foo,        Feature, Object, Kernel, BasicObject]
Base2.ancestors #=> [Base2, Foo, Patch, Feature, Object, Kernel, BasicObject]
Base1.include Patch
Base1.ancestors #=> [Base1, Patch, Foo, Feature, Object, Kernel, BasicObject]

Patch and Foo are out of order.

How to correctly prepend module in a class?

You don't need to separate out the instance methods into a seperate module. You can just place them in AccountControllerPatch and prepend whatever class you are monkeypatching with it.

module RedmineKapDesign
module Patches
module AccountControllerPatch
def successful_authentication(user)
logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}"
# Valid user
self.logged_user = user
logger.info "Setting.autologin? = #{Setting.autologin?}, params[:autologin] = #{params[:autologin]}"
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
set_autologin_cookie(user)
end
call_hook(:controller_account_success_authentication_after, {:user => user})
load_favourite_page_or_index
#redirect_back_or_default my_page_path
end
def load_favourite_page_or_index
user = User.current
favourite_page_field = CustomField.where(name: ["Favourite page", "favourite page", "favorite page", "Favourite page", "Любимая страница", "любимая страница", "Избранная страница", "избранная страница"]).first
page_url = user.custom_values.where(custom_field_id: favourite_page_field.id).first.value
if page_url.empty?
redirect_back_or_default my_page_path
else
redirect_to(page_url)
end
end
def self.hello
puts "Hello"
end
end
end
end

Using a seperate module is only really needed for class methods when using the module mixin pattern. In framework code using a seperate InstanceMethods module can be used for organizational purposes. But in a simple monkeypatch it just makes more of a mess.

# config/initializers/my_monkey_patch.rb
# You should explicity require classes in initializers
require Rails.root.join('path', 'to', 'monkeypatch')
require Rails.root.join('path', 'to', 'target')

::AccountController.prepend RedmineKapDesign::Patches::AccountControllerPatch


Related Topics



Leave a reply



Submit