ecfr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|