Skip to content

Instantly share code, notes, and snippets.

@rap1ds
Created February 2, 2016 08:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rap1ds/7366a3e39fb3453c2cdd to your computer and use it in GitHub Desktop.
Save rap1ds/7366a3e39fb3453c2cdd to your computer and use it in GitHub Desktop.
Rails 4.1 upgrade notes

Upgrading from Rails 4.0 to Rails 4.1

CSRF protection from remote <script> tags [1/1]

Or, “whaaat my tests are failing!!!?” or “my <script> widget is busted!!”

Cross-site request forgery (CSRF) protection now covers GET requests with JavaScript responses, too. This prevents a third-party site from remotely referencing your JavaScript with a <script> tag to extract sensitive data.

This means that your functional and integration tests that use

get :index, format: :js

will now trigger CSRF protection. Switch to

xhr :get, :index, format: :js

to explicitly test an XmlHttpRequest.

Note: Your own <script> tags are treated as cross-origin and blocked by default, too. If you really mean to load JavaScript from <script> tags, you must now explicitly skip CSRF protection on those actions.

  • [X] We dont’t have them

Spring [0/1]

If you want to use Spring as your application preloader you need to:

  1. Add gem 'spring', group: :development to your Gemfile.
  2. Install spring using bundle install.
  3. Springify your binstubs with bundle exec spring binstub --all.

NOTE: User defined rake tasks will run in the development environment by default. If you want them to run in other environments consult the Spring README.

  • [ ] Spring is interesting, but let’s have a look at it later

config/secrets.yml [1/1]

If you want to use the new secrets.yml convention to store your application’s secrets, you need to:

  1. Create a secrets.yml file in your config folder with the following content:
    development:
      secret_key_base:
    
    test:
      secret_key_base:
    
    production:
      secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
        
  2. Use your existing secret_key_base from the secret_token.rb initializer to set the SECRET\_KEY\_BASE environment variable for whichever users running the Rails application in production mode. Alternatively, you can simply copy the existing secret_key_base from the secret_token.rb initializer to secrets.yml under the production section, replacing ‘<%= ENV[“SECRET\_KEY\_BASE”] %>’.
  3. Remove the secret_token.rb initializer.
  4. Use rake secret to generate new keys for the development and test sections.
  5. Restart your server.
  • [X] Added secrets.yml

Changes to test helper [1/1]

If your test helper contains a call to ActiveRecord::Migration.check_pending! this can be removed. The check is now done automatically when you =require ‘rails/test_help’=, although leaving this line in your helper is not harmful in any way.

  • [X] rails/test_helper is now required in spec/spec_helper.rb

Cookies serializer [2/2]

Applications created before Rails 4.1 uses Marshal to serialize cookie values into the signed and encrypted cookie jars. If you want to use the new JSON-based format in your application, you can add an initializer file with the following content:

Rails.application.config.action_dispatch.cookies_serializer = :hybrid

This would transparently migrate your existing Marshal-serialized cookies into the new JSON-based format.

When using the :json or :hybrid serializer, you should beware that not all Ruby objects can be serialized as JSON. For example, Date and Time objects will be serialized as strings, and =Hash=es will have their keys stringified.

class CookiesController < ApplicationController
  def set_cookie
    cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014
    redirect_to action: 'read_cookie'
  end

  def read_cookie
    cookies.encrypted[:expiration_date] # => "2014-03-20"
  end
end

It’s advisable that you only store simple data (strings and numbers) in cookies. If you have to store complex objects, you would need to handle the conversion manually when reading the values on subsequent requests.

If you use the cookie session store, this would apply to the session and flash hash as well.

  • [X] Added an environment variable which let’s change the cookie serializer to :hybrid or :json
  • [X] Added UPGRADE notes

Flash structure changes [1/1]

Flash message keys are normalized to strings. They can still be accessed using either symbols or strings. Looping through the flash will always yield string keys:

