From c5ab7cafd80237f1866b2ee873a124271b04be85 Mon Sep 17 00:00:00 2001 From: Brian Moses Hall Date: Tue, 16 Jan 2024 14:53:04 -0500 Subject: [PATCH] DEV-993 Remove rights_database Dependency - Add `Database` class based on `rights_database` repo/gem. - Change `RIGHTS_DATABASE_CONNECTION_STRING` to `RIGHTS_API_DATABASE_CONNECTION_STRING` for consistency. - Internal consistency and transparency tweaks: - De-abbreviate `Services[:db_connection` to `Services[:database_connection]`. - Re-order `SUPPORT_TABLES` to keep it alphabetical - Move rspec boilerplate to bottom of `spec_helper.rb` - Add `climate_control` gem for manipulating `ENV` in new database specs. --- Gemfile | 4 +- Gemfile.lock | 13 ++--- docker-compose.yml | 4 +- lib/rights_api.rb | 1 + lib/rights_api/database.rb | 46 ++++++++++++++++ lib/rights_api/query.rb | 2 +- lib/rights_api/services.rb | 11 ++-- spec/integration/support_table_spec.rb | 2 +- spec/spec_helper.rb | 76 +++++--------------------- spec/unit/database_spec.rb | 46 ++++++++++++++++ 10 files changed, 124 insertions(+), 81 deletions(-) create mode 100644 lib/rights_api/database.rb create mode 100644 spec/unit/database_spec.rb diff --git a/Gemfile b/Gemfile index e0db0a1..22b9afe 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,11 @@ source "https://rubygems.org" gem "canister" +gem "climate_control" gem "json" +gem "mysql2" gem "puma" -gem "rights_database", github: "hathitrust/rights_database" +gem "sequel" gem "sinatra" gem "sinatra-contrib" diff --git a/Gemfile.lock b/Gemfile.lock index 22a50b0..43d0a85 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,3 @@ -GIT - remote: https://github.com/hathitrust/rights_database.git - revision: 2d152f9e2079a7f2997de6cc9fdb783d5ee1f030 - specs: - rights_database (0.1.0) - mysql2 - sequel - GEM remote: https://rubygems.org/ specs: @@ -13,6 +5,7 @@ GEM base64 (0.1.1) bigdecimal (3.1.4) canister (0.9.2) + climate_control (1.2.0) coderay (1.1.3) diff-lcs (1.5.0) docile (1.4.0) @@ -115,12 +108,14 @@ PLATFORMS DEPENDENCIES canister + climate_control json + mysql2 pry puma rack-test - rights_database! rspec + sequel simplecov simplecov-lcov sinatra diff --git a/docker-compose.yml b/docker-compose.yml index 8396c44..5692d1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: - gem_cache:/gems command: bundle exec rackup --host 0.0.0.0 -p 4567 environment: - RIGHTS_DATABASE_CONNECTION_STRING: "mysql2://ht_rights:ht_rights@mariadb/ht" + RIGHTS_API_DATABASE_CONNECTION_STRING: "mysql2://ht_rights:ht_rights@mariadb/ht" RIGHTS_API_LOGGER_LEVEL: 1 # Logger::INFO depends_on: - mariadb @@ -24,7 +24,7 @@ services: - gem_cache:/gems command: bash -c "/usr/local/bin/wait-for mariadb:3306 && bundle exec rspec" environment: - RIGHTS_DATABASE_CONNECTION_STRING: "mysql2://ht_rights:ht_rights@mariadb/ht" + RIGHTS_API_DATABASE_CONNECTION_STRING: "mysql2://ht_rights:ht_rights@mariadb/ht" depends_on: - mariadb diff --git a/lib/rights_api.rb b/lib/rights_api.rb index e5a1213..55c203e 100644 --- a/lib/rights_api.rb +++ b/lib/rights_api.rb @@ -4,6 +4,7 @@ module RightsAPI end require "rights_api/app" +require "rights_api/database" require "rights_api/query" require "rights_api/result" require "rights_api/schema" diff --git a/lib/rights_api/database.rb b/lib/rights_api/database.rb new file mode 100644 index 0000000..66ed4da --- /dev/null +++ b/lib/rights_api/database.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "sequel" + +module RightsAPI + class Database + # .connect will take + # * a full connection string (passed here OR in the environment + # variable RIGHTS_API_DATABASE_CONNECTION_STRING) + # * a set of named arguments, drawn from those passed in and the + # environment. Arguments are those supported by Sequel. + # + # Environment variables are mapped as follows: + # + # user: RIGHTS_API_DATABASE_USER + # password: RIGHTS_API_DATABASE_PASSWORD + # host: RIGHTS_API_DATABASE_HOST + # port: RIGHTS_API_DATABASE_PORT + # database: RIGHTS_API_DATABASE_DATABASE + # adapter: RIGHTS_API_DATABASE_ADAPTER + def connect(connection_string = nil, **) + return @connection if @connection + + connection_string ||= ENV["RIGHTS_API_DATABASE_CONNECTION_STRING"] + if connection_string.nil? + db_args = gather_args(**) + Sequel.connect(**db_args) + else + Sequel.connect(connection_string) + end + end + + private + + def gather_args(**args) + %i[user password host port database adapter].each do |arg| + args[arg] ||= ENV["RIGHTS_API_DATABASE_#{arg.to_s.upcase}"] + end + + args[:host] ||= "localhost" + args[:adapter] ||= :mysql2 + args[:database] ||= "ht" + args + end + end +end diff --git a/lib/rights_api/query.rb b/lib/rights_api/query.rb index 374d7be..0b5c946 100644 --- a/lib/rights_api/query.rb +++ b/lib/rights_api/query.rb @@ -17,7 +17,7 @@ def initialize(table_name:) # @return [Result] def run(id:) schema_class = Schema.class_for name: table_name - dataset = Services[:db_connection][Schema.table_for name: table_name] + dataset = Services[:database_connection][Schema.table_for name: table_name] if id where = {schema_class.query_for_field(field: schema_class.primary_key) => id} dataset = dataset.where(where) diff --git a/lib/rights_api/services.rb b/lib/rights_api/services.rb index f6f7d1c..db47c11 100644 --- a/lib/rights_api/services.rb +++ b/lib/rights_api/services.rb @@ -2,20 +2,21 @@ require "canister" require "logger" -require "rights_database" + +require_relative "database" module RightsAPI Services = Canister.new - Services.register(:rights_database) do - RightsDatabase + Services.register(:database) do + Database.new end Services.register(:logger) do Logger.new($stdout, level: ENV.fetch("RIGHTS_API_LOGGER_LEVEL", Logger::WARN).to_i) end - Services.register(:db_connection) do - Services[:rights_database].connect.tap do |connection| + Services.register(:database_connection) do + Services[:database].connect.tap do |connection| connection.logger = Services[:logger] end end diff --git a/spec/integration/support_table_spec.rb b/spec/integration/support_table_spec.rb index 1d653da..d8beffe 100644 --- a/spec/integration/support_table_spec.rb +++ b/spec/integration/support_table_spec.rb @@ -4,10 +4,10 @@ require "shared_examples" SUPPORT_TABLES = %w[ - attributes access_profiles access_statements access_statements_map + attributes reasons sources ] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cc1946e..aa44510 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,6 +17,20 @@ require_relative "../lib/rights_api" +def app + RightsAPI::App +end + +def parse_json(json) + JSON.parse(json, symbolize_names: true) +end + +def rights_api_endpoint + "/v1/" +end + +# BEGIN RSPEC BOILERPLATE + # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause @@ -62,66 +76,4 @@ # inherited by the metadata hash of host groups and examples, rather than # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups - - # The settings below are suggested to provide a good initial experience - # with RSpec, but feel free to customize to your heart's content. - # # This allows you to limit a spec run to individual examples or groups - # # you care about by tagging them with `:focus` metadata. When nothing - # # is tagged with `:focus`, all examples get run. RSpec also provides - # # aliases for `it`, `describe`, and `context` that include `:focus` - # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. - # config.filter_run_when_matching :focus - # - # # Allows RSpec to persist some state between runs in order to support - # # the `--only-failures` and `--next-failure` CLI options. We recommend - # # you configure your source control system to ignore this file. - # config.example_status_persistence_file_path = "spec/examples.txt" - # - # # Limits the available syntax to the non-monkey patched syntax that is - # # recommended. For more details, see: - # # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode - # config.disable_monkey_patching! - # - # # This setting enables warnings. It's recommended, but in some cases may - # # be too noisy due to issues in dependencies. - # config.warnings = true - # - # # Many RSpec users commonly either run the entire suite or an individual - # # file, and it's useful to allow more verbose output when running an - # # individual spec file. - # if config.files_to_run.one? - # # Use the documentation formatter for detailed output, - # # unless a formatter has already been configured - # # (e.g. via a command-line flag). - # config.default_formatter = "doc" - # end - # - # # Print the 10 slowest examples and example groups at the - # # end of the spec run, to help surface which specs are running - # # particularly slow. - # config.profile_examples = 10 - # - # # Run specs in random order to surface order dependencies. If you find an - # # order dependency and want to debug it, you can fix the order by providing - # # the seed, which is printed after each run. - # # --seed 1234 - # config.order = :random - # - # # Seed global randomization in this process using the `--seed` CLI option. - # # Setting this allows you to use `--seed` to deterministically reproduce - # # test failures related to randomization by passing the same `--seed` value - # # as the one that triggered the failure. - # Kernel.srand config.seed -end - -def app - RightsAPI::App -end - -def parse_json(json) - JSON.parse(json, symbolize_names: true) -end - -def rights_api_endpoint - "/v1/" end diff --git a/spec/unit/database_spec.rb b/spec/unit/database_spec.rb new file mode 100644 index 0000000..39b7ddc --- /dev/null +++ b/spec/unit/database_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "climate_control" + +RSpec.describe RightsAPI::Database do + describe "#initialize" do + it "creates RightsAPI::Database instance" do + expect(described_class.new).to be_an_instance_of(RightsAPI::Database) + end + end + + describe "#connect" do + it "connects with built-in connection string" do + expect(described_class.new).not_to be nil + end + + it "connects with explicit connection string" do + expect(described_class.new.connect(ENV["RIGHTS_API_DATABASE_CONNECTION_STRING"])).not_to be nil + end + + it "connects with connection arguments" do + ClimateControl.modify(RIGHTS_API_DATABASE_CONNECTION_STRING: nil) do + args = { + user: "ht_rights", + password: "ht_rights", + host: "mariadb", + database: "ht", + adapter: "mysql2" + } + expect(described_class.new.connect(**args)).not_to be nil + end + end + + it "connects with ENV variables" do + env = {RIGHTS_API_DATABASE_CONNECTION_STRING: nil, + RIGHTS_API_DATABASE_USER: "ht_rights", + RIGHTS_API_DATABASE_PASSWORD: "ht_rights", + RIGHTS_API_DATABASE_HOST: "mariadb", + RIGHTS_API_DATABASE_DATABASE: "ht", + RIGHTS_API_DATABASE_ADAPTER: "mysql2"} + ClimateControl.modify(**env) do + expect(described_class.new.connect).not_to be nil + end + end + end +end