Skip to content

Commit

Permalink
Rewrite GoldMiner main entrypoint
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MatheusRich committed Oct 30, 2023
1 parent d9cf295 commit 9f6301d
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 130 deletions.
38 changes: 22 additions & 16 deletions exe/gold_miner
Expand Up @@ -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."
44 changes: 22 additions & 22 deletions 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
1 change: 0 additions & 1 deletion lib/gold_miner/slack/client.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true

require "dry/monads"
require "slack-ruby-client"

class GoldMiner
Expand Down
110 changes: 19 additions & 91 deletions 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

Expand Down
10 changes: 10 additions & 0 deletions spec/support/test_factories.rb
Expand Up @@ -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",
Expand Down

0 comments on commit 9f6301d

Please sign in to comment.