Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ jobs:
--health-timeout 2s
--health-retries 5
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- uses: "ruby/setup-ruby@v1"
with:
ruby-version: 3.1.2
bundler-cache: true

- name: Install libvips
Expand All @@ -39,7 +38,7 @@ jobs:
bin/setup
bin/rails standard test:all

- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
if: failure()
with:
name: Capybara screenshots
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pick b86afaa Lesson 1: Our first lines of JavaScript
pick 2fc0b6b Lesson 2: The Audio Player
pick cf2942e Lesson 3: Infinite scroll
pick dfaeb73 Lesson 4: Typeahead Search
pick 0d97e49 Update Tutorial Assistant on setup (#18) # <- We need to move this
pick 0d97e49 Update setup script (#18) # <- We need to move this
```

4. Move the commit to **before** the "Getting Started" commit.
Expand All @@ -71,7 +71,7 @@ pick 66255b3 Extract `PodcastScoped` concern
pick 27e8306 Episode Search
pick 4276531 [BACKPORT]: Promote lazily-loaded Turbo Frame navigation
pick 304a037 Build assets during setup (#16)
pick 0d97e49 Update Tutorial Assistant on setup (#18) # <- Move it before "Getting Started"
pick 0d97e49 Update setup script (#18) # <- Move it before "Getting Started"
pick a3ada0f Getting Started
pick b86afaa Lesson 1: Our first lines of JavaScript
pick 2fc0b6b Lesson 2: The Audio Player
Expand All @@ -91,7 +91,7 @@ f805411 (HEAD -> main) Lesson 4: Typeahead Search
83f2ae3 Lesson 2: The Audio Player
45624d1 Lesson 1: Our first lines of JavaScript
14e6e70 Getting Started
0a4b7a9 Update Tutorial Assistant on setup (#18) # <- The commit is in the correct spot
0a4b7a9 Update setup script (#18) # <- The commit is in the correct spot
304a037 Build assets during setup (#16)
4276531 [BACKPORT]: Promote lazily-loaded Turbo Frame navigation
27e8306 Episode Search
Expand Down
8 changes: 7 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ GEM
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
ffi (1.17.0)
ffi (1.17.0-aarch64-linux-gnu)
ffi (1.17.0-arm-linux-gnu)
ffi (1.17.0-arm64-darwin)
Expand Down Expand Up @@ -224,6 +225,7 @@ GEM
matrix (0.4.2)
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.7)
minitest (5.23.1)
msgpack (1.7.2)
net-imap (0.4.12)
Expand All @@ -236,6 +238,9 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.16.5-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.5-arm-linux)
Expand Down Expand Up @@ -378,6 +383,7 @@ PLATFORMS
aarch64-linux
arm-linux
arm64-darwin
ruby
x86-linux
x86_64-darwin
x86_64-linux
Expand Down Expand Up @@ -415,4 +421,4 @@ RUBY VERSION
ruby 3.3.0p0

BUNDLED WITH
2.5.6
2.5.18
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,44 @@
**Hotwire-powered Podcast Player** 🔌

This course works by introducing a series of failing tests that you must get to
pass by introducing Hotwire patterns into the application. Once the tests pass,
move on to the next lesson.
pass by introducing Hotwire patterns into the application.

For more Hotwire resources, check out our [blog posts].

[blog posts]: https://thoughtbot.com/blog/tags/hotwire

## ⚙️ Setup

If this is your first time running the application, run `./bin/setup` to
install dependencies and seed the database.

## 🚀 Getting Started
### Troubleshooting

Once you've setup the application locally, you can start the [lesson plan][1].
If you run into a `Tailwindcss::Commands::ExecutableNotFoundException: Cannot find the
tailwindcss executable` error, you need to add your platform to `Gemfile.lock`. See this
[issue] for more details.

## 🏗 Local Development
[issue]: https://stackoverflow.com/a/70720842

## 🏗 Running the application

Run `./bin/dev` to start the development server and then navigate to
[http://localhost:3000](http://localhost:3000)
[http://localhost:3000](http://localhost:3000).

Note: Running `./bin/dev` will execute the background jobs to import the
episodes scheduled in the setup step above. It will take a few minutes.
If you run into issues due to a podcast or episode not being
found, see the jobs statuses on http://localhost:3000/good_job.
Once they are finished, the episodes should be accessible in the UI.

## 🚀 Getting Started

Once you've setup the application locally, you are ready to start the [lesson plan][1].

Once the tests pass, move on to the next lesson.

## Contributing

[Contributing](./CONTRIBUTING.md)
Please see [Contributing](./CONTRIBUTING.md).

[1]: ./lessons/README.md
37 changes: 0 additions & 37 deletions app/views/episodes/podcast/_frame.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -42,43 +42,6 @@

<p class="mt-2 text-base leading-7 text-slate-700 lg:line-clamp-4"><%= podcast.description %></p>
</section>

<section class="mt-10 lg:mt-12">
<h2 class="sr-only flex items-center font-mono text-sm font-medium leading-7 text-slate-900 lg:not-sr-only">
<%= inline_svg_tag "icons/graph.svg", class: "text-indigo-300 h-2.5 w-2.5" %>

<span class="ml-2.5">Listen</span>
</h2>

<div class="h-px bg-gradient-to-r from-slate-200/0 via-slate-200 to-slate-200/0 lg:hidden"></div>

<ul role="list" class="mt-4 flex justify-center gap-10 text-base font-medium leading-7 text-slate-700 sm:gap-8 lg:flex-col lg:gap-4">
<li class="flex">
<a class="group flex items-center" aria-label="Spotify" href="/">
<%= inline_svg_tag "icons/spotify.svg", class: "h-8 w-8 fill-slate-400 group-hover:fill-slate-600" %>
<span class="hidden sm:ml-3 sm:block">Spotify</span>
</a>
</li>
<li class="flex">
<a class="group flex items-center" aria-label="Apple Podcast" href="/">
<%= inline_svg_tag "icons/apple_podcasts.svg", class: "h-8 w-8 fill-slate-400 group-hover:fill-slate-600" %>
<span class="hidden sm:ml-3 sm:block">Apple Podcast</span>
</a>
</li>
<li class="flex">
<a class="group flex items-center" aria-label="Overcast" href="/">
<%= inline_svg_tag "icons/overcast.svg", class: "h-8 w-8 fill-slate-400 group-hover:fill-slate-600" %>
<span class="hidden sm:ml-3 sm:block">Overcast</span>
</a>
</li>
<li class="flex">
<a class="group flex items-center" aria-label="RSS Feed" href="/">
<%= inline_svg_tag "icons/rss.svg", class: "h-8 w-8 fill-slate-400 group-hover:fill-slate-600" %>
<span class="hidden sm:ml-3 sm:block">RSS Feed</span>
</a>
</li>
</ul>
</section>
</div>
</header>

Expand Down
25 changes: 15 additions & 10 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env ruby
require "fileutils"
require 'mkmf'

# path to your application root.
APP_ROOT = File.expand_path("..", __dir__)
Expand All @@ -13,24 +14,23 @@ FileUtils.chdir APP_ROOT do
# This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.

vips = MakeMakefile.find_executable('vips')
if !vips && !ENV["CI"]
puts "VIPS is not installed.\n"
printf "See https://www.libvips.org/install.html for install instructions.\n"
exit 1
end

puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")

# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end

puts "\n== Installing Tutorial Assistant =="
FileUtils.remove_dir("ta", force: true) if Dir.exist?("ta")
system! "git clone [email protected]:thoughtbot/tutorial-assistant.git ta/ > /dev/null 2>&1 || true"
system("bundle check") || system!("bundle install")

puts "\n== Preparing database =="
system! "bin/rails db:prepare"

unless ENV["CI"]
puts "\n== Seeding database =="
puts "\n== Seeding database. It might take a few minutes... =="
system! "bin/rails db:seed"
end

Expand All @@ -43,3 +43,8 @@ FileUtils.chdir APP_ROOT do
puts "\n== Restarting application server =="
system! "bin/rails restart"
end

at_exit do
File.delete('mkmf.log') if File.exist?('mkmf.log')
puts "\nFinished setting up"
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@
resolve "SearchResult" do |search_result|
[search_result.podcast, search_result.episode]
end

mount GoodJob::Engine => "good_job"
end
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def change
end
end

add_index :good_jobs, [:priority, :created_at], order: { priority: "DESC NULLS LAST", created_at: :asc },
add_index :good_jobs, [:priority, :created_at], order: {priority: "DESC NULLS LAST", created_at: :asc},
where: "finished_at IS NULL", name: :index_good_jobs_jobs_on_priority_created_at_when_unfinished,
algorithm: :concurrently
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ def change
dir.up do
unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at_cond)
add_index :good_jobs, [:cron_key, :created_at], where: "(cron_key IS NOT NULL)",
name: :index_good_jobs_on_cron_key_and_created_at_cond, algorithm: :concurrently
name: :index_good_jobs_on_cron_key_and_created_at_cond, algorithm: :concurrently
end
unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at_cond)
add_index :good_jobs, [:cron_key, :cron_at], where: "(cron_key IS NOT NULL)", unique: true,
name: :index_good_jobs_on_cron_key_and_cron_at_cond, algorithm: :concurrently
name: :index_good_jobs_on_cron_key_and_cron_at_cond, algorithm: :concurrently
end

if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at)
Expand All @@ -26,11 +26,11 @@ def change
dir.down do
unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at)
add_index :good_jobs, [:cron_key, :created_at],
name: :index_good_jobs_on_cron_key_and_created_at, algorithm: :concurrently
name: :index_good_jobs_on_cron_key_and_created_at, algorithm: :concurrently
end
unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_cron_at)
add_index :good_jobs, [:cron_key, :cron_at], unique: true,
name: :index_good_jobs_on_cron_key_and_cron_at, algorithm: :concurrently
name: :index_good_jobs_on_cron_key_and_cron_at, algorithm: :concurrently
end

if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_cron_key_and_created_at_cond)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def change
end
end

add_index :good_jobs, [:priority, :created_at], order: { priority: "ASC NULLS LAST", created_at: :asc },
add_index :good_jobs, [:priority, :created_at], order: {priority: "ASC NULLS LAST", created_at: :asc},
where: "finished_at IS NULL", name: :index_good_job_jobs_for_candidate_lookup,
algorithm: :concurrently
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

class CreateGoodJobProcessLockIds < ActiveRecord::Migration[8.0]
def change
reversible do |dir|
Expand Down
19 changes: 10 additions & 9 deletions db/migrate/20240610180543_create_good_job_process_lock_indexes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

class CreateGoodJobProcessLockIndexes < ActiveRecord::Migration[8.0]
disable_ddl_transaction!

Expand All @@ -7,23 +8,23 @@ def change
dir.up do
unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked)
add_index :good_jobs, [:priority, :scheduled_at],
order: { priority: "ASC NULLS LAST", scheduled_at: :asc },
where: "finished_at IS NULL AND locked_by_id IS NULL",
name: :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked,
algorithm: :concurrently
order: {priority: "ASC NULLS LAST", scheduled_at: :asc},
where: "finished_at IS NULL AND locked_by_id IS NULL",
name: :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked,
algorithm: :concurrently
end

unless connection.index_name_exists?(:good_jobs, :index_good_jobs_on_locked_by_id)
add_index :good_jobs, :locked_by_id,
where: "locked_by_id IS NOT NULL",
name: :index_good_jobs_on_locked_by_id,
algorithm: :concurrently
where: "locked_by_id IS NOT NULL",
name: :index_good_jobs_on_locked_by_id,
algorithm: :concurrently
end

unless connection.index_name_exists?(:good_job_executions, :index_good_job_executions_on_process_id_and_created_at)
add_index :good_job_executions, [:process_id, :created_at],
name: :index_good_job_executions_on_process_id_and_created_at,
algorithm: :concurrently
name: :index_good_job_executions_on_process_id_and_created_at,
algorithm: :concurrently
end
end

Expand Down
16 changes: 13 additions & 3 deletions lessons/lesson-1.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ Now we can build the controller.

```js
// app/javascript/controllers/hotkey_controller.js
import ApplicationController from "controllers/application_controller"
import { Controller } from '@hotwired/stimulus'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't know why this was needed. In lesson 2, this controllers inherits from ApplicationController and it works 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you following the existing guidance in the lesson plan by running ./ta/start-lesson 1 and then attempting to get tests to pass? Did you create an empty app/javascript/controllers/application_controller.js file?

// app/javascript/controllers/application_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
}

I think the short answer is that since we plan on moving away from Tutorial Assistant for this initial release, this shouldn't matter.

Instead, I think we need to work on our messaging and setup instructions to demonstrate how the app is built up from a Turbo-less Rails app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did follow the lesson plan but did not create an empty controller file. I've added the message about the turbo-less state in the first PR 👍🏼

A follow up question: without the teaching assistant, are you planning on removing the solutions from main?

import { install, uninstall } from "@github/hotkey"

export default class extends ApplicationController {
export default class extends Controller {
static targets = ["shortcut"]

shortcutTargetConnected(target) {
Expand All @@ -83,7 +83,7 @@ Anytime an element with a `data-hotkey-target="shortcut"` data attribute appears
on the page, this controller will enable that element to be accessed via the
hotkey. Additionally, we add a `aria-keyshortcuts` attribute to the element and
set the value to whatever the `hotkey` attribute is. In this case, that's
"Meta+k". We do this in an effort to exposes the existence of the shortcut to
"Meta+k". We do this in an effort to expose the existence of the shortcut to
assistive technologies so the presence of the shortcut can be communicated to
its users.

Expand Down Expand Up @@ -133,6 +133,16 @@ anchor link. The [@github/hotkey][] library works by triggering a focus event on
form fields, or a click event on other elements. In this case, hitting `Meta+k`
will automatically click the link to the search page.

### Check in

To complete this lesson:

- run `./bin/rails test` to verify the tests pass
- press ⌘ k anywhere in the episodes list page to verify the search shortcut works as expected
note: using `Control` instead of ⌘ should work as well for non-Mac users
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this based on github/hotkey#66 However, I have not tested in a non-Mac machine. I guess we can leave this and ask someone later to try it?


When you're ready, move on to the next lesson by running `./ta/start-lesson 2`.

[javascript_importmap_tags]: https://github.com/rails/importmap-rails#preloading-pinned-modules
[importmap]: https://github.com/WICG/import-maps
[@github/hotkey]: https://github.com/github/hotkey
Expand Down
8 changes: 0 additions & 8 deletions test/integration/episodes_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ class IndexTest < ActionDispatch::IntegrationTest
within :banner do
assert_link episode.podcast.title, href: podcast_episodes_path(episode.podcast)
assert_link text: episode.podcast.title, href: podcast_episodes_path(episode.podcast)
assert_link "Spotify"
assert_link "Apple Podcast"
assert_link "Overcast"
assert_link "RSS Feed"
assert_selector :section, "About", text: episode.podcast.description, count: 1
end
within :contentinfo do
Expand Down Expand Up @@ -153,10 +149,6 @@ class ShowTest < ActionDispatch::IntegrationTest
within :banner do
assert_link episode.podcast.title, href: podcast_episodes_path(episode.podcast)
assert_link text: episode.podcast.title, href: podcast_episodes_path(episode.podcast)
assert_link "Spotify"
assert_link "Apple Podcast"
assert_link "Overcast"
assert_link "RSS Feed"
assert_selector :section, "About", text: episode.podcast.description
end
within :contentinfo do
Expand Down