ecfr 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +11 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rspec_parallel +4 -0
- data/.rubocop.yml +28 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +6 -0
- data/Dockerfile +6 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +138 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/ecfr.gemspec +74 -0
- data/lib/ecfr/admin_service/agency/hierarchy.rb +27 -0
- data/lib/ecfr/admin_service/agency.rb +34 -0
- data/lib/ecfr/admin_service/api_documentation.rb +7 -0
- data/lib/ecfr/admin_service/base.rb +26 -0
- data/lib/ecfr/admin_service/build.rb +38 -0
- data/lib/ecfr/admin_service/ecfr_correction/cfr_reference.rb +17 -0
- data/lib/ecfr/admin_service/ecfr_correction.rb +78 -0
- data/lib/ecfr/admin_service/editorial_note/hierarchy.rb +19 -0
- data/lib/ecfr/admin_service/editorial_note.rb +40 -0
- data/lib/ecfr/admin_service/ibr_cfr_range/address.rb +17 -0
- data/lib/ecfr/admin_service/ibr_cfr_range/organization.rb +28 -0
- data/lib/ecfr/admin_service/ibr_cfr_range.rb +67 -0
- data/lib/ecfr/admin_service/issue/change.rb +19 -0
- data/lib/ecfr/admin_service/issue.rb +86 -0
- data/lib/ecfr/admin_service/site_notification.rb +34 -0
- data/lib/ecfr/admin_service/status.rb +7 -0
- data/lib/ecfr/attribute_caster.rb +72 -0
- data/lib/ecfr/attribute_method_definition.rb +92 -0
- data/lib/ecfr/base.rb +71 -0
- data/lib/ecfr/client.rb +318 -0
- data/lib/ecfr/common/hierarchy.rb +35 -0
- data/lib/ecfr/configuration.rb +58 -0
- data/lib/ecfr/constants.rb +21 -0
- data/lib/ecfr/default_documentation_setup.rb +39 -0
- data/lib/ecfr/default_status_setup.rb +46 -0
- data/lib/ecfr/diff_service/base.rb +17 -0
- data/lib/ecfr/diff_service/status.rb +34 -0
- data/lib/ecfr/extensible.rb +45 -0
- data/lib/ecfr/facet_attribute_method_definition.rb +47 -0
- data/lib/ecfr/faraday/user_agent/middleware.rb +14 -0
- data/lib/ecfr/ofr_profile_service/base.rb +20 -0
- data/lib/ecfr/ofr_profile_service/status.rb +7 -0
- data/lib/ecfr/parallel_client.rb +33 -0
- data/lib/ecfr/prince_xml_service/base.rb +17 -0
- data/lib/ecfr/prince_xml_service/pdf.rb +31 -0
- data/lib/ecfr/renderer_service/base.rb +31 -0
- data/lib/ecfr/renderer_service/content.rb +34 -0
- data/lib/ecfr/renderer_service/diff.rb +31 -0
- data/lib/ecfr/renderer_service/origin.rb +56 -0
- data/lib/ecfr/renderer_service/status.rb +7 -0
- data/lib/ecfr/request_representation.rb +12 -0
- data/lib/ecfr/search_service/api_documentation.rb +7 -0
- data/lib/ecfr/search_service/base.rb +23 -0
- data/lib/ecfr/search_service/content_version/count.rb +33 -0
- data/lib/ecfr/search_service/content_version/hierarchical_count.rb +17 -0
- data/lib/ecfr/search_service/content_version/hierarchical_count_node.rb +30 -0
- data/lib/ecfr/search_service/content_version/hierarchichal_result.rb +42 -0
- data/lib/ecfr/search_service/content_version/result.rb +110 -0
- data/lib/ecfr/search_service/content_version/suggestion.rb +76 -0
- data/lib/ecfr/search_service/content_version/summary.rb +27 -0
- data/lib/ecfr/search_service/content_version.rb +85 -0
- data/lib/ecfr/search_service/date_facet.rb +19 -0
- data/lib/ecfr/search_service/facet_base.rb +55 -0
- data/lib/ecfr/search_service/status.rb +7 -0
- data/lib/ecfr/search_service/title_facet.rb +18 -0
- data/lib/ecfr/subscriptions_service/base.rb +19 -0
- data/lib/ecfr/subscriptions_service/status.rb +7 -0
- data/lib/ecfr/subscriptions_service/subscription.rb +97 -0
- data/lib/ecfr/testing/extensions/admin_service/ecfr_correction_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/admin_service/issue_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/renderer_service/origin_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/search_service/content_version_result_extensions.rb +16 -0
- data/lib/ecfr/testing/extensions/search_service/date_facet_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/versioner_service/ancestors_extensions.rb +20 -0
- data/lib/ecfr/testing/extensions/versioner_service/title_extenstions.rb +16 -0
- data/lib/ecfr/testing/factories/admin_service/cfr_reference_factory.rb +14 -0
- data/lib/ecfr/testing/factories/admin_service/ecfr_correction_factory.rb +31 -0
- data/lib/ecfr/testing/factories/admin_service/issue_change_factory.rb +12 -0
- data/lib/ecfr/testing/factories/admin_service/issue_factory.rb +21 -0
- data/lib/ecfr/testing/factories/common/hierarchy_factory.rb +36 -0
- data/lib/ecfr/testing/factories/renderer_service/origin_factory.rb +32 -0
- data/lib/ecfr/testing/factories/search_service/content_version_count_factory.rb +20 -0
- data/lib/ecfr/testing/factories/search_service/content_version_result_factory.rb +76 -0
- data/lib/ecfr/testing/factories/search_service/date_facet_factory.rb +12 -0
- data/lib/ecfr/testing/factories/versioner_service/ancestors_factory.rb +26 -0
- data/lib/ecfr/testing/factories/versioner_service/metadata_node_info_factory.rb +15 -0
- data/lib/ecfr/testing/factories/versioner_service/node_summary_factory.rb +16 -0
- data/lib/ecfr/testing/factories/versioner_service/structure_factory.rb +57 -0
- data/lib/ecfr/testing/factories/versioner_service/title_factory.rb +36 -0
- data/lib/ecfr/testing/factory_bot_helpers/content_version.rb +38 -0
- data/lib/ecfr/testing/factory_bot_helpers/ecfr_gem_initialize_helpers.rb +51 -0
- data/lib/ecfr/testing/helpers/response_helper.rb +5 -0
- data/lib/ecfr/testing/strategies/ecfr_attribute_hash_strategy.rb +37 -0
- data/lib/ecfr/testing.rb +28 -0
- data/lib/ecfr/version.rb +5 -0
- data/lib/ecfr/versioner_service/ancestors/metadata_node_info.rb +22 -0
- data/lib/ecfr/versioner_service/ancestors/node_summary.rb +54 -0
- data/lib/ecfr/versioner_service/ancestors.rb +152 -0
- data/lib/ecfr/versioner_service/api_documentation.rb +7 -0
- data/lib/ecfr/versioner_service/base.rb +24 -0
- data/lib/ecfr/versioner_service/status.rb +7 -0
- data/lib/ecfr/versioner_service/structure.rb +120 -0
- data/lib/ecfr/versioner_service/title.rb +78 -0
- data/lib/ecfr/versioner_service/xml_content.rb +59 -0
- data/lib/ecfr.rb +90 -0
- data/lib/yard/attribute_handler.rb +87 -0
- data/lib/yard/metadata_handler.rb +87 -0
- metadata +389 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :ancestors, class: "Ecfr::VersionerService::Ancestors" do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
ancestors {
|
6
|
+
[ecfr_attribute_hash(:node_summary)]
|
7
|
+
}
|
8
|
+
|
9
|
+
metadata {
|
10
|
+
{
|
11
|
+
previous_node: ecfr_attribute_hash(:metadata_node_info),
|
12
|
+
next_node: ecfr_attribute_hash(:metadata_node_info)
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
trait :with_structure do
|
17
|
+
structure { ecfr_attribute_hash(:structure)["data"] }
|
18
|
+
end
|
19
|
+
|
20
|
+
source_xml_filesize { 2096 }
|
21
|
+
|
22
|
+
initialize_with {
|
23
|
+
new(attributes.deep_stringify_keys)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :metadata_node_info, class: "Ecfr::VersionerService::Ancestors::MetadataNodeInfo" do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
hierarchy {
|
6
|
+
ecfr_attribute_hash(:hierarchy, :with_defaults)
|
7
|
+
}
|
8
|
+
|
9
|
+
label { "Part 100 - Standards of Conduct" }
|
10
|
+
|
11
|
+
initialize_with {
|
12
|
+
new(attributes.deep_stringify_keys)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :node_summary, class: "Ecfr::VersionerService::Ancestors::MetadataNodeInfo" do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
identifier { "3" }
|
6
|
+
label { "Title 3 - The President" }
|
7
|
+
label_level { "Title 3" }
|
8
|
+
label_description { "The President" }
|
9
|
+
reserved { false }
|
10
|
+
type { "title" }
|
11
|
+
|
12
|
+
initialize_with {
|
13
|
+
new(attributes.deep_stringify_keys)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :structure, class: "Ecfr::VersionerService::Structure" do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
data {
|
6
|
+
{
|
7
|
+
identifier: "3",
|
8
|
+
label: "Title 3 - The President",
|
9
|
+
label_level: "Title 3",
|
10
|
+
label_description: "The President",
|
11
|
+
reserved: false,
|
12
|
+
type: "title",
|
13
|
+
children: [
|
14
|
+
{
|
15
|
+
identifier: "I",
|
16
|
+
label: " Chapter I - Executive Office of the President",
|
17
|
+
label_level: " Chapter I",
|
18
|
+
label_description: "Executive Office of the President",
|
19
|
+
reserved: false,
|
20
|
+
type: "chapter",
|
21
|
+
children: [
|
22
|
+
{
|
23
|
+
identifier: "101",
|
24
|
+
label: "Part 101 - Public Information Provisions of the Administrative Procedures Act",
|
25
|
+
label_level: "Part 101",
|
26
|
+
label_description: "Public Information Provisions of the Administrative Procedures Act",
|
27
|
+
reserved: false,
|
28
|
+
type: "part",
|
29
|
+
volumes: [
|
30
|
+
"1"
|
31
|
+
],
|
32
|
+
children: [
|
33
|
+
{
|
34
|
+
identifier: "101.1",
|
35
|
+
label: "§ 101.1 Executive Office of the President.",
|
36
|
+
label_level: "§ 101.1",
|
37
|
+
label_description: "Executive Office of the President.",
|
38
|
+
reserved: false,
|
39
|
+
type: "section",
|
40
|
+
volumes: [
|
41
|
+
"1"
|
42
|
+
],
|
43
|
+
received_on: "2018-10-17T01:00:00-0400"
|
44
|
+
}
|
45
|
+
]
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
]
|
50
|
+
}.to_json
|
51
|
+
}
|
52
|
+
|
53
|
+
initialize_with {
|
54
|
+
new(data)
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :title, class: "Ecfr::VersionerService::Title" do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
name { "General Provisions" }
|
6
|
+
number { 1 }
|
7
|
+
|
8
|
+
processing_in_progress { false }
|
9
|
+
reserved { false }
|
10
|
+
|
11
|
+
latest_amended_on { nil }
|
12
|
+
latest_issue_date { nil }
|
13
|
+
up_to_date_as_of { nil }
|
14
|
+
|
15
|
+
meta {
|
16
|
+
{
|
17
|
+
date: metadata_date,
|
18
|
+
import_in_progress: import_in_progress
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
transient do
|
23
|
+
metadata_date { nil }
|
24
|
+
import_in_progress { false }
|
25
|
+
end
|
26
|
+
|
27
|
+
initialize_with {
|
28
|
+
new(attributes.deep_stringify_keys)
|
29
|
+
}
|
30
|
+
|
31
|
+
trait :processing do
|
32
|
+
processing_in_progress { true }
|
33
|
+
import_in_progress { true }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module FactoryBotHelpers
|
2
|
+
module ContentVersion
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def hierarchy_headings(attributes)
|
6
|
+
headings = attributes[:hierarchy].compact.each_with_object({}) do |attr, hsh|
|
7
|
+
hsh[attr[0]] = if attr[0] == :section
|
8
|
+
"§ #{attr[1]}"
|
9
|
+
elsif attr[0] == :appendix
|
10
|
+
attr[1]
|
11
|
+
else
|
12
|
+
[attr[0].to_s.capitalize, attr[1]].join(" ")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
headings.merge(attributes[:hierarchy_headings])
|
17
|
+
end
|
18
|
+
|
19
|
+
def headings(attributes)
|
20
|
+
default_headings = {
|
21
|
+
title: "Energy",
|
22
|
+
subtitle: "Other Regulations Relating to Nuclear Power",
|
23
|
+
chapter: "Nuclear Regulatory Commission",
|
24
|
+
part: "Agency Rules of Practice and Procedure",
|
25
|
+
subpart: "Procedure for Imposing Requirements",
|
26
|
+
subject_group: "Serious Civil Penalties",
|
27
|
+
section: "Civil penalties",
|
28
|
+
appendix: "Possibilities, Procedures and Probabilities"
|
29
|
+
}
|
30
|
+
|
31
|
+
headings = attributes[:hierarchy].compact.each_with_object({}) do |attr, hsh|
|
32
|
+
hsh[attr[0]] = default_headings[attr[0]]
|
33
|
+
end
|
34
|
+
|
35
|
+
headings.merge(attributes[:headings])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module FactoryBotHelpers
|
2
|
+
module EcfrGemHelpers
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Allows an upstream user wants to override the factory defined in this gem
|
6
|
+
# be instantiated as another class defined in thier applicaiton (inheriting
|
7
|
+
# from a gem defined class). See the Extensible module for more details.
|
8
|
+
def override_factory_class(phactory, klass)
|
9
|
+
FactoryBot.modify do
|
10
|
+
factory phactory do
|
11
|
+
initialize_with {
|
12
|
+
attrs = FactoryBotHelpers::EcfrGemHelpers.pre_initialize_attrs_for(
|
13
|
+
phactory,
|
14
|
+
attributes
|
15
|
+
)
|
16
|
+
attrs.deep_stringify_keys! if attrs.respond_to?(:deep_stringify_keys)
|
17
|
+
klass.constantize.new(attrs)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Provides a single method that any factory can call for attribute
|
24
|
+
# manipulation and is used to when override_factory_class is used
|
25
|
+
# to generate a factory definied in this gem but as another class
|
26
|
+
# (that inherits from a gem defined class) defined in their application.
|
27
|
+
def pre_initialize_attrs_for(factory, attrs)
|
28
|
+
case factory
|
29
|
+
when :content_version_result
|
30
|
+
pre_initialize_attrs_for_content_version_result(attrs)
|
31
|
+
when :structure
|
32
|
+
attrs[:data]
|
33
|
+
else
|
34
|
+
attrs
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# These preinitialize helpers allow us to modify attributes before they are
|
41
|
+
# passed to the factory class for instantiation.
|
42
|
+
|
43
|
+
def pre_initialize_attrs_for_content_version_result(attrs)
|
44
|
+
# use some helpers to generate these structures based on the data provided
|
45
|
+
attrs.merge(
|
46
|
+
headings: FactoryBotHelpers::ContentVersion.headings(attrs).stringify_keys,
|
47
|
+
hierarchy_headings: FactoryBotHelpers::ContentVersion.hierarchy_headings(attrs).stringify_keys
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#
|
2
|
+
# A build strategy that take the initialized factory and returns the
|
3
|
+
# attribute hash. The attribute hash or array of attribute hashes can then
|
4
|
+
# be passed to the .build method on the Ecfr class being testing to receive
|
5
|
+
# a response object.
|
6
|
+
#
|
7
|
+
class EcfrAttributeHashStrategy
|
8
|
+
def initialize
|
9
|
+
@strategy = FactoryBot.strategy_by_name(:build).new
|
10
|
+
end
|
11
|
+
|
12
|
+
delegate :association, to: :@strategy
|
13
|
+
|
14
|
+
def result(evaluation)
|
15
|
+
hsh = {}
|
16
|
+
|
17
|
+
hsh.merge!(evaluation.object.attributes) if evaluation.object.attributes
|
18
|
+
|
19
|
+
# handle direct json results like structure that aren't cast to attributes
|
20
|
+
if evaluation.object.respond_to?(:data) && evaluation.object.data
|
21
|
+
hsh[:data] = evaluation.object.data
|
22
|
+
end
|
23
|
+
|
24
|
+
# handle classes that return metadata
|
25
|
+
key = if evaluation.object.class.respond_to?(:metadata_accessor) &&
|
26
|
+
evaluation.object.class.metadata_accessor
|
27
|
+
evaluation.object.class.metadata_accessor
|
28
|
+
else
|
29
|
+
"metadata"
|
30
|
+
end
|
31
|
+
if evaluation.object.respond_to?(:metadata) && evaluation.object.metadata
|
32
|
+
hsh[key] = evaluation.object.metadata.attributes
|
33
|
+
end
|
34
|
+
|
35
|
+
hsh.deep_stringify_keys
|
36
|
+
end
|
37
|
+
end
|
data/lib/ecfr/testing.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# we expect the client that includes the testing items to have
|
4
|
+
# factory bot installed
|
5
|
+
begin
|
6
|
+
Gem::Specification.find_by_name("factory_bot")
|
7
|
+
require "factory_bot"
|
8
|
+
rescue Gem::LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
# custom factory bot strategies
|
12
|
+
require_relative "testing/strategies/ecfr_attribute_hash_strategy"
|
13
|
+
FactoryBot.register_strategy(:ecfr_attribute_hash, EcfrAttributeHashStrategy)
|
14
|
+
|
15
|
+
# require all our test support files
|
16
|
+
current_path = __dir__
|
17
|
+
[
|
18
|
+
# generic testing helpers
|
19
|
+
"helpers",
|
20
|
+
# factory bot specific helper methods
|
21
|
+
"factory_bot_helpers",
|
22
|
+
# testing specific methods for each class
|
23
|
+
"extensions",
|
24
|
+
# factories
|
25
|
+
"factories"
|
26
|
+
].each do |dir|
|
27
|
+
Dir.glob(File.join(current_path, "testing/#{dir}/**/*.rb")) { |f| require f }
|
28
|
+
end
|
data/lib/ecfr/version.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
class Ancestors
|
4
|
+
class MetadataNodeInfo
|
5
|
+
include AttributeMethodDefinition
|
6
|
+
|
7
|
+
attribute :hierarchy,
|
8
|
+
type: Ecfr::Common::Hierarchy,
|
9
|
+
desc: "the heirarchy this node resides in"
|
10
|
+
|
11
|
+
attribute :label,
|
12
|
+
desc: "the display value of the hierarchy"
|
13
|
+
|
14
|
+
# currently the API returns null for each key in the object
|
15
|
+
# so we need to do an attribute check
|
16
|
+
def present?
|
17
|
+
@attributes.compact.present?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
class Ancestors
|
4
|
+
#
|
5
|
+
# This class provides a summary of important data for the
|
6
|
+
# level in the hierarchy that it represents.
|
7
|
+
#
|
8
|
+
# Some levels of the hierarchy (generally subject groups)
|
9
|
+
# use generated identifiers because they are not designated
|
10
|
+
# potions of the CFR hierarchy and thus are not completely
|
11
|
+
# stable over time.
|
12
|
+
#
|
13
|
+
class NodeSummary
|
14
|
+
include AttributeMethodDefinition
|
15
|
+
extend Extensible
|
16
|
+
|
17
|
+
attribute :descendant_range,
|
18
|
+
desc: "the range of sections that are descendents of this level"
|
19
|
+
attribute :identifier,
|
20
|
+
desc: "the shortened identifier portion of the
|
21
|
+
CFR level"
|
22
|
+
attribute :label,
|
23
|
+
desc: "the full description of the CFR level"
|
24
|
+
attribute :label_level,
|
25
|
+
desc: "the full identifier portion of the CFR level"
|
26
|
+
attribute :label_description,
|
27
|
+
desc: "the descriptive portion of the CFR level"
|
28
|
+
attribute :type,
|
29
|
+
desc: "the type of CFR level - the value will be a
|
30
|
+
stringified version of the attribute keys in
|
31
|
+
{Ecfr::Common::Hierarchy}"
|
32
|
+
|
33
|
+
attribute :generated_id,
|
34
|
+
type: :boolean,
|
35
|
+
desc: "whether the identifier was generated"
|
36
|
+
attribute :reserved,
|
37
|
+
type: :boolean,
|
38
|
+
desc: "whether this level represents a portion of
|
39
|
+
the CFR that is Reserved"
|
40
|
+
|
41
|
+
attribute :volumes,
|
42
|
+
type: Array(:string),
|
43
|
+
desc: "the Volumes this level of the hierarchy was present
|
44
|
+
within in the print edition - only appears on Part levels and below"
|
45
|
+
|
46
|
+
# NOTE: this is autogenerated when the parent Ancestors
|
47
|
+
# class is instantiated and not part of the API response
|
48
|
+
attribute :hierarchy,
|
49
|
+
type: Ecfr::Common::Hierarchy,
|
50
|
+
desc: "the hierarchy for this node"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
#
|
4
|
+
# Ancestors is used to retrieve data about the hierarchical
|
5
|
+
# structure of the CFR. The `.find` method expects to be
|
6
|
+
# provided a limited hierarchy, for example just a title
|
7
|
+
# and part - and will return the ancestors in the hierarchy
|
8
|
+
# that part is located within.
|
9
|
+
#
|
10
|
+
# As a performance optimization the full structure hierarchy
|
11
|
+
# to the provided hierarchy can also be returned as part of
|
12
|
+
# the response.
|
13
|
+
#
|
14
|
+
# Previous and next node attributes are used for navigating
|
15
|
+
# around the same level of hierarchy, for example from
|
16
|
+
# subpart to subpart, or from section to section even when
|
17
|
+
# the next section is in a sibling part (last section of
|
18
|
+
# one subpart -> first section of the next).
|
19
|
+
#
|
20
|
+
# Source xml filesize is an estimate of the size of the
|
21
|
+
# content that would be returned if the provided hierarchy
|
22
|
+
# was retrieved.
|
23
|
+
#
|
24
|
+
class Ancestors < Base
|
25
|
+
require_relative "ancestors/metadata_node_info"
|
26
|
+
require_relative "ancestors/node_summary"
|
27
|
+
|
28
|
+
attribute :ancestors,
|
29
|
+
type: Array(NodeSummary),
|
30
|
+
desc: "an array of ancestor hierarchy levels of the hierarchy found"
|
31
|
+
|
32
|
+
# previously this was structure_as_hierarchy
|
33
|
+
attribute :structure,
|
34
|
+
type: Ecfr::VersionerService::Structure,
|
35
|
+
value_key: "structure",
|
36
|
+
options: {format: "sideloaded", self: true},
|
37
|
+
desc: "the full hierarchy to the requested (limited) hierarchy"
|
38
|
+
|
39
|
+
metadata :previous_node,
|
40
|
+
type: MetadataNodeInfo,
|
41
|
+
desc: "the previous node in the hierarchy"
|
42
|
+
metadata :next_node,
|
43
|
+
type: MetadataNodeInfo,
|
44
|
+
desc: "the next node in the hierarchy"
|
45
|
+
|
46
|
+
metadata :source_xml_filesize,
|
47
|
+
type: :integer,
|
48
|
+
desc: "the approximate file size of the xml for the hierarchy found"
|
49
|
+
|
50
|
+
ANCESTORS_PATH = "v1/ancestry"
|
51
|
+
|
52
|
+
#
|
53
|
+
# Retrive ancestor date about a given (limited) hierarchy
|
54
|
+
# on a particular date
|
55
|
+
#
|
56
|
+
# @param [<Date, String>] date Date object, ISO date string, or 'current'
|
57
|
+
# @param [<Integer>] title_number the title number to search within
|
58
|
+
# @param [<Ecfr::Common::Hierarchy>] hierarchy_args a set
|
59
|
+
# of hierarchy keys that identify the content for which
|
60
|
+
# the full hierarchy should be found
|
61
|
+
# @param [<Hash>] options various options that can change
|
62
|
+
# the behavior of the response
|
63
|
+
# @option options [<Boolean>] :structure used in combination
|
64
|
+
# with the :descendent_depth option to include the structure
|
65
|
+
# hierarchy of the requested content
|
66
|
+
# @option options [<Integer>] :descendant_depth the number of
|
67
|
+
# levels of descendents to include in the structure
|
68
|
+
# @option options [<Integer>] :build_id internal use only - used to
|
69
|
+
# retreive data about a specific build
|
70
|
+
# @option options [<Boolean>] :metadata whether to include metadata in
|
71
|
+
# the response
|
72
|
+
# @option options [<Integer>] :max_items
|
73
|
+
# @option options [<Boolean>] :partial_ancestors_ok
|
74
|
+
# @option options [<Boolean>] :smart_folding
|
75
|
+
#
|
76
|
+
# @return [<Ancestors>]
|
77
|
+
#
|
78
|
+
def self.find(date:, title_number:, hierarchy_args: {}, options: {})
|
79
|
+
# ActiveSupport::HashWithIndifferentAccess does not have a ! method
|
80
|
+
hierarchy_args = hierarchy_args.symbolize_keys
|
81
|
+
options = options.symbolize_keys
|
82
|
+
|
83
|
+
options = hierarchy_args.merge(options).except(:title, :metadata)
|
84
|
+
options[:metadata] = true
|
85
|
+
|
86
|
+
perform(
|
87
|
+
:get,
|
88
|
+
ancestors_path(date, title_number),
|
89
|
+
params: options
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.ancestors_path(date, title_number)
|
94
|
+
"#{ANCESTORS_PATH}/#{date}/title-#{title_number}"
|
95
|
+
end
|
96
|
+
private_class_method :ancestors_path
|
97
|
+
|
98
|
+
# these parameters add items to the base response but do not affect our
|
99
|
+
# ability to cache that base response
|
100
|
+
PARAMETERS_NOT_AFFECTING_BASE_RESPONSE = %w[metadata structure descendant_depth]
|
101
|
+
|
102
|
+
# if the request only includes optional parameters that don't affect the
|
103
|
+
# basic part of the response, then we can catch a base form of that
|
104
|
+
# response by removing those optional return items (this keeps the cache
|
105
|
+
# consistent across request by not including extra items).
|
106
|
+
def self.cache_base_response(response, path, params)
|
107
|
+
if only_safe_params?(params)
|
108
|
+
cache_key = cache_key(method, path, {})
|
109
|
+
RequestStore.fetch(cache_key) do
|
110
|
+
response.except(:metadata, :structure)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
private_class_method :cache_base_response
|
115
|
+
|
116
|
+
def self.only_safe_params?(params)
|
117
|
+
# are all of the params keys included in the list?
|
118
|
+
params.keys.all? { |p| PARAMETERS_NOT_AFFECTING_BASE_RESPONSE.include?(p) }
|
119
|
+
end
|
120
|
+
|
121
|
+
# override enumerable setup because we don't have
|
122
|
+
# a single result root key
|
123
|
+
delegate(*SUPPORTED_ARRAY_ACCESSORS, to: :ancestors)
|
124
|
+
alias_method :all, :ancestors
|
125
|
+
|
126
|
+
def each
|
127
|
+
ancestors.each { |result| yield result }
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# We overload the inherited initialize method in order to modify how we
|
132
|
+
# represent the response to the end user.
|
133
|
+
# - we add a `hierarchy` key to each ancestor that includes it's parents
|
134
|
+
# - we set a `structure` attribute if structure was returned in the
|
135
|
+
# response
|
136
|
+
#
|
137
|
+
def initialize(attributes, options = {})
|
138
|
+
super(attributes, options)
|
139
|
+
|
140
|
+
ancestors.each_with_object({}) do |ancestor, hsh|
|
141
|
+
hsh[ancestor.type] = ancestor.identifier
|
142
|
+
ancestor.attributes["hierarchy"] = hsh.dup
|
143
|
+
end
|
144
|
+
|
145
|
+
# force casting of structure to a structure object
|
146
|
+
# such that it gets cached immediately - see
|
147
|
+
# Structure sideloaded format for details
|
148
|
+
structure if attributes["structure"]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
class Base < Ecfr::Base
|
4
|
+
require_relative "api_documentation"
|
5
|
+
require_relative "status"
|
6
|
+
|
7
|
+
# note: structure must be required before ancestors
|
8
|
+
require_relative "structure"
|
9
|
+
require_relative "ancestors"
|
10
|
+
require_relative "title"
|
11
|
+
require_relative "xml_content"
|
12
|
+
|
13
|
+
SERVICE_PATH = "/api/versioner"
|
14
|
+
|
15
|
+
def self.base_url
|
16
|
+
Ecfr.config.versioner_service_url || Ecfr.config.base_url
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.service_name
|
20
|
+
"Versioner Service"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|