I’d like to start with a question: Have you ever seen code like this?
class User < ActiveRecord::Base end User.new.tap do |user| user.name = "John Doe" user.username = "john.doe" user.password = "john123" end
I have. But what few developers know is that many methods in Active Record already accept a block, so you don't need to invoke
tap in the first place. And that's all because Active Record loves blocks! Let's go through some examples.
Using blocks with Active Record
When creating an Active Record object, either by using
create!, you can give a block straight to the method call instead of relying on
User.new do |user| user.name = "John Doe" user.username = "john.doe" user.password = "john123" end User.create do |user| user.name = "John Doe" user.username = "john.doe" user.password = "john123" end
And you can mix and match with hash initialization:
User.new(name: "John Doe") do |user| user.username = "john.doe" user.password = "john123" end
All these methods, when receiving a block,
yield the current object to the block so that you can do whatever you want with it. It's basically the same effect as using
tap. And it all happens after the attributes hash have been assigned and other internal Active Record code has been run during the object initialization, except by the
That's neat. That means we can stop using
tap in a few places now. But wait, there's more.
Active Record associations also love blocks
We talked about using blocks when building an Active Record object using
create, but associations like
has_many also work with that, when calling
create on them:
class User < ActiveRecord::Base has_many :posts end class Post < ActiveRecord::Base belongs_to :user end # has_many user = User.first user.posts.build do |post| post.title = "Active Record <3 Blocks" post.body = "I can give tap a break! <3 <3 <3" end # belongs_to post = Post.first post.build_user do |user| user.name = "John Doe <3 blocks" user.username = "john.doe" user.password = "john123" end
That's even better. That means we can stop using
tap in a few more places.
Wrapping up: Active Record <3 blocks
It is possible to avoid extra work, sometimes simple stuff such as using
tap with methods like
create, other times more complicated ones, by getting to know what the framework can give us for free.
There are other places inside Active Record that accept blocks, for instance
first_or_initialize and friends will execute the given block when the record is not found, to initialize the new one.
In short, next time you need a block when creating records using Active Record, take a minute to see if you can avoid using
tap by using an already existing feature. Remember: Active Record <3 blocks. And don't do that with blocks only, the main idea here is that you can learn more about the framework, and let it do more work for you.
How about you, do you have any small trick in Ruby or Rails that makes your work easier? Take a minute to share it with others in the comments.