Detailed descriptions of hook methods in Ruby

Source: Internet
Author: User

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.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.