The transition from Ruby 1.9 to 2 doesn't bring as many changes as 1.8 to 1.9, but there's still a number of performance improvements, new features, and refinements added. Here's some of the cooler stuff:
Ruby 2 has a few patches that dramatically improve performance. The biggest of
these is a substantial optimization to Kernel#require
, which speeds up Rails
startup dramatically. Ruby 2 also has improved GC, VM optimizations, and
improvements to floating point operations.
All Ruby scripts now default to UTF-8 encoding. This makes the #encoding: utf-8
magic comment no longer necessary.
Previous versions of Ruby could approximate keyword arguments by using a hash as a method argument:
def test options = {}
options = {bar: 'bar'}.merge options
[options[:foo], options[:bar]]
end
test foo: 'foo'
#=> ["foo", "bar"]
This works, but it's messier than it could be, it's hard to see with a glance at the method signature what arguments this method accepts, and it's difficult to set default values for arguments.
Ruby 2's new keyword arguments feature is still backed by a hash, but it's much easier to use now. Here's the same method rewritten to make use of it:
def test foo: nil, bar: 'bar'
[foo, bar]
end
test foo: 'foo'
#=> ["foo", "bar"]
Note that though these act like keyword arguments, they're still just a hash underneath, so they're missing some functionality and flexibility that Python's keyword arguments have. For example:
def test a, b
[a, b]
end
test a: 'foo', b: 'bar'
#=> ArgumentError: wrong number of arguments (1 for 2)
Ruby 2 introduces the concept of a lazy Enumerable object. A lazy Enumerable will not consume excess memory, and will process individual elements as needed instead of the entire array/range passed to it.
As an example, this code in Ruby 1.9 would never complete:
(1..Float::INFINITY).collect { |x| x * x }.first(10)
While with Ruby 2's lazy evaluation:
(1..Float::INFINITY).lazy.collect { |x| x * x }.first(10)
#=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Lazy enumeration has drawbacks, though. Ruby 2's lazy enumerators are much slower than normal ones, up to 4 times slower. This bug report goes into more detail about the issue.
Ruby 2 introduces the __dir__
method. It returns the same result as
File.dirname(File.realpath(__FILE__))
. It's useful when loading configuration
files or modifying load paths.
You can now use %i
and %I
to make an array of symbols:
%i( alpha bravo )
#=> [:alpha, :bravo]
Ruby 2 brings support for DTrace probes, as well as a new Ruby class named
TracePoint
that allows for inspecting an app's inner workings.
Rails 4 isn't quite out yet, still at release candidate 1 as of this writing,
but you can still preview the cool new features with gem install rails --pre
.
Here's a sampling of some of the new stuff you'll find coming to Rails 4:
Mainly because of optimizations in Ruby 2, Rails 4 is much faster than Rails 3, particularly with startup time.
Rails 4 brings support for PostgreSQL's Hstore. Hstore is a schema-less key-value store inside of Postgres that allows for storing hash-like datasets directly inside of a column.
If you have a version of Postgres that supports Hstore, you can enable it by running this in a Postgres shell:
CREATE EXTENSION hstore;
Alternatively, you can put this in a Rails migration:
class EnableHstore < ActiveRecord::Migration
def up
execute 'CREATE EXTENSION hstore'
end
def down
execute 'DROP EXTENSION hstore'
end
end
Once Hstore is enabled, you'll have to create a column with a type of 'hstore'.
class AddDataToUsers < ActiveRecord::Migration
def change
add_column :users, :data, :hstore
end
end
Now you can store any type of data you want in the data
column:
User.create(name: "TestUser", data: {age: 26})
User.last.data['age'] #=> 26
User.where("data @> (:k => :v)", k: 'age', v: '26').name #=> "TestUser"
Rails 4 also includes the convenience method store_accessor
, which makes it
even easier to access and update data inside the Hstore:
class Users < ActiveRecord::Base
store_accessor :data, :age
end
User.create(name: "TestUser", age: 26)
User.last.age #=> 26
The main caveat to using Hstore is difficulty in migrating to a new database system in the future; but realistically, how often do you switch your backing database system?
PATCH is the new primary HTTP method for updating resources in Rails 4. This more correctly adheres to the principles of REST and Hypermedia APIs. For now, PUT requests still route to update actions, but this may change in a future release.
A new module introduced in Rails 4, ActionController::Live
allows for
streaming data to clients. All you need to do is include
the module in
a controller, and your app will now be enabled to stream arbitrary data.
You will, however, need to use a threaded webserver, like Puma or Rainbows!, in order to stream data. Actions from streaming controllers run in a seperate thread.
Here's an example streaming controller from the Rails 4 documentation:
class MyController < ActionController::Base
include ActionController::Live
def index
100.times {
response.stream.write "hello world\n"
}
response.stream.close
end
end
This also allows for cool technologies like Server-sent events to be used.
Engine Yard doesn’t currently support live streaming out of the box. The default server options on Engine Yard are Unicorn and Passenger, and neither of these support live streaming yet.
Previous versions of Rails included dynamic finders for models, so we could use
methods like User.find_by_email
and
User.find_or_create_by_email_and_password
. These relied on method_missing
and had messy implementations.
Rails 4 introduces new find_by
, find_or_create_by
, and
find_or_initialize_by
methods that all take hashes for arguments. For example:
# Rails 3
User.find_by_name(user_name)
User.find_or_create_by_email_and_password(email, password)
# Rails 4
User.find_by(name: user_name)
User.find_or_create_by(email: email, password: password)
These will be a big pain point while upgrading to Rails 4, but you can continue
to use them with the activerecord-deprecated_finders
gem. This is a dependency
of Rails 4 to ease the transition, but will be deprecated in Rails 4.1, and
supported until Rails 5.
attr_accessible
is the system for mass-assignment protection in Rails 3. It
worked, but it's confusing and not very flexible. Some discussion happened
around how to improve Rails' handling of mass assignment, and from this
discussion the strong_parameters
gem was born.
As an example, let's take this controller example:
class UsersController < ApplicationController
def create
@user = User.create(params[:user])
# validation, redirection, etc
end
end
In Rails 3, you would protect against unexpected input with attr_accessible
declarations in the User model:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
end
With Rails 4 and Strong Parameters, this validation moves from the model to the controller layer:
class UsersController < ApplicationController
def create
@user = User.create(user_params)
# validation, redirection, etc
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
If you don’t use the correct params, an ActiveModel::ForbiddenAttributes
exception is raised.
There's a few outstanding issues with strong parameters, notably with nested attributes. These will likely be resolved sometime soon, but you may want to watch for edge cases with complex models when updating.
So what happens when you combine Ruby 2 and Rails 4?
Your app will be:
- Faster
- More secure
- Easier to debug
Hopefully this article's gotten you excited about some of the awesome things you can expect with the new Ruby and Rails. The upgrade process is definitely not as punishing as going from Rails 2 to Rails 3. And as DHH says, the water's fine.