RubyGuides
Share this post!

What is Dry-rb?

Looking for some new & interesting gems to try?

Then have a look at dry-rb, a set of gems that bring solutions to common problems. Dry-rb is composed of over 18 Ruby gems that can work together or by themselves.

Some of these gems include:

  • dry-initializer
  • dry-struct
  • dry-validations
  • dry-events
  • dry-transaction

In this post, you’ll learn about 3 of these gems so you can get a taste of what dry-rb has to offer!

How to Create Better Structs With dry-struct

A Ruby Struct is a kind of object that you can create from the Struct class, but they have some limitations.

For example:

You can create a struct with a number of missing arguments & get no complaints from Ruby.

With the dry-struct gem you can create stricter structs.

Here’s how:

require 'dry-struct'

module Types
  include Dry::Types.module
end

class Video < Dry::Struct
  attribute :title, Types::String
  attribute :views, Types::Views
  attribute :comments, Types::Array
end

Video.new(title: "abc", views: 10, comments: [])

Now if you miss one of the attributes, you get this error:

[Video.new] :comments is missing in Hash input

Not the most user-friendly error message, but it gets things done.

You can create your own types & add constraints to them:

module Types
  include Dry::Types.module

  Age = Integer.constrained(gt: 0)
end

This "gt" means "greater than".

So Age must be an integer greater than 0.

Can you have default values?

Yes, here's how:

module Types
  include Dry::Types.module

  Name = String.default('')
end

There is a shorter version for creating Dry structs, which resembles regular Ruby structs.

Example:

Book = Dry::Struct(title: Dry::Types["string"])

Book.new(title: "Computer Science 101")

Implementing the Observer Pattern With dry-events

The Observer design pattern is when one source publishes an update to many listeners, this could be a news update, stock alerts, new message in a group chat, etc.

It's a common pattern.

Ruby includes an observable module that helps you implement this.

But dry-events is another option.

You can create a publisher like this:

require 'dry/events/publisher'

class Blog
  include Dry::Events::Publisher[:blog]

  register_event('article.published')
  register_event('article.upated')
end

Now:

Any class can register itself to receive these events when they happen.

Here's how:

class Reader
  def on_article_published(event)
    puts "New article published: #{event[:title]}"
  end

  def on_article_updated(event)
    puts "Article has been updated: #{event[:title]}"
  end
end

blog   = Blog.new
reader = Reader.new

blog.subscribe(reader)

You can broadcast an event with the publish method:

blog.publish('article.published', title: 'How to Use The dry-events Gem')

One thing I noticed with this gem is that if you subscribe or publish an event that doesn't exist it will fail silently.

Dependency Injection With dry-auto_inject

What is a dependency?

A dependency is something that a class needs to work, like other objects & classes.

You can create these objects directly in your class...

Or you can use dependency injection.

Dependency injection is when the dependencies are "injected" into the class. In other words, they are passed in as parameters.

Example:

require 'faraday'

class GitHubAPI
  def initialize(http_client = Faraday.new)
    @http_client = http_client
  end
end

Doing this allows you to control the dependency & make testing easier.

Now:

dry-auto_inject creates these objects for you. All you have to do is declare what dependencies are needed.

In this example Fruit depends on Store:

require 'dry/auto_inject'

class Store
  def bananas
    puts 'Have some organic bananas!'
  end
end

Import = Dry::AutoInject({ 'store' => Store.new })

class Fruit
  include Import['store']

  def eat
    store.bananas
  end
end

fruit = Fruit.new
fruit.eat

You have to say how the object is created:

Import = Dry::AutoInject({ 'store' => Store.new })

And make it available for the class:

include Import['store']

Then you can create a Fruit with a different Store like this:

Fruit.new(store: MyStore.new)

You don't need a gem to use dependency injection, but it's interesting to see another way to do it.

Summary

You have learned about dry-rb, a set of interesting Ruby gems that you can use to solve specific design problems in your code.

Now it's your turn to practice & give this a try!

Thanks for reading.

2 comments
Artem says 6 months ago

It’s great!!! Please write article about new gem dry-schema (https://github.com/dry-rb/dry-schema), differents from dry-validation (https://github.com/dry-rb/dry-validation), how this gems can used in rails app, for example where i can place dry-schema in rails?

    Jesus Castello says 6 months ago

    Hi,
    Thanks for reading & leaving a comment! I haven’t looked into dry-schema yet, but it seems like you can find the documentation here.

Comments are closed