Skip to content

Instantly share code, notes, and snippets.

@doitian
Created February 12, 2012 08:33
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 doitian/1807324 to your computer and use it in GitHub Desktop.
Save doitian/1807324 to your computer and use it in GitHub Desktop.
auto index hash on specified attribute
module AttrChangeEmitter
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
def attr_change_emitter(*attrs)
attrs.each do |attr|
class_eval do
alias_method "#{attr}_without_emitter=", "#{attr}="
define_method "#{attr}_with_emitter=" do |v|
previous_value = send("#{attr}")
send "#{attr}_without_emitter=", v
attr_change_listeners_on(attr).each do |listener|
listener.call self, previous_value, v
end
end
alias_method "#{attr}=", "#{attr}_with_emitter="
end
end
end
end
module InstanceMethods
def attr_change_listeners_on(attr)
@attr_change_listeners_on ||= {}
@attr_change_listeners_on[attr.to_sym] ||= []
end
def add_attr_change_listener_on(attr, block)
listeners = attr_change_listeners_on(attr)
listeners << block unless listeners.include?(block)
end
def remove_attr_change_listener_on(attr, block)
attr_change_listeners_on(attr).delete block
end
end
end
class AttrChangeAwareHash
include Enumerable
def initialize(attr = :id)
@attr = attr.to_sym
@hash = {}
end
def each(&block)
@hash.values.each(&block)
end
def on_entity_attr_change(e, previous_value, new_value)
if @hash[previous_value].equal? e
@hash.delete(previous_value)
# remove the original one in slot new_value
delete_by_key(new_value)
@hash[new_value] = e
end
end
def add(v)
delete(v)
v.add_attr_change_listener_on(@attr, self.method(:on_entity_attr_change))
k = v.send(@attr)
@hash[k] = v
end
alias_method :<<, :add
def delete(v)
k = v.send(@attr)
delete_by_key(k) if @hash[k].equal?(v)
end
def delete_by_key(k)
v = @hash.delete(k)
v.remove_attr_change_listener_on(@attr, self.method(:on_entity_attr_change)) if v
v
end
def [](k)
@hash[k]
end
end
class Student
include AttrChangeEmitter
attr_accessor :id, :name
attr_change_emitter :id, :name
def initialize(id, name)
self.id = id
self.name = name
end
end
indexByIDA = AttrChangeAwareHash.new(:id)
indexByIDB = AttrChangeAwareHash.new(:id)
indexByName = AttrChangeAwareHash.new(:name)
s1 = Student.new(1, 'John')
s2 = Student.new(2, 'Bill')
s3 = Student.new(3, 'Kate')
indexByIDA << s1
indexByIDA << s3
indexByIDB << s1
indexByIDB << s2
indexByName << s1
indexByName << s2
indexByName << s3
puts indexByIDA[1].name # => John
puts indexByIDB[2].name # => Bill
puts indexByName['John'].id # => 1
s2.id = 15
s2.name = 'Batman'
p indexByIDB[2] # => nil
puts indexByIDB[15].name # => Batman
indexByName.each do |v|
v.name = v.name.downcase
end
p indexByName['John'] # => nil
puts indexByName['john'].id # => 1
p indexByName.collect { |v| [v.id, v.name] }
# => [[1, "john"], [3, "kate"], [15, "batman"]]
indexByName.delete_by_key 'john'
indexByName.delete(s2)
s2.id = 1 # set batman id to 1 to overwrite john
p indexByIDB.collect { |v| [v.id, v.name] }
# => [[1, "batman"]]
p indexByName.collect { |v| [v.id, v.name] }
# => [[3, "kate"]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment