Last active
August 29, 2015 14:14
-
-
Save sentientmonkey/9f213763ffc8a974cd17 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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