module DocumentHydrator

Extracted from ActiveSupport::Inflector in ActiveSupport 3.0.9.

Public Class Methods

hydrate_document(document, path_or_paths, hydration_proc) click to toggle source

Given a document hash, a path or array of paths describing locations of object IDs within the hash, and a function that will convert object IDs to a hash of subdocument hashes indexed by object ID, modifies the document hash so that all of the IDs referenced by the paths are replaced with the corresponding subdocuments.

Path examples:

document = {
  'owner' => 99,
  'clients' => [100, 101],
  'comments' => [
    { 'user' => 10, text => 'hi' },
    { 'user' => 11, text => 'hello' }
  ]
}

Each of these are valid paths:

- 'owner'
- 'clients'
- 'comments.user'

Returns the document to allow for chaining.

# File lib/document_hydrator.rb, line 30
def hydrate_document(document, path_or_paths, hydration_proc)
  hydrate_documents([document],path_or_paths, hydration_proc)
  document
end
hydrate_documents(documents, path_or_paths, hydration_proc) click to toggle source
# File lib/document_hydrator.rb, line 35
def hydrate_documents(documents, path_or_paths, hydration_proc)
  # Traverse the documents replacing each ID with a corresponding dehydrated document
  dehydrated_subdocuments = Hash.new { |h, k| h[k] = Hash.new }
  documents.each do |document|
    paths = path_or_paths.kind_of?(Array) ? path_or_paths : [path_or_paths]
    paths.each do |path|
      replace_ids_with_dehydrated_documents(document, path.split('.'), dehydrated_subdocuments)
    end
  end

  # Rehydrate the documents that we discovered during traversal all in one go
  ids = dehydrated_subdocuments.keys
  hydrated_subdocuments = hydration_proc.call(ids)
  ids.each {|id| dehydrated_subdocuments[id].replace(hydrated_subdocuments[id])}

  documents
end

Private Class Methods

replace_ids_with_dehydrated_documents(document, path_steps, dehydrated_documents) click to toggle source
# File lib/document_hydrator.rb, line 55
def replace_ids_with_dehydrated_documents(document, path_steps, dehydrated_documents)
  step = path_steps.first
  next_steps = path_steps[1..-1]
  if document.has_key?(step)
    subdocument = document[step]
    if next_steps.empty?
      # End of the path, do the hydration, dropping any _id or _ids suffix
      if step =~ /_ids?$/
        document.delete(step)
        step = step.sub(/_id(s?)$/, '')
        step = Inflector.pluralize(step) if $1 == 's'
      end
      document[step] =
        case subdocument
        when Array
          subdocument.map {|id| dehydrated_documents[id] }
        when nil
          nil
        else
          dehydrated_documents[subdocument]
        end
    else
      # Keep on stepping
      if subdocument.kind_of?(Array)
        subdocument.each { |item| replace_ids_with_dehydrated_documents(item, next_steps, dehydrated_documents) }
      else
        replace_ids_with_dehydrated_documents(subdocument, next_steps, dehydrated_documents)
      end
    end
  end
end