From 9f6301d14df68e88277824bbc4ee8680de5ea680 Mon Sep 17 00:00:00 2001 From: Matheus Richard Date: Mon, 23 Oct 2023 12:42:48 -0300 Subject: [PATCH] Rewrite `GoldMiner` main entrypoint Previously, the `GoldMiner` module (now a class) had methods for each part of the mining process: exploration, smithing, and distribution. The orchestration of when and how these methods were called was done in the `exe/gold_miner` script. This wasn't ideal since there's no way to guarantee the order in which the methods are called, and a script is arguably not the best place to put this logic. Furthermore, that arrangement wasn't flexible, as there was no way to specify different explorers, smiths, or distributors. This commit rewrites the `GoldMiner` class to have an `initialize` method that receives the explorer, smith, and distributor as arguments. It also adds a `#mine` method that orchestrates the mining process. --- exe/gold_miner | 38 +++++++----- lib/gold_miner.rb | 44 ++++++------- lib/gold_miner/slack/client.rb | 1 - spec/gold_miner_spec.rb | 110 ++++++--------------------------- spec/support/test_factories.rb | 10 +++ 5 files changed, 73 insertions(+), 130 deletions(-) diff --git a/exe/gold_miner b/exe/gold_miner index d731e14..6fa7bca 100755 --- a/exe/gold_miner +++ b/exe/gold_miner @@ -3,26 +3,32 @@ $LOAD_PATH.unshift("#{__dir__}/../lib") require "gold_miner" +require "dotenv" -def debug(msg) - return if ENV["DEBUG"].nil? +def load_env_file + Dotenv.load! + Dry::Monads::Success() +rescue Errno::ENOENT + Dry::Monads::Failure("Could not load env file #{env_file.inspect}") +end - puts "[DEBUG] #{msg}" +def prepare + load_env_file + .bind { GoldMiner::Slack::Client.build(api_token: ENV["SLACK_API_TOKEN"]) } + .fmap { |slack_client| + GoldMiner::SlackExplorer.new(slack_client, GoldMiner::AuthorConfig.default) + } end channel = ARGV.first || "dev" -t0 = Time.now - -GoldMiner - .mine_in(channel) - .fmap { |gold_container| - debug "Found #{gold_container.gold_nuggets.size} messages in #{Time.now - t0} seconds." - - blog_post = GoldMiner.smith_blog_post(gold_container) - GoldMiner.distribute(blog_post) - } +prepare.bind { |slack_explorer| + GoldMiner + .new( + explorer: slack_explorer, + smith: GoldMiner::BlogPostSmith.new, + distributor: GoldMiner::TerminalDistributor.new + ) + .mine(channel, start_on: GoldMiner::Helpers::Time.last_friday) +} .or { |error| abort "[ERROR] #{error}" } - -puts -debug "Done in #{Time.now - t0} seconds." diff --git a/lib/gold_miner.rb b/lib/gold_miner.rb index b9e3dbd..0ecfa91 100644 --- a/lib/gold_miner.rb +++ b/lib/gold_miner.rb @@ -1,38 +1,38 @@ # frozen_string_literal: true -require "dotenv" +require "dry/monads" require "zeitwerk" require "openai" Zeitwerk::Loader.for_gem.setup class GoldMiner - class << self - def mine_in(slack_channel, slack_client: GoldMiner::Slack::Client, env_file: ".env") - Dotenv.load!(env_file) + include Dry::Monads[:result] - prepare(slack_client) - .fmap { |client| explore(slack_channel, client) } - end + def initialize(explorer:, smith:, distributor:, env_file: ".env") + @explorer = explorer + @smith = smith + @distributor = distributor + @env_file = env_file + end - def smith_blog_post(gold_container, ...) - BlogPostSmith.new(...).smith(gold_container) - end + def mine(location, start_on:) + explore(location, start_on:) + .bind { |gold_container| smith(gold_container) } + .bind { |blog_post| distribute(blog_post) } + end - def distribute(blog_post) - TerminalDistributor.new.distribute(blog_post) - end + private - private + def explore(location, start_on:) + Success(@explorer.explore(location, start_on:)) + end - def prepare(slack_client) - slack_client.build(api_token: ENV["SLACK_API_TOKEN"]) - end + def smith(gold_container) + Success(@smith.smith(gold_container)) + end - def explore(slack_channel, slack_client) - SlackExplorer - .new(slack_client, AuthorConfig.default) - .explore(slack_channel, start_on: Helpers::Time.last_friday) - end + def distribute(blog_post) + Success(@distributor.distribute(blog_post)) end end diff --git a/lib/gold_miner/slack/client.rb b/lib/gold_miner/slack/client.rb index 25122d0..93a0392 100644 --- a/lib/gold_miner/slack/client.rb +++ b/lib/gold_miner/slack/client.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "dry/monads" require "slack-ruby-client" class GoldMiner diff --git a/spec/gold_miner_spec.rb b/spec/gold_miner_spec.rb index 71112d9..89e95dd 100644 --- a/spec/gold_miner_spec.rb +++ b/spec/gold_miner_spec.rb @@ -1,100 +1,28 @@ # frozen_string_literal: true -require "dry-monads" +require "dry/monads" +require "dotenv" RSpec.describe GoldMiner do include Dry::Monads[:result] - describe ".mine_in" do - it "loads the API token from the given env file" do - slack_client_builder = spy("Slack::Client builder") - - GoldMiner.mine_in("dev", slack_client: slack_client_builder, env_file: "./spec/fixtures/.env.test") - - expect(slack_client_builder).to have_received(:build).with(api_token: "test-token") - end - - it "returns interesting messages from the given channel" do - message_author = TestFactories.create_slack_user - slack_message = TestFactories.create_slack_message(user: message_author) - search_result = [slack_message] - - slack_client = instance_double(GoldMiner::Slack::Client, search_messages: search_result) - slack_client_builder = double(GoldMiner::Slack::Client, build: Success(slack_client)) - - result = GoldMiner.mine_in("dev", slack_client: slack_client_builder, env_file: "./spec/fixtures/.env.test") - gold_nuggets = result.value!.gold_nuggets - - expect(gold_nuggets).to eq [ - TestFactories.create_gold_nugget( - content: slack_message.text, - author: TestFactories.create_author( - id: message_author.username, - name: message_author.name, - link: "#to-do" - ), - source: slack_message.permalink - ) - ] - end - end - - describe ".smith_blog_post" do - it "creates a blog post from a gold container" do - date = "2022-10-07" - travel_to date do - with_env("OPEN_AI_API_TOKEN" => nil) do - channel = "dev" - gold_nuggets = [ - TestFactories.create_gold_nugget, - TestFactories.create_gold_nugget - ] - blog_post_class = spy("BlogPost class") - container = TestFactories.create_gold_container( - gold_nuggets: gold_nuggets, - origin: channel, - packing_date: Date.today - ) - GoldMiner.smith_blog_post(container, blog_post_class:) - - expect(blog_post_class).to have_received(:new).with( - slack_channel: channel, - gold_nuggets: gold_nuggets, - since: Date.parse(date), - writer: instance_of(GoldMiner::BlogPost::SimpleWriter) - ) - end - end - end - - context "when the OPEN_AI_API_TOKEN is set" do - it "uses the OpenAiWriter" do - date = "2022-10-07" - token = "test-token" - travel_to date do - with_env("OPEN_AI_API_TOKEN" => token) do - channel = "dev" - gold_nuggets = [ - TestFactories.create_gold_nugget, - TestFactories.create_gold_nugget - ] - blog_post_class = spy("BlogPost class") - container = TestFactories.create_gold_container( - gold_nuggets: gold_nuggets, - origin: channel, - packing_date: Date.today - ) - GoldMiner.smith_blog_post(container, blog_post_class:) - - expect(blog_post_class).to have_received(:new).with( - slack_channel: channel, - gold_nuggets: gold_nuggets, - since: Date.parse(date), - writer: instance_of(GoldMiner::BlogPost::OpenAiWriter) - ) - end - end - end + describe "#mine" do + it "explores, smiths and distributes gold from the given location and date" do + location = "dev" + start_date = Date.parse("2023-10-20") + gold_container = TestFactories.create_gold_container + explorer = spy("Explorer", explore: gold_container) + blog_post = TestFactories.create_blog_post + smith = spy("Smith", smith: blog_post) + distributor = spy("Distributor", distribute: nil) + + gold_miner = GoldMiner.new(explorer: explorer, smith: smith, distributor: distributor) + result = gold_miner.mine(location, start_on: start_date) + + expect(explorer).to have_received(:explore).with(location, start_on: start_date) + expect(smith).to have_received(:smith).with(gold_container) + expect(distributor).to have_received(:distribute).with(blog_post) + expect(result).to be_success end end diff --git a/spec/support/test_factories.rb b/spec/support/test_factories.rb index 520a9a2..cdb6883 100644 --- a/spec/support/test_factories.rb +++ b/spec/support/test_factories.rb @@ -28,6 +28,16 @@ def create_gold_nugget(overriden_attributes = {}) GoldMiner::GoldNugget.new(**default_attributes.merge(overriden_attributes)) end + def create_blog_post(overriden_attributes = {}) + default_attributes = { + slack_channel: "design", + gold_nuggets: [create_gold_nugget], + since: Date.today + } + + GoldMiner::BlogPost.new(**default_attributes.merge(overriden_attributes)) + end + def create_slack_user(overriden_attributes = {}) default_attributes = { id: "U123",