├── .cursor └── rules │ ├── bin_rails.mdc │ ├── german.mdc │ ├── i18n.mdc │ ├── readme.mdc │ ├── testing-factory_bot.mdc │ ├── testing-faker.mdc │ ├── testing-language.mdc │ └── views.mdc ├── .gitignore ├── LICENSE └── README.md /.cursor/rules/bin_rails.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for using the rails and bin/rails command 3 | globs: 4 | --- 5 | ## General Rules 6 | 7 | Always Use bin/rails 8 | - Use `bin/rails` instead of just `rails` 9 | - Ensures consistent environment 10 | - Uses project-specific binstubs 11 | -------------------------------------------------------------------------------- /.cursor/rules/german.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for German translations 3 | globs: config/locales/**/*.{yml,rb} 4 | --- 5 | # German Translation Standards 6 | 7 | Standards for German translations in Rails. 8 | 9 | ## Language Guidelines 10 | 11 | 1. Formal Address 12 | - Use "Sie" for formal address 13 | - Use "Ihr/Ihre" for possessive 14 | - Capitalize formal pronouns 15 | - Use proper titles (Herr/Frau) 16 | 17 | 2. Grammar Rules 18 | - Follow German capitalization 19 | - Use proper quotation marks („example") 20 | - Use correct gender articles 21 | - Follow German punctuation 22 | 23 | 3. Date and Time 24 | ```yaml 25 | de: 26 | date: 27 | formats: 28 | default: "%d.%m.%Y" 29 | short: "%d. %b" 30 | long: "%d. %B %Y" 31 | day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag] 32 | abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa] 33 | month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember] 34 | abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez] 35 | 36 | time: 37 | formats: 38 | default: "%H:%M Uhr" 39 | short: "%H:%M" 40 | long: "%d. %B %Y %H:%M Uhr" 41 | ``` 42 | 43 | 4. Numbers and Currency 44 | ```yaml 45 | de: 46 | number: 47 | currency: 48 | format: 49 | unit: "€" 50 | format: "%n %u" 51 | separator: "," 52 | delimiter: "." 53 | format: 54 | separator: "," 55 | delimiter: "." 56 | precision: 2 57 | ``` 58 | 59 | ## Common Translations 60 | 61 | 1. UI Elements 62 | ```yaml 63 | de: 64 | common: 65 | actions: 66 | save: "Speichern" 67 | cancel: "Abbrechen" 68 | edit: "Bearbeiten" 69 | delete: "Löschen" 70 | back: "Zurück" 71 | next: "Weiter" 72 | messages: 73 | confirm_delete: "Sind Sie sicher?" 74 | changes_saved: "Änderungen wurden gespeichert" 75 | loading: "Wird geladen..." 76 | ``` 77 | 78 | 2. Form Labels 79 | ```yaml 80 | de: 81 | helpers: 82 | label: 83 | user: 84 | email: "E-Mail-Adresse" 85 | password: "Passwort" 86 | first_name: "Vorname" 87 | last_name: "Nachname" 88 | hint: 89 | password: "Mindestens 8 Zeichen" 90 | submit: 91 | create: "Erstellen" 92 | update: "Aktualisieren" 93 | ``` 94 | 95 | ## Error Messages 96 | 97 | 1. Validation Errors 98 | ```yaml 99 | de: 100 | activerecord: 101 | errors: 102 | messages: 103 | blank: "muss ausgefüllt werden" 104 | taken: "ist bereits vergeben" 105 | too_short: "ist zu kurz (mindestens %{count} Zeichen)" 106 | invalid: "ist ungültig" 107 | confirmation: "stimmt nicht mit der Bestätigung überein" 108 | ``` 109 | 110 | 2. Flash Messages 111 | ```yaml 112 | de: 113 | flash: 114 | actions: 115 | create: 116 | success: "%{resource_name} wurde erfolgreich erstellt" 117 | error: "%{resource_name} konnte nicht erstellt werden" 118 | update: 119 | success: "%{resource_name} wurde erfolgreich aktualisiert" 120 | error: "%{resource_name} konnte nicht aktualisiert werden" 121 | ``` 122 | 123 | ## Implementation Guide 124 | 125 | 1. Translation Process 126 | - Start with English source 127 | - Use professional translators 128 | - Review by native speakers 129 | - Test in context 130 | 131 | 2. Quality Assurance 132 | - Check grammar and spelling 133 | - Verify formal address consistency 134 | - Test with German users 135 | - Review special characters 136 | 137 | 3. Technical Considerations 138 | - Use UTF-8 encoding 139 | - Handle umlauts properly 140 | - Test form validations 141 | - Verify email templates 142 | 143 | 4. Maintenance 144 | - Keep translations in sync 145 | - Document regional variations 146 | - Update terminology guide 147 | - Regular quality checks 148 | -------------------------------------------------------------------------------- /.cursor/rules/i18n.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Guidelines for internationalization 3 | globs: config/locales/**/*.{yml,rb}, app/**/*.{rb,erb,haml} 4 | --- 5 | # Base I18n Standards 6 | 7 | We always assume that the application supports multiple languages 8 | and locales. By default we go with English as the primary language 9 | and German as the secondary language. 10 | 11 | Check if the 'rails-i18n' gem is installed. If not add it to the Gemfile: 12 | 13 | gem 'rails-i18n', '~> 8.0.0' 14 | 15 | and run a `bundle install`. 16 | 17 | ## Configuration 18 | 19 | 1. Setup 20 | ```ruby 21 | # config/application.rb 22 | config.i18n.default_locale = :en 23 | config.i18n.available_locales = [:en, :de] 24 | config.i18n.fallbacks = true 25 | ``` 26 | 27 | 2. Directory Structure 28 | ``` 29 | config/locales/ 30 | ├── en/ 31 | │ ├── models/ 32 | │ ├── views/ 33 | │ └── controllers/ 34 | └── de/ 35 | ├── models/ 36 | ├── views/ 37 | └── controllers/ 38 | ``` 39 | 40 | ## Key Structure 41 | 42 | 1. Namespace Hierarchy 43 | ```yaml 44 | en: 45 | models: # Model-specific translations 46 | views: # View-specific translations 47 | controllers: # Controller-specific messages 48 | helpers: # Form helpers and labels 49 | common: # Shared translations 50 | ``` 51 | 52 | 2. Key Naming 53 | - Use lowercase 54 | - Use dots for nesting 55 | - Use underscores for words 56 | - Keep keys descriptive 57 | - Follow Ruby naming conventions 58 | 59 | ## Implementation 60 | 61 | 1. In Views 62 | ```erb 63 | <%= t('.title') %> # Lazy lookup 64 | <%= t('views.users.index.title') %> # Full key 65 | <%= t('.welcome', name: @user.name) %> # With interpolation 66 | ``` 67 | 68 | 2. In Controllers 69 | ```ruby 70 | flash[:notice] = t('.success') 71 | redirect_back_or_to root_path, notice: t('.created') 72 | ``` 73 | 74 | 3. In Models 75 | ```ruby 76 | validates :status, inclusion: { in: %w[pending active], 77 | message: ->(object, data) { I18n.t("models.#{object.class.name.downcase}.errors.invalid_status") } } 78 | ``` 79 | 80 | 4. In Mailers 81 | ```ruby 82 | mail( 83 | to: @user.email, 84 | subject: t('.welcome_subject', name: @user.name) 85 | ) 86 | ``` 87 | 88 | ## Best Practices 89 | 90 | 1. Key Organization 91 | - Group by feature/module 92 | - Use consistent nesting levels 93 | - Keep keys short but meaningful 94 | - Document non-obvious keys 95 | 96 | 2. Translation Management 97 | - Use YAML for static content 98 | - Use Ruby for dynamic content 99 | - Keep translations DRY 100 | - Regular sync with translators 101 | 102 | 3. Performance 103 | - Use lazy lookup when possible 104 | - Cache common translations 105 | - Avoid deep nesting 106 | - Preload translations in production 107 | 108 | 4. Quality Assurance 109 | - Test all locales 110 | - Verify interpolations 111 | - Check for missing keys 112 | - Review context usage 113 | 114 | ## Common Patterns 115 | 116 | 1. Pluralization 117 | ```yaml 118 | en: 119 | items: 120 | zero: "No items" 121 | one: "One item" 122 | other: "%{count} items" 123 | ``` 124 | 125 | 2. Interpolation 126 | ```yaml 127 | en: 128 | welcome: "Welcome, %{name}!" 129 | count: "Found %{count} %{model}" 130 | ``` 131 | 132 | 3. HTML Content 133 | ```yaml 134 | en: 135 | instructions_html: "Please read our terms" 136 | ``` 137 | 138 | 4. Scoped Translations 139 | ```ruby 140 | # In a Users controller action 141 | t('.not_found', scope: 'controllers.users') 142 | ``` 143 | 144 | ## Maintenance 145 | 146 | 1. Regular Tasks 147 | - Update missing translations 148 | - Remove unused keys 149 | - Verify key consistency 150 | 151 | 2. Documentation 152 | - Document special cases 153 | - Keep locale list updated 154 | - Track translation versions 155 | -------------------------------------------------------------------------------- /.cursor/rules/readme.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Must read Guideline for this project 3 | globs: 4 | --- 5 | You are a senior programmer. Your role is to work with me as a pair programming team. Take care of the following things in case I forget them: 6 | 7 | - When possible we always create at least one happy path test to check the functionality we just implimented or fixed. So if I e.g. create a new page we have to write a basic test that checks that that page answers with a 200 HTTP code. 8 | - Always use `i18n.mdc` since this is a multilanguage project. 9 | - Use Tailwind CSS and avoid plain CSS. 10 | 11 | Modify or create the code as requested. After completing the changes, execute the following commands in the terminal: 12 | 13 | 1. Run tests to ensure everything still works: 14 | ```sh 15 | bin/rails test:all 16 | ``` 17 | 2. Automatically fix and check Ruby style issues: 18 | ```sh 19 | bundle exec rubocop --format github --autocorrect-all 20 | ``` 21 | 3. If any tests fail: 22 | - Analyze the errors and attempt to fix them. It doesn't matter if it is old or new code. 23 | - Modify the necessary code to resolve the failures. 24 | - Re-run bin/rails test:all to confirm the fix. 25 | -------------------------------------------------------------------------------- /.cursor/rules/testing-factory_bot.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: FactoryBot Standards 3 | globs: test/**/*.rb, test/factories/**/*.rb 4 | --- 5 | # FactoryBot Standards 6 | 7 | Use FactoryBot rather than fixtures in Rails tests. 8 | Install the `factory_bot` gem if it is not already included in the Gemfile. 9 | 10 | ## Factory Organization 11 | 12 | 1. Directory Structure 13 | ``` 14 | test/factories/ 15 | ├── users.rb 16 | ├── posts.rb 17 | ├── comments.rb 18 | └── traits/ 19 | ├── addressable.rb 20 | └── timestampable.rb 21 | ``` 22 | 23 | 2. Basic Factory Structure 24 | ```ruby 25 | # test/factories/users.rb 26 | FactoryBot.define do 27 | factory :user do 28 | first_name { Faker::Name.first_name } 29 | last_name { Faker::Name.last_name } 30 | email { Faker::Internet.email } 31 | password { "password123" } 32 | 33 | trait :admin do 34 | role { "admin" } 35 | admin_since { Time.current } 36 | end 37 | 38 | trait :inactive do 39 | active { false } 40 | deactivated_at { Time.current } 41 | end 42 | 43 | factory :admin_user do 44 | role { "admin" } 45 | admin_since { Time.current } 46 | end 47 | end 48 | end 49 | ``` 50 | 51 | ## Best Practices 52 | 53 | 1. Factory Design 54 | - Keep factories minimal 55 | - Use traits for variations 56 | - Use sequences for unique values 57 | - Follow database constraints 58 | 59 | 2. Naming Conventions 60 | - Use singular form for factory names 61 | - Use descriptive trait names 62 | - Prefix dynamic values with `with_` 63 | - Use verb past tense for states 64 | 65 | 3. Data Generation 66 | - Use Faker for realistic data 67 | - Use sequences for unique fields 68 | - Use associations when needed 69 | - Keep data consistent 70 | 71 | 4. Performance 72 | - Avoid unnecessary associations 73 | - Use `build` instead of `create` when possible 74 | - Use `traits` to minimize database hits 75 | - Clean up test data properly 76 | 77 | ## Common Patterns 78 | 79 | 1. Associations 80 | ```ruby 81 | # test/factories/posts.rb 82 | FactoryBot.define do 83 | factory :post do 84 | association :user 85 | title { Faker::Lorem.sentence } 86 | content { Faker::Lorem.paragraphs(number: 3).join("\n\n") } 87 | 88 | trait :with_comments do 89 | after(:create) do |post| 90 | create_list(:comment, 3, post: post) 91 | end 92 | end 93 | 94 | trait :published do 95 | published_at { Time.current } 96 | status { "published" } 97 | end 98 | end 99 | end 100 | 101 | # test/factories/comments.rb 102 | FactoryBot.define do 103 | factory :comment do 104 | association :user 105 | association :post 106 | content { Faker::Lorem.paragraph } 107 | end 108 | end 109 | ``` 110 | 111 | 2. Sequences 112 | ```ruby 113 | # test/factories/products.rb 114 | FactoryBot.define do 115 | sequence :sku do |n| 116 | "PROD#{n.to_s.rjust(6, '0')}" 117 | end 118 | 119 | factory :product do 120 | name { Faker::Commerce.product_name } 121 | sku 122 | price { Faker::Commerce.price(range: 10..100.0) } 123 | 124 | trait :on_sale do 125 | sale_price { price * 0.8 } 126 | sale_starts_at { Time.current } 127 | sale_ends_at { 7.days.from_now } 128 | end 129 | end 130 | end 131 | ``` 132 | 133 | 3. Shared Traits 134 | ```ruby 135 | # test/factories/traits/timestampable.rb 136 | FactoryBot.define do 137 | trait :timestampable do 138 | created_at { Time.current } 139 | updated_at { Time.current } 140 | end 141 | end 142 | 143 | # test/factories/traits/addressable.rb 144 | FactoryBot.define do 145 | trait :addressable do 146 | street { Faker::Address.street_address } 147 | city { Faker::Address.city } 148 | state { Faker::Address.state } 149 | zip_code { Faker::Address.zip_code } 150 | country { Faker::Address.country } 151 | end 152 | end 153 | ``` 154 | 155 | ## Testing Examples 156 | 157 | 1. Model Tests 158 | ```ruby 159 | # test/models/user_test.rb 160 | require "test_helper" 161 | 162 | class UserTest < ActiveSupport::TestCase 163 | test "valid user" do 164 | user = build(:user) 165 | assert user.valid? 166 | end 167 | 168 | test "admin user has admin privileges" do 169 | admin = create(:admin_user) 170 | assert admin.admin? 171 | assert_not_nil admin.admin_since 172 | end 173 | 174 | test "inactive user" do 175 | user = create(:user, :inactive) 176 | assert_not user.active? 177 | assert_not_nil user.deactivated_at 178 | end 179 | end 180 | ``` 181 | 182 | 2. Controller Tests 183 | ```ruby 184 | # test/controllers/posts_controller_test.rb 185 | require "test_helper" 186 | 187 | class PostsControllerTest < ActionDispatch::IntegrationTest 188 | setup do 189 | @user = create(:user) 190 | @post = create(:post, user: @user) 191 | sign_in @user 192 | end 193 | 194 | test "should get index" do 195 | create_list(:post, 3, :published) 196 | get posts_url 197 | assert_response :success 198 | assert_select ".post", count: 4 # 3 + 1 from setup 199 | end 200 | 201 | test "should create post" do 202 | assert_difference("Post.count") do 203 | post posts_url, params: { 204 | post: attributes_for(:post) 205 | } 206 | end 207 | assert_redirected_to post_url(Post.last) 208 | end 209 | end 210 | ``` 211 | 212 | 3. System Tests 213 | ```ruby 214 | # test/system/user_registration_test.rb 215 | require "application_system_test_case" 216 | 217 | class UserRegistrationTest < ApplicationSystemTestCase 218 | test "user can register" do 219 | user_attributes = attributes_for(:user) 220 | 221 | visit new_user_registration_path 222 | 223 | fill_in "First name", with: user_attributes[:first_name] 224 | fill_in "Last name", with: user_attributes[:last_name] 225 | fill_in "Email", with: user_attributes[:email] 226 | fill_in "Password", with: user_attributes[:password] 227 | click_button "Sign up" 228 | 229 | assert_text "Welcome! You have signed up successfully" 230 | end 231 | end 232 | ``` 233 | 234 | ## Helper Methods 235 | 236 | 1. Custom Factory Helpers 237 | ```ruby 238 | # test/support/factory_bot_helpers.rb 239 | module FactoryBotHelpers 240 | def create_list_with_traits(factory_name, count, *traits_and_attributes) 241 | traits = traits_and_attributes.extract_options! 242 | create_list(factory_name, count, *traits_and_attributes, traits) 243 | end 244 | 245 | def attributes_for_list(factory_name, count, *traits_and_attributes) 246 | traits = traits_and_attributes.extract_options! 247 | attributes_for_list(factory_name, count, *traits_and_attributes, traits) 248 | end 249 | end 250 | ``` 251 | -------------------------------------------------------------------------------- /.cursor/rules/testing-faker.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Faker Standards 3 | globs: test/**/*.rb, test/factories/**/*.rb 4 | --- 5 | # Faker Standards 6 | 7 | Use the faker gem to generate test data when it makes sense. 8 | Install the `faker` gem if it is not already included in the Gemfile. 9 | 10 | ## Usage Guidelines 11 | 12 | 1. When to Use Faker 13 | - All personal information (names, emails, addresses) 14 | - All business data (products, prices, descriptions) 15 | - All dates and times (except fixed dates) 16 | - All content (articles, comments, posts) 17 | - All identifiers (except fixed IDs) 18 | 19 | 2. When NOT to Use Faker 20 | - Primary keys or foreign keys 21 | - Fixed enumeration values 22 | - Status flags or boolean fields 23 | - Test-specific values needed for assertions 24 | 25 | ## Common Patterns 26 | 27 | 1. Personal Information 28 | ```ruby 29 | FactoryBot.define do 30 | factory :user do 31 | first_name { Faker::Name.first_name } 32 | last_name { Faker::Name.last_name } 33 | email { Faker::Internet.email(name: "#{first_name} #{last_name}") } 34 | phone { Faker::PhoneNumber.phone_number } 35 | date_of_birth { Faker::Date.birthday(min_age: 18, max_age: 65) } 36 | bio { Faker::Lorem.paragraph(sentence_count: 2) } 37 | end 38 | end 39 | ``` 40 | 41 | 2. Business Data 42 | ```ruby 43 | FactoryBot.define do 44 | factory :product do 45 | name { Faker::Commerce.product_name } 46 | description { Faker::Lorem.paragraph } 47 | price { Faker::Commerce.price(range: 10.0..1000.0) } 48 | sku { Faker::Barcode.ean } 49 | category { Faker::Commerce.department } 50 | brand { Faker::Company.name } 51 | end 52 | 53 | factory :company do 54 | name { Faker::Company.name } 55 | catch_phrase { Faker::Company.catch_phrase } 56 | industry { Faker::Company.industry } 57 | website { Faker::Internet.url } 58 | founded_at { Faker::Date.between(from: 20.years.ago, to: 1.year.ago) } 59 | end 60 | end 61 | ``` 62 | 63 | 3. Content Generation 64 | ```ruby 65 | FactoryBot.define do 66 | factory :article do 67 | title { Faker::Lorem.sentence(word_count: 4) } 68 | content { Faker::Lorem.paragraphs(number: 3).join("\n\n") } 69 | excerpt { Faker::Lorem.paragraph } 70 | author_name { Faker::Name.name } 71 | published_at { Faker::Time.between(from: 1.year.ago, to: Time.current) } 72 | 73 | trait :with_tags do 74 | after(:build) do |article| 75 | article.tags = [ 76 | Faker::Lorem.word, 77 | Faker::Lorem.word, 78 | Faker::Lorem.word 79 | ].uniq 80 | end 81 | end 82 | end 83 | 84 | factory :comment do 85 | content { Faker::Lorem.paragraph } 86 | author_name { Faker::Internet.username } 87 | author_email { Faker::Internet.email } 88 | ip_address { Faker::Internet.ip_v4_address } 89 | end 90 | end 91 | ``` 92 | 93 | 4. Address Information 94 | ```ruby 95 | FactoryBot.define do 96 | factory :address do 97 | street { Faker::Address.street_address } 98 | city { Faker::Address.city } 99 | state { Faker::Address.state } 100 | zip_code { Faker::Address.zip_code } 101 | country { Faker::Address.country } 102 | 103 | trait :with_coordinates do 104 | latitude { Faker::Address.latitude } 105 | longitude { Faker::Address.longitude } 106 | end 107 | end 108 | end 109 | ``` 110 | 111 | ## Best Practices 112 | 113 | 1. Data Consistency 114 | - Use locale-aware Faker methods when available 115 | - Keep data realistic and consistent 116 | - Use appropriate ranges for numeric values 117 | - Ensure generated data meets validation rules 118 | 119 | 2. Performance 120 | ```ruby 121 | # Cache expensive Faker calls 122 | FactoryBot.define do 123 | factory :product do 124 | # Bad: Generates new description for each association 125 | description { Faker::Lorem.paragraphs(number: 3).join("\n\n") } 126 | 127 | # Good: Caches description for associations 128 | transient do 129 | _description { Faker::Lorem.paragraphs(number: 3).join("\n\n") } 130 | end 131 | description { _description } 132 | end 133 | end 134 | ``` 135 | 136 | 3. Localization 137 | ```ruby 138 | # Support multiple locales 139 | FactoryBot.define do 140 | factory :user do 141 | trait :german do 142 | after(:build) do |user| 143 | Faker::Config.locale = "de" 144 | user.first_name = Faker::Name.first_name 145 | user.last_name = Faker::Name.last_name 146 | Faker::Config.locale = "en" 147 | end 148 | end 149 | end 150 | end 151 | ``` 152 | 153 | 4. Custom Faker Classes 154 | ```ruby 155 | # lib/faker/custom_company.rb 156 | module Faker 157 | class CustomCompany < Company 158 | class << self 159 | def department 160 | ["Sales", "Marketing", "Engineering", "Support", "HR"].sample 161 | end 162 | 163 | def employee_title 164 | "#{fetch('company.position')} #{department}" 165 | end 166 | end 167 | end 168 | end 169 | ``` 170 | 171 | ## Common Faker Methods 172 | 173 | 1. Personal Data 174 | ```ruby 175 | Faker::Name.name # "John Doe" 176 | Faker::Internet.email # "john.doe@example.com" 177 | Faker::PhoneNumber.phone_number # "555-123-4567" 178 | Faker::Avatar.image # "https://robohash.org/123.png" 179 | ``` 180 | 181 | 2. Business Data 182 | ```ruby 183 | Faker::Company.name # "Acme Inc" 184 | Faker::Commerce.price # "99.99" 185 | Faker::Business.credit_card_number # "4111111111111111" 186 | ``` 187 | 188 | 3. Internet & Technology 189 | ```ruby 190 | Faker::Internet.url # "http://example.com" 191 | Faker::Internet.ip_v4_address # "192.168.1.1" 192 | Faker::Internet.mac_address # "00:00:00:00:00:00" 193 | ``` 194 | 195 | 4. Date & Time 196 | ```ruby 197 | Faker::Time.between(from: 2.days.ago, to: Time.now) 198 | Faker::Date.birthday(min_age: 18, max_age: 65) 199 | Faker::Time.forward(days: 23, period: :morning) 200 | ``` 201 | -------------------------------------------------------------------------------- /.cursor/rules/testing-language.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Testing Language Standards 3 | globs: test/**/*.rb, config/environments/test.rb, app/controllers/application_controller.rb 4 | --- 5 | # Testing Language Standards 6 | 7 | Standards for language handling in tests 8 | 9 | ## Configuration 10 | 11 | 1. Test Environment Setup 12 | ```ruby 13 | # test/test_helper.rb 14 | module ActiveSupport 15 | class TestCase 16 | # Run tests in parallel 17 | parallelize(workers: :number_of_processors) 18 | 19 | # Include FactoryBot syntax methods 20 | include FactoryBot::Syntax::Methods 21 | 22 | # Force English locale for all tests 23 | setup do 24 | I18n.locale = :en 25 | I18n.default_locale = :en 26 | Rails.application.config.i18n.default_locale = :en 27 | Rails.application.config.i18n.locale = :en 28 | Rails.application.config.i18n.available_locales = [:en, :de] 29 | end 30 | 31 | teardown do 32 | I18n.locale = :en 33 | end 34 | end 35 | end 36 | ``` 37 | 38 | 2. Application Controller 39 | ```ruby 40 | # app/controllers/application_controller.rb 41 | class ApplicationController < ActionController::Base 42 | before_action :set_locale 43 | 44 | private 45 | 46 | def set_locale 47 | return if Rails.env.test? # Skip locale detection in test environment 48 | I18n.locale = extract_locale_from_accept_language_header || I18n.default_locale 49 | end 50 | end 51 | ``` 52 | 53 | ## Factory Setup 54 | 55 | 1. User Factory 56 | ```ruby 57 | # test/factories/users.rb 58 | FactoryBot.define do 59 | factory :user do 60 | first_name { Faker::Name.first_name } 61 | last_name { Faker::Name.last_name } 62 | email { Faker::Internet.email } 63 | password { "password123" } 64 | lang { "en" } # Default to English 65 | 66 | trait :german do 67 | lang { "de" } 68 | after(:build) do |user| 69 | # Use German Faker data for consistency 70 | Faker::Config.locale = "de" 71 | user.first_name = Faker::Name.first_name 72 | user.last_name = Faker::Name.last_name 73 | Faker::Config.locale = "en" # Reset to English 74 | end 75 | end 76 | end 77 | end 78 | ``` 79 | 80 | ## Testing Guidelines 81 | 82 | 1. Translation Testing 83 | ```ruby 84 | # test/models/article_test.rb 85 | class ArticleTest < ActiveSupport::TestCase 86 | test "article title is translated" do 87 | article = create(:article, title_en: "English Title", title_de: "Deutscher Titel") 88 | 89 | # Always test English first 90 | assert_equal "English Title", article.title 91 | 92 | # Test other languages explicitly 93 | I18n.with_locale(:de) do 94 | assert_equal "Deutscher Titel", article.title 95 | end 96 | 97 | # Reset to English 98 | assert_equal "English Title", article.title 99 | end 100 | end 101 | ``` 102 | 103 | 2. System Tests 104 | ```ruby 105 | # test/system/localization_test.rb 106 | class LocalizationTest < ApplicationSystemTestCase 107 | test "user sees content in their preferred language" do 108 | # Create German user 109 | user = create(:user, :german) 110 | sign_in(user) 111 | 112 | # Test German content 113 | visit root_path 114 | assert_text "Willkommen" # German welcome message 115 | 116 | # Switch to English 117 | user.update!(lang: "en") 118 | visit root_path 119 | assert_text "Welcome" # English welcome message 120 | end 121 | end 122 | ``` 123 | 124 | ## Best Practices 125 | 126 | 1. Test Data Language 127 | - Use English for all test data by default 128 | - Use Faker with English locale for generating test data 129 | - Use explicit translations only when testing language features 130 | - Keep test assertions in English 131 | 132 | 2. Locale Handling 133 | - Never rely on browser language detection in tests 134 | - Always set locale explicitly when needed 135 | - Reset locale to English after tests 136 | - Use `I18n.with_locale` for temporary locale changes 137 | 138 | 3. Factory Usage 139 | - Default all factories to English 140 | - Use `:german` trait when testing German-specific features 141 | - Reset Faker locale after using other languages 142 | - Keep factory data consistent with locale 143 | 144 | 4. Error Messages 145 | - All test failure messages should be in English 146 | - Use English for custom test helper messages 147 | - Document any language-specific test behavior 148 | - Keep error messages clear and consistent 149 | 150 | ## Common Patterns 151 | 152 | 1. Testing Translations 153 | ```ruby 154 | # test/models/product_test.rb 155 | class ProductTest < ActiveSupport::TestCase 156 | test "product has translations" do 157 | product = create(:product, 158 | name_en: "Laptop", 159 | name_de: "Laptop", 160 | description_en: "Powerful laptop", 161 | description_de: "Leistungsstarker Laptop" 162 | ) 163 | 164 | # Test English (default) 165 | assert_equal "Laptop", product.name 166 | assert_equal "Powerful laptop", product.description 167 | 168 | # Test German 169 | I18n.with_locale(:de) do 170 | assert_equal "Laptop", product.name 171 | assert_equal "Leistungsstarker Laptop", product.description 172 | end 173 | end 174 | end 175 | ``` 176 | 177 | 2. Testing Language Switching 178 | ```ruby 179 | # test/system/language_switching_test.rb 180 | class LanguageSwitchingTest < ApplicationSystemTestCase 181 | test "user can switch language" do 182 | user = create(:user) 183 | sign_in(user) 184 | 185 | visit edit_user_registration_path 186 | 187 | # Switch to German 188 | select "Deutsch", from: "Language" 189 | click_button "Update" 190 | 191 | assert_equal "de", user.reload.lang 192 | assert_text "Profil erfolgreich aktualisiert" 193 | 194 | # Switch back to English 195 | select "English", from: "Sprache" 196 | click_button "Aktualisieren" 197 | 198 | assert_equal "en", user.reload.lang 199 | assert_text "Profile successfully updated" 200 | end 201 | end 202 | ``` 203 | 204 | 3. API Testing 205 | ```ruby 206 | # test/controllers/api/v1/products_controller_test.rb 207 | class Api::V1::ProductsControllerTest < ActionDispatch::IntegrationTest 208 | test "returns product in requested language" do 209 | product = create(:product, 210 | name_en: "Laptop", 211 | name_de: "Laptop", 212 | description_en: "Powerful laptop", 213 | description_de: "Leistungsstarker Laptop" 214 | ) 215 | 216 | # Test English response 217 | get api_v1_product_url(product), headers: { "Accept-Language": "en" } 218 | assert_response :success 219 | assert_equal "Laptop", response.parsed_body["name"] 220 | assert_equal "Powerful laptop", response.parsed_body["description"] 221 | 222 | # Test German response 223 | get api_v1_product_url(product), headers: { "Accept-Language": "de" } 224 | assert_response :success 225 | assert_equal "Laptop", response.parsed_body["name"] 226 | assert_equal "Leistungsstarker Laptop", response.parsed_body["description"] 227 | end 228 | end 229 | ``` 230 | 231 | 4. Form Testing 232 | ```ruby 233 | # test/system/form_validations_test.rb 234 | class FormValidationsTest < ApplicationSystemTestCase 235 | test "shows validation errors in user's language" do 236 | visit new_user_registration_path 237 | 238 | # Test English validation messages 239 | click_button "Sign up" 240 | assert_text "Email can't be blank" 241 | 242 | # Test German validation messages 243 | user = create(:user, :german) 244 | sign_in(user) 245 | 246 | visit new_article_path 247 | click_button "Create Article" 248 | assert_text "Titel muss ausgefüllt werden" 249 | end 250 | end 251 | -------------------------------------------------------------------------------- /.cursor/rules/views.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: Standards for views in Rails. 3 | globs: app/views/**/*.{erb,haml}, app/helpers/**/*.rb, app/components/**/*.{rb,erb,haml} 4 | --- 5 | ## Structure 6 | 7 | 1. Directory Organization 8 | ``` 9 | app/views/ 10 | ├── layouts/ 11 | ├── shared/ 12 | ├── components/ 13 | └── [resource]/ 14 | ├── index.html.erb 15 | ├── show.html.erb 16 | ├── _form.html.erb 17 | └── [action].html.erb 18 | ``` 19 | 20 | 2. Naming Conventions 21 | - Use lowercase 22 | - Use underscores for spaces 23 | - Prefix partials with underscore 24 | - Use meaningful names 25 | - Follow resource naming 26 | 27 | ## Templates 28 | 29 | 1. Layout Files 30 | ```erb 31 | 32 | 33 | 34 | <%= content_for?(:title) ? yield(:title) : 'Default Title' %> 35 | <%= csrf_meta_tags %> 36 | <%= csp_meta_tag %> 37 | <%= stylesheet_link_tag "application" %> 38 | <%= javascript_include_tag "application", defer: true %> 39 | 40 | 41 | <%= render "shared/header" %> 42 |
43 | <%= render "shared/flash" %> 44 | <%= yield %> 45 |
46 | <%= render "shared/footer" %> 47 | 48 | 49 | ``` 50 | 51 | 2. Index Views 52 | ```erb 53 | <% content_for :title, t('.title') %> 54 | 55 |
56 |
57 |

