Class: Ecfr::VersionerService::Ancestors

Inherits:
Base show all
Extended by:
ResponseHelper
Defined in:
lib/ecfr/versioner_service/ancestors.rb,
lib/ecfr/versioner_service/ancestors/node_summary.rb,
lib/ecfr/versioner_service/ancestors/metadata_node_info.rb,
lib/ecfr/testing/extensions/versioner_service/ancestors_extensions.rb

Overview

Ancestors is used to retrieve data about the hierarchical structure of the CFR. The ‘.find` method expects to be provided a limited hierarchy, for example just a title and part - and will return the ancestors in the hierarchy that part is located within.

As a performance optimization the full structure hierarchy to the provided hierarchy can also be returned as part of the response.

Previous and next node attributes are used for navigating around the same level of hierarchy, for example from subpart to subpart, or from section to section even when the next section is in a sibling part (last section of one subpart -> first section of the next).

Source xml filesize is an estimate of the size of the content that would be returned if the provided hierarchy was retrieved.

Defined Under Namespace

Classes: MetadataNodeInfo, NodeSummary

Constant Summary collapse

ANCESTORS_PATH =
"v1/ancestry"
PARAMETERS_NOT_AFFECTING_BASE_RESPONSE =

these parameters add items to the base response but do not affect our ability to cache that base response

%i[metadata]
SIDELOAD_PARAMETERS =
%i[structure descendant_depth]

Constants inherited from Base

Base::SUPPORTED_ARRAY_ACCESSORS

Instance Attribute Summary collapse

Attributes inherited from Base

#metadata, #request_data, #response_status, #results

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ResponseHelper

stubbed_response

Methods inherited from Base

base_url, service_name, service_path

Methods inherited from Base

basic_auth_client_options, metadata, metadata_key, result_key

Methods included from Extensible

#inherited

Methods included from AttributeMethodDefinition

included

Methods inherited from Client

build, cache_key, client, client_pool, delete, execute, get, handle_response, perform, post, purge

Methods included from ParallelClient

included

Constructor Details

#initialize(attributes, options = {}) ⇒ Ancestors

We overload the inherited initialize method in order to modify how we represent the response to the end user.

- we add a `hierarchy` key to each ancestor that includes it's parents
- we set a `structure` attribute if structure was returned in the
  response


159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/ecfr/versioner_service/ancestors.rb', line 159

def initialize(attributes, options = {})
  super(attributes, options)

  ancestors.each_with_object({}) do |ancestor, hsh|
    hsh[ancestor.type] = ancestor.identifier
    ancestor.attributes["hierarchy"] = hsh.dup
  end

  # force casting of structure to a structure object
  # such that it gets cached immediately - see
  # Structure sideloaded format for details
  structure if attributes["structure"]
end

Instance Attribute Details

#ancestors[NodeSummary] (readonly) Also known as: all

an array of ancestor hierarchy levels of the hierarchy found

Returns:



28
29
30
# File 'lib/ecfr/versioner_service/ancestors.rb', line 28

attribute :ancestors,
type: Array(NodeSummary),
desc: "an array of ancestor hierarchy levels of the hierarchy found"

#next_nodeMetadataNodeInfo (readonly)

the next node in the hierarchy

Returns:



42
43
44
# File 'lib/ecfr/versioner_service/ancestors.rb', line 42

 :next_node,
type: MetadataNodeInfo,
desc: "the next node in the hierarchy"

#previous_nodeMetadataNodeInfo (readonly)

the previous node in the hierarchy

Returns:



39
40
41
# File 'lib/ecfr/versioner_service/ancestors.rb', line 39

 :previous_node,
type: MetadataNodeInfo,
desc: "the previous node in the hierarchy"

#source_xml_filesizeInteger (readonly)

the approximate file size of the xml for the hierarchy found

Returns:

  • (Integer)


46
47
48
# File 'lib/ecfr/versioner_service/ancestors.rb', line 46

 :source_xml_filesize,
type: :integer,
desc: "the approximate file size of the xml for the hierarchy found"

#structureEcfr::VersionerService::Structure (readonly)

the full hierarchy to the requested (limited) hierarchy



33
34
35
36
37
# File 'lib/ecfr/versioner_service/ancestors.rb', line 33

attribute :structure,
type: Ecfr::VersionerService::Structure,
value_key: "structure",
options: {format: "sideloaded", self: true},
desc: "the full hierarchy to the requested (limited) hierarchy"

Class Method Details

.cache_base_response(response, method, path, params) ⇒ Object

if the request only includes optional parameters that don’t affect the basic part of the response, then we can cache a base form of that response by removing those optional return items (this keeps the cache consistent across request by not including extra items).



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/ecfr/versioner_service/ancestors.rb', line 107

def self.cache_base_response(response, method, path, params)
  non_hierarchy_params = params.except(
    *Ecfr::Constants::Hierarchy::HIERARCHY_LEVELS.map(&:to_sym)
  )

  if only_safe_params?(non_hierarchy_params)
    cache_key = cache_key(method, path, params.except(*SIDELOAD_PARAMETERS))

    # in the case the the response is the base response the cache keys
    # will match - fetch will just return that and this will essentially
    # be a no op
    RequestStore.fetch(cache_key) do
      excluded_items = params.key?(:metadata) ?
        [:structure] : [:metadata, :structure]

      response_body = JSON.parse(response.body)
        .except(*excluded_items.map(&:to_s))

      # return a ducktyped object like the response object
      OpenStruct.new(
        body: response_body.to_json,
        status: response.status
      )
    end
  end
end

.find(date:, title_number:, hierarchy_args: {}, options: {}) ⇒ <Ancestors>

Retrive ancestor date about a given (limited) hierarchy on a particular date

Parameters:

  • date (<Date, String>)

    Date object, ISO date string, or ‘current’

  • title_number (<Integer>)

    the title number to search within

  • hierarchy_args (<Ecfr::Common::Hierarchy>) (defaults to: {})

    a set of hierarchy keys that identify the content for which the full hierarchy should be found

  • options (<Hash>) (defaults to: {})

    various options that can change the behavior of the response

Options Hash (options:):

  • :structure (<Boolean>)

    used in combination with the :descendent_depth option to include the structure hierarchy of the requested content

  • :descendant_depth (<Integer>)

    the number of levels of descendents to include in the structure

  • :build_id (<Integer>)

    internal use only - used to retreive data about a specific build

  • :metadata (<Boolean>)

    whether to include metadata in the response

  • :max_items (<Integer>)
  • :partial_ancestors_ok (<Boolean>)
  • :smart_folding (<Boolean>)

Returns:



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/ecfr/versioner_service/ancestors.rb', line 78

def self.find(date:, title_number:, hierarchy_args: {}, options: {})
  # ActiveSupport::HashWithIndifferentAccess does not have a ! method
  hierarchy_args = hierarchy_args.symbolize_keys
  options = options.symbolize_keys

  options = hierarchy_args.merge(options).except(:title, :metadata)
  options[:metadata] = true

  perform(
    :get,
    ancestors_path(date, title_number),
    params: options.compact
  )
end

.only_safe_params?(params) ⇒ Boolean

private_class_method :cache_base_response

Returns:

  • (Boolean)


135
136
137
138
139
140
141
# File 'lib/ecfr/versioner_service/ancestors.rb', line 135

def self.only_safe_params?(params)
  # are all of the params keys included in the one of the lists?
  params.keys.all? do |p|
    (PARAMETERS_NOT_AFFECTING_BASE_RESPONSE +
      SIDELOAD_PARAMETERS).include?(p)
  end
end

.response_for(ancestors) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/ecfr/testing/extensions/versioner_service/ancestors_extensions.rb', line 4

def self.response_for(ancestors)
  # ancestors can be a hash of attributes from the :ancestors factory,
  # or one+ NodeSummary attribute hashes from the :node_summary
  # factory
  results = if ancestors.is_a?(Hash) && ancestors.key?("ancestors")
    ancestors
  else
    {
      ancestors: ancestors.is_a?(Array) ? ancestors : [ancestors]
    }
  end

  build(
    response: stubbed_response(results.to_json)
  )
end

Instance Method Details

#eachObject



148
149
150
# File 'lib/ecfr/versioner_service/ancestors.rb', line 148

def each
  ancestors.each { |result| yield result }
end