davydovanton / shallow_attributes

Simple and lightweight Virtus analog.
MIT License
101 stars 18 forks source link

Immutability #24

Open maxim opened 5 years ago

maxim commented 5 years ago

Great lib, love how small and efficient it is. Would be great if it could produce immutable objects, either frozen by default, or with skipped attribute writers. Curious to hear your thoughts.

davydovanton commented 5 years ago

hey, thanks for feedback! About immutability. I think it's a good idea. I think we can use something like this:

class User
  include ShallowAttributes

  attribute :name, String
  attribute :age, Integer
  attribute :birthday, DateTime
end

user = User.new
user.name = 'Anton' # => okay

class ImmutableUser
  include ImmutableShallowAttributes

  attribute :name, String
  attribute :age, Integer
  attribute :birthday, DateTime
end

user = ImmutableUser.new
user.name = 'Anton' # => raise error

WDYT?

maxim commented 5 years ago

Sounds like a good option. What do you think about per-attribute?

attribute :name, String, writer: false # true/false/:protected

And for any such non-true attribute, it would also be excluded from mass assignment via attributes=.

davydovanton commented 5 years ago

so, I think it can be hard for implementing. Also, usually you need to use full immutable object raise than immutable attribute (only my experience) 🤔

maxim commented 5 years ago

Definitely agree that usually you want full-immutable. I've seen the exception recently where sometimes you have a persistable object and you want to assign id field only after persistence. OTOH, this can also be done by dup-ing it.

Whether it's per-attribute, or full-immutable, your code is really well designed to accommodate it. I was able to get like 80% there by simply overwriting initialize_setter in a subclass with

def initialize_setter(name, *)
  super.tap { private name } 
end

This achieved the effect that you couldn't call a setter from outside, but initialize still worked. However, mutation was still possible via attributes=. I didn't find an easy way to patch that.