Skip to content

Instantly share code, notes, and snippets.

@Jell
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jell/e276d8198e3191796580 to your computer and use it in GitHub Desktop.
Save Jell/e276d8198e3191796580 to your computer and use it in GitHub Desktop.
Immutable Record
## DEFINITION ##
# Compact version, with just a hint of meta-programming
Item = ImmutableRecord.new(:foo, :bar)
# Strict Equivalent, without meta-programming:
class ItemBis < ImmutableRecord::Value
def self.attributes
[:foo, :bar]
end
attr_reader(*attributes)
end
# Acceptable, but one extra nameless ancestor (same thing as with Struct)
class ItemTer < ImmutableRecord.new(:foo, :bar)
end
## OVERHEAD/INTERFACE ##
Item.ancestors
#=> [Item, ImmutableRecord::Value, Object, ...]
Item.instance_methods - Object.instance_methods
#=> [:bar, :foo]
Item.methods - Object.methods
#=> [:attributes]
Item.attributes
#=> [:bar, :foo]
## USAGE ##
item = Item.new(foo:1, bar:2)
#=> #<Item:0x007faaecb26b70 @foo=1, @bar=2>
Item.new(foo:1, bar:2).eql?(Item.new(foo:1, bar:2))
#=> true
Item.new(foo:1, bar:2).eql?(Item.new(foo:1, bar:3))
#=> false
{
Item.new(foo: 1, bar: 2) => "yay!"
}.fetch(Item.new(foo: 1, bar: 2))
#=> "yay!"
module ImmutableRecord
def self.new (*attributes)
unless attributes.all? { |attr| attr.is_a?(Symbol) }
raise ArgumentError, "attributes should be symbols!"
end
instance_attributes = attributes.dup.freeze
Class.new(Value) do
attr_reader(*instance_attributes)
define_singleton_method "attributes" do
instance_attributes
end
end
end
class Value < Object
def initialize (opts)
missing_keys = self.class.attributes - opts.keys
if missing_keys.any?
raise ArgumentError, "Missing attribute(s): #{missing_keys.inspect}"
end
extra_keys = opts.keys - self.class.attributes
if extra_keys.any?
raise ArgumentError, "Unknown attribute(s): #{extra_keys.inspect}"
end
self.class.attributes.each do |attr|
instance_variable_set("@#{attr}", opts.fetch(attr))
end
end
def == (other)
eql?(other)
end
def eql? (other)
hash == other.hash
end
def hash
self.class.hash ^
self.class.attributes.map(&method(:public_send)).hash
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment