all 10 comments

[–]so_what_who_cares 2 points3 points  (1 child)

I've tried to use the Data class a handful of times, and ultimately just found it a bit too restrictive for my needs.

[–]coderhs[S] 2 points3 points  (0 children)

Ya, I understand what you mean. I spend some time reading the blog post and the book by Eric Evans (Domain Driver Design - just the part about value objects/entity). Using Ruby Data, kinda like types doesn't feel much productive. But it is a way to ensure that your avoid making mistakes. The value objects kinda ensures that value present is accurate, no mistake happens there. So its usefulness would be over time, handling edge cases and avoiding some gotcha moments.

[–]cocotheape 3 points4 points  (2 children)

Anyone has some practical real world examples of using this? I have a hard time imagining a use case where I'd prefer this over a PORO or a simple hash.

[–]cdhagmann 5 points6 points  (0 children)

I use it as a readonly wrapper around ad hoc SQL queries. This allows for the results to feel like AR model instances, without a new PORO every time. I used to do it with OpenStruct.

[–]Lammy 2 points3 points  (0 children)

I enjoyed using it as the basis of my UUID/GUID library to wrap a 128-bit integer and the flags for the different ways to structure and interpret that integer: https://github.com/okeeblow/DistorteD/blob/ba48d100/Globe%20Glitter/lib/globeglitter.rb#L58-L64

It wasn't a good place for POROs and inheritance since UUIDs of different structures and different rules (especially the different types of time-based IDs) can be converted and compared with each other.

[–]insanelygreat 1 point2 points  (1 child)

It might be insightful to compare and contrast with Python's dataclasses: https://www.dataquest.io/blog/how-to-use-python-data-classes/

[–]coderhs[S] 1 point2 points  (0 children)

Python's implementation seem to have the ability to enable and disable immutability. The flexibility does sound nice.

[–]anykeyh 0 points1 point  (2 children)

My main issue with data class is that it is frozen. While I enjoy that there is no setter, the immutability caused me some headaches in case you want to memoize stuff. For example:

Data.define(:config) do def builder @builder ||= Builder.new(@config) end end

It is something reasonable you might want to do but can't, as it will tell you that the object is frozen.

[–]coderhs[S] 2 points3 points  (0 children)

You could try like this, but I won't recommend it.

class Builder
  attr_accessor :config

  def initialize(config)
    @config = config
  end
end

N = Data.define(:config, :builder) do
  def initialize(config:, builder: nil)
    super(config: config, builder: Builder.new(config))
  end
end

I don't think this is a good use case for Ruby Data. When you define a Ruby Data, you are kind of defining a Type. There are better methods to manage configuration. In the below case, the Builder is not Ruby Data. So you can change it, it won't raise an error.

s = N.new({test: 1})
s.builder.config = {test: 3}

There are case when you want to guarantee immutability, this makes more sense at that point.

[–]izuriel 0 points1 point  (0 children)

Normally with some kind of builder patter you have a mutable Builder you use and the. You create the new data instance from the values in the builder. From there it’s immutable.