Common mistakes in Project Ruby on Rails

Common mistakes in Project Ruby on Rails

Hello everyone, I'm Raymond from the TAX team at MFV. I'm excited to share some valuable insights that can greatly assist you in your work. Today, I would like to discuss the topic of "Common Mistakes in Ruby on Rails Projects." I believe that this discussion will provide you with valuable knowledge and help you avoid pitfalls in your development process. I hope you find this information beneficial.
Common mistakes in Project Ruby on Rails

As a Ruby on Rails (ROR) developer, you have probably heard that "Ruby on Rails will not be able to meet the development needs of an Enterprise-level project," right? However, I do not agree with this viewpoint because, in the current Enterprise-level project, where micro-services (SaaS projects) are preferred, it is not advisable to limit a project to a single language, regardless of the language itself.

However, I still to be able to understand why ROR may not be as popular for Enterprise-level projects. One of the common reasons I often come across is that ROR developers sometimes make mistakes from the beginning when building and developing projects. Below are some common mistakes I frequently observe when working on a ROR project.

1. Not following Code convention

Imagine Ruby as a human language and Rails as a country, so when you live in a new place, how would you start?

My approach is as follows: First, I would learn basic words and then try to learn the pronunciation and grammar. The next step, I need to obey the laws and use the commonly spoken language in that country with some general conventions to facilitate living and working.

In programming, my approach is the same: Learn the basic syntax and then follow the code conventions in the program language and framework before trying to use them for programming. Following conventions from the beginning helps me communicate easily within the code with other developers. Besides saving a lot of time in reading and understanding code, following conventions also helps me avoid mistakes in the code.

For example:

In work, my team has some conventions for naming functions as follows:

  • Use nouns or noun phrases for functions with clear output other than true/false.

  • Use verbs or sentences starting with verbs for functions with true/false output or only return. (edited) 

def params_after_processed
     # return params
end

def process_params
   # return
end

With this rule (convention), you can see that our team will have no trouble finding functions and can easily determine the output of each function without having to read through all the lines of code. 

2. Not defining responsibilities for each layer

The strength of Ruby in Rails is rapid development with the MVC model. Additionally, we also usually implement layers such as Service, FormObject. However, after dividing the class, most of us ignore the factor that clearly defines the task for each class that has been divided.

I used to join to rebuild from scratch in a project because it was too difficult to maintain or add any new features. And the reason for this cost is that the project has loop circles that don't have an end. For me, this is a valuable lesson for considering the need to clearly define responsibilities for each layer from the beginning.

And here are the responsibility for each layer in a project that I have implemented:

 

Layer's name

Responsibility

Level

Controller

Only use for rendering and returning HTTP statuses.

0

Background job, Group service

Call and catch the errors from the Service layer.
Don't try to push the business logic.

1

Service

Process the business logic and call model to save.

2

Validation (FormObject or Dry::Validation)

Use for validation of input data before saving to DB or processing business logic.

If FormObject, we can call the model to save DB.

3

Model

Only use to save DB and mapping data from DB. Additionally, we can set the validations of the relationship in DB.

4

 

(I will have a post for the topic Layers in Rails)

You can see that by assigning responsibility to each layer, if there are any bugs or issues, we can quickly determine where they are and their impact by tracing back from level 4 -> 0.

3. Abusing DB transactions

This is another mistake that our team made when developing in ROR and caused many incidents related to DB Deadlock.

As the Dev, we usually try to open DB transactions with very small scopes that are not worth worrying about. However, when the logic grows over time, we try to push the code to process the logic and it makes accidentally the commit time of a DB transaction become longer, leading to the consequence of DB Deadlock. After receiving some negative feedback from the boss, our team sat down to research and agree on the principles of using DB transactions:

  • Avoid calling 3rd party within a DB transaction.

  • Create and assign all data to variables and only bring the logic that depends on the record's id into the DB transaction.

  • Avoid calling functions within a DB transaction.

  • Clearly define the flow of tables and only put data that truly depends on each other within the DB transaction.

For example:

We have the context: After the user bought successfully at POS, POS will send the request with data including user info and order id, we need to get order info from 3rd party (SaleOrderService) by id and save this order if the user info did register and accumulate points based on the formula for the user. Then, we send the notice to the app of the user.

 
# before refactor
def submit
  return if @user.nil?

  order =  SaleOrderService.get_order(@order_id)
  return if order.nil?

  ActiveRecord::Base.transaction do
    user_order = Order.new(**order, user: @user)
    user_order.save!

    save_point_of(user_order) # calculate point based on formula
  end
end

# after refactor
def submit
  return if @user.nil?

  order =  SaleOrderService.get_order(@order_id)
  return if order.nil?

  user_order = Order.new(**order, user: @user)  
  point = point_of(user_order) # calculate point based on formula
  
  ActiveRecord::Base.transaction do
    user_order.save!
    point.save!
  end
end

Looking at the example, we can handle the information before saving it outside the DB transaction and reduce the waiting time for a transaction commit.

4. Refactoring without plan

The mindset of improving code is highly commendable. However, refactoring everywhere and anytime must be avoided. It will take time to test the impact after each refactoring phase.

Refactoring should only happen when you ensure the following:

  • The current code must have a high code coverage (our team sets the target at 90%).

  • The logic in the code must have clear documentation.

  • The impact of modifying that file must be within the allowed limits in terms of development and testing costs.

  • Double-check the full impact before refactoring.

5. Not caring about input/output types

It seems like ignoring the type of input or output is a habit of ROR developers because it allows for the quick development of ROR projects. However, ignoring the type of input/output actually creates technical debt in the long run.

And if you are a back-end developer, focusing on writing and exporting APIs, checking the input type before processing is mandatory to help increase the security level.

For example:

I have the authentication feature and I need a API to input OTP (6 numbers) for verify. I have the code.

def verify
  user = User.find_by(otp: permitted_params[:otp])
  ...
end

Let's imagine what happens when I send the request like this:

URL: http://example.com/login/otp
Method: POST
Headers: { ... }
Request body: { opt: [000001, 000002, 000003, 000004, ..., 999999] }

Let's give me feedback if you cannot see the issues.

6. Conclusion

Every language has its own strengths and weaknesses. Utilizing the strengths and limiting the weaknesses of a language will help us build a project with better quality. And my answer to the question "Can Ruby on Rails meet the requirements for developing Enterprise projects?" is Yes. However, we still need to consider the goals and direction of the project for further discussion because, clearly, if you want to build an AI, ROR is not a suitable choice.

More like this

Những bước đầu của microservices trên B2B SaaS
Jan 20, 2022

Những bước đầu của microservices trên B2B SaaS

Data synchronization between services
Feb 20, 2024

Data synchronization between services

Implementing Domain Driven Design
Nov 15, 2023

Implementing Domain Driven Design