module ETL #:nodoc: module Transform #:nodoc: # Transform which looks up the value and replaces it with a foriegn key reference class ForeignKeyLookupTransform < ETL::Transform::Transform # The resolver to use if the foreign key is not found in the collection attr_accessor :resolver # Initialize the foreign key lookup transform. # # Configuration options: # *:collection: A Hash of natural keys mapped to surrogate keys. If this is not specified then # an empty Hash will be used. This Hash will be used to cache values that have been resolved already # for future use. # *:resolver: Object or Class which implements the method resolve(value) def initialize(control, name, configuration={}) super @collection = (configuration[:collection] || {}) @resolver = configuration[:resolver] @resolver = @resolver.new if @resolver.is_a?(Class) end # Transform the value by resolving it to a foriegn key def transform(name, value, row) fk = @collection[value] unless fk raise ResolverError, "Foreign key for #{value} not found and no resolver specified" unless resolver raise ResolverError, "Resolver does not appear to respond to resolve method" unless resolver.respond_to?(:resolve) fk = resolver.resolve(value) raise ResolverError, "Unable to resolve #{value} to foreign key for #{name} in row #{ETL::Engine.rows_read}" unless fk @collection[value] = fk end fk end end # Alias class name for the ForeignKeyLookupTransform. class FkLookupTransform < ForeignKeyLookupTransform; end end end # Resolver which resolves using ActiveRecord. class ActiveRecordResolver # The ActiveRecord class to use attr_accessor :ar_class # The find method to use (as a symbol) attr_accessor :find_method # Initialize the resolver. The ar_class argument should extend from # ActiveRecord::Base. The find_method argument must be a symbol for the # finder method used. For example: # # ActiveRecordResolver.new(Person, :find_by_name) # # Note that the find method defined must only take a single argument. def initialize(ar_class, find_method) @ar_class = ar_class @find_method = find_method end # Resolve the value def resolve(value) rec = ar_class.__send__(find_method, value) rec.nil? ? nil : rec.id end end class SQLResolver # Initialize the SQL resolver. Use the given table and field name to search # for the appropriate foreign key. The field should be the name of a natural # key that is used to locate the surrogate key for the record. # # The connection argument is optional. If specified it can be either a symbol # referencing a connection defined in the ETL database.yml file or an actual # ActiveRecord connection instance. If the connection is not specified then # the ActiveRecord::Base.connection will be used. def initialize(table, field, connection=nil) @table = table @field = field @connection = (connection.respond_to?(:quote) ? connection : ETL::Engine.connection(connection)) if connection @connection ||= ActiveRecord::Base.connection end def resolve(value) @connection.select_value("SELECT id FROM #{@table} WHERE #{@field} = #{@connection.quote(value)}") end end