flash["string"] = "a string"
flash[:symbol] = "a symbol"

# Rails < 4.1
flash.keys # => ["string", :symbol]

# Rails >= 4.1
flash.keys # => ["string", "symbol"]

Make sure you are comparing Flash message keys against strings.

  • [X] I’m pretty sure we don’t access the Flash like this

Changes in JSON handling [0/0]

There are a few major changes related to JSON handling in Rails 4.1.

MultiJSON removal [1/1]

MultiJSON has reached its end-of-life and has been removed from Rails.

If your application currently depend on MultiJSON directly, you have a few options:

  1. Add ‘multi\_json’ to your Gemfile. Note that this might cease to work in the future
  2. Migrate away from MultiJSON by using obj.to_json, and JSON.parse(str) instead.

WARNING: Do not simply replace MultiJson.dump and MultiJson.load with JSON.dump and JSON.load. These JSON gem APIs are meant for serializing and deserializing arbitrary Ruby objects and are generally unsafe.

  • [X] Removed MultiJSON from gem file

JSON gem compatibility

Historically, Rails had some compatibility issues with the JSON gem. Using JSON.generate and JSON.dump inside a Rails application could produce unexpected errors.

Rails 4.1 fixed these issues by isolating its own encoder from the JSON gem. The JSON gem APIs will function as normal, but they will not have access to any Rails-specific features. For example:

class FooBar
  def as_json(options = nil)
    { foo: 'bar' }
  end
end

>> FooBar.new.to_json # => "{\"foo\":\"bar\"}"
>> JSON.generate(FooBar.new, quirks_mode: true) # => "\"#<FooBar:0x007fa80a481610>\""

New JSON encoder [2/2]

The JSON encoder in Rails 4.1 has been rewritten to take advantage of the JSON gem. For most applications, this should be a transparent change. However, as part of the rewrite, the following features have been removed from the encoder:

  1. Circular data structure detection
  2. Support for the encode_json hook
  3. Option to encode BigDecimal objects as numbers instead of strings

If your application depends on one of these features, you can get them back by adding the =activesupport-json_encoder= gem to your Gemfile.

  • [X] Updated DelayedJob gem
  • [X] Make sure this can be rolled back: Can be, after DJ update

JSON representation of Time objects [1/1]

#as_json for objects with time component (Time, DateTime, ActiveSupport::TimeWithZone) now returns millisecond precision by default. If you need to keep old behavior with no millisecond precision, set the following in an initializer:

ActiveSupport::JSON::Encoding.time_precision = 0
  • [X] Change test (plan_webhook_spec.rb:117) that break because wrong precision

Usage of return within inline callback blocks [1/1]

Previously, Rails allowed inline callback blocks to use return this way:

class ReadOnlyModel < ActiveRecord::Base
  before_save { return false } # BAD
end

This behavior was never intentionally supported. Due to a change in the internals of ActiveSupport::Callbacks, this is no longer allowed in Rails 4.1. Using a return statement in an inline callback block causes a LocalJumpError to be raised when the callback is executed.

Inline callback blocks using return can be refactored to evaluate to the returned value:

class ReadOnlyModel < ActiveRecord::Base
  before_save { false } # GOOD
end

Alternatively, if return is preferred it is recommended to explicitly define a method:

class ReadOnlyModel < ActiveRecord::Base
  before_save :before_save_callback # GOOD

  private
    def before_save_callback
      return false
    end
end

This change applies to most places in Rails where callbacks are used, including Active Record and Active Model callbacks, as well as filters in Action Controller (e.g. before_action).

See this pull request for more details.

  • [X] Removed return statements from inline callbacks. Found 2.

Methods defined in Active Record fixtures [1/1]

Rails 4.1 evaluates each fixture’s ERB in a separate context, so helper methods defined in a fixture will not be available in other fixtures.

Helper methods that are used in multiple fixtures should be defined on modules included in the newly introduced ActiveRecord::FixtureSet.context_class, in test_helper.rb.

