class AddSoftDeleteToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :deleted_at, :datetime
add_index :users, :deleted_at, where: "deleted_at IS NULL", name: "index_users_on_live"
class AddSlugToArticles < ActiveRecord::Migration[7.1]
def change
add_column :articles, :slug, :string
add_index :articles, :slug, unique: true
reversible do |dir|
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "App" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
import { Turbo } from "@hotwired/turbo-rails"
Turbo.StreamActions.redirect = function () {
const url = this.getAttribute("url")
if (!url) return
require 'activerecord-import'
class BulkImportPostsService
BATCH_SIZE = 1000
def initialize(csv_file_path)
<!DOCTYPE html>
<html>
<head>
<title><%= content_for(:title) || "App" %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<div class="bookmarks">
<h1>Saved Bookmarks</h1>
<table>
<tbody id="bookmarks">
<%= render partial: "bookmark", collection: @bookmarks %>
require "securerandom"
class RedisMutex
class LockError < StandardError; end
UNLOCK_SCRIPT = <<~LUA.freeze
let installed = false;
function notify(message, level = "error") {
document.dispatchEvent(
new CustomEvent("flash:show", { detail: { message, level } })
);
class PresenceRegistry
TTL = 30 # seconds a user counts as present without a heartbeat
def initialize(room_id, redis: REDIS)
@room_id = room_id
@redis = redis
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["wrapper", "template", "anchor"]
add(event) {
class Article < ApplicationRecord
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
scope :published, -> { where.not(published_at: nil) }