What’s New and Awesome in Ruby 2?
__Note:__This blog is brought to you by our good friends at The Hybrid Group. Be sure to check them out!
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:
Faster
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.
UTF-8 By Default
All Ruby scripts now default to UTF-8 encoding. This makes the #encoding: utf-8
magic comment no longer necessary.
Keyword Arguments
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)
Lazy Enumerators
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.
Kernel#__dir__
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.
Literal Symbol Array Syntax
You can now use %i
and %I
to make an array of symbols:
%i( alpha bravo )
#=> [:alpha, :bravo]
Debugging
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.
What’s new and awesome in Rails 4?
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:
Faster
Mainly because of optimizations in Ruby 2, Rails 4 is much faster than Rails 3, particularly with startup time.
Postgres Hstore
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?
HTTP PATCH
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.
Streaming
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.
No More Dynamic Finders
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.
Strong Parameters
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.
Put Them Together
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.
For more details on Rails 4, read our related posts:
Share your thoughts with @engineyard on Twitter