Code360 powered by Coding Ninjas X Naukri.com. Code360 powered by Coding Ninjas X Naukri.com
Table of contents
1.
Introduction
2.
Problem
3.
What is a Contract in Ruby?
4.
Frequently Asked Questions
4.1.
What types of contracts can be used?
4.2.
What are the advanced types of contracts?
4.3.
What are the advantages of using contracts?
4.4.
What is pattern matching?
4.5.
What is Typechecking by contracts?
5.
Conclusion
Last Updated: Mar 27, 2024

Enforcing Software Contracts in Ruby

Author Lucifer go
0 upvote

Introduction

Contracts lets you clearly and beautifully express how your code behaves while freeing you from writing tons of boilerplate, defensive code. 

In other words, they are contracts that allow you to define the correct input and output for a code and how your code should behave depending on certain conditions. An excellent way to summarize all of this would be: contracts that allow you to "type" your code.

Problem

Using strategies like duck typing and range validation, you want your methods to validate their arguments without overburdening your code with conditions to test them. Here, contracts come to the rescue. 

We can have more consistency in our inputs and outputs thanks to contracts. Our system's data flow is now more transparent. And most of the typical mistakes in our system can be promptly found and remedied. It's also simpler to comprehend what a method performs, requires, and returns. Additionally, it offers some form of continuously updated documentation.

What is a Contract in Ruby?

A contract in Ruby ensures what kind of input a method expects (pre-condition) and what it outputs (post-condition). It will define how our method behaves but also check its behavior.

The gem Contracts.ruby allows us to decorate our methods with code that checks that the inputs and outputs correspond to the contract specifies. Of course, one is not obliged to annotate each method, but I think that specifying a contract for your public API can only be beneficial.

Although contracts are not always the best option, they can come in help when we want to stick to our original reasoning and handle particular instances in a different way. The decision to employ them ultimately rests with you, but if you do, be sure to assess your use cases, and if they do not provide you any advantages, use them in other circumstances and look for a different solution.

How to Implement Contract in Ruby?

First, install the gem by using the following code on the terminal:

gem install contracts.


First, we need to include the gem core and then the Builtin module. We can use it in a similar way to how we use a Java or Python decorator. 

require 'contracts'
class New_Class
  include Contracts::Core
  include Contracts::Builtin
  Contract Num => Num
  def four_times(x)
    x * 4
  end
end
You can also try this code with Online Ruby Compiler
Run Code


As the name suggests, the built-in module includes many ready-to-use standard contracts.

We can define the contracts after including the required modules.

Here the first part is the expected input and the next par => is the expected output:

In => Out
Contract Num => Num
def four_times(x)
  x * 4
end
You can also try this code with Online Ruby Compiler
Run Code


If we name our four_times method with a number, we will obtain the desired result, but not if we try to name it with a string value (to mention an example), we should see a detailed error message.

puts New_Class.new.four_times(3)
puts New_Class.new.four_times('3')
You can also try this code with Online Ruby Compiler
Run Code


An error was thrown as follows: -

ParamContractError : (Contract violation for argument 1 of 2: Expected: Num,
Actual: "3"
Value guarded in: Object::double
With Contract: Num => Num
At: file.rb:5 )


What if you need your function to take input as a string too? We can validate the input and cast it:

def four_times(y)
   y = y.to_i unless y.is_a?(FixNum)
   y * 4
  end
You can also try this code with Online Ruby Compiler
Run Code


The problem with this code is that the straightforward method can be way more difficult and can be unreadable if we need to add more types. In these cases, contracts are beneficial since they allow us to solve this issue by maintaining our methods as simple as possible.

Contract Num => Num

def four_times(x)
  x * 4
end
Contract String => Num
def four_times(x)
  four_times(x.to_i)
end
You can also try this code with Online Ruby Compiler
Run Code


Thus, all the string type requires is a new contract, but it gets better. Now that we know the input is a string, we can utilize the original method with the assurance that we will only send a number to the original function. It works similarly but without modifying our original method and adding validations, conversions, etc.

Frequently Asked Questions

What types of contracts can be used?

Besides the classics Num, String, and Bool, we can use more exciting types like Any when we have no type constraint, None when you need no argument, Or if our argument can be of different types, for example, Or[Fixnum, Float] and Not if our argument can't be of a particular type, like Not[nil].

What are the advanced types of contracts?

We can use contracts with advanced types like lists. The contract of multiply method indicates that it wants a list of values of the type Num. Other types include Hashes, methods, and tokens.

What are the advantages of using contracts?

We can have more consistency in our inputs and outputs thanks to contracts. Our system's data flow is now more transparent. And most of the typical mistakes in our system can be promptly found and remedied. It's also simpler to comprehend what a method performs, requires, and returns. Additionally, it offers some form of continuously updated documentation.

What is pattern matching?

For a given value, pattern-matching will determine whether or not it matches a pattern. Action is prompted if this is the situation. It resembles the Java method of overloading somewhat. It may be compared to a vast switch case, but it would be far more beautiful.

What is Typechecking by contracts?

Contracts offer type checking for function input and output at its most basic level. If the function is called with or returns something different from the anticipated classes of the parameters and return value, you will receive a properly prepared error notice.

Conclusion

In this article, we have extensively discussed how to enforce software contracts in the Ruby programming language.

You can also check our previous blogs on Ruby ProgrammingRuby Essentials, and Passing Arguments in Ruby. If you are eager to learn advanced front-end web development, Coding Ninjas is here with one of the best courses available, which you can find here

Happy Learning!

Live masterclass