SKILL.md
$27
3. Conditional Rails (CRITICAL - never require Rails directly)
require_relative "gemname/railtie" if defined?(Rails)
4. Module with config and errors
module GemName
class Error < StandardError; end
class InvalidConfigError < Error; end
class << self
attr_accessor :timeout, :logger
attr_writer :client
end
self.timeout = 10 # Defaults set immediately
end
## Class Macro DSL Pattern
The signature Kane pattern—single method call configures everything:
Usage
class Product < ApplicationRecord
searchkick word_start: [:name]
end
Implementation
module GemName
module Model
def gemname(**options)
unknown = options.keys - KNOWN_KEYWORDS
raise ArgumentError, "unknown keywords: #{unknown.join(", ")}" if unknown.any?
mod = Module.new
mod.module_eval do
define_method :some_method do
# implementation
end unless method_defined?(:some_method)
end
include mod
class_eval do
cattr_reader :gemname_options, instance_reader: false
class_variable_set :@@gemname_options, options.dup
end
end
end
end
## Rails Integration
**Always use `ActiveSupport.on_load`—never require Rails gems directly:**
WRONG
require "active_record"
ActiveRecord::Base.include(MyGem::Model)
CORRECT
ActiveSupport.on_load(:active_record) do
extend GemName::Model
end
Use prepend for behavior modification
ActiveSupport.on_load(:active_record) do
ActiveRecord::Migration.prepend(GemName::Migration)
end
## Configuration Pattern
Use `class << self` with `attr_accessor`, not Configuration objects:
module GemName
class << self
attr_accessor :timeout, :logger
attr_writer :master_key
end
def self.master_key
@master_key ||= ENV["GEMNAME_MASTER_KEY"]
end
self.timeout = 10
self.logger = nil
end
## Error Handling
Simple hierarchy with informative messages:
module GemName
class Error < StandardError; end
class ConfigError < Error; end
class ValidationError < Error; end
end
Validate early with ArgumentError
def initialize(key:)
raise ArgumentError, "Key must be 32 bytes" unless key&.bytesize == 32
end
## Testing (Minitest Only)
test/test_helper.rb
require "bundler/setup"
Bundler.require(:default)
require "minitest/autorun"
require "minitest/pride"
test/model_test.rb
class ModelTest < Minitest::Test
def test_basic_functionality
assert_equal expected, actual
end
end
## Gemspec Pattern
Zero runtime dependencies when possible:
Gem::Specification.new do |spec|
spec.name = "gemname"
spec.version = GemName::VERSION
spec.required_ruby_version = ">= 3.1"
spec.files = Dir[".{md,txt}", "{lib}//"]
spec.require_path = "lib"
# NO add_dependency lines - dev deps go in Gemfile
end