What's new in Rails 5?
21st September 2015Rails 5 was announced on RailsConf past April. David Heinemeier Hansson highlighted a few of the features that we can expect with the new release of Ruby on Rails.
He also talked about his vision on how building monolithic Ruby on Rails application is not that bad. You can agree or disagree with David’s opinions, but the truth is that modern Web Applications are more than just HTML and CSS and this is clear in the Ruby on Rails world, proof of this is the inclusion of Rails API and ActionCable
.
The web has changed and, with it, Ruby on Rails also needs to change. So let us take a quick look at what is changing for the new version, Rails 5.
Support for Ruby 2.2.2 or newer
Rails 5 is a new major version of Ruby On Rails, this implicates that the Rails API and requirements can change to deprecate, add new, and/or improve existing APIs, but also to take advantage of important changes in the Ruby language as well.
The later is the most important reason on why Rails 5 will only work with Ruby 2.2.2 or better.
In Ruby On Rails applications, we usually pass symbols
all over the place, doing this open the possibility of DOS attacks when our memory is consumed by symbols
that never get garbage collected.
Ruby 2.2.0 introduced changes in its garbage collector to be able to collect symbols
.
Another reason for Rails 5 to support Ruby 2.2.2 is to take advantage of new Incremental GC which will help to reduce memory consumption by our Rails applications.
Rails 5 also started to leak Ruby 2.0 syntax into its source. Keyword Arguments and Module#prepend are examples of the adoption of newer Ruby.
API Deprecation and Cleanup
Going through Rails commits , we will find changes related to removing code that was marked as deprecated
in previous versions of Rails.
Notable removed APIs are:
ActionMailer
#deliver
and#deliver!
methods have been removed*_path
helper in email views
ActiveRecord
- Support for
protected_attributes
gem - Support for
activerecord-deprecated_finders
gem
ActionPack assertions
assert_template
andassigns()
assertions are deprecated and moved into its own gem rails-controller-testing.
Clean up in Rails 5 also includes dead code and unnecessary tests. Also, mocha
is being removed from Rails tests in favor of plain Minitest
stubbing.
Performance improvements
With Ruby 2.2.2, Rails 5 should get an improvement in performance, less memory usage, and less time spent in GC. This alone should help alone to bring this performance boost, but it's not the only reason on why Rails 5 should perform better.
Seems that core team is determined to have a better/faster framework. Reducing object allocations, freezing immutable strings (BTW there is a discussion about having immutable strings by default in Ruby 3), removing unnecessary dependencies, and optimizing common operations have all helped to make Rails 5 faster.
The following are examples of commits focused on performance, it is worth to check them out, study, and understand the changes that have helped to reduce object allocations or code optimization because some of the lessons that can be learned in those commits can be applied to our own apps.
- Beyond Ludicrous Speed
- Speed up ActionController::Renderer
normalize_keys
by ~28% - 10X speed improvements for AS::Dependencies.loadable_constants_for_path
- Speed up code and avoid unnecessary MatchData objects
- Freeze string literals when not mutated.
So, what is new in Rails 5?
So far we have seen how Rails 5 will be faster with the introduced changes but we haven’t seen (yet) what is new in terms of functionality or new API.
#or method in ActiveRecord::Relation
Finally ActiveRecord::Relation
is getting #or
method, this will allow us to write queries with ActiveRecord
DSL
as follows:
Book.where('status = 1').or(Book.where('status = 3'))
\# => SELECT * FROM books WHERE (status = 1) OR (status = 3)
#or
method accepts a second relation as a parameter that is combined with an or
. #or
can also accept a relation in a form of model scope.
class Book < ActiveRecord::Base
scope :new_coming, -> { where(status: 3) }
end
Book.where('status = 1').or(Book.new_coming)
\# => SELECT * FROM books WHERE (status = 1) OR (status = 3)
#belongs_to is required by default
From now on every Rails application will have a new configuration option config.active_record.belongs_to_required_by_default = true
, it will trigger a validation error when trying to save a model where belongs_to
associations are not present.
config.active_record.belongs_to_required_by_default
can be changed to false
and with this keep old Rails behavior or we can disable this validation on each belongs_to
definition, just passing an additional option optional: true
as follows:
class Book < ActiveRecord::Base
belongs_to :author, optional: true
end
ActiveRecord’s attribute API
This new API adds functionality on top of ActiveRecord
models, it made possible to override an attribute type to be a different type.
Consider the case where we have a field in the database defined as decimal
but in our app we only care for the integer
part of the number. We can in our app just ignore the decimal part and format our number everywhere we need to use it to only display the integer part.
With attribute API we can do this in an easy way:
class Book < ActiveRecord::Base
end
book.quantity # => 12.0
class Book < ActiveRecord::Base
attribute :quantity, :integer
end
book.quantity # => 12
Here we are overriding the automatically generated attribute from the database schema to be cast in our model as an integer
instead the original decimal
. For every interaction of our model with the database, the attribute will be treated as a decimal
as it should be.
We can even define our own custom types just by creating a class derived from ActiveRecord::Type::Value
and implementing its contract to #cast
, #serialize
, and #deserialize
values.
Custom attributes will honor ActiveModel::Dirty
to track changes in our models. Also, these new attributes can be virtual, so there is no need to be backed by a table column.
has_secure_token landed in ActiveRecord
An ActiveRecord
model now can have token attributes in an easy way. Common scenarios for token attributes are for cases when we need to create an invitation token or a password reset token for example.
Here is an example on how this works:
class Invite < ActiveRecord::Base
has_secure_token :invitation_code
end
invite = Invite.new
invite.save
invite.invitation_code # => 44539a6a59835a4ee9d7b112
invite.regenerate_invitation_code # => true
SecureRandom
is being used to generate a 24-character token.
We can use the model generator to create needed migration for a token attribute, it requires from us to pass attribute type as token.
$ rails g model invite invitation_code:token
Within the migration, a unique index will be created for our token column.
NOTE: Kenn Ejima in the comments, let me know that has_secure_token
uses Base58
instead hex
for token generation, this will work fine with PostgreSQL database, but other databases where collation is set to case insensitive will be in trouble. Please check the issue in Github where this is being discussed.
MySQL ActiveRecord adapter gets JSON support
If you happen to run your Rails application on top of MySQL 5.7.8 then your database have a new native JSON
data type.
From Rails 5 you should be able to use this new data type within your ActiveRecord
models.
Render a template outside controllers
Rails 5 allows you to render templates or inline code outside controllers. This feature is important and useful for ActiveJob
and the new ActionCable
(we will discuss this one later).
ActionController::Renderer
is what makes this happen and it’s available in our ApplicationController
class.
Let's dig into few examples on how this works.
# render inline code
ApplicationController.render inline: '<%= "Hello Rails" %>' # => "Hello Rails"
# render a template
ApplicationController.render 'sample/index' # => Rendered sample/index.html.erb within layouts/application (0.0ms)
# render an action
SampleController.render :index # => Rendered sample/index.html.erb within layouts/application (0.0ms)
# render a file
ApplicationController.render file: ::Rails.root.join('app', 'views', 'sample', 'index.html.erb') # => Rendered sample/index.html.erb within layouts/application (0.8ms)
This is really nice but what if we need to pass assigns
or locals
to our template?
# Pass assigns
ApplicationController.render assigns: { rails: 'Rails' }, inline: '<%= "Hello #{@rails}" %>' # => "Hello Rails"
# Pass locals
ApplicationController.render locals: { hello: 'Hello' }, assigns: { rails: 'Rails' }, inline: '<%= "#{hello} #{@rails}" %>' # => "Hello Rails"
Now if we have to use route helper’s functions like root_url
or any other helper that needs access to the environment
we can do it, but what ActionController::Renderer
will receive is not a real environment
. Take in account that we might use this functionality outside HTTP request.
ApplicationController.render inline: '<%= root_url %>' # => "http://example.org/"
# Inspect ActionController::Renderer environment
ApplicationController.renderer.defaults # => {:http_host=>"example.org", :https=>false, :method=>"get", :script_name=>"", "rack.input"=>""}
# To modify this environment we have to explicitly create a renderer
renderer = ApplicationController.renderer.new(
http_host: 'michelada.io'
) # => #<#<Class:0x007fdf9985a338>:0x007fdf947981c0 @env={"HTTP_HOST"=>"michelada.io", "HTTPS"=>"off", "SCRIPT_NAME"=>"", "rack.input"=>"", "REQUEST_METHOD"=>"GET", "action_dispatch.routes"=>#<ActionDispatch::Routing::RouteSet:0x007fdf93d29450>}>
renderer.render inline: '<%= root_url %>' # => "http://michelada.io/"
Better Minitest test runner
I have been using Minitest
with Rails since test_unit
was removed from Rails. I really like the simplicity of Minitest
and I never missed anything from RSpec
, pretty much Minitest
fulfilled all my needs for testing.
The only issue that I had in the past with Minitest
in Rails, was that the runner seems so basic. This is not the case anymore, its integration with Rails has been improved.
Now when you execute bin/rails test -h
you get the following help screen with a few nice things:
- Run tests filtered by a pattern
- Execute a single test by specifying file and line number
- Execute tests in specific files/directories
$ bin/rails test -h
minitest options:
-h, --help Display this help.
-s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake
-v, --verbose Verbose. Show progress processing files.
-n, --name PATTERN Filter run on /regexp/ or string.
Known extensions: pride, rails
-p, --pride Pride. Show your testing pride!
Usage: bin/rails test [options] [files or directories]
You can run a single test by appending a line number to a filename:
bin/rails test test/models/user_test.rb:27
You can run multiple files and directories at the same time:
bin/rails test test/controllers test/integration/login_test.rb
Rails options:
-e, --environment ENV Run tests in the ENV environment
-b, --backtrace Show the complete backtrace
Finally, the reporter gives you a command to rerun failed tests
$ bin/rails test
Run options: --seed 41988
# Running:
F
Finished in 0.028552s, 35.0239 runs/s, 35.0239 assertions/s.
1) Failure:
UserTest#test_the_truth [/Users/marioch/Development/proyectos/edge/cinco/test/models/user_test.rb:5]:
Failed assertion, no message given.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
Failed tests:
bin/rails test test/models/user_test.rb:4
Turbolinks 3
Turbolinks has been part of Rails since version 4, probably one of the features that people hate it or love; there is no middle ground here.
With Rails 5 we will be receiving a new version that, with the help of HTML5 custom data attributes, we will expect better speed and rendering in our Rails applications.
The most significative change in this new version is the Partial Replacement feature. From the client side, we will be able to tell Turbolinks what content do we need to change/replace and what we don’t.
Turbolinks will look for HTML5 custom attributes data-turbolinks-permanent
and data-turbolinks-temporary
to decide the replacement strategy in our DOM
.
To trigger a replacement in the client side we could use Turbolinks.visit
or Turbolinks.replace
to update our DOM
. The difference between visit
and replace
is that the first one will issue a GET
to the server to obtain the HTML that must be used to replace our DOM
while replace
expects from us the HTML that should be used for its operation.
With both functions, we can pass a hash with an id
or an array of id
of HTML elements to change
orkeep
.
Action | Result |
---|---|
Turbolinks.visit(url, { change: ['entries'] }) | Will replace any DOM element with custom attribute data-turbolinks-temporary and any element with its id listed in change. |
Turbolinks.visit(url) | Will keep only DOM elements with custom attribute data-turbolinks-permanent and replace everything. |
Turbolinks.visit(url, { keep: ['flash'] }) | Will keep only DOM elements with custom attribute data-turbolinks-permanent and any element with its id listed in keep, everything else will be replaced. |
Turbolinks.visit(url, { flush: true }) | Will replace everything |
We can trigger the same functionality from the server-side with redirect_to
and render
, both can receive change
, keep
and flush
as options but redirect_to
can also receive turbolinks
with true
or false
to force a redirect with or without Turbolinks.
Whether you like Turbolinks or not, this might be a good time to try out and find out if it could be a good fit somewhere in your application.
Rails API
Rails API was the opinionated way to build APIs with Ruby on Rails. Rails API was a separate gem with its own generator to build the skeleton of API applications.
Its goal was to help to create fast Rails API application by removing unnecessary middleware while providing sensitive defaults for these kinds of applications.
Starting with Rails 5, Rails API is integrated into the framework, there is no need to include additional gems. To create a new API only Rails app we just pass the --api
option to rails
command.
$ rails new michelada-api --api
The new application will include ActiveModelSerializers
and will remove JQuery
and Turbolinks
gems from our Gemfile
. Also, config/application.rb
and aplication_controller.rb
files to have config.api_only = true
option and to not check for CSRF
protection, also it will be inherited from ActionController::API
.
# application.rb file
module MicheladaApi
class Application < Rails::Application
config.api_only = true
end
end
# application_controller.rb file
class ApplicationController < ActionController::API
end
If you want to learn more about creating Rails API application, here is a starter post from WyeWorks: “HOW TO BUILD A RAILS 5 API ONLY AND EMBER APPLICATION”
ActionCable
ActionCable is the new thing in Rails 5, it is a framework for real time communication over web sockets.
An ActionCable
server will expose one or more channels into where a consumer will be able to subscribe via web socket connection, each channel broadcast messages to all subscribers in realtime.
Current ActionCable
dependencies include the need of Redis and its feature PubSub. It also requires on the Ruby side faye-websocket and celluloid although these may change in the future.
An application with ActionCable
support requires of 3 basic components:
- Connection: Is a class derived from
ActionCable::Connection::Base
, this is where authorization and connection establishments happen. - Channel: This is what we will expose via web sockets and it is derived from
ActionCable::Channel::Base
- Client: Is a javascript library to become an
ActionCable
client.
ActionCable
will require from us to start an additional server to respond to web sockets connections.
If you want to explore ActionCable
, the Rails team has put together a Github repository with a sample application.
Conclusions
This is not the definitive list of changes or new features in Rails 5 but is just a quick glimpse on what can we expect on the new version.
There is no definitive date for when this might happen, but it still might happen this year. There is still 68 open pull request for Rails 5 milestone in Github.
Without a doubt, it is time to prepare our applications for the upgrade; just because of the performance improvements are worth it. If you are on Rails 4.x, the upgrade is almost painless.
View Comments