require 'active_support' require 'flydata' module Flydata module Source def self.create(de) MasterContext.new(de) end def self.register(component_class, type_class) @component_classes ||= {} source_sym = underscored_source_module_sym(component_class) unless source_sym raise "Class must must be under a 'SourceXxxx' module" end component_sym = underscored_class_name_sym(type_class) @component_classes[component_key(source_sym, component_sym)] = component_class end DATA_ENTRY_TYPE_MAP = { "RedshiftMysqlDataEntry" => :source_mysql, "RedshiftFileDataEntry" => :source_file, "FileDataEntry" => :source_file, "RedshiftPostgresqlDataEntry" => :source_postgresql, "RedshiftZendeskDataEntry" => :source_zendesk, "RedshiftOracleDataEntry" => :source_oracle, } def self.component_class_for(component_sym, de) source_sym = DATA_ENTRY_TYPE_MAP[de['type']] unless source_sym raise "No source components are available for this data entry" end @component_classes ||= {} component_key = component_key(source_sym, component_sym) unless @component_classes.has_key?(component_key) require_component(source_sym, component_sym) unless @component_classes.has_key?(component_key) @component_classes[component_key] = nil end end klass = @component_classes[component_key] unless klass raise "Component '#{component_sym}' is not defined in source '#{source_sym}'" end klass end COMMAND_SUBDIR = "command" # Returns require-friendly paths to the source specific command directory. # ex) ["flydata/source_mysql/command", "flydata/source_postgres/command"] def self.command_dir_paths result = [] DATA_ENTRY_TYPE_MAP.values.each do |source_key| path = File.join(source_dir_path(source_key), COMMAND_SUBDIR) if File.exists?(path) result << File.join("flydata", source_key.to_s, COMMAND_SUBDIR) end end result end private def self.underscored_source_module_sym(klass) source_module = klass.name.split("::").detect{|m| m.start_with?("Source")} source_module ? underscore_sym(source_module) : nil end def self.underscored_class_name_sym(klass) underscore_sym(klass.name.split("::").last) end def self.underscore_sym(name) ActiveSupport::Inflector.underscore(name).to_sym end def self.component_key(source_key, component_key) "#{source_key}:#{component_key}" end def self.require_component(source_key, component_key) path = source_dir_path(source_key) path = File.join(path, "**", "#{component_key}.rb") paths = Dir.glob(path) paths.each {|pth| require pth} end def self.source_dir_path(source_key) File.absolute_path(File.join(__FILE__, "..", source_key.to_s)) end class MasterContext def initialize(de) @de = de end attr_reader :de def method_missing(method_sym, *args) source_class = Source.component_class_for method_sym, de source_class.new(self, *args) end end end end