TDD is essential for Ruby on Rails development: Rspec gem makes it easy

Testing is an essential part of any software development process. Ruby on Rails development is no exception, and the RSpec gem is an invaluable tool for writing tests. By utilizing TDD (Test Driven Development) with RSpec, you can ensure that your code works as expected and can be quickly and efficiently modified as needed.

I recently had the experience of working on an old Ruby on Rails project without any tests. This demonstrated to me how essential TDD is to a successful development process. In this blog post, I'll discuss why TDD is essential for Ruby on Rails development, and how the Rspec gem makes it easier.

TDD (Test Driven Development) is essential for creating robust Ruby on Rails applications. The Rspec gem is an invaluable tool that makes TDD easier and more efficient for developers.

This blog post will discuss the importance of TDD and how the Rspec gem can help make it simpler. I will also share my recent experience working on a project that did not have any tests in place, and what I learned from it. With the right tools, like Rspec, TDD can be an invaluable asset to any Ruby on Rails developer.

What is Test-Driven Development?

Test-driven development (TDD) is a software development process where tests are written for the code before the code itself is implemented. The code is then written to pass the tests, and the process is repeated as new features are added. This approach helps to ensure that the code is working as intended and helps to catch bugs early in the development process.

Tests also provide an excellent way to document changes to your codebase, which can help other developers understand what's going on more quickly. Additionally, TDD encourages more refactoring of existing code, which can help make the codebase easier to maintain in the long run.

Why is TDD important for Ruby on Rails development?

Test-driven development (TDD) is imperative for Ruby on Rails development for several reasons:

Ensures Code Quality: By writing tests before writing the code, TDD helps to ensure that the code is working as intended and helps to catch bugs early in the development process.

Facilitates change: TDD helps to ensure that the code is modular and easy to change, which is important for maintaining a large codebase over time.

Improves Design: The process of writing tests before writing the code can help developers to think more carefully about the design of their code and to write more modular, reusable code.

Increases Confidence: By having a comprehensive test suite, developers can make changes to the codebase with more confidence, knowing that their changes will not break existing functionality.

Aids in agile development: TDD is one of the core practices in agile development, which is a popular methodology for developing software in small, incremental steps. Agile development allows developers to quickly adapt to changing requirements, which is critical in today's fast-paced business environment.

Overall, TDD is a powerful tool for improving the quality, maintainability, and reliability of Ruby on Rails code, and is an invaluable practice for any Rails developer to master.

I have had the opportunity to work on several projects where TDD was used extensively and it was extremely beneficial — by using tests as a guide throughout development, we were able to quickly add new features without breaking existing functionality. TDD also made refactoring much easier because I knew that if I broke something, the tests would alert me right away. This experience has certainly made me realize just how valuable Test Driven Development is, particularly when dealing with larger projects. With the wide range of testing frameworks available for Ruby on Rails such as RSpec, MiniTest, Cucumber, etc., there is no excuse for not incorporating TDD into your development workflow — start testing your code now!

How can the Rspec gem help with writing tests?

RSpec is a popular testing framework for Ruby and Ruby on Rails applications that can help with writing tests in several ways:

Syntax: RSpec provides a simple and readable syntax for writing tests that makes it easy to understand what the test is doing and what it is checking for.

Matchers: RSpec provides a wide variety of built-in matchers, which are methods that can be used to check that the code behaves in a certain way. This allows developers to write tests that are expressive and easy to read.

Describe and Context: RSpec uses the describe and context methods for grouping related tests together. This allows for better organization and readability of test suites.

Mocks and Stubs: RSpec provides built-in support for creating mocks and stubs, which are objects that mimic the behavior of real objects in your code. This allows developers to test how their code interacts with other objects without having to use real objects.

BDD: RSpec is built on top of the Behavior-Driven Development (BDD) methodology, which emphasizes writing tests that describe the behavior of the code, rather than its implementation. This helps to ensure that the code is easy to understand and maintain.

Rails support: RSpec is a popular testing framework for Ruby on Rails development, and it provides a lot of built-in support for testing Rails-specific features, such as controllers, views, routes, and more.

Overall, RSpec is a powerful and flexible testing framework that can help developers create clear, maintainable, and reliable tests for their Ruby and Ruby on Rails applications. Test-driven development (TDD) is integral to writing high-quality tests and ensuring that code meets business requirements. TDD encourages developers to think about their tests before they start coding, and it also ensures that tests are written for each feature before the feature itself is implemented. This leads to higher-quality code and fewer bugs in production.

