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.
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.
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.
Some of the gems used are either poorly compatible or not compatible with Rails 5 at all, which is why they have to either be cut and replaced with the native capabilities of the rails, or to finish the gems to working condition. In any case, you need to pick them and answer the questions: "how did it work?", "Will it work?", "What should it do to make it work?". Then start to fork / contribute / patch.
Updates of major versions of gems following the major version of Rails and, therefore, major changes in interfaces, APIs, and other squiggles that gems provide. This entails mass replacements and / or re-cutting what previously worked and did not ask for food.
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:
Now at odds with everything.
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 .
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.
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.
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.
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.
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.
It is somewhere delhi and it does not work anymore.
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.
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
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.
# Model.where(content_type: SharedFile) # Model.where(content_type: SharedFile.name ) # Model.where(content_type: 'SharedFile')
ApplicationRecord
instead of ActiveRecord::Base
. That is, an intermediate abstract class appears: class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
Significantly changed the principle of database mapping in the model. In particular, this concerns the column_names
method. It no longer returns the attributes defined by the user, for example, through attribute (details can be found in the diffs of this method in AR between 4 and 5 rails). Now the concept of attributes_to_define_after_schema_loads
appeared. And now, if you have logic based on "virtual" attributes, and you want column_names
to still return everything, you need to do something like this:
def self.column_names super + attributes_to_define_after_schema_loads.keys end
The constant ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES
. As far as I know, many people used it and now there are two options: to bring her back with her hands, or to switch to using FALSE_VALUES
, which was left alive.
reload
(for force reload) is cut out. Now you need to call reload on a specific relation. For example: first_model = Model.first first_model.has_many_relation_name.reload
Similar thing for has_one associations:
second_model = Model.second second_model.reload_has_one_relation_name
Stop before_
callbacks when returning false
- sawn. Now you need to explicitly raise throw(:abort)
in callbacks. Here is the MR with a notable holivar about it.
The raise_in_transactional_callbacks raise_in_transactional_callbacks=
parameter is raise_in_transactional_callbacks=
, which is responsible for throwing exceptions from after_commit
/ after_rollback
in the event that false returns from them (he had previously quietly wrote a message to the log and could not throw out the action). The thing was written down in Rails 4, but, it seems, very few people attended to the rewriting. Now it's time, and soon the old behavior will be cut out.
All belong_to
have become mandatory by default. To get everything in place, you need to poke the config.active_record.belongs_to_required_by_default parameter.
There was the ActiveRecord::Relation#update
method, which allows you to update the Relation
, causing callbacks and validations. In essence, this is sugar: there will be many queries to the database and inside it is just a map. Details in mr .
LEFT JOIN
without side effects. Prior to Rails 5, the OUTER JOIN
could have been written explicitly via joins , or we could include includes + references , or equivalent eager_load . As an overhang, we get the download of all the above reports, since the main purpose of all of the above (with the exception of joins
) is to assist in solving N + 1. Now there is ActiveRecord::Relation#left_outer_joins
, which simply does the left join and nothing extra. For details, go to mr .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.
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.
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.
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?".
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.
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
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.
Found a couple:
uniq
on ActiveRecord::Associations::CollectionProxy
, which turned out to be famous and has already been fixed in 5.1;Updated it to 3.1.1 and povladilo many different little things:
email
check for case sensitive;should validate_uniqueness_of(:name)
before should validate_uniqueness_of(:name)
passed even when scope
for uniqueness
specified in the model, then the test now drops and you need to explicitly indicate should validate_uniqueness_of(:name).scoped_to(:vendor_id)
;should serialize(:metadata).as(Hash)
falls with cast_type
;allow_value('foo', 'bar', 'baz')
took only the first value of the passed arguments, and now it was fixed and began to take everything.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 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.
"His example to others is science ..."
get
dropped from stack level too deep in the cookies area. They broke a pair of light heads and for the second time almost didn’t believe in magic, until the monkipatch associated with RequestStore was excavated .csrf
token: one before the device and one after. This did not help simplify the search for a problem.ActiveRecord::Type
, which was pretty dug up in new rails: it was , it was , and they transferred it to ActiveModel
.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.
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.
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.
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