📜 ⬆️ ⬇️

From Rails 4 to Rails 5: how it was

Once there was a cloud service provider and he wanted to keep up with progress. And he decided to upgrade from Rails 4.2.8 to Rails 5.0.2. And how it was, that fell off along the way, that vdarilo on the forehead with acceleration and what experience from this carried out - read under the cut.


I will narrate separately, since most of the features and problems are in no way connected with each other. Therefore, if you meet something dull or famous - feel free to move on to the next item, it may be more interesting there.


The purpose of the story: there will be no universal transition algorithm, I just want to share the information found and the details of varying degrees of intimacy about why you can’t just go to Rails 5 by correcting versions in Gemfile.


Anamnesis


Our application is average: Ruby 2.2.3, three with a tail, hundreds of gems, ~ 1500 action games, postgres, elastic, radish. In general, nothing special. Is that the tests covered by 90% +. This, it seems, is not very typical for some modern applications.


Start over


Although Rails 5.2 is already looming on the horizon, no one is in a hurry to update the major version. Therefore, the Internet is not so much information about the features of the transition, as we would like. There is a more or less informative description here . There are reductions for 5.0 and 5.1 (by the way, who won't read them yet?). There is off-road with the main steps to upgrade to Rails 5. It’s good, but I want more information, especially regarding gems, their compatibility and other facilities, which usually fall off during updates. Although, in fairness, if we had studied it in more detail, we would not have walked through a pair of noble grabeleks (they will be discussed below). Therefore, further I will mention some points from the guide (that is, some basic changes), which, however, turned out to be quite important for us.


Causes major problems or instead of TL; DR



Gems update


For something to fall, you need to update something. Naturally, you need to start with the rails heme and its dependencies. Here you can hang on a day or two, and there are no recipes. Just a lot of dancing with a tambourine around Gemfile , Gemfile.lock and a gradual understanding of the fact that the update will need even a good half of everything that is in the project, so that at least it will be bundled. The stages are as follows:



Tin as it is


Now at odds with everything.


alias_method_chain cut


For those who still do not know: now you need to do prepend . In short: this thing adds a module after the class, and not before, like the well-known include . As a bonus, we get super instead of with / without methods. Details and examples look here .


Many new default configs


They are generated via rails app:update . But this problem is very dull and just creates files, and if you already had one of them, merge yourself and let the force come with you.


Heme device_async


For new versions of the device, it is not needed and is replaced by a couple of lines of code by off-mantle . In fairness, it works starting with Rails 4.2, but in the 5th rails the gem finally stops working and you still need to rewrite everything to ActiveJob.


Heme cancan


It needs to be replaced by CanCanCan . Since CanCan is running out of support and a cloud of deprecation is pouring in, which will become a problem in Rails 5.1. In general, there should be no big replacement problems. We had our CanCan endings under inherited_resources , so we suffered a bit more.


Heme grape-swagger


We had extended patched option. He was able to substitute the API-key in the request. But the drinker in the last major version of the Swagger happened notable. Therefore, after switching to 3.0, we could not find any familiar landmarks and the old patch was stupid to insert nowhere. As a result, it turned out that there is now a documented feature for custom http-headers, but it didn’t take off and had to be a little contributory . Now everything works.


Insert invalid objects in has_many association


When the association of an invalid object is inserted into has_many, everything falls in Rails 5 (the object is persisted and invalid we only made it in memory). Previously there was a preservation and the original object from the database got into association, and the changes from the memory either skipped (or skipped validation), or xs that happened to it, did not dig deep.


concat for Relation


It is somewhere delhi and it does not work anymore.


Not a bug, but deprecation about uniq


Uniq was replaced with distinct and in 5.1 they finally uniq for Reilation. As I understand it, it finally came to the realization that Relation, which mimics Array, is not a feature, but a lot of crap.


Heme postgres_ext


Need to decisively cut. All that he can do is quite simply replaced with raw-sql and / or Arel. The heme itself, for the time being, is not compatible with Rails 5. When trying to use it in new rails, we get errors in unexpected places, for example, when calling count on an STI class:


 ArgumentError: wrong number of arguments (given 1, expected 2) # /Users/username/.rvm/gems/ruby-2.3.3@gemset/gems/arel-7.1.4/lib/arel/visitors/reduce.rb:12:in `visit' # /Users/username/.rvm/gems/ruby-2.3.3@gemset/gems/postgres_ext-3.0.0/lib/postgres_ext/arel/4.1/visitors/postgresql.rb:22:in `block in 

Simple_form heme


For some reason, almost nothing happened to us. He just continued to work. The opportunity to do simultaneously include_blank and require for the field just fell off.


Miscellaneous around ActiveRecord



params in controllers


Now this is not HashWithIndifferentAccess , but an independent class ActionController::Parameters , which mimics a hash for backward compatibility, but swears strongly if it is used as a hash, that is, if they do merge , update , etc. That is, if I correctly understood the idea, params two goals: to store what has come from the client and to filter the content using Strong Parameters . No other modifications are needed on it. In other words, modifying data directly in params , exactly like adding yours there, is a bad form. Make a separate object for these actions, either call params.to_h or even params.to_unsafe_hash and after that work with the hash as before.


Adding errors to the model through errors [] =


ActiveModel::Errors#[]= cut in Rails 5.1, at the end you need to use model.errors.add(:name, "can't be blank") . Messages in the logs about this, of course, are present.


Little about arel


If in eq (and, most likely, in any similar predicate) from Arel for the id field (with other fields everything is OK) to transfer the object itself, then in 4 rails an idish (pk) pulled out of this object and was substituted into the query, and in the 5th rails this does not happen. Instead of trying to pull pk out of an object, it simply substitutes NULL and it turns out something like "account_id = NULL". Example:


 #  Rails 4 MyModel.arel_table[:my_field].eq(MyModel.first).to_sql => "my_models"."my_field" = 1 MyModel.arel_table[:id].eq(MyModel.first).to_sql => "my_models"."id" = 1 #  Rails 5 MyModel.arel_table[:my_field].eq(MyModel.first).to_sql => "my_models"."my_field" = 1 MyModel.arel_table[:id].eq(MyModel.first).to_sql => "my_models"."id" = NULL 

Therefore: try to always explicitly indicate the value.


About skip_callback


skip_callback(:create, :after, :my_method) in 4 rails allows you to transfer any parameters and does not fall even if there is no such method in the chain of callbacks, in 5 rails a similar method raises an exception if you have not found a method that need to skipnut. Therefore, strange falls can happen and will have to delve into industrial archeology in order to understand "was there a boy?".


Little revolution in render


Change :text and other formats to an explicit indication of mime_type . Replace :nothing on :head and so on. That is, they return to their origins and make the names closer to the essence, and not accessible to any domohozkoy.


Changing the test run order


Now the default order of the run :random . If your tests depend on the execution sequence, this is not very good, but returning the old behavior is quite simple:


 Rails.application.configure do config.active_support.test_order = :sorted end 

Observers


They are in the 5th rails so far (or already) no one supports. The original observers stopped at 4 rails. In the wizard they have declared support for the 5th, but nothing works. There is some Japanese with an alternative to observers for the 5th rail (which is very similar to copy-paste and rebranding), but this thing does not work either. The best way out is to use native callbacks from AR.


ActiveRecord bugs


Found a couple:



Heme shoulda-matchers


Updated it to 3.1.1 and povladilo many different little things:



Migrations


Changed the way of forming the names of the indices and a few more things that completely broke backward compatibility. Therefore, it is necessary to explicitly indicate to all migrations in which version they were created. Specify via class parameter:


 class OldMigrationName < ActiveRecord::Migration[4.2] ... end class NewMigrationName < ActiveRecord::Migration[5.0] ... end 

It was also given the opportunity to specify foreign_key in options for references . And the default settings of the generator have changed a little: now it makes pk (which id) by default not an integer, but uuid.


For some reason, we also had problems defining models within migrations. That is, in the form of the form:


 class SomeMigration < ActiveRecord::Migration class StubForModel < ActiveRecord::Base has_one :something end def up StubForModel.destroy_all end def down # do nothing end end 

someone strongly swears at what Base cannot find. Until you figure out the cause, so be vigilant. As we will understand - we will update the description.


The sequence of callbacks in the controller


The csrf token check (protect from forgery) in Rails 4 always went up and was executed by the first callback in the controller. In Rails 5, it does not go anywhere and runs according to where it is defined and if you need to raise it, you must explicitly specify prepend: true .


All is good, but we, it seems, did not carefully read the guides, the links to which resulted in the beginning. We just fell off authentication. We dug up the whole device and almost believed in magic. It turned out that at first the device successfully authenticates and cuts the token from the parameters, and then comes protect from forgery and strongly resents the curve (actually missing) token.


Mankipatchi


"His example to others is science ..."



Why am I all this: core_ext should not be. Totally. And the transition to the new rails is an excellent reason to start with them and try to cut as much as possible, and look at everything else with a very picky look and recall the history (undoubtedly very colorful) preceding the appearance of this extension. So it will be much easier to understand when it will fall off in the transition process.


Ruby version upgrade


Honestly, we are overwhelmed. Vigorously sticking 2.4.0 after 2.2.3, we grabbed a bunch of unknown garbage and decided not to quit and go into two stages: first Ruby 2.3.3, in which there is nothing very revolutionary, plus the transition to new rails. And then - update Ruby. Therefore, so far we can not say anything about the new cut. Unless there is an opinion of experienced comrades that if you have compiled native extensions for gems, then you have bypassed most of the transition problems and there should not be any serious obstacles.


Magic (problem to think about if you have time and desire)


Regardless of the transition to new rails, just a strange code, which was dug up during the transition and that worked, although it should not (summary of the three files of AR-classes):


  class Base TYPES = {first: First, second: Second} end class First < Base; end class Second < Base; end 

This thing can not work according to all the laws of the genre (and in general it does not work on clean projects under Rails 4/5). Since when accessing someone from the heirs, the autoload goes into the cyclic loading of classes and falls. If you first refer to the base class, and then to the heirs, then everything is fine. But for some reason we worked in Rails 4, but fell off in Rails 5, although we did not find an explicit reference to Base to load it earlier than all the others, and we didn’t touch anything related to autoload at all. If someone knows more ways how it can work or what interesting happened to the autoload in Rails 5, please let us know.


the end


Thank you for reading. I hope at least something will come in handy (or has already come in handy) and we have at least a little bit saved you time to go.


Please feel free to share your knowledge and experience on the topic in the comments and not so much.


')

Source: https://habr.com/ru/post/326706/


All Articles