Examples of Value Objects with Ruby's Data Class

Ask questions Research chat →

https://allaboutcoding.ghinda.com/example-of-value-objects-using-rubys-data-class · scraped

deploy

Scraped Content

— 660 words · 2026-05-19 12:34:41 UTC ·

Excerpt

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742979806403/973bead1-7a96-42e2-bf87-7cddc1aba4ef.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp) Last week, I wrote an article about how to create value objects in Ruby - the idiomatic way. This week, I will share some real examples of using the data object to show some real examples. ## Remove boilerplate constructor code If you are defining classes and expose the initializer parameters as getters and you plan to make them immutable, then I think you just found the most common case for using the Data class: Instead of this: ```plain text class Link attr_reader :url, :source def initialize(url:, source:) @url = url @source = source end end ``` I write this: You can of course also write the simple form, but I do recommend the the previous way with inheritance (will followup in another article about this): ## When calling an external API When calling an external API that returns JS
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742979806403/973bead1-7a96-42e2-bf87-7cddc1aba4ef.png?w=1600&h=840&fit=crop&crop=entropy&auto=compress,format&format=webp) Last week, I wrote an article about how to create value objects in Ruby - the idiomatic way. This week, I will share some real examples of using the data object to show some real examples. ## Remove boilerplate constructor code If you are defining classes and expose the initializer parameters as getters and you plan to make them immutable, then I think you just found the most common case for using the Data class: Instead of this: ```plain text class Link attr_reader :url, :source def initialize(url:, source:) @url = url @source = source end end ``` I write this: You can of course also write the simple form, but I do recommend the the previous way with inheritance (will followup in another article about this): ## When calling an external API When calling an external API that returns JSON, I like to implement a method that returns a Response object. Here I define a Response object with two computed properties: - parsed_body - success ```plain text class Response < Data.define(:body, :status, :headers) HTTP_SUCCESS_STATUS_CODES = (200..299) def success? = HTTP_SUCCESS_STATUS_CODES.include?(status) def parsed_body = JSON.parse(body, symbolize_names: true) def failed? = !success? end ``` Mind you that you cannot memoize using instance variables inside a Data class due to immutability. If you try something like this you will get FrozenError ```plain text class Response < Data.define(:body, :status, :headers) def parsed_body @parsed_body ||= JSON.parse(body, symbolize_names: true) end end r = Response.new(body: "{}", status: 200, headers: {}) r.parsed_body # can't modify frozen Response: #<data Response body="{}", status=200, headers={}> (FrozenError) ``` A more full example might look like this using httparty gem ```plain text require 'httparty' require 'json' class Response < Data.define(:body, :status, :headers) HTTP_SUCCESS_STATUS_CODES = (200..299) def success? = HTTP_SUCCESS_STATUS_CODES.include?(status) def parsed_body = JSON.parse(body, symbolize_names: true) def failed? = !success? end def get(url, query: {}) response = HTTParty.get(url, query) Response.new(body: response.body, status: response.code, headers: response.headers) end response = get( 'https://bsky.social/xrpc/' \ 'com.atproto.identity.resolveHandle?handle=lucianghinda.com') puts response.parsed_body[:did] # did:plc:1362asasdah213212 puts response.success? # true ``` From this example you can for example expand it to add a RateLimit object: ```plain text require 'httparty' require 'json' class RateLimit < Data.define(:limit, :remaining, :reset) end class Response < Data.define(:body, :status, :headers, :rate_limit) HTTP_SUCCESS_STATUS_CODES = (200..299) def success? = HTTP_SUCCESS_STATUS_CODES.include?(status) def parsed_body = JSON.parse(body, symbolize_names: true) def failed? = !success? end def get(url, query: {}) response = HTTParty.get(url, query) rate_limit = RateLimit.new( limit: response.headers['ratelimit-limit'].to_i, remaining: response.headers['ratelimit-remaining'].to_i, reset: response.headers['ratelimit-reset'].to_i ) Response.new( body: response.body, status: response.code, headers: response.headers, rate_limit: rate_limit ) end ``` You can even add a constructor to RateLimit for example: ```plain text class RateLimit < Data.define(:limit, :remaining, :reset) def self.from_headers(headers) limit = headers['ratelimit-limit'].to_i remaining = headers['ratelimit-remaining'].to_i reset = headers['ratelimit-reset'].to_i new(limit: limit, remaining: remaining, reset: reset) end end ``` Here is an example I found in TheOdinProject: ```plain text # Source: https://github.com/TheOdinProject/theodinproject/app/models/flag.rb class Flag < ApplicationRecord Reason = Data.define(:name, :description, :value) REASONS = [ { name: :broken, description: 'Link does not work', value: 10 }, { name: :insecure, description: 'Link is not secure or safe', value: 20 }, { name: :spam, description: 'Spam or misleading', value: 30 }, { name: :inappropriate, description: 'Inappropriate imagery or language', value: 40 }, { name: :other, description: 'Other', value: 50 } ].map { |reason| Reason.new(**reason) } end ``` Furthe on you will see they are using REASONS in a Rails enum: And then in the view as a list of choices: ```plain text <% Flag::REASONS.each do |reason| %> <div class="relative flex items-center"> <div class="absolute flex h-6 items-center"> <%= form.radio_button( :reason, reason.name, data: { test_id: "flag-reason-#{reason.name}"}, class: 'h-4 w-4 border-gray-300 dark:border-gray-500 dark:bg-gray-700/50' # ... %> </div> <div class="pl-7 text-sm leading-6"> <%= form.label( :reason, reason.description, value: reason.name, class: 'block text-sm font-medium text-gray-700 dark:text-gray-200 dark:text-gray-200' %> </div> </div> <% end %> ``` If you like this article: 👐 Interested in learning how to improve your developer testing skills? Join my live online workshop about goodenoughtesting.com - to learn test design techniques for writing effective tests 👉 Join my Short Ruby Newsletter for weekly Ruby updates from the community

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Agent findings

error URL returned 404 health · Jul 6
Export as Markdown

Attachments

+ Annotate selection

Add Annotation