What we discovered from upgrading Rails 4.2.0 to Rails 5.0.6
What we discovered from upgrading Rails 4.2.0 to Rails 5.0.6
In this article, we would like to share our personal experiences when upgrading our Rails application from version 4.2.0 to version 5.0.6. We will also share the issues that we found before and after the upgrade.
Omise is a payments company and our priority is to maintain the service stability and steer clear of any possibility of downtime. As our codebase has been developed with Ruby on Rails, we are aware of the risks involved when upgrading to a newer version of the framework and have prepared ourselves to tackle any issue that may arise.
We have a highly tested codebase. In order to merge new code, we get at least two green lights from developers within the team. As two heads are better than one, getting a second and third person’s opinion helps verify the security of the new code and ensures that the quality is at its best.
Upgrading to Rails 5.0.6
Following our in-depth research, we decided on Rails 5.0.6 due to its stability status according to many sources. After running the upgrade in Gemfile, we found that many tests failed as we had expected.
Update Dependencies (Gems)
After experiencing that many dependencies broke upon the upgrade, we realized that Gems also needed to be updated in order to correlate with the newer version of Rails. If Gems are not updated, it can affect our tests and produce inaccurate results or can even bring the application down.
Requested parameters are no longer hash
This was one of the biggest changes that we found. Previously when requesting a parameter from clients, we would have to manually check if it was hashed or not.
response_params = params[:response]
next if response_params.kind_of?(Hash)
In Rails 5, the default object type of a requested parameter is no longer hash. It will come as an object ActionController::Parameters and we have to adapt our codes accordingly.
response_params = params[:response]
next if response_params.kind_of?(ActionController::Parameters)
There were many cases that we could not use attributes in parameters directly because it is not being hash. You will have to permit it first.
response_params = params[:response]
amount = response_params.permit(:amount)
When we upgraded to the newer version, it changed the behavior in transmitting data. It requires for us to create a permit per parameter when transmitting to other services. However, we have a shortcut without the need for creating a permit for each parameter. By permitting all parameters at once, we are able to transmit to other services without manually permitting one by one. For example, we can use this permit-all trick to pass data to Elasticsearch.
response_params = params.to_unsafe_h
ActionController::Parameters behavior changes
In previous versions of Rails (lower than 5), this is an example of a behavioral change.
val = "value"
params = ActionController::Parameters.new(val)
> {}
params["value"]
> "value"
params["abcd"]
> "value"
The above example is considered inappropriate because the older version allows us to input incorrect or unexpected values which can result in the same outcome as inputting the initiated value. In Rails 5, this action will cause an error as per below.
NoMethodError: undefined method `with_indifferent_access’ for nil:NilClass
Change request arguments to a new style
In Rails version lower than 5, we can request and send parameters and headers to prioritize parameters.
post path, params, headers
In Rails 5, we need to specify the argument name for each parameter. What’s great about this is that we can swap parameter positions with ease.
post path, params: params, headers: headers
Custom query in ActiveRecords
We could manually sort data and order from ActiveRecord before upgrading to Rails 5.
ActiveRecord::Base.send(:sanitize_sql_array, ["position(#{column}::text in ?)", ids.join(",")])
Raw query would be like this.
.order(position(id::text in 'xxxxx,xxxxx'))
But in Rails 5, we cannot manually sort and reorder the data because method does_not_support_reverse? in gems/activerecord-5.0.x/lib/active_record/relation/query_methods.rb method does not support, instead we would have to write it like this:
conditions = ids.map do |id|
key = "#{table}.#{column}"
value = ActiveRecord::Base.connection.quote(id)
"#{key}=#{value} desc"
end
klass.where(column => ids).order(conditions)
Eventually, raw query would be like this:
.order("'uid' desc,'uid' desc")
We are not sure whether it is slower or faster but it fixes this point.
Allowing single inheritance to work without model inheritance
The single inheritance of Rails usually allows us to create column types for superclass tables and to identify the class and subclass object.
class TestAccount < Account
end
first_account = Account.first
first_account.class.name
> "TestAccount"
In Rails 4, if we wanted to reach the subclass object that has a column type, we can identify with table_name.
class Tester < ActiveRecord::Base
self.table_name = "accounts"
end
But in Rails 5, we cannot identify table_name, because it would result in an error.
ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: TestAccount is not a subclass of Tester
So, we had to override class method find_sti_class like this.
class Tester < ActiveRecord::Base
self.table_name = "accounts"
def self.find_sti_class(type_name)
type_name.camelize.constantize
end
end
Upgrading to production
Upgrade to staging first
Since we upgraded to Rails 5.0.6, our CI has been building successfully for every test but we are still not 100% confident if it will defect something in future production. Once we have merged Rails 5 and our CI deploys to the staging server, an examination is required for the whole team when pulling into their branch to double check if there are any issues from backwards compatibility. We will also have our QA test and run an End-to-End script for testing after.
Since we upgraded to Rails 5.0.6, our CI has been building successfully for every test which gives us some measure of confidence. Of course, there’s always the possibility of a bug related to the upgrade that our tests weren’t designed to detect.
When to upgrade to production?
We realize the significant change of our system, and so decided to deploy to production once we are confident. After identifying and fixing the latest bug, we decided to wait at least one week for our staging server to stabilize.
Issues after upgrading to production
Rake command deprecating
While rake command is not something new for us, in Rails 5, we noticed that merging the two commands helps to improve the consistency level. This is done by enabling rails command to support all rake behavior. But the backlash for merging rake and rails is, when we still use rake commands it will run all tests rather than specific tests. For example, before upgrading to Rails 5, rake command can conduct specific tests but in Rails 5, rake command can still run but cannot conduct specific tests. Rails Team would recommend that all users should shift to rails command.
Issues from outside services with needed permit parameters
Since request parameters are no longer hashed, we have to remember to permit requested parameters that are being sent to outside services otherwise it would send nothing.
บทความอื่นๆ
ขอบคุณ!
ขอบคุณที่ลงทะเบียนกับโอมิเซะ