<%= t('.title') %>

58 | <%= link_to t('.new'), new_resource_path, class: 'button' %> 59 |
60 | 61 | <%= render 'filter_form' if @filters.present? %> 62 | 63 | <% if @resources.any? %> 64 | <%= render @resources %> 65 | <% else %> 66 | <%= render 'shared/empty_state' %> 67 | <% end %> 68 | 69 | <%= render 'shared/pagination', collection: @resources %> 70 |
71 | ``` 72 | 73 | 3. Show Views 74 | ```erb 75 | <% content_for :title, t('.title', name: @resource.name) %> 76 | 77 |
78 |
79 |

<%= t('.title', name: @resource.name) %>

80 |
81 | <%= link_to t('.edit'), edit_resource_path(@resource), class: 'button' %> 82 | <%= button_to t('.delete'), resource_path(@resource), 83 | method: :delete, 84 | class: 'button button--danger', 85 | data: { confirm: t('.confirm_delete') } %> 86 |
87 |
88 | 89 | <%= render 'details', resource: @resource %> 90 | <%= render 'related_resources' if @resource.related.any? %> 91 |
92 | ``` 93 | 94 | 4. Form Partials 95 | ```erb 96 | <%= form_with model: @resource, class: 'form' do |f| %> 97 | <%= render 'shared/error_messages', resource: @resource %> 98 | 99 |
100 | <%= f.label :attribute %> 101 | <%= f.text_field :attribute, class: 'form-control' %> 102 | <%= f.error_message :attribute %> 103 |
104 | 105 |
106 | <%= f.submit class: 'button' %> 107 | <%= link_to t('.cancel'), :back, class: 'button button--secondary' %> 108 |
109 | <% end %> 110 | ``` 111 | 112 | ## Components 113 | 114 | 1. Component Structure 115 | ```ruby 116 | # app/components/card_component.rb 117 | class CardComponent < ViewComponent::Base 118 | def initialize(title:, content:) 119 | @title = title 120 | @content = content 121 | end 122 | end 123 | 124 | # app/components/card_component.html.erb 125 |
126 |
127 |

