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
How to Test for a Redirect with Rspec and Capybara
How to Generate PDF from Markdown Using Pure Ruby
Connecting Using Https to a Server with a Certificate Signed by a Ca I Created
How to Use Truly Local Variables in Ruby Proc/Lambda
Count the Length (Number of Lines) of a CSV File
Ruby on Rails - Devise Users/Sign_Out Not Working
Other Ruby Map Shorthand Notation
Ruby on Rails: If Current Page? Is Homepage, Don't Show Form
Get the Name of a Local Variable
Netbeans 6.9.1 + Rails 3 + Ruby 1.9.2P0 Debugging
Rails Force Models to Eager Load
Ruby If .. Elsif .. Else on a Single Line
How to Parse Consecutive Tags with Nokogiri
Rails Check_Box_Tag How to Pass Value When Checked Ajaxily
Gem::Ext::Builderror: Error: Failed to Build Gem Native Extension Bcrypt-Ruby
Remove "Www", "Http://" from String
Using Ruby, Reading a File, Containing Name/Value Pairs into a Hash