Created
February 12, 2012 08:33
-
-
Save doitian/1807324 to your computer and use it in GitHub Desktop.
auto index hash on specified attribute
This file contains 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
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