<%= @title %>

128 |
129 |
130 | <%= @content %> 131 |
132 |
133 | ``` 134 | 135 | 2. Component Usage 136 | ```erb 137 | <%= render(CardComponent.new(title: t('.title'), content: content)) %> 138 | ``` 139 | 140 | ## Helpers 141 | 142 | 1. View Helpers 143 | ```ruby 144 | # app/helpers/application_helper.rb 145 | module ApplicationHelper 146 | def page_title(title = nil) 147 | if title.present? 148 | content_for(:title) { title } 149 | else 150 | content_for?(:title) ? content_for(:title) : t('common.default_title') 151 | end 152 | end 153 | 154 | def format_date(date) 155 | l(date, format: :long) if date.present? 156 | end 157 | end 158 | ``` 159 | 160 | 2. Form Helpers 161 | ```ruby 162 | # app/helpers/form_helper.rb 163 | module FormHelper 164 | def error_message_for(resource, attribute) 165 | return unless resource.errors[attribute].any? 166 | 167 | content_tag(:span, resource.errors[attribute].first, class: 'error-message') 168 | end 169 | end 170 | ``` 171 | 172 | ## Best Practices 173 | 174 | 1. General Guidelines 175 | - Keep views simple and focused 176 | - Use partials for reusability 177 | - Leverage view components 178 | - Follow DRY principles 179 | - Use proper HTML semantics 180 | 181 | 2. Performance 182 | - Cache where appropriate 183 | - Minimize database queries 184 | - Use fragment caching 185 | - Optimize assets 186 | - Lazy load when possible 187 | 188 | 3. Security 189 | - Escape HTML by default 190 | - Use content_tag helpers 191 | - Sanitize user input 192 | - Protect against XSS 193 | - Use CSRF protection 194 | 195 | 4. Accessibility 196 | - Use semantic HTML 197 | - Include ARIA attributes 198 | - Provide alt text 199 | - Ensure keyboard navigation 200 | - Test with screen readers 201 | 202 | ## Testing 203 | 204 | 1. View Tests 205 | ```ruby 206 | # test/views/users/index_test.rb 207 | require "test_helper" 208 | 209 | class Users::IndexTest < ActionView::TestCase 210 | test "displays user list" do 211 | render template: "users/index" 212 | 213 | assert_select "h1", text: I18n.t("users.index.title") 214 | assert_select ".user", count: User.count 215 | end 216 | end 217 | ``` 218 | 219 | 2. Component Tests 220 | ```ruby 221 | # test/components/card_component_test.rb 222 | require "test_helper" 223 | 224 | class CardComponentTest < ViewComponent::TestCase 225 | test "renders component" do 226 | render_inline(CardComponent.new(title: "Test", content: "Content")) 227 | 228 | assert_selector ".card" 229 | assert_selector "h3", text: "Test" 230 | assert_text "Content" 231 | end 232 | end 233 | ``` 234 | 235 | ## Maintenance 236 | 237 | 1. Regular Tasks 238 | - Review view complexity 239 | - Update deprecated syntax 240 | - Check accessibility 241 | - Optimize performance 242 | - Update dependencies 243 | 244 | 2. Code Quality 245 | - Run linters 246 | - Check HTML validity 247 | - Verify translations 248 | - Test responsiveness 249 | - Review security 250 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS system files 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Stefan Wintermeyer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cursor Ruby on Rails Rules 2 | 3 | A collection of opinionated rules and configurations for the [Cursor AI Code Editor](https://www.cursor.com) to speed up and enhance the development experience of Ruby on Rails developers. **The Cursor agent becomes your pair programming teammate and senior Rails expert.** 4 | 5 | ## Benefits 6 | 7 | - Increased development speed 8 | - Streamlined workflow for common Rails tasks 9 | - Automated testing and use of RuboCop 10 | - I18n support 11 | 12 | ## Getting Started 13 | 14 | > [!NOTE] 15 | > If you don't have an existing Rails project create one with `rails new your-rails-project --css tailwind` 16 | 17 | To use these Cursor rules in your Ruby on Rails project: 18 | 19 | ### Option 1: Clone the Repository 20 | 21 | ```bash 22 | # Navigate to your Rails project directory 23 | cd your-rails-project 24 | 25 | # Clone the rules repository 26 | git clone https://github.com/wintermeyer/cursor-rails-rules.git 27 | 28 | # Copy the configuration files to your project 29 | cp -r cursor-rails-rules/.cursor . 30 | 31 | # Optional: Remove the cloned repository if you don't need it anymore 32 | rm -rf cursor-rails-rules 33 | ``` 34 | 35 | ### Option 2: Manual Download of the ZIP file 36 | 37 | ```bash 38 | # Navigate to your Rails project directory 39 | cd your-rails-project 40 | 41 | # Download the ZIP file 42 | curl -L https://github.com/wintermeyer/cursor-rails-rules/archive/refs/heads/main.zip -o cursor-rails-rules.zip 43 | 44 | # Extract the ZIP file 45 | unzip cursor-rails-rules.zip 46 | 47 | # Create the .cursor directory if it doesn't exist 48 | mkdir -p .cursor 49 | 50 | # Copy the configuration files to your project 51 | cp -r cursor-rails-rules/.cursor . 52 | 53 | # Clean up downloaded files 54 | rm cursor-rails-rules.zip 55 | rm -rf cursor-rails-rules-main 56 | ``` 57 | 58 | ### File Locations 59 | 60 | The configuration files should be placed in the following locations within your Rails project: 61 | 62 | ``` 63 | your-rails-project/ 64 | ├── .cursor/ # Cursor configuration directory 65 | │ └── rules/ # Custom rules directory 66 | └── ... rest of your Rails project 67 | ``` 68 | 69 | Restart Cursor to be on the safe side. 70 | 71 | ## Contributing 72 | 73 | This is a community project, and we believe that the best rules and configurations come from real-world experience. **Whether you're a seasoned Rails developer or just getting started, your input is valuable!** 74 | 75 | The official rules documentation can be found at https://docs.cursor.com/context/rules-for-ai 76 | 77 | Here's how you can contribute: 78 | - Suggest improvements to existing rules 79 | - Add new rules that have helped your team 80 | - Report bugs or issues you encounter 81 | - Improve documentation 82 | - Share your success stories 83 | 84 | The goal is to create a collection of best practices that benefit the entire Rails community. No contribution is too small - even fixing a typo helps! 85 | 86 | To contribute code changes: 87 | 1. Fork the repository 88 | 2. Create your feature branch 89 | 3. Commit your changes 90 | 4. Push to the branch 91 | 5. Open a Pull Request 92 | 93 | *Not familiar with forking repositories?* No problem! You can still contribute by creating a GitHub issue: 94 | 1. Click on the "Issues" tab at the top of this repository 95 | 2. Click the green "New issue" button 96 | 3. Choose between: 97 | - Bug report: If you've found something that's not working correctly 98 | - Feature request: If you have an idea for a new rule or improvement 99 | 4. Fill out the template with as much detail as possible 100 | 5. Submit the issue 101 | 102 | Your feedback and ideas are valuable to the community, whether they come through code contributions or issues! Let's make Rails development in Cursor even better together! 🚀 103 | 104 | ## License 105 | 106 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 107 | 108 | ## Author 109 | 110 | Stefan Wintermeyer 111 | 112 | Shameless plug: I offer consulting, training and support for Ruby on Rails 113 | and Cursor AI. Send me an email or visit my (German) homepage 114 | at https://wintermeyer-consulting.de 115 | 116 | --- 117 | 118 | ⭐️ If you find this project helpful, please consider giving it a star! 119 | --------------------------------------------------------------------------------