Rails 4: What's New

A special thanks to Andy Lindeman for proofreading this article (and of course, for putting these together in his presentation. Check out Andy's book, Upgrading to Rails 4 for more Rails 4 goodness.

Rails 4 is on the horizon, and there's lot's of new stuff to be excited about. Andy Lindeman has a great presentation called, "Rails 4 Whirlwind Tour" on Vimeo. But not everyone has 40 minutes to spare, so I put together the Cliff notes, along with some of my own commentary:

Rails.queue

This actually isn't much code, it's mostly just framework-level acknowledgement of the existence of queued jobs. It's a common format, whereby all queued systems (such as delayed_job, resque, etc) define some class that responds to #run. You can then add any class/method to the Rails queue. By default and in development, queued items will just run synchronously like anything else. By you can easily change config.queue in your environment files to be "async", or in the future, "delayed_job", "resque", etc. Thus, it'll be trivial to swap out queuing systems.

strong_parameters

attr_accessible and attr_protected are going to be gone from models. The logic for protecting mass-assignment variables will instead be moved to the controller and will be slightly different. I think this is a good change, as authentication/authorization makes the most sense at the controller level, and this goes right along with it. This also solves issues where you have some automated script, or you're in the console, changing protected attributes via mass-assignment. Obviously you should be allowed to do that, since no user input is involved, but the current implementation of model-level mass-assignment protection gets in the way.

An example of the new way in the controller:

class PostsController... def create @post = Post.create(post_params) end private def post_params # This says that params[:post] is required, but inside that, only params[:post][:title] and params[:post][:body] are permitted # Unpermitted params will be stripped out params.require(:post).permit(:title, :body) end end

Furthermore, in the above example, if you try to pass unpermitted params from the controller to the Post model, it'll raise an ActiveModel::ForbiddenAttributesError.

Tubolinks

With turbolinks enabled, in browsers that support it, regular links will be fetched via ajax, and the existing page title and document body will be updated with the parsed title and body from the response, and the URL will be manually updated via pushstate. This is similar to how jquery-mobile works. The advantage is that you can use the asset pipeline to render large, all-app-encompassing js and css files on the first request, and then the app won't have to re-parse any js or css on subsequent requests. Seems cool, but not sure how well it will work. I've come across some weird bugs in jquery-mobile due to this that I could only solve by explicitly telling jquery-mobile to treat specific links as normal, non-ajax links.

cache_digests

Page caching and action caching are gone. The way of the future is only using fragment caching in the views of a Rails app. If you need higher level caching, then use Varnish or some other http-based cache proxy. Furthermore, fragment caches will now be named based on a hash of the fragment contents, so the Rails app will automatically know to bust a cache if you change a record such that it actually changes the contents of the hash, but also if you change the actual source code markup of a fragment (which didn't automatically happen before).

ActionController::Live

You can stream stuff to the browser, so that the browser can "chunk" stuff or write stuff in a live fashion as it receives data. Warning: you'd want to use a server that handles concurrency well, like puma.

class MyController... include ActionController::Live def index 100.times { response.stream.write "hello

" } end end

Then, you could have a websocket and some javascript or whatever on the front-end that processes the data in real-time. Apparently this could already be done in Rails 3.2, but only with view HTML, not with arbitrary data, like strings, or json, or videos, or whatever.

PATCH verb (REST)

In REST, PUT is technically supposed to contain an entire copy of the object with changes. In Rails, it's typically used with only a few updated attributes though, which is what PATCH is actually for. Like PUT, most browsers don't natively support this, so it's being implemented the same where forms do a post with the parameter "_method=PATCH" . The PATCH verb will be the default for updates and map to the update controller action. PUT will still work though.

Relation#all

Post.all used to execute the db query and return an array. Now Post.all will return an ActiveRecord::Relation like other scopes. To execute and get an array, use Post.to_a .

Also, Post.scoped used to be the way to return the currently scoped Relation object. Now, .scoped is deprecated, and .all should be used for this.

Object#id

If you called nil.id (usually mistakenly because you expected some variable to be defined as a record), you'd get "RuntimeError: Called id for nil, which would mistakenly be 4...". Now you just get "NoMethodError: undefined method id' for nil:NilClass", which is true, because Rails 4 required ruby 1.9.3, and ruby 1.9.3 no longer defined id` for vanilla objects.

This is all just craziness because Rails decided to use the name "id" to define the primary key, which conflicts with what ruby knows as object ids. Glad it's getting a little simpler, but this could have all been easily avoided.

Relation#none

If for whatever reason, you want to chain onto a scope such that it returns no records, you can chain on .none. E.g. Post.some_scope.none will return an ActiveRecord::Relation object that would act as an empty array. I've had a couple instance where I needed to do this in the past, and have always defined a scope or something that appends .where("1=0") or something like that. It's the sort of thing that's hard to imagine why you'd need it, until one day you find that you need it.

Relation#first and Relation#last

Calling .first and .last on a Relation will now add "ORDER BY id ASC" to the executed sql. This didn't used to happen, so in Rails 3, if you did this without an explicit sort on the scope, the db would kind of just give you whatever, but depending on the table and records, it'd usually be the first or last by id (but didn't have to be).

Rails plugins

They're gone.

You can manually include them yourself though by putting them in lib and including them in an initializer. But you should really just be using gems.

Rails 2 Model.find(:[all|:first:last]) syntax

Deprecated.

Relation#references

If you did stuff like:

Post.includes(:comments).where("comments.created_at > ?", ...)

Rails 3 would know that your where-clause contained conditions on the comments table, and thus it should implement the sql as a LEFT OUTER JOIN so that the WHERE would work (as opposed to implementing as two separate queries, one for posts and one for comments). Rails will no longer make that assumption (well technically it will, but you'll get a deprecation warning). You need to explicitly tell it that your WHERE clause depends on the associated comments table by doing:

Post.includes(:comments).where("comments.created_at > ?", ...).references(:comments)

Relation#find_ dynamic finders

Gone => Use instead:

find_all_by_... => where(…)

scoped_by_... => where(…)

find_last_by_... => where(…).last

find_or_initialize_by... => where(…).first_or_initialize

find_or_create_by... => where(…).first_or_create

find_or_create_by_...! => where(…).first_or_create!

This is great, another win for consistency with Rails 3 relation syntax.

Eagerly-evaluated scopes

Gone.

scope :published, where(:published => true)

...is evaluated at the time the model file is read (on rails initialization). This is fine, except when you include stuff like:

scope :published, where("published_at => ?", Time.now)

To avoid this confusion, all scopes should use lambdas now.

authenticity_token no longer included in forms by default

When you submit a non-GET, non-ajax form in Rails, the rails back-end requires an authenticity-token in the parameters to prevent cross-site forgery attacks. If an ajax link or form doesn't include it, rails.js will automatically append it. However, Rails 3 would also include the authenticity_token parameter in forms automatically so that the forms could do non-ajax, non-GET requests (i.e. degrade gracefully when JavaScript is disabled).

This caused a problem though with people who were fragment-caching the forms, because the authenticity token would be wrong on subsequent requests when the form was pulled from the cache. So, apparently their solution was to stop including the authenticity token.

I personally think this is a bad decision. The number of people fragment-caching forms is much smaller than the number of people who benefit from gracefully-degrading remote forms. Not to mention, people who use fragment caching are usually more experienced developers with more mature apps, and so they're better able to handle the authenticity-token problem than beginner-level developers using remote forms.

"RailsVer"

4.0 mostly deprecates, 4.1 removes support (but many of the features can still be had with gems). 5.0 will remove support for some of the gems. With 4.1 release, 3.1 will stop being actively developed.

About the author: Steve Schwartz // Owner of Alfa Jango, CTO of Genomenon, co-founder of Carcode (acquired by Edmunds.com in 2014), engineer, developer, open-source enthusiast, guitarist, and racecar driverist.





