2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
# File 'lib/ruby-immutable-struct.rb', line 2
def self.new(*attributes, &block)
attributes = attributes.map(&:to_sym)
if !attributes.all? { |a| a.to_s =~ /^[_a-z][_a-zA-Z0-9_]*/ }
raise "ImmutableStruct only allows attributes that look like Ruby variables: /[_a-z][a-zA-Z0-9_]*/. That keeps things fast and simple."
end
klass = Class.new do
attr_reader(*attributes)
class_eval <<-EOT
def initialize(*args)
# args.length == 1 is faster than args[0].is_a?(Hash). So check it
# first -- speed demons won't be using hashes, so let's save them the
# is_a? call.
if args.length == 1 && args[0].is_a?(Hash)
hash = args[0]
#{attributes.map{ |a| "@#{a} = hash[:#{a}]" }.join(';')}
else
#{attributes.map.with_index{ |a, i| "@#{a} = args[#{i}]" }.join(';')}
end
after_initialize
freeze
end
def after_initialize
# Implementations may override this
end
def merge(hash)
merged_hash = to_h.merge!(hash)
self.class.new(merged_hash)
end
def to_h
{ #{attributes.map{ |a| "#{a}: @#{a}" }.join(',')} }
end
def to_a
[ #{attributes.map{ |a| "@#{a}" }.join(',')} ]
end
def ==(other)
#{attributes.map{ |a| "@#{a} == other.#{a}" }.join(' && ')}
end
alias :eql? :==
def hash
to_a.hash
end
def inspect
"#<#\{self.class.name} #\{to_a.map(&:inspect).join(',')}>"
end
def to_s
inspect
end
EOT
end
klass.class_exec(&block) if !block.nil?
klass
end
|