class ImmutableStruct
Creates classes for value objects/read-only records. Most useful when creating model objects for concepts not stored in the database.
This will create a class that has attr_readers for all given attributes, as well as a hash-based constructor. Further, the block given to with_attributes will be evaluated as if it were inside a class definition, allowing you to add methods, include or extend modules, or do whatever else you want.
Public Class Methods
Create a new class with the given read-only attributes.
- attributes
-
list of symbols or strings that can be used to create attributes. Any attribute with a question mark in it (e.g.
:foo?
) will create an attribute without a question mark that passes through the raw value and an attribute with the question mark that coerces that value to a boolean. You would initialize it with the non-question-mark value An attribute that is an array of one symbol will create an attribute named for that symbol, but that doesn't return nil, instead returning theto_a
of the value passed to the construtor. - block
-
if present, evaluates in the context of the new class, so
def
,def.self
,include
andextend
should all work as in a normal class definition.
Example:
Person = ImmutableStruct.new(:name, :location, :minor?) p = Person.new(name: 'Dave', location: Location.new("DC"), minor: false) p.name # => 'Dave' p.location # => <Location: @where="DC"> p.minor # => false p.minor? # => false p = Person.new(name: 'Rudy', minor: "yup") p.name # => 'Rudy' p.location # => nil p.minor # => "yup" p.minor? # => true
# File lib/immutable-struct.rb, line 39 def self.new(*attributes,&block) klass = Class.new do attributes.each do |attribute| if attribute.to_s =~ /(^.*)\?$/ raw_name = $1 attr_reader raw_name define_method(attribute) do !!instance_variable_get("@#{raw_name}") end elsif attribute.kind_of?(Array) and attribute.size == 1 attr_reader attribute[0] else attr_reader attribute end end define_method(:initialize) do |*args| attrs = args[0] || {} attributes.each do |attribute| if attribute.kind_of?(Array) and attribute.size == 1 ivar_name = attribute[0].to_s instance_variable_set("@#{ivar_name}", (attrs[ivar_name.to_s] || attrs[ivar_name.to_sym]).to_a) else ivar_name = attribute.to_s.gsub(/\?$/,'') instance_variable_set("@#{ivar_name}",attrs[ivar_name.to_s] || attrs[ivar_name.to_sym]) end end end define_method(:==) do |other| return false unless other.is_a?(klass) attributes.all? { |attribute| self.send(attribute) == other.send(attribute) } end alias_method :eql?, :== end klass.class_exec(&block) unless block.nil? imethods = klass.instance_methods(include_super=false) klass.class_exec(imethods) do |imethods| define_method(:to_h) do imethods.inject({}) do |hash, method| next hash if [:==, :eql?].include?(method) hash.merge(method.to_sym => self.send(method)) end end end klass end