#!/usr/bin/env ruby require 'flapjack/data/contact' require 'flapjack/data/tag' module Flapjack module Data class Entity attr_accessor :name, :id TAG_PREFIX = 'entity_tag' def self.all(options = {}) raise "Redis connection not set" unless redis = options[:redis] redis.keys("entity_id:*").collect {|k| k =~ /^entity_id:(.+)$/; entity_name = $1 self.new(:name => entity_name, :id => redis.get("entity_id:#{entity_name}"), :redis => redis) }.sort_by(&:name) end # NB: should probably be called in the context of a Redis multi block; not doing so # here as calling classes may well be adding/updating multiple records in the one # operation def self.add(entity, options = {}) raise "Redis connection not set" unless redis = options[:redis] raise "Entity name not provided" unless entity['name'] && !entity['name'].empty? if entity['id'] existing_name = redis.hget("entity:#{entity['id']}", 'name') redis.del("entity_id:#{existing_name}") unless existing_name == entity['name'] redis.set("entity_id:#{entity['name']}", entity['id']) redis.hset("entity:#{entity['id']}", 'name', entity['name']) redis.del("contacts_for:#{entity['id']}") if entity['contacts'] && entity['contacts'].respond_to?(:each) entity['contacts'].each {|contact_id| next if Flapjack::Data::Contact.find_by_id(contact_id, :redis => redis).nil? redis.sadd("contacts_for:#{entity['id']}", contact_id) } end self.new(:name => entity['name'], :id => entity['id'], :redis => redis) else # empty string is the redis equivalent of a Ruby nil, i.e. key with # no value redis.set("entity_id:#{entity['name']}", '') nil end end def self.find_by_name(entity_name, options = {}) raise "Redis connection not set" unless redis = options[:redis] entity_id = redis.get("entity_id:#{entity_name}") if entity_id.nil? # key doesn't exist return unless options[:create] self.add({'name' => entity_name}, :redis => redis) end self.new(:name => entity_name, :id => (entity_id.nil? || entity_id.empty?) ? nil : entity_id, :redis => redis) end def self.find_by_id(entity_id, options = {}) raise "Redis connection not set" unless redis = options[:redis] entity_name = redis.hget("entity:#{entity_id}", 'name') return if entity_name.nil? || entity_name.empty? self.new(:name => entity_name, :id => entity_id, :redis => redis) end # NB: if we're worried about user input, https://github.com/mudge/re2 # has bindings for a non-backtracking RE engine that runs in linear # time def self.find_all_name_matching(pattern, options = {}) raise "Redis connection not set" unless redis = options[:redis] redis.keys('entity_id:*').inject([]) {|memo, check| a, entity_name = check.split(':') if (entity_name =~ /#{pattern}/) && !memo.include?(entity_name) memo << entity_name end memo }.sort end def contacts contact_ids = @redis.smembers("contacts_for:#{id}") if @logger @logger.debug("#{contact_ids.length} contact(s) for #{id} (#{name}): " + contact_ids.length) end contact_ids.collect {|c_id| Flapjack::Data::Contact.find_by_id(c_id, :redis => @redis) }.compact end def check_list @redis.keys("check:#{@name}:*").map {|k| k =~ /^check:#{@name}:(.+)$/; $1} end def check_count checks = check_list return if checks.nil? checks.length end def tags @tags ||= load_tags end def add_tags(*enum) enum.each do |t| Flapjack::Data::Tag.create("#{TAG_PREFIX}:#{t}", [@id], :redis => @redis) tags.add(t) end end def delete_tags(*enum) enum.each do |t| tag = Flapjack::Data::Tag.find("#{TAG_PREFIX}:#{t}", :redis => @redis) tag.delete(@id) tags.delete(t) end end private # NB: initializer should not be used directly -- instead one of the finder methods # above will call it def initialize(options = {}) raise "Redis connection not set" unless @redis = options[:redis] raise "Entity name not set" unless @name = options[:name] @id = options[:id] @logger = options[:logger] end def load_tags entity_tags = ::Set.new tag_data = @redis.keys("#{TAG_PREFIX}:*").inject([]) do |memo, entity_tag| tag = Flapjack::Data::Tag.find(entity_tag, :redis => @redis) memo << entity_tag.sub(/^#{TAG_PREFIX}:/, '') if tag.include?(@id.to_s) memo end entity_tags.merge(tag_data) end end end end