Skip to content

Instantly share code, notes, and snippets.

@sentientmonkey
Last active August 29, 2015 14:14
Show Gist options
  • Select an option

  • Save sentientmonkey/9f213763ffc8a974cd17 to your computer and use it in GitHub Desktop.

Select an option

Save sentientmonkey/9f213763ffc8a974cd17 to your computer and use it in GitHub Desktop.
class ImmutableObject
def initialize
freeze
end
end
class BaseImmutableStruct < ImmutableObject
attr_reader :members
def initialize(members)
@members = members
super()
end
def inspect
"#<struct #{members.map{ |m| "#{m}=\"#{send m}\"" }.join ' '}>"
end
alias_method :to_s, :inspect
def length
members.length
end
alias_method :size, :length
def [](member)
if member.is_a? Fixnum
send members[member]
else
send member
end
end
def hash
to_h.hash
end
def to_h
members.map { |m| [m, send(m)] }.to_h
end
def to_a
members.map { |m| send(m) }
end
alias_method :values, :to_a
def each(&block)
to_a.each(&block)
end
def each_pair(&block)
to_h.each(&block)
end
def clone
self.class.new(*values)
end
def change(changes)
new_values = to_h.merge(changes).values
self.class.new(*new_values)
end
def ==(other)
self.class == other.class && self.hash == other.hash
end
end
class ImmutableStruct
def self.new(*args, &block)
new_class = Class.new(BaseImmutableStruct, &block)
new_class.class_eval %Q(
def initialize(#{args.join ', ' })
#{args.map{ |arg| "@#{arg} = #{arg}" }.join '; ' }
members = #{args.map{ |arg| ":#{arg}" }.join ', ' }
super(members)
end
)
args.each do |meth|
new_class.class_eval "def #{meth}; @#{meth}; end"
end
new_class
end
end
require 'minitest/autorun'
require 'minitest/pride'
require './immutable_struct.rb'
class TestImmutableStruct < Minitest::Test
NAME = 'Bubba'
EMAIL = 'bubba@example.com'
ADDRESS = 'Bubba <bubba@example.com>'
class User < ImmutableStruct.new(:name, :email)
def address
"#{name} <#{email}>"
end
end
def setup
@user_class = ImmutableStruct.new :name, :email
@user = @user_class.new NAME, EMAIL
@values = [NAME, EMAIL]
@hash = {name: NAME, email: EMAIL}
@length = 2
end
def test_can_create_new_struct
refute_nil @user
end
def test_struct_has_getters
assert_equal NAME, @user.name
assert_equal EMAIL, @user.email
end
def test_struct_does_not_have_setters
refute @user.respond_to? :name=
refute @user.respond_to? :email=
end
def test_struct_can_return_members
assert_equal [:name, :email], @user.members
end
def test_struct_can_to_s
assert_equal '#<struct name="Bubba" email="bubba@example.com">', @user.to_s
end
def test_struct_can_inspect
assert_equal '#<struct name="Bubba" email="bubba@example.com">', @user.inspect
end
def test_struct_can_access_members_as_symbols
assert_equal NAME, @user[:name]
assert_equal EMAIL, @user[:email]
end
def test_struct_can_access_members_as_strings
assert_equal NAME, @user["name"]
assert_equal EMAIL, @user["email"]
end
def test_struct_can_access_members_as_numbers
assert_equal NAME, @user[0]
assert_equal EMAIL, @user[1]
end
def test_struct_can_convert_to_hash
assert_equal @hash, @user.to_h
end
def test_struct_can_convert_to_array
assert_equal @values, @values.to_a
end
def test_struct_has_values
assert_equal @values, @user.values
end
def test_struct_can_each
acc = []
@user.each { |value| acc << value }
assert_equal @values, acc
end
def test_struct_can_each_pair
acc = {}
@user.each_pair { |key,value| acc[key] = value }
assert_equal @hash, acc
end
def test_struct_has_length
assert_equal @length, @user.length
end
def test_struct_has_size
assert_equal @length, @user.size
end
def test_struct_can_add_methods
user_class = ImmutableStruct.new :name, :email do
def awesome
@name
end
end
user = user_class.new NAME, EMAIL
assert_equal NAME, user.awesome
end
def test_struct_cannot_add_methods_that_modify_attributes
user_class = ImmutableStruct.new :name, :email do
def name!
@name = "John"
end
end
user = user_class.new NAME, EMAIL
assert_raises(RuntimeError) { user.name! }
end
def test_struct_can_be_subclassed
user = User.new NAME, EMAIL
assert_equal NAME, user.name
assert_equal EMAIL, user.email
assert_equal ADDRESS, user.address
end
def test_struct_can_clone
user = @user.clone
assert_equal @user.values, user.values
end
def test_struct_can_make_changes
user = @user.change name: 'John'
assert_equal 'John', user.name
end
def test_struct_can_compare
assert_equal @user, @user.dup
assert_equal @user, @user.clone
refute_equal @user, @user.change(name: 'John')
user_class = ImmutableStruct.new :name, :email
user = user_class.new NAME, EMAIL
refute_equal @user, user
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment