ActiveRecord's New Takes a Block, Kid

Engineering Insights

#
Min Read
Published On
March 13, 2025
Updated On
March 17, 2025
ActiveRecord's New Takes a Block, Kid

Making New ActiveRecord Models (Let's Try It Again)

If we want to make a new instance of an ActiveRecord model with particular attributes, we have a number of options.

We can pass the attributes in as a hash:

u = User.new(first_name: "Jordan", last_name: "Knight")

We can set the attributes after creating the object:

u = User.new
u.first_name = "Jordan"
u.last_name = "Knight"

And there's a third option - we can also pass new a block:

u = User.new do |user|
  user.first_name = "Jordan"
  user.last_name = "Knight"
end

When Could We Use This? (The Block)

Let's say we have a system that members of a band use to check their tour schedule. Band members are users, and when we add a member, we want to make a user for them.

def add_member(first_name:, last_name:)
  @members << User.new(first_name: first_name, last_name: last_name)
end

Additionally, users have a username attribute, and we want to keep that unique within a given band. We also want the system to define the username when we add a band member.

def add_member(first_name:, last_name:)
  username = "#{band_name}_#{last_name}"
  if @members.pluck(:last_name).include?(last_name)
    username << unique_value
  end

  @members << User.new(first_name: first_name, last_name: last_name, username: username)
end

def unique_value
  ...
end

However, if we prefer the aesthetic, we can also define those attributes in a block:

def add_member(first_name:, last_name:)
  @members << User.new do |user|
    user.first_name = first_name
    user.last_name = last_name

    user.username = "#{band_name}_#{last_name}"
    if @members.pluck(:last_name).include?(last_name)
      user.username << unique_value
    end
  end
end

Seeing The Result (The Right Stuff)

Let's check our work to see the usernames of our band members.

[1] pry(main)> band = Band.new("New Kids on the Block")
[2] pry(main)> band.add_member(first_name: "Jordan", last_name: "Knight")
[3] pry(main)> band.add_member(first_name: "Donnie", last_name: "Wahlberg")
[4] pry(main)> band.add_member(first_name: "Jonathan", last_name: "Knight")
[5] pry(main)> band.members.pluck(:username)
=> ["New_Kids_on_the_Block_Knight",
    "New_Kids_on_the_Block_Wahlberg",
    "New_Kids_on_the_Block_Knight_65"]

Jonathan's username has additional characters appended to it, as Jordan already claimed the username "New_Kids_on_the_Block_Knight".

Tap Dance (Step By Step)

If you're familiar with Ruby's tap method, you might be wondering what all the fuss is about. We can do the same thing with tap:

def add_member(first_name:, last_name:)
  @members << User.new.tap do |user|
    user.first_name = first_name
    user.last_name = last_name

    user.username = "#{band_name}_#{last_name}"
    if @members.pluck(:last_name).include?(last_name)
      user.username << unique_value
    end
  end
end

This works with any Ruby object, not just those that inherit from ActiveRecord::Base, so why bother with having to know if we can pass a block to new or not, based on what the object inherits from?

That's fair, but new is not the only ActiveRecord method that takes a block. Others include create, build, and find_or_initialize_by. There the differences with tap start to show:

new_user = User.create(first_name: "Jordan", last_name: "Knight").tap do |u|
  u.first_name = "Jonathan"
end

Our new_user has the first name of Jonathan, resulting from the call to tap:

new_user.first_name
=> "Jonathan"

However, that's only persisted in memory - not in the database. What we stored in the database is what we passed to create.

new_user.reload.first_name
=> "Jordan"

We can also pass a block to create directly:

new_user = User.create(first_name: "Jordan", last_name: "Knight") do |u|
  u.first_name = "Jonathan"
end

And in that case, the first name of the user in memory and in the database is Jonathan.

new_user.first_name
=> "Jonathan"
new_user.reload.first_name
=> "Jonathan"

Why would we mix setting attributes with create both by passing a hash and a block, either with or without tap? Other than to explain quirks and differences in what method you're passing a block to, I am also interested in knowing. If you have real-world use cases, let me know!

Finding Blocks in Rails Source Code (Face the Music)

If you're curious about where in Rails' source code new is set up to take a block, we can start by looking in ActiveRecord::Base. As of the time this article was published, there's not much implementation in that class. Instead, we have to look in the Core module to find the initialize method that takes a block.

Initializing an ActiveRecord model with a block is also defined in the documentation.

Thanks for hangin' tough to the end of this article. I hope you learned a thing or two about passing blocks to ActiveRecord methods.

Learn more about how The Gnar builds Ruby on Rails applications.

Author headshot
Written by
, The Gnar Company

Related Insights

See All Articles
News
Is Your Team Ready for AI? Here's How to Find Out in 2 Minutes

Is Your Team Ready for AI? Here's How to Find Out in 2 Minutes

Most teams aren't getting real value from AI tools — not because the tools don't work, but because their foundations aren't ready. Discover the five factors that predict AI success and take a free 2-minute assessment to find out where your team stands.
Product Insights
AI Integration Agency With Guaranteed Outcomes

AI Integration Agency With Guaranteed Outcomes

Stop the "Vanished Agency" cycle. As a premier AI integration agency, The Gnar Company moves beyond flashy demos by wiring AI into your CRM and ERP to trigger real actions. Get a successful AI implementation with guaranteed outcomes and our signature 12-month bug-free warranty.
Engineering Insights
Top React Native App Development Companies In 2026

Top React Native App Development Companies In 2026

Compare the top React Native app development companies of 2026. Discover how to vet senior engineers, avoid technical debt, and why our 12-month bug-free warranty sets the standard for high-performance mobile builds.
Previous
Next
See All Articles