Additionally, TDD increases confidence in code by ensuring that changes do not break existing functionality. Finally, TDD helps prevent “technical debt” from accumulating by making sure that tests are written for newly added features as they are developed. All in all, Test Driven Development is an invaluable tool for successful Ruby on Rails development and RSpec makes it easier.

How can I start doing TDD in Ruby on Rails with RSpec?

Here is a general outline of how to start doing Test-Driven Development (TDD) with RSpec in a Ruby on Rails application:

Install RSpec: You can install RSpec by adding the rspec-rails gem to your Gemfile and running bundle install.

Generate the RSpec configuration files: Run the following command to generate the RSpec configuration files: rails generate rspec:install

Write a test: Before writing any code, write a test that describes the behavior that you want to implement. RSpec tests are typically organized in the spec folder and are named with the format *_spec.rb.

Run the test: Run the test by executing RSpec command. Since you haven't written any code yet, the test should fail.

Write the code: Write the code to make the test pass. Keep in mind that the goal is to make the test pass and not to add unnecessary complexity.

Refactor: Once the test is passed, you can refactor the code if needed.

Repeat: Continue writing tests for new features or changes, and repeat the process.

It's important to note that TDD is an iterative process, and it may take some time to get used to. It's also critical to have a thorough understanding of the project's requirements. This will enable you to have a clear understanding of the problem you're trying to solve before you start writing tests.

Additionally, it's wise to start with the smallest possible feature or unit of code and test that before moving on to more complex features. And also, it is critical to have a comprehensive testing strategy in place, covering different types of tests such as unit tests, integration tests, acceptance tests, and more.

Test-driven development also encourages developers to pay attention to design decisions early on. For example, when tests are written first, developers must think about software architecture right away instead of waiting until later stages. Furthermore, test-driven development allows teams to catch bugs earlier in the development process and develop their applications faster. Additionally, test-driven development encourages developers to write modular code so that individual components can be tested independently of one another.

Lastly, test-driven development provides greater confidence when making modifications since developers know that tests will alert them to any unexpected side effects. In conclusion, test-driven development is an invaluable practice that helps ensure quality software development.

Using conventional commits to automate explicit commit stories

I'm constantly trying to improve as a developer. This entails refining the coding process and automating coding-related chores. My ability to write commit messages is one area where I have made progress.

When pressed for time, one terrible tendency is to disregard documentation. When missing paperwork is found, it becomes significant.

That's one thing I'm working on being better at. How can we make better commit messages since they are a form of documentation that is most frequently disregarded?

Enhance your git log with conventional commits introduced me to Conventional Commits. I enjoyed the essay and have been manually writing traditional commits for the past few days.

In all of my projects, I wanted to automate the enforcement of traditional commit messages. I used Google to get the tools I required to set it up for all commit messages globally:

Writing a conventional commit message is made easier with Commitizen and Commitlint. GIT global hooks can be used to verify that each commit is formatted correctly. fail if the commit message doesn't adhere to the guidelines

Commitizen

The useful script Commitizen prompts and constructs the components of a typical commit statement.

Installing it is the first step: (Extract from README)

the command npm install -g commitizen

Install the commitizen adaptor you choose worldwide, such as cz-conventional-changelog:

The command is: npm install -g cz-commitizen-changelog

Make a .czrc file with the commitizen adapter's path in it.

echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc

Instead of using 'git commit' after installing Commitizen, use 'git cz' to create a commit.

First, the type of commit will be requested:

Commit Type

The commit subject, description, breaking changes, and open problems are then requested:

Commit Details

We can move on to the following step now that the commit is prepared.

Commitlint

A tool called Commitlint will check your commit message to see if it complies with a standard you specify.

It's just as simple to install globally as Commitizen. Some configuration is required for items not covered in the README.

Install the CLI and the desired configuration:

npm install -g @commitlint/cli @commitlint/config-conventional

Create a global config file:

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > ~/commitlint.config.js

That's all. You can test it to make sure the install is correct:

echo 'must fail' | commitlint

If you want to see a passing message try:

echo 'fix(broken links) fix broken links in homepage'

We are now prepared to connect everything together.

Git Hook

Until it is incorporated into a git hook, commitlint doesn't do much on its own. If the commit message deviates from the norm, we shall reject it from within this hook.

