Ruby's philosophy is based on a basic element that makes programmers happy. Ruby is very focused on the happiness of programmers and offers a number of different ways to implement it. Its metaprogramming capabilities enable programmers to write code that is generated dynamically at run time. Its threading capabilities allow programmers to have an elegant way of writing multithreaded code. Its hook method allows programmers to extend the behavior of a program while it is running.
These features, and some other cool language aspects, make Ruby one of the first choices to write code. This article explores some of the important hook methods in Ruby. We will discuss the hook methods in different ways, such as what they are, what they are used for, and how we use them to solve different problems. We also have a look at some popular Ruby frameworks/gem how packages/libraries use them to provide very cool features.
Here we go.
What is a hook method?
The hook method provides a way to extend the behavior of a program while the program is running. Suppose you have such a feature that you can receive a notification whenever a subclass inherits some particular parent class, or it is more graceful to handle a method that is not callable on an object rather than having the compiler throw an exception. These situations are the use of hook methods, but their usage is not limited to this. Different frames/libraries use different hook methods to implement their functions.
In this article we will discuss the following hook methods:
1.included
2.extended
3.prepended
4.inherited
5.method_missing
Included
Ruby gives us a way to write modular code for other modules/classes using modules (modules) (called Mixed classes (mixins) in other languages). The concept of a module is simple, it is a separate block of code that can be used elsewhere.
For example, if we want to write some code that calls a particular method at any time, it returns a static string. Let's call this method name. You may also want to use the same code elsewhere. This is best done by creating a new module. Let's create a:
Module person
def name
Puts "My name are person"
End
End
This is a very simple module, and only one name method is used to return a static string. Use this module in our program:
Class User
Include person
End
Ruby offers a number of different ways to use modules. Include is one of them. What the include does is that the method defined within the module is available on the instance variable of one class. In our case, it is a way to change the method defined in the person module into a User class instance object. This is equivalent to writing the name method in the User class, but the benefits defined in module are reusable. To invoke the name method we need to create a User instance object and then invoke the name method on the object. For example:
User.new.name
=> My name is person
Let's take a look at the hook method based on include. Included is a hook method provided by Ruby that is invoked when you include a module in some module or class. To update the person module:
Module person
def self.included (Base)
Puts "#{base} included #{self}"
End
def name
"My name is person"
End
End
You can see a new method included is defined as the class method of the person module. This included method is invoked when you execute an include person in another module or class. One of the parameters that the method receives is a reference to the class that contains the module. Try running User.new.name and you will see the following output:
User included person
My name was person
As you can see, base returns the class name that contains the module. Now that we have a reference to the class that contains the person module, we can implement the functionality we want through metaprogramming. Let's take a look at how devise uses included hooks.
The included in the Devise
Devise is one of the most widely used authentication gem packages in Ruby. It was developed primarily by my favorite programmer Josévalim and is now being maintained by some great contributors. Devise provides us with perfect features from registration to login, from forgetting passwords to retrieving passwords, and so on. It allows us to configure a variety of modules using simple syntax in the user model:
Devise:d atabase_authenticatable,: registerable, validatable
The Devise method used in our model is defined here. To make it easier for me to paste this code in the following section:
def devise (*modules)
Options = modules.extract_options!. Dup
Selected_modules = Modules.map (&:to_sym). uniq.sort_by do |s|
Devise::all.index (s) | | -1 # Follow Devise::all order
End
devise_modules_hook! Todo
Include Devise::models::authenticatable
Selected_modules.each do |m|
MoD = Devise::models.const_get (m.to_s.classify)
If mod.const_defined? ("Classmethods")
Class_mod = Mod.const_get ("Classmethods")
Extend Class_mod
If class_mod.respond_to? (: Available_configs)
Available_configs = Class_mod.available_configs
Available_configs.each do |config|
Next unless Options.key? (config)
Send (: "#{config}=", Options.delete (config))
End
End
End
Include MoD
End
Self.devise_modules |= Selected_modules
Options.each {|key, value| send (: "#{key}=", Value)}
End
End
The module names passed to the Devise method in our model are saved as an array in the *modules. Call extract_options! for the incoming module method to extract the options that might be passed in. Each method is called in 11 lines, and each module is represented in the code block by M. In line 12, M will be converted to a constant (class name), so use m.to.classify for example: validatable such a symbol becomes validatable. Casually speaking, classify is the Activesupport method.
Devise::models.const_get (m.to_classify) Gets a reference to the module and assigns it to the MoD. Include the module in line 27 using include mod. The Validatable module in the example is defined here. The validatable included hook method is defined as follows:
def self.included (Base)
Base.extend Classmethods
assert_validations_api! (base)
Base.class_eval do
Validates_presence_of:email, if:: Email_required?
Validates_uniqueness_of:email, Allow_blank:true, if:: Email_changed?
Validates_format_of:email, With:email_regexp, Allow_blank:true, if:: Email_changed?
Validates_presence_of:p Assword, if::p assword_required?
Validates_confirmation_of:p Assword, if::p assword_required?
Validates_length_of:p Assword, Within:password_length, allow_blank:true
End
End
At this point the model is base. The Class_eval code block on line 5th takes the class as the context for the evaluation operation. Code written through Class_eval is the same as pasting code into a file that opens the class directly. Devise is class_eval to include validation in our user model.
When we try to register with devise or log in, we see these validations, but we don't write the validation code. Devise is using the included hook to achieve this. It's very elegant.
Extended
Ruby also allows developers to extend (extend) a module, which is a bit different from the inclusion (include). Extend is a method that applies a method that is defined in a module to a class, rather than an instance. Let's take a look at a simple example:
Module person
def name
"My name is person"
End
End
Class User
Extend person
End
Puts User.Name # => my name's person
As you can see, we call the name method defined within the person module as the class method of User. Extend adds a method within the person module to the User class. Extend can also be used to use the method within a module as a single instance method (singleton methods). Let's look at another example:
# We are using same person module and the User class from previous example.
u1 = User.new
U2 = User.new
U1.extend person
Puts U1.name # => my name's person
Puts U2.name # => undefined method ' name ' for #<user:0x007fb8aaa2ab38> (Nomethoderror)
We created two User instance objects and raised the person as a parameter in the U1 with the Extend method. Using this invocation method, the person's name method is valid only for U1, and is not valid for other instances.
As with included, the hook method corresponding to extend is extended. This method is invoked when a module performs a extend operation by another module or class. Let's take a look at an example:
# Modified version of person module
Module person
def self.extended (Base)
Puts "#{base} extended #{self}"
End
def name
"My name is person"
End
End
Class User
Extend person
End
The result of this code is the output User extended person.
The introduction of extended is over, let's see how ActiveRecord uses it.
The extended in the ActiveRecord
ActiveRecord is an ORM framework that is widely used in Ruby and Rails. It has many cool features, so using it is the first choice for ORM in many cases. Let's go inside the ActiveRecord and see how ActiveRecord uses callbacks. (We are using Rails v3.2.21)
ActiveRecord here Extend the Activerecord::models module.
Extend Activemodel::callbacks
Activemodel provides a set of interfaces that are used in the model class. They allow actionpack to interact with models that are not ActiveRecord. Here, inside Activemodel::callbacks you will see the following code:
def self.extended (Base)
Base.class_eval do
Include Activesupport::callbacks
End
End
Activemodel::callbacks called the Class_eval method on base that is Activerecord::callbacks, and contains the Activesupport::callbacks module. As we mentioned earlier, it is the same to call Class_eval on a class and manually write the code in this class. Activesupport::callbacks provides Activerecord::callbacks with a callback method in Rails.
Here we discuss the Extend method and the corresponding hook extended. And also learned how Activerecord/activemodel uses the above methods to provide us with the available functionality.
prepended
Another way to use the method defined inside the module is called Prepend. Prepend is introduced in Ruby 2.0 and is very different from include and extend. Methods introduced with include and extend can be overridden by a target module/class redefinition. For example, if we define a method named name in a module, and also define a method with the same name in the target module/class. So the name method defined in our class will overwrite the in the module. And prepend is not the same, it will cover the methods introduced in the Prepend module/class. Let's take a look at a simple example:
Module person
def name
"My name belongs to Person"
End
End
Class User
Include person
def name
"My name belongs to User"
End
End
Puts User.new.name
=> my name belongs to User
Now let's look at the prepend:
code as follows:
Module person
def name
"My name belongs to Person"
End
End
Class User
prepend person
def name
"My name belongs to User"
End
End
Puts User.new.name
=> My name belongs to person
Using prepend person overrides the same method in User, so the result for the terminal output is my name belongs to person. Prepend is actually adding a method to the front of the method chain. When the name method defined within the User class is invoked, super is invoked to invoke the name of the person module.
The callback name corresponding to the prepend is (you should have guessed) prepended. It is invoked when a module is preset to another module/class. Let's look at the effect. To update the definition of the person module:
Module person
def self.prepended (Base)
Puts "#{self} prepended to #{base}"
End
def name
"My name belongs to Person"
End
End
You should see the following results when you run this code:
Person prepended to User
My name belongs to person
Prepend was introduced to remove the ugliness of the Alias_method_chain hack, which was used extensively by rails and other libraries to achieve the same functions as prepend. Because Prepend is only available in the Ruby >= 2.0 version, you should upgrade your Ruby version if you plan to use prepend.
Inherited
Inheritance is one of the most important concepts in object-oriented objects. Ruby is an object-oriented programming language and provides the ability to inherit a subclass from a base/parent class. Let's look at a simple example:
Class Person
def name
"My name is person"
End
End
Class User < person
End
Puts User.new.name # => my name's person
We created a person class and a child class User. The method defined in person also becomes part of the User. This is very simple to inherit. You may be curious, is there any way to be notified when a class inherits from another class? Yes, Ruby has a hook called inherited that can be implemented. Let's take another look at this example:
Class Person
def self.inherited (Child_class)
Puts "#{child_class} inherits #{self}"
End
def name
"My name is person"
End
End
Class User < person
End
Puts User.new.name
As you can see, the inherited class method will be invoked when the person class is inherited by other subclasses. The results of running the above code are as follows:
User Inherits Person
My name was person
Let's take a look at how Rails uses inherited in its code.
The inherited in Rails
The rails application has an important class named application, defined within the Config/application.rb file. This class performs a number of different tasks, such as running all railties, engines, and initialization of plug-ins. An interesting event about the application class is that two instances cannot be run in the same process. If we try to modify this behavior, rails throws an exception. Let's see how rails implements this feature.
The application class inherits from the Rails::application, which is defined here. The inherited hook is defined in line 62, and it is invoked when our Rails application application class inherits Rails::application. The code for the inherited hook is as follows:
Class << Self
def inherited (Base)
Raise "You cannot have more than one rails::application" if rails.application
Super
Rails.application = Base.instance
rails.application.add_lib_to_load_path!
Activesupport.run_load_hooks (: before_configuration, Base.instance)
End
End
Class << Self is another way to define class methods in Ruby. The 1th line in inherited is to check if the rails.application already exists. Throws an exception if it exists. The first time this code is run, Rails.application returns false and then calls Super. Here super is the rails::engine inherited hook, because Rails::application inherits from Rails::engine.
On the next line, you will see that Rails.application is assigned to Base.instance. The rest is to set up the rails application.
This is how rails skillfully uses inherited hooks to implement a single instance of our Rails application class.
Method_missing
Method_missing may be the most widely used hook in Ruby. It is used in many popular Ruby frameworks/gem packages/libraries. This hook method is invoked when we attempt to access a method that does not exist on an object. Let's take a look at a simple example:
Class Person
def name
"My name is person"
End
End
p = person.new
Puts P.name # => my name's person
Puts P.address # => undefined method "Address" for #<person:0x007fb730a2b450> (Nomethoderror)
We define a simple person class, which has only one name method. It then creates an instance object of person and invokes the name and address two methods, respectively. Because name is defined in person, this operation is fine. However, the person does not define an address, which throws an exception. Method_missing Hooks can gracefully capture these undefined methods and avoid such exceptions. Let's revise the person class:
Class Person
def method_missing (Sym, *args)
' #{sym} Not defined on #{self} '
End
def name
"My name is person"
End
End
p = person.new
Puts P.name # => my name's person
Puts P.address # => address isn't defined on #<person:0x007fb2bb022fe0>
method_missing receives two parameters: the name of the method being invoked and the arguments passed to the method. First Ruby will look for the method we are trying to invoke, and if the method is not found, it will look for the Method_missing method. Now we overload the method_missing in person, so Ruby will call it instead of throwing an exception.
Let's see how Rake uses method_missing.
The Method_missing in Rake
Rake is one of the most widely used gem packages in Ruby. Rake uses method_missing to provide access to the arguments passed to the rake task. Start by creating a simple rake task:
Task:hello do
Puts "Hello"
End
If you perform this task by calling rake Hello, you will see the output hello. Let's extend this rake task to receive a parameter (a person name) and greet him:
Task:hello,: Name do |t, args|
Puts "Hello #{args.name}"
End
T is the name of the task, and args saves the arguments passed over. As you can see, we call Args.name to get the name parameter passed to the Hello task. Run the task and pass a parameter:
Rake hello["Imran Latif"]
=> Hello Imran Latif
Let's see how Rake uses method_missing to provide us with the parameters that are passed to the task.
The Args object in the above task is a rake::taskarguments instance, which is defined here. This class is responsible for managing the parameters passed to the rake task. Looking at the Rake::taskarguments code, you will find that there is no defined method to pass the parameters to the task. So how does Rake provide the parameters to the task? The answer is that Rake uses the method_missing to achieve this function cleverly. Look at the definition of line 64th method_missing:
def method_missing (Sym, *args)
Lookup (SYM.TO_SYM)
End
Defining method_missing in this class is to ensure access to those undefined methods, rather than throwing exceptions by Ruby. In method_missing, it calls the lookup method:
Def lookup (name)
If @hash. Has_key? (name)
@hash [Name]
elsif @parent
@parent. Lookup (name)
End
End
Method_missing invokes lookup and passes the method name in symbol (symbol) to it. The lookup method is found in the @hash, which is created in the Rake::taskarguments constructor. If the parameter is included in the @hash, it returns, and if not in the @hash, Rake attempts to invoke @parent lookup. If the parameter is not found, nothing is returned.
This is how Rake skillfully uses method_missing to provide access to the arguments passed to the Rake task. Thanks Jim Weirich for writing rake.
Conclusion
We discussed 5 important ruby hook methods, explored how they work, and how the popular framework/gem packages use them to provide some elegant functionality. I hope you will like this article. Tell us about your favorite ruby hooks in the comments and the problems you use to solve them.