My opinionated ruby on rails template
at main 227 lines 5.3 kB view raw
1# frozen_string_literal: true 2 3say 'Setting up caching strategies...', :green 4 5gem 'identity_cache' 6gem 'cityhash' # Required for identity_cache 7 8say ' Configuring IdentityCache...', :cyan 9initializer 'identity_cache.rb', <<~RUBY 10 # frozen_string_literal: true 11 12 # IdentityCache - Blob level caching for Active Record 13 # See: https://github.com/Shopify/identity_cache 14 15 IdentityCache.logger = Rails.logger 16 IdentityCache.cache_backend = Rails.cache 17RUBY 18 19say ' Creating Cacheable concern...', :cyan 20file 'app/models/concerns/cacheable.rb', <<~RUBY 21 # frozen_string_literal: true 22 23 # Include in models for identity caching 24 # 25 # Usage: 26 # class User < ApplicationRecord 27 # include Cacheable 28 # 29 # cache_index :email 30 # cache_has_many :posts 31 # end 32 # 33 # User.fetch(1) # Cached fetch by id 34 # User.fetch_by_email(email) # Cached fetch by index 35 # 36 module Cacheable 37 extend ActiveSupport::Concern 38 39 included do 40 include IdentityCache 41 end 42 43 class_methods do 44 # Expire cache when record is updated 45 def expire_cache_for(record) 46 record.expire_cache if record.respond_to?(:expire_cache) 47 end 48 end 49 end 50RUBY 51 52say ' Creating CacheHelper...', :cyan 53file 'app/helpers/cache_helper.rb', <<~RUBY 54 # frozen_string_literal: true 55 56 # Helper methods for view caching 57 # 58 # Russian Doll Caching: 59 # The key insight is that when a nested object changes, 60 # only its cache (and parent caches) are invalidated. 61 # 62 # Example view structure: 63 # <% cache @project do %> 64 # <%= render @project.tasks %> 65 # <% end %> 66 # 67 # # _task.html.erb 68 # <% cache task do %> 69 # <%= task.name %> 70 # <%= render task.comments %> 71 # <% end %> 72 # 73 module CacheHelper 74 # Cache with automatic expiry based on record updated_at 75 # Also includes a version number for manual cache busting 76 # 77 # Usage: 78 # <% cache_with_version @user, 'v2' do %> 79 # ... 80 # <% end %> 81 # 82 def cache_with_version(record, version = 'v1', options = {}, &block) 83 cache([version, record], options, &block) 84 end 85 86 # Cache a collection with automatic key generation 87 # Useful for lists that change frequently 88 # 89 # Usage: 90 # <% cache_collection @users do |user| %> 91 # <%= render user %> 92 # <% end %> 93 # 94 def cache_collection(collection, options = {}, &block) 95 cache([collection.cache_key_with_version, collection.size], options, &block) 96 end 97 98 # Time-based cache for content that should refresh periodically 99 # 100 # Usage: 101 # <% cache_for 5.minutes, 'dashboard_stats' do %> 102 # ... 103 # <% end %> 104 # 105 def cache_for(duration, key, options = {}, &block) 106 expires_key = (Time.current.to_i / duration.to_i) 107 cache([key, expires_key], options, &block) 108 end 109 end 110RUBY 111 112say ' Creating CacheWarmer job...', :cyan 113file 'app/jobs/cache_warmer_job.rb', <<~RUBY 114 # frozen_string_literal: true 115 116 # Warm caches for frequently accessed data 117 # 118 # Schedule this job to run periodically: 119 # CacheWarmerJob.perform_later 120 # 121 # Or warm specific caches: 122 # CacheWarmerJob.perform_later(caches: ['users', 'settings']) 123 # 124 class CacheWarmerJob < ApplicationJob 125 queue_as :low 126 127 def perform(caches: nil) 128 warmers = caches || default_warmers 129 warmers.each { |warmer| send("warm_\#{warmer}") } 130 end 131 132 private 133 134 def default_warmers 135 %w[settings] 136 end 137 138 # Add your cache warming methods here 139 # 140 # def warm_users 141 # User.active.find_each do |user| 142 # Rails.cache.fetch(user.cache_key_with_version) { user } 143 # end 144 # end 145 146 def warm_settings 147 # Example: warm application settings 148 # AppConfig.all.each do |config| 149 # Rails.cache.fetch(['app_config', config.key]) { config.value } 150 # end 151 end 152 end 153RUBY 154 155say ' Documenting caching patterns...', :cyan 156file 'docs/caching.md', <<~MARKDOWN 157 # Caching Strategies 158 159 ## Fragment Caching (Views) 160 161 Basic fragment cache: 162 ```erb 163 <% cache @user do %> 164 <%= @user.name %> 165 <% end %> 166 ``` 167 168 ## Russian Doll Caching 169 170 Nested caches that auto-expire: 171 ```erb 172 <% cache @project do %> 173 <h1><%= @project.name %></h1> 174 <%= render @project.tasks %> 175 <% end %> 176 177 <!-- _task.html.erb --> 178 <% cache task do %> 179 <%= task.name %> 180 <% end %> 181 ``` 182 183 **Important:** Use `touch: true` on associations: 184 ```ruby 185 class Task < ApplicationRecord 186 belongs_to :project, touch: true 187 end 188 ``` 189 190 ## Model Caching (IdentityCache) 191 192 ```ruby 193 class User < ApplicationRecord 194 include Cacheable 195 196 cache_index :email 197 cache_has_many :posts, embed: true 198 end 199 200 # Usage 201 User.fetch(1) # Cached 202 User.fetch_by_email('a@b.com') # Cached 203 user.fetch_posts # Cached with user 204 ``` 205 206 ## Low-Level Caching 207 208 ```ruby 209 Rails.cache.fetch('expensive_operation', expires_in: 1.hour) do 210 ExpensiveService.call 211 end 212 ``` 213 214 ## Cache Warming 215 216 ```ruby 217 # Run periodically 218 CacheWarmerJob.perform_later 219 220 # Or specific caches 221 CacheWarmerJob.perform_later(caches: ['users']) 222 ``` 223MARKDOWN 224 225say 'Caching strategies configured!', :green 226say ' Use `include Cacheable` in models for identity caching', :cyan 227say ' See docs/caching.md for patterns', :cyan