module FixtureFileHelpers
  def file_sha(path)
    Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
  end
end
ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers
  • [X] We don’t use fixtures

I18n enforcing available locales [1/1]

Rails 4.1 now defaults the I18n option enforce_available_locales to true. This means that it will make sure that all locales passed to it must be declared in the available_locales list.

To disable it (and allow I18n to accept any locale option) add the following configuration to your application:

config.i18n.enforce_available_locales = false

Note that this option was added as a security measure, to ensure user input cannot be used as locale information unless it is previously known. Therefore, it’s recommended not to disable this option unless you have a strong reason for doing so.

  • [X] This was `true` already. Removed the value, no need to duplicate default.

Mutator methods called on Relation [1/1]

Relation no longer has mutator methods like #map! and #delete_if. Convert to an Array by calling #to_a before using these methods.

It intends to prevent odd bugs and confusion in code that call mutator methods directly on the Relation.

# Instead of this
Author.where(name: 'Hank Moody').compact!

# Now you have to do this
authors = Author.where(name: 'Hank Moody').to_a
authors.compact!
  • [X] I’m pretty confident we are not using mutators like this

Changes on Default Scopes [1/1]

Default scopes are no longer overridden by chained conditions.

In previous versions when you defined a default_scope in a model it was overridden by chained conditions in the same field. Now it is merged like any other scope.

Before:

class User < ActiveRecord::Base
  default_scope { where state: 'pending' }
  scope :active, -> { where state: 'active' }
  scope :inactive, -> { where state: 'inactive' }
end

User.all
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'

User.where(state: 'inactive')
# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

After:

class User < ActiveRecord::Base
  default_scope { where state: 'pending' }
  scope :active, -> { where state: 'active' }
  scope :inactive, -> { where state: 'inactive' }
end

User.all
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'active'

User.where(state: 'inactive')
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' AND "users"."state" = 'inactive'

To get the previous behavior it is needed to explicitly remove the default_scope condition using unscoped, unscope, rewhere or except.

class User < ActiveRecord::Base
  default_scope { where state: 'pending' }
  scope :active, -> { unscope(where: :state).where(state: 'active') }
  scope :inactive, -> { rewhere state: 'inactive' }
end

User.all
# SELECT "users".* FROM "users" WHERE "users"."state" = 'pending'

User.active
# SELECT "users".* FROM "users" WHERE "users"."state" = 'active'

User.inactive
# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
  • [X] We had only one default_scope definition and it didn’t have this problem

Rendering content from string [1/1]

Rails 4.1 introduces :plain, :html, and :body options to render. Those options are now the preferred way to render string-based content, as it allows you to specify which content type you want the response sent as.

  • render :plain will set the content type to text/plain
  • render :html will set the content type to text/html
  • render :body will not set the content type header.

From the security standpoint, if you don’t expect to have any markup in your response body, you should be using render :plain as most browsers will escape unsafe content in the response for you.

We will be deprecating the use of render :text in a future version. So please start using the more precise :plain, :html, and :body options instead. Using render :text may pose a security risk, as the content is sent as text/html.

  • [X] Changed two render :text statements to render :plain

PostgreSQL json and hstore datatypes [1/1]

Rails 4.1 will map json and hstore columns to a string-keyed Ruby Hash. In earlier versions, a HashWithIndifferentAccess was used. This means that symbol access is no longer supported. This is also the case for store_accessors based on top of json or hstore columns. Make sure to use string keys consistently.

  • [X] We are not using PostgreSQL

Explicit block use for ActiveSupport::Callbacks [1/1]

Rails 4.1 now expects an explicit block to be passed when calling ActiveSupport::Callbacks.set_callback. This change stems from ActiveSupport::Callbacks being largely rewritten for the 4.1 release.

# Previously in Rails 4.0
set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }

# Now in Rails 4.1
set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
  • [X] I didn’t find any usage of set_callback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment