Detailed descriptions of hook methods in Ruby
This article mainly introduces the detailed explanation of the hook method in Ruby. This article explains what is the hook method, extended, extended, prepended, inherited, and so on in included, Devise, for more information, see
Ruby's philosophy is based on a basic element, that is, to make programmers happy. Ruby pays great attention to the joy of programmers and provides many different methods to implement it. Its meta-programming capability allows programmers to write code dynamically generated at runtime. Its thread function allows programmers to Write multi-threaded code in an elegant way. Its Hook method allows programmers to expand their behavior while the program is running.
These features, as well as some other cool languages, make Ruby a top choice for coding. This article will discuss some important hook methods in Ruby. We will discuss hook methods from different aspects, such as what they are, what they are used for, and how we use them to solve different problems. We also know how popular Ruby frameworks, Gem packages, and libraries use them to provide very cool features.
Let's get started.
What is the hook method?
The Hook method provides a way to expand the program behavior when the program is running. If this function is available, you can receive a notification whenever a subclass inherits some specific parent classes, or it is more elegant to handle non-callable methods on an object rather than letting the compiler throw an exception. These cases use the Hook method, but their usage is not limited to this. Different frameworks/libraries use different hook methods to implement their functions.
In this article, we will discuss the following hook methods:
1. Added
2. extended
3. prepended
4. inherited
5. method_missing
Included
Ruby provides us with a way to use modules (called mixins in other languages) to write modular code for other modules/classes. The concept of a module is simple. It is an independent code block that can be used elsewhere.
For example, if we want to write some code to call a specific method at any time, a static string will be returned. Let's call this method name. You may want to use the same piece of code elsewhere. It is best to create a new module. Let's create one:
The Code is as follows:
Module Person
Def name
Puts "My name is 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:
The Code is as follows:
Class User
Include Person
End
Ruby provides some different methods to use modules. Include is one of them. What include does is to make the methods defined in the module available on the instance variables of a class. In our example, the method defined in the Person module is changed to a User-class instance object. This is equivalent to writing the name method in the User class, but the advantage of defining it in the module is that it can be reused. To call the name method, we need to create a User instance object, and then call the name Method on this object. For example:
The Code is as follows:
User. new. name
=> My name is Person
Let's take a look at the include-based Hook method. Extended is a hook method provided by Ruby. When you include a module in some modules or classes, it will be called. Update the Person module:
The Code is as follows:
Module Person
Def self. included (base)
Puts "# {base} pinned ded # {self }"
End
Def name
"My name is Person"
End
End
You can see that a new method, sorted ded, is defined as the class method of the Person module. When you execute include Person in other modules or classes, the extended ded method will be called. A parameter received by this method is a reference to the class containing this module. Run User. new. name and you will see the following output:
The Code is as follows:
User Defined ded Person
My name is Person
As you can see, base returns the class name that contains this module. Now we have a reference to a class that contains the Person module. We can use metaprogramming to implement the functions we want. Let's take a look at how Devise uses the sorted hook.
Included in Devise
Devise is one of the most widely used authentication gem packages in Ruby. It was mainly developed by my favorite programmer Jos é Valim and is now maintained by some amazing contributors. Devise provides complete functions, including registration, logon, password retrieval, and so on. It allows us to use simple syntax in the user model to configure various modules:
The Code is as follows:
Devise: database_authenticatable,: registerable,: validatable
The devise method used in our model is defined here. For convenience, I paste this code below:
The Code is as follows:
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! Do
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 name passed to the devise method in our model will be saved as an array in * modules. Call extract_options for the input module! Method to extract possible input options. Call the each Method In line 11, and each module is represented by m in the code block. In 12 rows, m will be converted to a constant (class name), so using m. to. classify a symbol such as validatable will become Validatable. Classify is the method of ActiveSupport.
Devise: Models. const_get (m. to_classify) gets the reference of this module and assigns it to mod. Use include mod in line 27 to include this module. The Validatable module in this example is defined here. The Validatable extended Hook method is defined as follows:
The Code is 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: password, if: password_required?
Validates_confirmation_of: password, if: password_required?
Validates_length_of: password, within: password_length, allow_blank: true
End
End
The model is base. In the class_eval code block of Row 3, this class is used as the context for value calculation. The code written using class_eval is the same as the Code pasted into a file directly open this class. Devise uses class_eval to include verification in our user model.
When we try to use Devise registration or login, we will see these verifications, but we have not written these verification codes. Devise uses the extended hook to implement these functions. Very elegant.
Extended
Ruby also allows developers to expand (extend) a module, which is a little different from include. Extend applies methods defined in modules as class methods, rather than instance methods. Let's look at a simple example:
The Code is as follows:
Module Person
Def name
"My name is Person"
End
End
Class User
Extend Person
End
Puts User. name # => My name is Person
As you can see, we call the name method defined in the Person module as a User class method. Extend adds the methods in the Person module to the User class. Extend can also be used to use the methods in the module as singleton methods ). Let's look at another example:
The Code is as follows:
# We are using same Person module and User class from previous example.
U1 = User. new
U2 = User. new
U1.extend Person
Puts u1.name # => My name is Person
Puts u2.name # => undefined method 'name' # (NoMethodError)
We created two User instance objects and called the extend Method on u1. With this call method, the name method of Person is only valid for u1 and is invalid for other instances.
As with extended, the hook method corresponding to extend is extended. This method is called when a module is extend by another module or class. Let's take an example:
The Code is as follows:
# 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 execution result of this Code is output User extended Person.
The introduction to extended is complete. Let's see how ActiveRecord uses it.
Extended in ActiveRecord
ActiveRecord Is An ORM framework widely used in Ruby and Rails. It has many cool features, so it is the first choice for ORM in many cases. Let's go to ActiveRecord to see how ActiveRecord uses callback. (We use Rails v3.2.21)
ActiveRecord here extend ActiveRecord: Models module.
The Code is as follows:
Extend ActiveModel: Callbacks
ActiveModel provides a set of interfaces used in model classes. They allow ActionPack to interact with models that are not ActiveRecord. Here, you will see the following code inside ActiveModel: Callbacks:
The Code is as follows:
Def self. extended (base)
Base. class_eval do
Include ActiveSupport: Callbacks
End
End
ActiveModel: Callbacks calls the class_eval Method for base, that is, ActiveRecord: Callbacks, and includes the ActiveSupport: Callbacks module. As we mentioned earlier, calling class_eval for a class is the same as writing code in this class manually. ActiveSupport: Callbacks provides the callback method in Rails for ActiveRecord: Callbacks.
Here we have discussed the extend method and the corresponding hook extended. We also learned how ActiveRecord/ActiveModel uses the above method to provide available functions for us.
Prepended
Another method defined in the module's internal method is called prepend. Prepend is introduced in Ruby 2.0 and is very different from include and extend. The methods introduced by include and extend can be redefined and overwritten by the target module/class. For example, if we define a method named name in a module and define a method with the same name in the target module/class. The name method defined in our class will overwrite the module. Prepend is different. It overwrites the methods defined in the modules/classes that prepend introduces. Let's look at a simple example:
The Code is as follows:
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 take a look at the prepend situation:
The Code is 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 will overwrite the method of the same name in the User, so the output result in the terminal is My name belongs to Person. Prepend is actually adding a method to the front end of the method chain. When you call the name method defined in the User class, super is called to call the name of the Person module.
The callback name corresponding to prepend (you should have guessed it) prepended. A module is called when it is preset to another module/class. Let's take a look at the effect. Update the definition of the Person module:
The Code is as follows:
Module Person
Def self. prepended (base)
Puts "# {self} prepended to # {base }"
End
Def name
"My name belongs to Person"
End
End
Run the code again and you will see the following results:
The Code is as follows:
Person prepended to User
My name belongs to Person
Prepend is introduced to remove the ugliness of alias_method_chain hack, which has been widely used by Rails and other libraries to achieve the same features as prepend. Because prepend can only be used in Ruby> = 2.0, if you plan to use prepend, you should upgrade your Ruby version.
Inherited
Inheritance is one of the most important object-oriented concepts. Ruby is an object-oriented programming language and provides the ability to inherit a subclass from the Base/parent class. Let's take a simple example:
The Code is as follows:
Class Person
Def name
"My name is Person"
End
End
Class User <Person
End
Puts User. new. name # => My name is Person
We have created a Person class and a sub-class User. The methods defined in Person are also part of the User. This is a simple inheritance. You may wonder, is there any way to receive notifications when a class is inherited by other classes? Yes, Ruby has a hook named inherited. Let's take a look at this example:
The Code is as follows:
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, when the Person class is inherited by other sub-classes, the inherited class method will be called. The result of running the above Code is as follows:
The Code is as follows:
User inherits Person
My name is Person
Let's see how Rails uses inherited in its code.
Inherited in Rails
An important class in a Rails Application is application, which is defined in the config/Application. rb file. This class executes many different tasks, such as running all Railties, engine and plug-in initialization. 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 will throw an exception. Let's take a look at how Rails implements this feature.
The Application class is inherited from Rails: Application, which is defined here. The inherited hook is defined in row 62 and will be called when the Rails Application class inherits Rails: Application. The code for the inherited hook is as follows:
The Code 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 of defining class methods in Ruby. In inherited, row 1st checks whether Rails. application already exists. If yes, an exception is thrown. When this code is run for the first time, Rails. application returns false and calls super. Here, super is the inherited hook of Rails: Engine, because Rails: Application inherits from Rails: Engine.
In the next line, you will see that Rails. application is assigned base. instance. The rest is to set up the Rails application.
This is how Rails cleverly uses the inherited hook 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, and libraries. This Hook method is called when we try to access a method that does not exist on an object. Let's look at a simple example:
The Code is as follows:
Class Person
Def name
"My name is Person"
End
End
P = Person. new
Puts p. name # => My name is Person
Puts p. address # => undefined method 'address' # (NoMethodError)
We have defined a simple Person class with only one name method. Create an instance object for Person and call the name and address methods respectively. Because name is defined in Person, this operation is okay. However, the Person does not define the address, which throws an exception. The method_missing hook elegantly captures undefined methods to avoid such exceptions. Let's modify the Person class:
The Code is as follows:
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 is Person
Puts p. address # => address not defined on #
Method_missing receives two parameters: the called method name and the parameter passed to the method. First, Ruby will look for the method we are trying to call. If the method is not found, it will look for the method_missing method. Now we reload method_missing in Person, so Ruby will call it instead of throwing an exception.
Let's take a look at how Rake uses method_missing.
Method_missing in Rake
Rake is one of the most widely used gem packages in Ruby. Rake uses method_missing to provide parameters for access to the Rake task. First, create a simple rake task:
The Code is as follows:
Task: hello do
Puts "Hello"
End
If you call rake hello to execute this task, you will see Hello output. Let's extend this rake task to receive a parameter (a person's name) and greet him:
The Code is as follows:
Task: hello,: name do | t, args |
Puts "Hello # {args. name }"
End
T is the task name, And args saves the passed parameters. As you can see, we call args. name to obtain the name parameter passed to the hello task. Run the task and pass a parameter:
The Code is as follows:
Rake hello ["Imran Latif"]
=> Hello Imran Latif
Let's take a look at how Rake uses method_missing to provide us with parameters passed to the task.
The args object in the preceding task is a Rake: TaskArguments instance, which is defined here. This class is responsible for managing the parameters passed to the Rake task. View the code of Rake: TaskArguments. You will find that no related method is defined to pass the parameter to the task. So how does Rake provide parameters to the task? The answer is that Rake uses method_missing to skillfully implement this function. Let's take a look at the definition of row 64th method_missing:
Copy the Code as follows:
Def method_missing (sym, * args)
Lookup (sym. to_sym)
End
Method_missing is defined in this class to ensure that undefined methods can be accessed, rather than Ruby throws an exception. In method_missing, it calls the lookup method:
The Code is as follows:
Def lookup (name)
If @ hash. has_key? (Name)
@ Hash [name]
Elsif @ parent
@ Parent. lookup (name)
End
End
Method_missing calls lookup and passes the method name in the form of a Symbol to it. The lookup method will be searched in @ hash, which is created in the Rake: TaskArguments constructor. If @ hash contains this parameter, return. If @ hash does not contain this parameter, Rake tries to call @ parent lookup. If this parameter is not found, nothing is returned.
This is how Rake cleverly uses method_missing to provide parameters for access to the Rake task. Thanks to Jim Weirich for writing Rake.
Conclusion
We discussed five important Ruby hook methods, explored how they work, and how some popular frameworks/gem packages use them to provide some elegant features. I hope you like this article. Please tell us your favorite Ruby hooks in the comments and the problems you solve using them.