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.