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,78 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class EcfrCorrection < Base
|
|
4
|
+
require_relative "ecfr_correction/cfr_reference"
|
|
5
|
+
|
|
6
|
+
result_key :ecfr_corrections
|
|
7
|
+
|
|
8
|
+
attribute :corrective_action,
|
|
9
|
+
desc: "description of action taken"
|
|
10
|
+
attribute :fr_citation,
|
|
11
|
+
desc: "Federal Register citation in form of '80 FR 12345'"
|
|
12
|
+
|
|
13
|
+
attribute :id,
|
|
14
|
+
type: :integer
|
|
15
|
+
attribute :position,
|
|
16
|
+
type: :integer,
|
|
17
|
+
desc: "position of correction in list"
|
|
18
|
+
attribute :title,
|
|
19
|
+
type: :integer,
|
|
20
|
+
desc: "CFR Title"
|
|
21
|
+
attribute :year,
|
|
22
|
+
type: :integer,
|
|
23
|
+
desc: "year of corrective action"
|
|
24
|
+
|
|
25
|
+
attribute :error_corrected,
|
|
26
|
+
type: :date,
|
|
27
|
+
desc: "date error was corrected"
|
|
28
|
+
attribute :error_occurred,
|
|
29
|
+
type: :date,
|
|
30
|
+
desc: "date error first occured"
|
|
31
|
+
attribute :last_modified,
|
|
32
|
+
type: :date,
|
|
33
|
+
desc: "date this correction was last modified"
|
|
34
|
+
|
|
35
|
+
attribute :cfr_references,
|
|
36
|
+
type: Array(CfrReference),
|
|
37
|
+
desc: "the portions of the CFR this correction applies to"
|
|
38
|
+
|
|
39
|
+
attribute :display_in_toc,
|
|
40
|
+
type: :boolean,
|
|
41
|
+
desc: "whether this correction should be displayed on table of contents pages"
|
|
42
|
+
|
|
43
|
+
CORRECTIONS_PATH = "v1/corrections.json"
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Retrieve the list of all corrections
|
|
47
|
+
#
|
|
48
|
+
# @return [[<EcfrCorrection>]] an array of eCFR correction records
|
|
49
|
+
#
|
|
50
|
+
def self.all
|
|
51
|
+
perform(
|
|
52
|
+
:get,
|
|
53
|
+
CORRECTIONS_PATH
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# Retrieve a list of corrections filtered by the provided parameters
|
|
59
|
+
#
|
|
60
|
+
# @param [<Hash>] args parameters by which to filter the results; supported args:
|
|
61
|
+
# - :date
|
|
62
|
+
# - :error_corrected_date
|
|
63
|
+
# - :title
|
|
64
|
+
#
|
|
65
|
+
# @return [[<EcfrCorrection>]] an array of eCFR correction records
|
|
66
|
+
#
|
|
67
|
+
def self.where(args)
|
|
68
|
+
args.slice!(:date, :error_corrected_date, :title)
|
|
69
|
+
|
|
70
|
+
perform(
|
|
71
|
+
:get,
|
|
72
|
+
CORRECTIONS_PATH,
|
|
73
|
+
params: args
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class EditorialNote
|
|
4
|
+
class Hierarchy
|
|
5
|
+
include AttributeMethodDefinition
|
|
6
|
+
extend Extensible
|
|
7
|
+
|
|
8
|
+
attribute :title,
|
|
9
|
+
desc: "Title number"
|
|
10
|
+
attribute :part,
|
|
11
|
+
desc: "Part identifier"
|
|
12
|
+
attribute :subpart,
|
|
13
|
+
desc: "Subpart identifier"
|
|
14
|
+
attribute :section,
|
|
15
|
+
desc: "Section identifier"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class EditorialNote < Base
|
|
4
|
+
require_relative "editorial_note/hierarchy"
|
|
5
|
+
|
|
6
|
+
result_key :editorial_notes
|
|
7
|
+
|
|
8
|
+
attribute :editorial_note,
|
|
9
|
+
desc: "content of the editorial note"
|
|
10
|
+
|
|
11
|
+
attribute :end_date,
|
|
12
|
+
type: :date,
|
|
13
|
+
desc: "last date the editorial note applies to"
|
|
14
|
+
attribute :start_date,
|
|
15
|
+
type: :date,
|
|
16
|
+
desc: "first date the editorial note applies to"
|
|
17
|
+
|
|
18
|
+
attribute :hierarchies,
|
|
19
|
+
type: Array(Hierarchy),
|
|
20
|
+
desc: "the hierarchies of the content this editorial note applies to"
|
|
21
|
+
|
|
22
|
+
EDITORIAL_NOTES_PATH = "v1/editorial-notes"
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Retrieve a list of editorial notes
|
|
26
|
+
#
|
|
27
|
+
# @param [<Hash>] args parameters by which to filter the results; supported args:
|
|
28
|
+
# - :title
|
|
29
|
+
#
|
|
30
|
+
# @return [[<EditorialNote>]] an array of editorial note records
|
|
31
|
+
#
|
|
32
|
+
def self.where(args)
|
|
33
|
+
perform(
|
|
34
|
+
:get,
|
|
35
|
+
"#{EDITORIAL_NOTES_PATH}/title-#{args[:title]}.json"
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class IbrCfrRange
|
|
4
|
+
class Address
|
|
5
|
+
include AttributeMethodDefinition
|
|
6
|
+
extend Extensible
|
|
7
|
+
|
|
8
|
+
attribute :address,
|
|
9
|
+
desc: "generally a physical address"
|
|
10
|
+
attribute :address_group_1,
|
|
11
|
+
desc: "first level description of address"
|
|
12
|
+
attribute :address_group_2,
|
|
13
|
+
desc: "second level description of address"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class IbrCfrRange
|
|
4
|
+
class Organization
|
|
5
|
+
include AttributeMethodDefinition
|
|
6
|
+
extend Extensible
|
|
7
|
+
|
|
8
|
+
attribute :details,
|
|
9
|
+
desc: "generally contact information for the organization"
|
|
10
|
+
attribute :name,
|
|
11
|
+
desc: "organization name"
|
|
12
|
+
|
|
13
|
+
class Material
|
|
14
|
+
include AttributeMethodDefinition
|
|
15
|
+
|
|
16
|
+
attribute :cfr_references,
|
|
17
|
+
desc: "locations in the CFR where this material is incorporated by reference"
|
|
18
|
+
attribute :details,
|
|
19
|
+
desc: "information about what was incorporated - generally publication title, etc."
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attribute :materials,
|
|
23
|
+
type: Array(Material),
|
|
24
|
+
desc: "array of materials that have been incorporated by reference"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class IbrCfrRange < Base
|
|
4
|
+
require_relative "ibr_cfr_range/address"
|
|
5
|
+
require_relative "ibr_cfr_range/organization"
|
|
6
|
+
|
|
7
|
+
result_key :ibr_cfr_ranges
|
|
8
|
+
|
|
9
|
+
attribute :label,
|
|
10
|
+
desc: "description - generally the agency responsible"
|
|
11
|
+
attribute :notes,
|
|
12
|
+
desc: "notes about the IBR content"
|
|
13
|
+
attribute :part_range,
|
|
14
|
+
desc: "the range of parts the IBR item applies to"
|
|
15
|
+
|
|
16
|
+
attribute :title,
|
|
17
|
+
type: :integer,
|
|
18
|
+
desc: "CFR title number"
|
|
19
|
+
|
|
20
|
+
attribute :organizations,
|
|
21
|
+
type: Array(Organization),
|
|
22
|
+
desc: "array of organizations responsible for IBR materials and more details"
|
|
23
|
+
|
|
24
|
+
attribute :addresses,
|
|
25
|
+
type: Array(Address),
|
|
26
|
+
desc: "address information for district offices, etc."
|
|
27
|
+
|
|
28
|
+
IBR_CFR_RANGES_PATH = "v1/incorporation-by-reference"
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Retrieve a summarized list of all IBR information
|
|
32
|
+
#
|
|
33
|
+
# @return [[<IbrCfrRange>]] an array of IBR information
|
|
34
|
+
# limited to `title`, `part_range`, and `label` fields
|
|
35
|
+
#
|
|
36
|
+
def self.all
|
|
37
|
+
perform(
|
|
38
|
+
:get,
|
|
39
|
+
"#{IBR_CFR_RANGES_PATH}.json"
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Retrieve a detailed list of IBR information for the request IBR CFR range
|
|
45
|
+
#
|
|
46
|
+
# @param [<String, Integer>] title CFR title
|
|
47
|
+
# @param [<String>] part_range the part range in the form "1-99"
|
|
48
|
+
#
|
|
49
|
+
# @return [<IbrCfrRange>] contains all data available for specified params
|
|
50
|
+
#
|
|
51
|
+
def self.find(title, part_range)
|
|
52
|
+
perform(
|
|
53
|
+
:get,
|
|
54
|
+
ibr_cfr_range_path(title, part_range),
|
|
55
|
+
perform_options: {attributes_key: "ibr_cfr_range"}
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.ibr_cfr_range_path(title, part_range)
|
|
60
|
+
part_range = ERB::Util.url_encode(part_range)
|
|
61
|
+
|
|
62
|
+
"#{IBR_CFR_RANGES_PATH}/title-#{title}/#{part_range}.json"
|
|
63
|
+
end
|
|
64
|
+
private_class_method :ibr_cfr_range_path
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class Issue
|
|
4
|
+
class Change
|
|
5
|
+
extend Ecfr::Constants::ChangeTypes
|
|
6
|
+
|
|
7
|
+
include AttributeMethodDefinition
|
|
8
|
+
extend Extensible
|
|
9
|
+
|
|
10
|
+
attribute :change_description,
|
|
11
|
+
desc: "CFR references that this change affects"
|
|
12
|
+
|
|
13
|
+
attribute :change_type,
|
|
14
|
+
type: :symbol,
|
|
15
|
+
desc: "indicates the type of change - corresponds to the keys in {KNOWN_CHANGE_TYPES}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class Issue < Base
|
|
4
|
+
require_relative "issue/change"
|
|
5
|
+
|
|
6
|
+
result_key :issues
|
|
7
|
+
|
|
8
|
+
attribute :description,
|
|
9
|
+
desc: "US standard date description"
|
|
10
|
+
|
|
11
|
+
attribute :titles,
|
|
12
|
+
type: Array(:integer),
|
|
13
|
+
desc: "array of CFR title numbers affected in this issue"
|
|
14
|
+
|
|
15
|
+
attribute :end_date,
|
|
16
|
+
type: :date,
|
|
17
|
+
desc: "end date of issue"
|
|
18
|
+
attribute :issue_date,
|
|
19
|
+
type: :date,
|
|
20
|
+
desc: "date of issue"
|
|
21
|
+
attribute :start_date,
|
|
22
|
+
type: :date,
|
|
23
|
+
desc: "start date of issue"
|
|
24
|
+
|
|
25
|
+
attribute :changes,
|
|
26
|
+
type: Array(Change),
|
|
27
|
+
desc: "array of change types and CFR reference data"
|
|
28
|
+
|
|
29
|
+
metadata_key :meta
|
|
30
|
+
|
|
31
|
+
# @return [String, nil]
|
|
32
|
+
metadata :next,
|
|
33
|
+
desc: "date of next issue in ISO format"
|
|
34
|
+
# @return [String, nil]
|
|
35
|
+
metadata :previous,
|
|
36
|
+
desc: "date of previous issue in ISO format"
|
|
37
|
+
|
|
38
|
+
ISSUES_PATH = "v1/issues"
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Retrieves a summary of all available issues
|
|
42
|
+
#
|
|
43
|
+
# @return [[<Issue>]] does not include the `changes` or `metadata` items
|
|
44
|
+
#
|
|
45
|
+
def self.all
|
|
46
|
+
perform(
|
|
47
|
+
:get,
|
|
48
|
+
"#{ISSUES_PATH}.json"
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# Retrieves detailed issue data for the specified date.
|
|
54
|
+
# If the isssue spans multiple days and date in that timespan will return the issue
|
|
55
|
+
#
|
|
56
|
+
# @param [<Date, DateTime, Time, String<'current'>] date the string 'current' can be provided to retrieve the newest issue at time of request
|
|
57
|
+
#
|
|
58
|
+
# @return [[<Issue>]] returns array containing single issue for the date provided
|
|
59
|
+
#
|
|
60
|
+
def self.find(date:)
|
|
61
|
+
perform(
|
|
62
|
+
:get,
|
|
63
|
+
issues_path(date)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.issues_path(date)
|
|
68
|
+
"#{ISSUES_PATH}/#{date_slug_for(date)}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# supports :current, Date, DateTime, Time
|
|
72
|
+
def self.date_slug_for(date_like)
|
|
73
|
+
return "current" if date_like == :current
|
|
74
|
+
|
|
75
|
+
if date_like.respond_to?(:to_date)
|
|
76
|
+
date_like = date_like.to_date
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
return date_like.iso8601 if date_like.is_a?(Date)
|
|
80
|
+
|
|
81
|
+
raise "Unknown input for Ecfr::AdminService::Issue"
|
|
82
|
+
end
|
|
83
|
+
private_class_method :date_slug_for
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
module AdminService
|
|
3
|
+
class SiteNotification < Base
|
|
4
|
+
result_key :notifications
|
|
5
|
+
|
|
6
|
+
attribute :last_updated_by,
|
|
7
|
+
desc: "email of user who last updated the notification"
|
|
8
|
+
attribute :message,
|
|
9
|
+
desc: "notification content"
|
|
10
|
+
attribute :notification_type,
|
|
11
|
+
desc: "where the notification should be displayed; either `global` or `global_top`"
|
|
12
|
+
|
|
13
|
+
attribute :active,
|
|
14
|
+
type: :boolean
|
|
15
|
+
|
|
16
|
+
attribute :updated_at,
|
|
17
|
+
type: :datetime
|
|
18
|
+
|
|
19
|
+
SITE_NOTIFICATIONS_PATH = "v1/notifications.json"
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
# Retrieves all active notifications
|
|
23
|
+
#
|
|
24
|
+
# @return [[<SiteNotification>]] array of site notifications
|
|
25
|
+
#
|
|
26
|
+
def self.all
|
|
27
|
+
perform(
|
|
28
|
+
:get,
|
|
29
|
+
SITE_NOTIFICATIONS_PATH
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
class AttributeCaster
|
|
3
|
+
#
|
|
4
|
+
# Given a value and a type return the value cast to the type.
|
|
5
|
+
# Some types do not actually modify the value but exist in order
|
|
6
|
+
# to support automated documentation via YARD.
|
|
7
|
+
#
|
|
8
|
+
# @param [<Integer, Float, String>] val the value as
|
|
9
|
+
# parsed from a JSON response
|
|
10
|
+
# @param [<Symbol, Class>] type a type to cast the value to,
|
|
11
|
+
# classes will be instantiated with the value
|
|
12
|
+
# @param [<Hash>] options options are passed to classes when they
|
|
13
|
+
# are instantiated. {base: false} is merged into these
|
|
14
|
+
# options to indicate that metadata attributes (among other
|
|
15
|
+
# things) should not be populated. See {Ecfr::Base#initialize}
|
|
16
|
+
#
|
|
17
|
+
# @return a typecast value
|
|
18
|
+
#
|
|
19
|
+
# @note some API endpoints do not return a key for values that are
|
|
20
|
+
# false (such as processing_in_progress). Because the attribute
|
|
21
|
+
# is not include in the response it can not be typecast as
|
|
22
|
+
# boolean false (it will return nil however which is still falsey)
|
|
23
|
+
#
|
|
24
|
+
def self.cast_attr(val, type, options = {})
|
|
25
|
+
return val if val.nil?
|
|
26
|
+
|
|
27
|
+
case type
|
|
28
|
+
when :integer
|
|
29
|
+
val # JSON parse was sufficient
|
|
30
|
+
when :float
|
|
31
|
+
val # JSON parse was sufficient
|
|
32
|
+
when :boolean
|
|
33
|
+
ActiveModel::Type::Boolean.new.cast(val)
|
|
34
|
+
when :date
|
|
35
|
+
return val if val.is_a?(Date)
|
|
36
|
+
Date.parse(val.to_s)
|
|
37
|
+
when :datetime
|
|
38
|
+
return val if val.is_a?(DateTime)
|
|
39
|
+
DateTime.parse(val.to_s)
|
|
40
|
+
when :symbol
|
|
41
|
+
val.to_sym
|
|
42
|
+
when ->(type) { type.respond_to?(:new) }
|
|
43
|
+
instantiate(type, val, options.merge(base: false))
|
|
44
|
+
# type.new(val, options.merge(base: false))
|
|
45
|
+
when ->(type) { type.is_a?(Array) && type.first.respond_to?(:new) }
|
|
46
|
+
val.map { |v| instantiate(type.first, v, options.merge(base: false)) } # type.first.new(v, options.merge(base: false)) }
|
|
47
|
+
when ->(type) { type.is_a?(Array) && type.first == :symbol }
|
|
48
|
+
val.map(&:to_sym)
|
|
49
|
+
when ->(type) { type.is_a?(Array) && [:integer, :string].include?(type.first) }
|
|
50
|
+
val # JSON parse was sufficient
|
|
51
|
+
when nil
|
|
52
|
+
val # JSON parse was sufficient
|
|
53
|
+
else
|
|
54
|
+
raise "Undefined type '#{type}' provided to 'attribute'/'metadata'!"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Support inheritance by instantiating the class
|
|
60
|
+
# inheriting from our internal class if it's been
|
|
61
|
+
# defined
|
|
62
|
+
#
|
|
63
|
+
def self.instantiate(klass, val, options)
|
|
64
|
+
if const_defined?("#{klass}::KLASS")
|
|
65
|
+
klass::KLASS.new(val, options)
|
|
66
|
+
else
|
|
67
|
+
klass.new(val, options)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :instantiate
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
#
|
|
3
|
+
# Provides the base functionality for attribute and metadata
|
|
4
|
+
# definition.
|
|
5
|
+
#
|
|
6
|
+
module AttributeMethodDefinition
|
|
7
|
+
module ClassMethods
|
|
8
|
+
#
|
|
9
|
+
# Provides attribute definition. The attribute defined is
|
|
10
|
+
# expected to be part of the response. If the key in the
|
|
11
|
+
# response is different than the attribute name (eg you want
|
|
12
|
+
# to rename it) then the `value_key` option can be used.
|
|
13
|
+
#
|
|
14
|
+
# When the :type option is :boolean we also define a
|
|
15
|
+
# predicate method (eg: we define both foo and foo?)
|
|
16
|
+
#
|
|
17
|
+
# @param [<Symbol>] attr name of the attribute to define
|
|
18
|
+
# @param [<Hash>] options
|
|
19
|
+
# @option options [<Symbol, Class>] type a type to cast the
|
|
20
|
+
# JSON parsed value to. See {Ecfr::AttributeCaster}.
|
|
21
|
+
# @option options [<String>] desc
|
|
22
|
+
# @option options [<String>] value_key the name of the key
|
|
23
|
+
# in the repsonse (only needed if the attribute name is
|
|
24
|
+
# different than the key in the response)
|
|
25
|
+
# @option options [<Hash>] options options to be passed to
|
|
26
|
+
# Class when it is instantiated (when :type is a class)
|
|
27
|
+
#
|
|
28
|
+
# @note If no type option is provided, String, is assumed by
|
|
29
|
+
# the YARD documentation generated. Types other than String
|
|
30
|
+
# are expected to be explicitely defined.
|
|
31
|
+
#
|
|
32
|
+
def attribute(attr, options = {})
|
|
33
|
+
define_method attr do
|
|
34
|
+
val = extract_value(attr, options)
|
|
35
|
+
|
|
36
|
+
# sideloaded classes need their referrer to properly
|
|
37
|
+
# cache themselves
|
|
38
|
+
if options[:options] && options[:options][:self]
|
|
39
|
+
options[:options][:referrer] = self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
Ecfr::AttributeCaster.cast_attr(val, options[:type], options[:options] || {})
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if options[:type] == :boolean
|
|
46
|
+
define_method "#{attr}?" do
|
|
47
|
+
val = extract_value(attr, options)
|
|
48
|
+
Ecfr::AttributeCaster.cast_attr(val, options[:type], options[:options] || {})
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.included(base)
|
|
55
|
+
base.instance_eval do
|
|
56
|
+
attr_reader :attributes, :options
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
base.extend(ClassMethods)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize(attributes = {}, options = {})
|
|
63
|
+
@attributes = attributes
|
|
64
|
+
@options = options
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Returns the value for the requested attribute from the
|
|
71
|
+
# set of attributes. A value_key option can be passed
|
|
72
|
+
# for attributes that don't match the key in the response
|
|
73
|
+
# Return nil if no attributes are present at all (in the
|
|
74
|
+
# case of metadata this means the key wasn't present in the
|
|
75
|
+
# response)
|
|
76
|
+
#
|
|
77
|
+
# @param [Symbol, String] attr attribute name
|
|
78
|
+
# @param [Hash] options
|
|
79
|
+
# @option options [Symbol, String] value_key the key in the
|
|
80
|
+
# response to retreive instead of the attribute name
|
|
81
|
+
#
|
|
82
|
+
def extract_value(attr, options)
|
|
83
|
+
return nil unless @attributes
|
|
84
|
+
|
|
85
|
+
if options[:value_key]
|
|
86
|
+
@attributes[options[:value_key].to_s]
|
|
87
|
+
else
|
|
88
|
+
@attributes[attr.to_s]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/ecfr/base.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Ecfr
|
|
2
|
+
class Base < Client
|
|
3
|
+
include AttributeMethodDefinition
|
|
4
|
+
include Enumerable
|
|
5
|
+
extend Extensible
|
|
6
|
+
|
|
7
|
+
class_attribute :result_root
|
|
8
|
+
class_attribute :metadata_accessor
|
|
9
|
+
self.metadata_accessor = "metadata"
|
|
10
|
+
|
|
11
|
+
def self.result_key(key)
|
|
12
|
+
self.result_root = key.to_s
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.metadata_key(key)
|
|
16
|
+
self.metadata_accessor = key.to_s
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.basic_auth_client_options
|
|
20
|
+
{
|
|
21
|
+
basic_auth: {
|
|
22
|
+
username: Ecfr.config.ecfr_basic_auth_username,
|
|
23
|
+
password: Ecfr.config.ecfr_basic_auth_password
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.metadata(*metadata)
|
|
29
|
+
Metadata.attribute(*metadata)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
attr_reader :metadata, :results, :response_status, :request_data
|
|
33
|
+
|
|
34
|
+
SUPPORTED_ARRAY_ACCESSORS = %i[empty? first last size]
|
|
35
|
+
delegate(*SUPPORTED_ARRAY_ACCESSORS, to: :results)
|
|
36
|
+
alias_method :all, :results
|
|
37
|
+
|
|
38
|
+
def initialize(attributes = {}, options = {})
|
|
39
|
+
default_options = {base: true}
|
|
40
|
+
options = default_options.merge(options)
|
|
41
|
+
|
|
42
|
+
@response_status = options.delete(:response_status)
|
|
43
|
+
@request_data = options.delete(:request_data)
|
|
44
|
+
|
|
45
|
+
# - handle result_root only if the first time instantiating
|
|
46
|
+
# the class from the results
|
|
47
|
+
# - some responses won't be a parsed attribute hash
|
|
48
|
+
if options[:base] && attributes.is_a?(Hash)
|
|
49
|
+
@metadata = Metadata.new(attributes.delete(self.class.metadata_accessor))
|
|
50
|
+
|
|
51
|
+
if result_root && attributes[result_root]
|
|
52
|
+
@results = attributes[result_root].map do |r|
|
|
53
|
+
self.class.new(r, base: false)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
super(attributes)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def each
|
|
62
|
+
@results.each { |result| yield result }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class Metadata
|
|
66
|
+
include AttributeMethodDefinition
|
|
67
|
+
|
|
68
|
+
attr_reader :attributes
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|