Overview
By the end of this chapter, you will be able to open classes to add/modify methods; implement monkey patching; use methods such as method_missing and define_method to dynamically create methods at runtime; generate HTTP requests using the built-in Ruby HTTP client net/http; create GET and POST requests using Ruby and create your own gems to share your reusable code.
In the previous chapter, we learned about a number of advanced topics, including blocks, procs, and lambdas, which we will use in this chapter when we dive deep into the world of metaprogramming. We will also learn when we should not apply metaprogramming concepts by not always crossing the line into monkey patching (which is changing the behavior of classes and ultimately making the code confusing and unmaintainable).
In the second part of this chapter, we will learn a key programming skill that is required in order to create any real-world application that is communicating with external APIs: how to make GET data from a backend server and how to submit data to said server.
The final part of this chapter will help you share and distribute your Ruby code with others. So far, you have learned how to use Ruby gems, but, by the end of this chapter, you will be able to create your own RubyGem package and share it with the world.
We introduced metaprogramming in the previous chapter and defined it as code that generates code. Ruby has numerous powerful methods that make it possible to allow code that writes code. Furthermore, metaprogramming is most often used to create flexible interfaces. This is especially useful when you create Ruby gems that are pluggable Ruby libraries in other Ruby programs. Metaprogramming is also used in creating Ruby-based frameworks, such as Sinatra, Ruby on Rails, or when you create your own framework. Ruby on Rails, the most popular Ruby framework, is loaded with metaprogramming magic.
In the previous chapter, we stated that metaprogramming makes use of multiple elements available within the Ruby language. We discussed three such elements: blocks, procs, and lambdas. In this section, we will take a practical approach in order to understand metaprogramming and discuss topics such as opening classes in Ruby, monkey patching, and some in-built Ruby methods that allow you to create methods dynamically at runtime.
Opening classes is a way in Ruby that allows us to make changes to methods that reside inside the class or to add new methods. This is also called functional reloading or monkey patching. In Ruby, a class is never closed; you can always add new methods to an existing class. This applies not just to classes you create, but in-built standard Ruby classes too. Besides adding new methods to further enhance the power of a class, you can also override the in-built methods. This is called monkey patching.
You should be careful with open classes, especially when you are modifying or overriding an in-built method. Since that alteration will be valid throughout your application, you must be careful and meticulous while writing such code. It is advisable to alias the method or write a new method altogether. We will learn more about this with the help of examples in the next section.
Now, let's understand the syntax to open a class by means of a simple example. In the following example, we create a Ruby file, open_class_integer.rb, and then create an integer class that will add the number 8 to the integer that is provided:
class Integer
def add_eight
self + 8
end
end
puts 9.add_eight
Run this Ruby file from the Terminal. The output will be as follows:
$ ruby open_class_integer.rb
> 17
This is a very simple example where we have added a new method, add_eight, to Ruby's Integer class. So, in our Ruby program, if we call this method on an integer, it will add 8 to it just like in the preceding example, where we have called the method on an integer object with the value of 9, and the result is 17.
The String class is an in-built Ruby class. In this exercise, we will open that class and add a new method, add_prefix, to it. This method will prefix whatever string is passed to it. The following steps will help you to complete the exercise:
class String
def add_prefix
"My favorite book is " + self
end
end
puts "Ruby Fundamentals".add_prefix
Thus, we have successfully managed to add a method to an in-built Ruby class.
Another feature of metaprogramming is monkey patching, where we can override the existing method of a class by opening the class and modifying the definition at runtime. For example, if you are using a third-party library that is conflicting with your class method, which leads to a bug, then in this case, we monkey patch that specific method of the class.
Monkey patching is a debatable concept since it places tremendous power in the hands of developers and should be used very carefully, and only in situations where it is really required. Let's now look at an example of monkey patching in order to understand it practically.
An array class in Ruby is an in-built class and includes many out-of-the-box methods. An out-of-the-box feature or functionality (also called OOTB, or off-the-shelf), particularly in software, is a feature or functionality of a product that works immediately, without any special installation, without any configuration, or without modification.
One of the methods most commonly used is size, which returns the size of an array.
Now, let's try to monkey patch this method:
my_array = ["Hello", "World"]
puts my_array.size
Executing this file from the Terminal will provide the following result:
This was the expected result, since the size of this array object, my_array, is clearly 2. However, imagine we have a requirement where we must always increment the size of an array by 3 for our program. In order to meet such a requirement, we can monkey patch the size method of the Ruby array class using the following code:
class Array
def size
self.length + 3
end
end
my_array = ["Hello", "World"]
puts my_array.size
The output should be as shown in the following screenshot:
Here, we are overriding the in-built size method and adding 3 to whatever result we get using another array method length.
Now, the Ruby program for array object sizes has a new definition, everywhere. This was a very straightforward example to facilitate an understanding of monkey patching.
Let's now add an error to see how much damage incorrect code can do to an in-built class.
We will now add some logic to divide the integer value 1 by 0, which would result in an error:
class Array
def size
1/0
end
end
my_array = ["Hello", "World"]
puts my_array.size
$ ruby monkey_patch_error.rb
The output would appear as follows:
Here, you can see the implementation of incorrect logic using monkey patching and how this would spoil this class for this Ruby program.
In this exercise, we will create a program that would add the value 2 to any number passed. Then, we will monkey patch the same class to subtract the value 2 from any number passed to it. The following steps will help you to complete this exercise:
class Calculation
attr_accessor :first_number
def sum
self.first_number + 2
end
end
calc_obj1 = Calculation.new
calc_obj1.first_number = 10
puts calc_obj1.sum
class Calculation
def sum
self.first_number - 2
end
end
Note that we only had to modify the definition of the sum method, while the initialization of first_number stays as it is in the original class definition.
We are subtracting 2 from the initial value, which is set using the sum method.
calc_obj2 = Calculation.new
calc_obj2.first_number = 10
puts calc_obj2.sum
The output should be as follows:
It is amazing when you first discover that you can modify methods right in the core classes, but monkey patching executed incorrectly can render the program a train wreck. Always think through the edge cases in the case of monkey patching. It is advisable to avoid using monkey patching for core classes and libraries. Its application is preferred in relation to classes created on your own.
method_missing is an important tool in the toolbox of Ruby metaprogramming. It is a callback method you can use that gets called when an object tries to call a method that is missing.
Imagine you have created a class that has two methods, and, in your program, you call a method that does not exist in the class. Since this method is not present, then the search moves up the hierarchy, eventually reaching BasicObject.
In BasicObject, there is a private method called method_missing. The system now cycles through one more time to look for method_missing in our class and checks whether there are any matches for method_missing to produce the desired result.
Essentially, in our class, we are simply overriding method_missing from BasicObject.
Let's understand this with a simple example by creating a MyClass class with no method. Initialize its object, obj1, and call the xyz method, which does not exist:
class MyClass
end
obj1 = MyClass.new.xyz
Run this code from the Terminal:
$ ruby method_missing_basics.rb
The output should be as follows:
It is quite clear that we have no method as xyz in MyClass, which we can use with the object, obj1.
Let's now modify our class and add method_missing to it:
class MyClass
def method_missing(method_name, *args, &block)
puts "The method you have specified #{method_name} does not exist"
end
end
obj1 = MyClass.new.xyz
Run this code from the Terminal:
$ruby method_missing_basics.rb
The output should be as follows:
When we modified our code and added method_missing, we did get a puts statement placed in the method instead of giving an undefined error method.
Let's now understand some of the various parameters accepted by method_missing:
def method_missing (method_name, *args, &block)
end
If you look closely at these three parameters, we can create a method dynamically at runtime, which helps us to create code that creates code, and that is why method_missing is a key feature of metaprogramming.
Note
Methods defined using method_missing are also known as ghost methods, as there are an endless number of such methods and all can be tackled by defining and responding to calls via method_missing.
Whenever we override method_missing, it's a good practice to also define respond_to_missing?. This method is called when respond_to? is called.
respond_to? is a commonly used method and, if a ghost method exists, it should return true. However, if we do not define respond_to_missing?, the computer will return false, even though the missing method is defined.
Let's now look at the following example, where we define a class, MyClass, as having the method_missing method defined for the method, xyz, which returns a string. However, this method does not have respond_to_missing? defined:
class MyClass
def method_missing (method_name, *args, &block)
if method_name.to_s == "xyz"
puts "You are now in ghost method"
else
super
end
end
end
obj1 = MyClass.new
obj1.xyz
puts obj1.respond_to?(:xyz)
Run this file from the Terminal with the following command:
$ ruby respond_to_missing_basics.rb
The output should be as follows:
Please note that even though we have method_missing defined for the method, xyz, we still get a false value for respond_to?.
Let's also define respond_to_missing? for any ghost method that starts with an x:
class MyClass
def method_missing(method_name, *args, &block)
if method_name.to_s == "xyz"
puts "You are now in ghost method"
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('x') || super
end
end
obj1 = MyClass.new
obj1.xyz
puts obj1.respond_to?(:xyz)
Run this file from the Terminal using the following command:
$ ruby respond_to_missing_basics.rb
The output should be as follows:
Now, we get an appropriate response to the respond_to? method.
In this exercise, we will be creating a company class that includes employee details, such as name, ID, and location, using the in-built ostruct library. We will be implementing method_missing to give an appropriate response to methods that start with employee_:
require 'ostruct'
class Company
def initialize(name, id, location)
@name = name
@id = id
@location = location
end
def employee
OpenStruct.new(name: @name, id: @id, department: @location)
end
end
new_employee = Company.new("Akshat", "007", "Tokyo")
puts new_employee.employee_location
puts new_employee.respond_to?(:employee_location)
$ruby exercise3.rb
The output should be as follows:
As you can see, we see an error because we are calling a method on the company class object that does not exist. If you look closely, the error itself is indicating to us that there is no method like this, and so we should create one.
We have created an object of the company class and then called the employee_location method. However, that method does not exist and so the program breaks. We have also called a respond_to method on new_employee.
If you replace this with the employee_location method when called, you will get a false value since no respond_to_missing? method exists.
Exercise10.03.rb
14 def method_missing(method_name, *args, &block)
15 if method_name.to_s =~ /employee_(.*)/
16 employee.send($1, *args, &block)
17 else
18 super
19 end
20 end
21
22 def respond_to_missing?(method_name, include_private = false)
23 method_name.to_s.start_with?('employee') || super
24 end
25 end
Here, we are using regular expressions for matching. Regular expressions help us to match a pattern for a string. The pattern should be between the forward slashes (/).
For example, in the preceding code, we are using /employee_(.*)/, so anything starting with employee_ will be matched. The $ variable will retain the information about the last match.
$ ruby exercise3.rb
The output should be as follows:
We have now achieved the desired result. We sent employee_location, hence, we got the location as Tokyo in our result. Had we called a ghost method, employee_age, we would have not obtained any result. Play around by creating more ghost methods to see whether there are any changes in the results.
Similar to method_missing, define_method helps us to create methods dynamically at runtime. Hence, it qualifies as a tool in Ruby's metaprogramming toolbox. Essentially, define_method works in tandem with method_missing to make the code DRY (Do-not Repeat Yourself), where, instead of using def, we use define_method.
Let's look at its syntax:
class Factory
define_method("operate_machinery") do |argument|
"Starting machines on all assembly lines in #{argument}"
end
define_method("package_products") do |argument|
"Packaging #{argument} products "
end
define_method("send_for_distribution") do |argument|
"Sending for distribution for #{argument}"
end
define_method("generate_exit_pass") do |argument|
"Generate exit pass for #{argument}"
end
end
factory = Factory.new
puts factory.operate_machinery("Manchester")
puts factory.package_products("healthcare")
puts factory.send_for_distribution("medical stores")
puts factory.generate_exit_pass("trucks")
If you look here, we have a different syntax for defining a method instead of def and end; we are using define_method and end. In the preceding code snippet, we have a class, Factory, that has various define_method methods that return a statement along with the argument passed.
Let's run this program to see the output. Run the following command from the Terminal:
$ ruby define_method_basics.rb
The output should be as follows:
define_method is best used with lists where we can loop around a list and place define_method in it to keep creating methods when called. We will learn about this in the next exercise.
In this exercise, we will create a Ruby program that will deliver a welcome message to customers across five countries: France, Japan, England, Germany, and Brazil:
class WelcomeMessage
countries = %w(fr jp eng de br)
countries.each do |country|
define_method("message_for_#{country}") do |argument|
"Welcome to Ruby Fundamentals, this is a reader from #{argument}."
end
end
End
greeting = WelcomeMessage.new
puts greeting.message_for_fr("France")
puts greeting.message_for_jp("Japan")
puts greeting.message_for_eng("England")
puts greeting.message_for_de("Germany")
puts greeting.message_for_br("Brazil")
$ ruby exercise.rb
The output should be as follows:
HTTP (Hypertext Transfer Protocol) is used to make requests and receive responses over the internet. HTTP helps in transferring data from one point to another over a network.
Such requests are most common when you interact with an external server, or APIs, to receive data or submit data to them. Essentially, no real-world applications are possible without making HTTP requests The most common way of organizing APIs is to use the constraints set by REST. REST (REpresentational State Transfer) is an architectural type that has prescribed standards for various systems to communicate with one another. These APIs are characterized by the separation of concerns between client and server (for example, if we make a Ruby program that interacts with an API, our program with the client and API to which the request is made will be the server).
For now, we will not go into detail about REST, but we will learn how to use it and how easy it makes it to interact with backend APIs.
With REST, there is a proper structure to make a request that generally comprises the following:
There are five commonly used HTTP verbs that are used to interact with resources when making a request in REST:
Ruby has an in-built HTTP client called net/http. An HTTP client helps to make various types of HTTP requests using one of the methods explained previously, along with the URL and payload if required. The payload is a set of data that may be required by an external server to take some sort of action; for example, in the case of a search request, the payload could be the search keyword we have punched in the text field and then submitted. Using the in-built net/http package, you can make an HTTP request right away.
Let's see how a GET request will look with net/http. Let's first understand the syntax of a GET request with Ruby's net/http library:
require 'net/http'
Net::HTTP.get('example.com', '/index.html')
This is a simple GET request made using net/http, where we are trying to get a complete index.html page from http://example.com. Usually, we save this response and copy the relevant data from it. This technique is used in web scraping to harvest huge amounts of data from other internet websites.
In this exercise, we will use the Ruby net/http library and make use of the https://www.packtpub.com URL to get the data:
require 'net/http'
uri = URI('https://www.packtpub.com')
response = Net::HTTP.get_response(uri)
puts response.code
puts response.body
$
ruby http_get.rb
The output should be as follows:
As we can see, with response.code, we received the status code 200 for our request from the server, and with response.body, we received the data from the GET request we made.
Status codes are provided from the server side, which is in response to a client request made. HTTP status codes are divided into five categories. The first digit of the code defines the class of response. The last two digits do not have any specific class or categorization. The first digit is key to understanding the state of your request. Listed here are five types of status codes:
1xx: Informational
This means that the client request has been received and the process is continuing. This is a client-side status code, and is mainly informational.
2xx: Success
This means that the client request was successfully received, understood, and accepted.
3xx: Redirection
This means that further action must be taken in order to complete the request.
4xx: Client Error
This status code is received in case there has been an error from the client. This means that the request contains incorrect syntax or cannot be fulfilled.
5xx: Server Error
This means that the server failed to fulfill an apparently valid request. This status code indicates that the server is incapable of completing the requests.
Note
Read more on status codes here: https://packt.live/2OP9Rtr.
In this exercise, we will be generating a POST request using the Ruby gem, httparty, which simplifies the submission of complicated requests.
Use the following demo URL, https://my-json-server.typicode.com/typicode/demo/posts, to make the request.
For parameters of the POST request, use the following format:
body: {
id: integer_id, title: ""
}
The following steps will help you to complete the exercise:
$ gem install httparty
This will install the gem on your system, and you can require it in any Ruby program.
Note
To find out more about this gem, you can refer to its documentation at https://packt.live/2MfSm42.
require 'httparty'
res = HTTParty.post("https://my-json-server.typicode.com/typicode/demo/posts", body: { id: 5, title: "Ruby Fundamentals" })
puts res.code
puts res.body
$ ruby http_post.rb
The output should be as follows:
As you can see, the output is very clean. We received status code 201. Since it starts with a 2, it means we have succeeded, and 201 indicates precisely that the resource has been created. The response body is the value of the data that is used to create the request.
As covered in Chapter 7, Introduction to Ruby Gems, a gem is a way for Ruby to package and distribute Ruby programs and libraries. So far, we have been using many open source Ruby gems, which has a positive impact on the speed of development.
In this section, we will learn to create a Ruby gem. Also, RubyGems is a package manager for the Ruby programming language and comes with tools baked in by default, which makes it really easy to create a gem and distribute it.
Let's jump right in to creating a simple Ruby gem.
Before you create your first file for your gem, you must make sure to find a suitable name for your gem. RubyGems has official documentation for naming conventions and it is advised to use this for improved programming experiences.
Note
You can refer to this naming convention at https://packt.live/2Bcz1dN.
In this exercise, we will be creating our own Ruby gem, before installing it and then publishing it in a Ruby program:
The code that will be used for our package will reside in the lib directory. A good practice is to have one Ruby file with the same name as the gem since, in our case, it will be required as ruby-fundamental-gem.
class RubyFundamental
def self.hello
puts "Hello World this is my first gem!"
end
end
Gem::Specification.new do |s|
s.name = 'ruby-fundamental-gem'
s.version = '0.0.1'
s.date = '2019-06-30'
s.summary = "Simple gem to learn how to create gems"
s.description = "A basic welcome message gem"
s.authors = ["Akshat Paul"]
s.email = '[email protected]'
s.homepage = %q{http://www.akshatpaul.com/}
s.files = ["lib/ruby-fundamental-gem.rb"]
s.license = 'MIT'
end
$ gem build ruby-fundamental-gem.gemspec
The output should be as follows:
$ gem install ruby-fundamental-gem-0.0.1.gem
The output should be as follows:
Our gem is successfully installed. Now, let's try using this gem by requiring it. Open IRB and use the following code to test it:
$ irb
require 'ruby-fundamental-gem'
RubyFundamental.hello
The output should be as follows:
We have now successfully installed the gem.
Log in and set up your credentials using the https://rubygems.org/sign_up URL.
Simply paste this URL in the browser. You will be prompted to add your credentials and the key will start downloading automatically.
Run the following command to publish your gem:
$ gem push gem_name.gem
Thus, we have successfully created our own Ruby gem and published it for the world to use.
In this activity, we will be creating a Ruby program to get JSON data from a public API, and then parse and print the response. Also, we will post new data to the public API.
The public APIs to be used for this activity are as follows:
Note that the second link will become functional only after the activity is completed. To post data using the preceding backend API, only the name and address fields are required to create new records like this:
{
property: {
name: "my_name", require 'ostruct'
address: "my_address"
}
}
The following steps should help you to complete this activity:
You can implement the same code with a few more lines by using the in-built net/http library and the JSON library.
The output should be as follows:
The output should be as follows:
Note
The solution to the activity can be found on page 486.
In this chapter, we learned skills that are commonly used in real-world problems, such as metaprogramming, communicating with backend APIs, and creating reusable gems. No Ruby framework or open source library is complete without making use of a lot of metaprogramming. We learned various techniques as well as some of the in-built features of Ruby, including open class, method_missing, respond_to_missing?, monkey patching, and define_method. Next, we learned how to make HTTP requests to interact with external APIs over a network. Lastly, we learned how to package and distribute our reusable code by creating gems ourselves.
In the next chapter, we will learn about the most popular Ruby framework or, should we say, one of the world's most widely used server-side web application frameworks – Ruby on Rails.
18.223.237.131