18
.
02
.
2024
18
.
02
.
2024
Ruby
Software

Lazy attributes in Ruby

Krzysztof Wawer
Ruby Developer
Lazy Attributes in Ruby - Krzysztof Wawer

What are lazy attributes?

Lazy attributes are properties or variables in a programming language that are not computed until their value is specifically requested or accessed. Instead of being calculated immediately when the object is created or when the attribute is defined, lazy attributes defer their computation until needed. This can be useful for optimizing performance, especially when dealing with large or complex data structures, by only computing values when required.

Do we have lazy attributes in Ruby?

Ruby doesn't have built-in lazy attributes like some other languages. However, you can achieve lazy initialization of attributes in Ruby using various techniques. We can see lazy attributes in dry-initializer, reform, … . Let’s focus right now on example with dry-initializer .

# game.rb

class Game
  extend Dry::Initializer

  option :board_id,    default: -> { server.fetch_board_id }
  option :duration,    default: -> { server.fetch_game_duration }
  option :max_players, default: -> { 4 }

  # ...
end

The syntax involving lambda might appear unconventional when compared to other Ruby examples. However, a significant advantage of using lambda or proc is that it allows us to receive a value after invoking call method. This capability proves especially beneficial when the default value is obtained from a database query.

To gain a better understanding of this feature, let's explore a simpler example involving hashes.

default_game_attributes = {
  max_players: -> { server.fetch_max_players }
  duration: -> { server.fetch_game_duration }
}

game_params = {
  board_id: 1,
  max_players: 2
}

game_attributes = {
  **default_game_attributes,
  **game_params
}

game_attributes
# => {
#   board_id: 1,
#   duration: #<Proc:0x000000010d6c3208@-e:1 (lambda)>
#   max_players: 2
# }

The code above optimizes by saving a database query for fetching the max_players value in the default_game_attributes hash. However, it introduces a side effect: the duration key now holds a lambda object instead of a number. This tradeoff needs careful consideration. Before using the game_attributes hash elsewhere, we must resolve every callable object. This can be achieved with a simple method.

def resolve_lazy_hash(hsh)
  hsh.deep_transform_values do |value|
    value.respond_to?(:call) ? value.call : value
  end
end

resolve_lazy_hash(game_attributes)
# => {
#   board_id: 1,
#   duration: 60
#   max_players: 2
# }

Back to the class

# game.rb

class Game
  extend Dry::Initializer

  option :board_id,    default: -> { server.fetch_board_id }
  option :duration,    default: -> { server.fetch_game_duration }
  option :max_players, default: -> { 4 }

  # ...
end

dry-initializer operates by requiring a proc (or any callable object) for default values, making it impossible to assign raw values directly. In the example above, one additional requirement is needed: Game class must have #server method. Let's implement it.

# game.rb

class Game
  extend Dry::Initializer

  option :board_id,    default: -> { server.fetch_board_id }
  option :duration,    default: -> { server.fetch_game_duration }
  option :max_players, default: -> { 4 }

  def server
    @server ||= Server.new
  end
end

# server.rb

class Server
  def logger
    logger ||= Logger.new(STDOUT)
  end

  def fetch_board_id
    logger.info("#fetch_board_id called")

    1
  end

  def fetch_game_duration
    logger.info("#fetch_game_duration called")

    60
  end
end

Once we have everything we need, we can run it.

game = Game.new(board_id: 123)
pp game

# ...]  INFO -- : #fetch_game_duration called
#<Game:0x000000010c27dfa0 @board_id=123, @duration=60, @max_players=4, @server=#<Game::Server:0x000000010c27db68>>

game = Game.new
pp game

# ...]  INFO -- : #fetch_board_id called
# ...]  INFO -- : #fetch_game_duration called
#<Game:0x0000000108a1efd8 @board_id=1, @duration=60, @max_players=4, @server=#<Game::Server:0x0000000108a1eb78>>

When are lazy attributes useful?

Hash and attributes are quite similar. Wrapping values with proc gives two features:

  • access to instance methods
  • postpone computations until we need the value
Krzysztof Wawer
Ruby Developer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

Branding: How to style your Jira?

14
.
11
.
2023
Lukasz Jackiewicz
Tutorial
Design
Project Management

How to start your UX/UI designer career

14
.
11
.
2023
Bartłomiej Bednarski
Design
Tutorial
HR

WebUSB - Bridge between USB devices and web browsers

14
.
11
.
2023
Burak Aybar
Ruby on Rails
Frontend
Backend
Tutorial

Visuality comes to town - this time it's Poznań

14
.
11
.
2023
Michał Piórkowski
Visuality
HR

CSS Modules in Rails

14
.
11
.
2023
Adam Król
Ruby on Rails
Tutorial
Backend
Frontend

How to choose a software house.

14
.
11
.
2023
Michał Piórkowski
Ruby on Rails
Business
Visuality

JSON API versus the NIH syndrome

14
.
11
.
2023
Nadia Miętkiewicz
Backend
Frontend
Tutorial

From Idea to Concept

02
.
10
.
2024
Michał Krochecki
Ruby on Rails
Business
Startups

Styling React Components

14
.
11
.
2023
Umit Naimian
Ruby on Rails
Frontend
Tutorial

How good design can help your business grow

14
.
11
.
2023
Lukasz Jackiewicz
Design
Business
Marketing

TODO not. Do, or do not.

29
.
11
.
2023
Stanisław Zawadzki
Ruby on Rails
Software

CS Lessons #003: Density map in three ways

14
.
11
.
2023
Michał Młoźniak
Ruby
Backend
Tutorial
Software

Clean code for the win

14
.
11
.
2023
Michał Piórkowski
Ruby on Rails
Backend
Frontend
Business

Crowd-operated Christmas Lights

14
.
11
.
2023
Nadia Miętkiewicz
Ruby on Rails
Backend

How to startup and be mature about it

14
.
11
.
2023
Rafał Maliszewski
Ruby on Rails
Startups
Business

A journey of a thousand miles begins with workshops

14
.
11
.
2023
Michał Piórkowski
Software
HR

CS Lessons #002: Data structures

14
.
11
.
2023
Michał Młoźniak
Ruby
Software

Summary of Phoenix workshop at Visuality

14
.
11
.
2023
Karol Słuszniak
Ruby on Rails
Visuality
Backend

CS Lessons #000: Introduction and motivation

14
.
11
.
2023
Michał Młoźniak
Ruby
Software

CS Lessons #001: Working with binary files

14
.
11
.
2023
Michał Młoźniak
Ruby
Software

Working with 40-minute intervals

14
.
11
.
2023
Sakir Temel
Software
HR

THE MATURE TECH STARTUP DILEMMA: WHAT'S NEXT

14
.
11
.
2023
Susanna Romantsova
Startups

Win MVP workshop!

14
.
11
.
2023
Susanna Romantsova
Startups

FINTECH WEEK IN OSLO: WHATs & WHYs

14
.
11
.
2023
Susanna Romantsova
Conferences

MY FIRST MONTH AT VISUALITY

14
.
11
.
2023
Susanna Romantsova
Visuality
HR