Loading...
Loading...
Write Rails code in 37signals/classic style: rich models, CRUD controllers, concerns, state-as-records, Minitest. Use when writing or modifying Ruby on Rails application code.
npx skill4agent add julianrubisch/skills jr-rails-classicrails g modelrails g controllerrails g migrationapp/models/strftimecard.closeboard.publishcard.closed?card.golden?CloseablePublishableWatchableCards::ClosuresControllerscope :activescope :chronologically| Action | Route |
|---|---|
| close a card | |
| archive a card | |
| watch a board | |
class Card::Closure < ApplicationRecord
belongs_to :card
belongs_to :creator, class_name: "User"
end
module Closeable
extend ActiveSupport::Concern
included do
has_one :closure, dependent: :destroy
end
def closed? = closure.present?
def close(creator: Current.user) = create_closure!(creator: creator)
def reopen = closure&.destroy
end
# Querying
Card.joins(:closure) # closed
Card.where.missing(:closure) # openclass Card < ApplicationRecord
include Assignable, Closeable, Golden, Watchable, Searchable
end# app/models/event/description.rb
class Event::Description
def initialize(event) = @event = event
def to_s = # ...
end# Model — minimal
class User < ApplicationRecord
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
normalizes :email, with: ->(e) { e.strip.downcase }
end
# Migration — hard constraints
add_index :users, :email, unique: true
add_foreign_key :cards, :boards| Instead of | Prefer | When |
|---|---|---|
| | Join model needs attributes or callbacks |
| 1:N | M:N ( | Relationship may grow |
| | Type-safe variants with different schemas |
:dependenthas_manyhas_one:inverse_of# Delegated types
class Message < ApplicationRecord
delegated_type :messageable, types: %w[Comment Reply Announcement]
end
# store_accessor for JSON/JSONB
class User < ApplicationRecord
store_accessor :settings, :theme, :notifications_enabled
end# UUIDs (UUIDv7 — time-sortable)
create_table :cards, id: :uuid do |t|
t.references :board, type: :uuid, foreign_key: true
end
# Counter caches
class Comment < ApplicationRecord
belongs_to :card, counter_cache: true
end
# Default values with Current
class Card < ApplicationRecord
belongs_to :creator, class_name: "User", default: -> { Current.user }
endmodule CardScoped
extend ActiveSupport::Concern
included { before_action :set_card }
private
def set_card
@card = Card.find(params[:card_id])
@board = @card.board
end
def render_card_replacement
render turbo_stream: turbo_stream.replace(@card)
end
end
class Cards::ClosuresController < ApplicationController
include CardScoped
def create = @card.close && render_card_replacement
def destroy = @card.reopen && render_card_replacement
endclass Current < ActiveSupport::CurrentAttributes
attribute :session, :user, :account, :request_id
delegate :user, to: :session, allow_nil: true
endmodule Watchable
def notify_watchers_later = NotifyWatchersJob.perform_later(self)
def notify_watchers
watchers.each { |w| WatcherMailer.notification(w, self).deliver_later }
end
end
class NotifyWatchersJob < ApplicationJob
def perform(card) = card.notify_watchers
endstrftime# config/initializers/date_formats.rb
Date::DATE_FORMATS[:default] = "%d.%m.%Y"
Time::DATE_FORMATS[:default] = "%d.%m.%Y %H:%M"
Time::DATE_FORMATS[:time_only] = "%H:%M"active_link_to| Concern | Gem |
|---|---|
| Frontend | turbo-rails, stimulus-rails, importmap-rails |
| Assets | propshaft |
| Jobs | Solid Queue |
| Cache/Cable | Solid Cache, Solid Cable |
| Authorization | Pundit |
| Deployment | Kamal + Thruster |
hwc-*