Similar to the earlier packages, I wanted to configure this once for all of my projects rather than having to go through each one individually. I consequently implemented a global git hook. (Subject for another post)

I had global git hooks set up, so I added a commit-msg file with a simple script. This script compares it with commitlint, and fails if the message is not following the rules:

#!/bin/bash

cat $1 | commitlint

Conclusion

You may enable conventional commit in your workflow with these three simple actions. If you are in the practice of making atomical commits, you will benefit the most from them. After utilizing this method for a while, you'll see its beauty more clearly. Run git log —one-line to view the gorgeous list of commits with all the relevant information.

Updating redux-form fields using bindActionCreators

I have a redux-form with a dropdown that depends on the value chosen from another dropdown.

I have a filter approach that looks ideal for condensing the alternatives from the state to fill my dependant dropdown.

I discovered that in order to change the value in the store, I needed to choose a dropdown item from the dependant dropdown.

I learned about redux-form [Action Creators] through this source: https://redux-form.com/6.0.0-alpha.4/docs/api/actioncreators.md/. They are internal processes from redux-form that allow us to dispatch them as necessary.

When filtering the dependent dropdown options, I was interested in changing that field. For situations like this, redux-form provides the change method.

It was quite easy to set up.

import { bindActionCreators } from 'redux'
import { Field, change } from 'redux-form'

// other imports ...
const mapDispatchToProps = (dispatch) => ({
  updateField: bindActionCreators((field, data) => {
    change(FORM_NAME, field, data)
  }, dispatch)
})

Then using it:

this.props.updateField('dependent_field_name', newValue)

Something important to note and quoting redux's documentation on bindActionCreators:

The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it.

Using Service Objects in Ruby on Rails

When an application reaches a certain scale, architectural concerns start to surface. Basic guidelines for clean code are present in Rails and follow the Model View Controller structure:

  • No fat models; prevent them from becoming bloated.
    Keep views simplistic; don't include any logic.
    Don't overload the controllers with weight; keep them lean.

It also begs the fundamental question, "Where do I put all that code?"

Let's talk about service objects.

Ruby service objects can take the shape of a class or module that executes an operation and helps remove logic from other parts of the MVC framework. As an easy illustration, consider the following controller:

class PostsController < ApplicationController
  def create
    @title = params[:title]
    @content = params[:content]
    @post = Post.new(title: @title, content: @content)
    if @post.save
      flash.notice = 'Post saved'
      render @post
    else
      flash.alert = flash_error_message(@post)
      redirect_to :new
    end
  end
end

Once you comprehend the design pattern, extracting part of this into a service object is straightforward.

  • create a services folder in the Rails' app folder
  • create the service object file, in this example create_post.rb
  • extract the functionality to the CreatePost class/module
  • reload the Rails app and try it

Service objects as modules

I made a service that closely resembles a factory design pattern using a module approach:

module CreatePost
  class << self
    def execute(params)
      title = params[:title]
      content = params[:content]
      post = Post.new(title: title, content: content)
    end
  end
end

Consequently, the controller became much easier to control:

class PostsController < ApplicationController
  def create
    @post = CreatePost.execute(params)
    if @post.save
      flash.notice = 'Post saved'
      render @post
    else
      flash.alert = flash_error_message(@post)
      redirect_to :new
    end
  end
end

Service objects as classes

We utilize classes when we need to hold instance variables and other methods. Our code might be changed as follows using a class:

class CreatePost
  def initialize(params)
    @title = params[:title]
    @content = params[:content]
  end

  def call
    Post.new(title: @title, content: @content)
  end
end

The controller's code would be:

class PostsController < ApplicationController
  def create
    @post = CreatePost.new(params).call
    if @post.save
      flash.notice = 'Post saved'
      render @post
    else
      flash.alert = flash_error_message(@post)
      redirect_to :new
    end
  end
end

Organizing service objects with modules

Our "services" folder tends to expand significantly as we start using services. By employing folders and modules to build a modular framework, we can control this growth.

The variety of service objects and their many purposes in our program might be reflected in the "services" subdirectory. We utilize Ruby modules to namespace-group them.

module Post
  module Build
    def self.call(params)
      title = params[:title]
      content = params[:content]
      Post.new(title: title, content: content)
    end
  end
end

To enable Rails to load them, we must put them in folders that correspond to our module structure.

services/post/build.rb
services/post/update.rb
services/comments/build.rb
...

By doing so, we may scale our use of service objects to match the expansion of our program.