@sun-asterisk/sunlint 1.3.40 → 1.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/rule-selection-service.js +11 -0
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/dart/D001-recommended-lints.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D002-dispose-resources.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D003-prefer-widget-classes.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D004-avoid-shrinkwrap.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D005-widget-nesting.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D006-large-callbacks.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D007-lifecycle-order.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D008-long-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D009-function-parameters.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D010-cyclomatic-complexity.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D011-named-parameters.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D012-named-booleans.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D013-single-public-class.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D014-safe-collection-access.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D015-copywith-consistency.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D016-project-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D017-review-dependencies.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D018-no-commented-code.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D019-single-child-wrappers.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D020-if-else-limit.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D021-negated-booleans.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D022-setstate-usage.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D023-unnecessary-overrides.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D024-avoid-unnecessary-statefulwidget.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D025-nested-ternaries.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/go/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C014-dependency-injection.md +85 -0
- package/skill-assets/sunlint-code-quality/rules/go/C017-no-constructor-logic.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/go/C018-generic-errors.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/C020-no-unused-imports.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C022-no-unused-variables.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/C023-no-duplicate-names.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/C024-centralize-constants.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/C029-catch-log-root-cause.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/C030-custom-error-classes.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/go/C033-separate-data-access.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go/C035-error-context-logging.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C041-no-hardcoded-secrets.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C042-boolean-naming.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/C052-controller-parsing.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/go/C060-superclass-logic.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/go/C067-no-hardcoded-config.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S003-open-redirect.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/go/S004-no-log-credentials.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/go/S005-server-authorization.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S006-default-credentials.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S007-output-encoding.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S009-approved-crypto.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/S010-csprng.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S011-encrypted-client-hello.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/S012-secrets-management.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S013-tls-connections.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/go/S016-no-sensitive-query-string.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S017-parameterized-queries.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/go/S019-email-input-sanitization.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S020-eval-code-execution.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S022-context-escaping.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S023-dynamic-js-encoding.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S025-server-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/S026-tls-encryption.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S027-mtls-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S028-upload-limits.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S029-csrf-protection.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S030-directory-browsing.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S031-secure-cookie-flag.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S032-httponly-cookie.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S033-samesite-cookie.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S034-host-prefix-cookie.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S035-app-hostnames.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S036-internal-file-paths.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/S037-anti-cache-headers.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S039-tls-certificate-validation.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/S041-logout-invalidation.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S042-long-lived-sessions.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S044-critical-changes-reauth.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S045-brute-force-protection.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S047-oauth-csrf-protection.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S048-oauth-redirect-validation.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S049-auth-code-expiry.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S050-token-entropy.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S051-password-length.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S052-otp-entropy.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S053-generic-error-messages.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S054-no-default-admin.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S055-content-type-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S056-log-injection.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S057-synchronized-time.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S058-ssrf-protection.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB001-use-snake-case.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB002-use-camel-case.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB003-use-screaming-snake-case.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB004-predicate-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB005-dangerous-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB006-indentation.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB007-line-length.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB008-rescue-exception.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB009-save-bang.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB010-avoid-n-plus-one.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB011-use-find-each.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB012-sql-injection.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB013-prefer-has-many-through.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB014-dependent-associations.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB015-modern-validations.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB016-thin-controllers.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB017-avoid-fat-models.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB018-service-objects.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB019-avoid-metaprogramming.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB020-use-pluck.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB021-use-size.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB022-order-by-timestamps.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB023-where-missing.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB024-method-length.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB025-parameter-limits.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB026-avoid-deep-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB027-guard-clauses.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB028-class-length.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB029-meaningful-names.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB030-dry-principle.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB031-mvc-architecture.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB032-use-concerns.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB033-moderate-callbacks.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB034-use-decorators.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB035-comprehensive-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB036-frozen-string-literal.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB037-it-parameter.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB038-modern-enum-syntax.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB039-solid-adapters.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB040-rails-authentication.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB041-async-query-loading.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB042-hotwire-turbo.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB043-use-propshaft.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB044-structured-logging.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB045-prism-parser.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW001-block-based-kvo.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW002-class-delegate-protocol.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW003-compiler-protocol-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW004-contains-over-filter-count.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW005-convenience-type.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW006-discarded-notification-center-observer.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW007-discouraged-direct-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW008-discouraged-optional-boolean.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW009-empty-count.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW010-empty-string.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW011-explicit-init.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW012-fatal-error-message.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW013-for-where.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW014-force-cast.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW015-force-try.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW016-force-unwrapping.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW017-function-parameter-count.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW018-large-tuple.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW019-legacy-constructor.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW020-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW021-no-extension-access-modifier.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW022-overridden-super-call.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW023-override-in-extension.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW024-private-over-fileprivate.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW025-private-unit-test.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW026-prohibited-super-call.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW027-sorted-first-last.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW028-syntactic-sugar.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW029-unused-closure-parameter.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW030-unused-enumerated.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW031-unused-optional-binding.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW032-valid-ibinspectable.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW033-vertical-parameter-alignment.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW034-void-return.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW035-weak-delegate.md +28 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use find_each for large collections
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Reduce memory consumption when iterating over large datasets.
|
|
5
|
+
tags: rails, performance, database, memory, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use find_each for large collections
|
|
9
|
+
|
|
10
|
+
Reduce memory consumption when iterating over large datasets. Use `find_each` or `find_in_batches` instead of `each` for large ActiveRecord collections. `find_each` loads records in batches (default 1000) to avoid loading all records into memory at once.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Loads ALL users into memory, which can crash the process for large datasets
|
|
16
|
+
User.all.each do |user|
|
|
17
|
+
user.do_something
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct:**
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# Loads users in batches of 1000
|
|
25
|
+
User.find_each do |user|
|
|
26
|
+
user.do_something
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** RuboCop (`Rails/FindEach`)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid SQL injection with parameterized queries
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Prevent SQL injection vulnerabilities.
|
|
5
|
+
tags: ruby, rails, security, sql-injection, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid SQL injection with parameterized queries
|
|
9
|
+
|
|
10
|
+
Prevent SQL injection vulnerabilities. Never use string interpolation in SQL queries. Use parameterized queries with `?` or named placeholders (`:name`). Use hash conditions for `where` clauses instead of SQL fragments.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Vulnerable):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Direct interpolation is dangerous
|
|
16
|
+
User.where("name = '#{params[:name]}'")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (Safe):**
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Parameterized
|
|
23
|
+
User.where("name = ?", params[:name])
|
|
24
|
+
|
|
25
|
+
# Hash-based (Recommended)
|
|
26
|
+
User.where(name: params[:name])
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Tools:** Brakeman, RuboCop (`Rails/SquishedSQLHeredocs`)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Prefer has_many :through over HABTM
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Allow for additional attributes and validations on join models.
|
|
5
|
+
tags: rails, database, associations, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Prefer has_many :through over HABTM
|
|
9
|
+
|
|
10
|
+
Allow for additional attributes and validations on join models. Use `has_many :through` instead of `has_and_belongs_to_many` for many-to-many relationships. This provides more flexibility for adding attributes or validations to the join table later.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User < ApplicationRecord
|
|
16
|
+
has_and_belongs_to_many :roles
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class User < ApplicationRecord
|
|
24
|
+
has_many :assignments
|
|
25
|
+
has_many :roles, through: :assignments
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Assignment < ApplicationRecord
|
|
29
|
+
belongs_to :user
|
|
30
|
+
belongs_to :role
|
|
31
|
+
# Can add validations or extra fields here
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Tools:** RuboCop (`Rails/HasAndBelongsToMany`)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Always define dependent option for associations
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Prevent orphaned records and ensure data integrity.
|
|
5
|
+
tags: rails, database, data-integrity, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Always define dependent option for associations
|
|
9
|
+
|
|
10
|
+
Prevent orphaned records and ensure data integrity. Always specify the `dependent` option for `has_many` and `has_one` associations to define what happens to associated records when the parent is deleted.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User < ApplicationRecord
|
|
16
|
+
has_many :posts # No dependent option
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class User < ApplicationRecord
|
|
24
|
+
has_many :posts, dependent: :destroy
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** Custom linter, Manual Review
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use new-style validations
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Follow modern Rails conventions for model validations.
|
|
5
|
+
tags: rails, conventions, validations, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use new-style validations
|
|
9
|
+
|
|
10
|
+
Follow modern Rails conventions for model validations. Use the `validates` method with a hash of options. Avoid old-style validations like `validates_presence_of`.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User < ApplicationRecord
|
|
16
|
+
validates_presence_of :email
|
|
17
|
+
validates_uniqueness_of :email
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct:**
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
class User < ApplicationRecord
|
|
25
|
+
validates :email, presence: true, uniqueness: true
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Tools:** RuboCop (`Rails/Validation`)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Keep controllers thin
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Maintain single responsibility and improve testability.
|
|
5
|
+
tags: rails, architecture, controllers, mvc
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Keep controllers thin
|
|
9
|
+
|
|
10
|
+
Maintain single responsibility and improve testability. Controllers should only handle HTTP requests and responses (parsing params, calling logic, redirecting/rendering). Extract complex business logic into service objects, models, or concerns.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class OrdersController < ApplicationController
|
|
16
|
+
def create
|
|
17
|
+
@order = Order.new(order_params)
|
|
18
|
+
# Complex business logic in controller
|
|
19
|
+
if @order.save
|
|
20
|
+
Inventory.decrease(@order.product_id)
|
|
21
|
+
EmailService.send_confirmation(@order.user)
|
|
22
|
+
redirect_to @order
|
|
23
|
+
else
|
|
24
|
+
render :new
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Correct:**
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
class OrdersController < ApplicationController
|
|
34
|
+
def create
|
|
35
|
+
@order = Order.new(order_params)
|
|
36
|
+
if OrderCreationService.call(@order)
|
|
37
|
+
redirect_to @order
|
|
38
|
+
else
|
|
39
|
+
render :new
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Tools:** RuboCop (`Metrics/MethodLength`)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid fat models
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Prevent models from becoming monolithic and hard to maintain.
|
|
5
|
+
tags: rails, architecture, models, mvc
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid fat models
|
|
9
|
+
|
|
10
|
+
Prevent models from becoming monolithic and hard to maintain. Keep models focused on associations, validations, and simple domain logic. Extract complex workflows or unrelated concerns into service objects or specific classes.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# User model handling too many things (auth, analytics, profile, etc.)
|
|
16
|
+
class User < ApplicationRecord
|
|
17
|
+
def generate_analytics_report
|
|
18
|
+
# complex logic unrelated to core User state
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def process_subscription_upgrade
|
|
22
|
+
# complex workflow
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Correct:**
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
class User < ApplicationRecord
|
|
31
|
+
# core logic only
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class AnalyticsReportGenerator
|
|
35
|
+
def self.call(user)
|
|
36
|
+
# complex logic
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** RuboCop (`Metrics/ClassLength`)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use service objects for complex business logic
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Improve code organization and testability.
|
|
5
|
+
tags: rails, architecture, service-objects, patterns
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use service objects for complex business logic
|
|
9
|
+
|
|
10
|
+
Improve code organization and testability. Extract complex business logic (payments, onboarding, multi-step processes) into service objects. Service objects should have a single public method (e.g., `call`, `execute`) and reside in `app/services/`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Logic inside model):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class Payment < ApplicationRecord
|
|
16
|
+
def process!
|
|
17
|
+
# API calls, state changes, email triggers...
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (Decoupled Service):**
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class PaymentProcessor
|
|
26
|
+
def initialize(payment)
|
|
27
|
+
@payment = payment
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def call
|
|
31
|
+
# API calls, state changes, email triggers...
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Tools:** Custom linter, Manual Review
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid needless metaprogramming
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Maintain code clarity and debuggability.
|
|
5
|
+
tags: ruby, metaprogramming, readability, simplicity
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid needless metaprogramming
|
|
9
|
+
|
|
10
|
+
Maintain code clarity and debuggability. Avoid using metaprogramming (`define_method`, `method_missing`, `class_eval`) unless absolutely necessary. Metaprogramming makes code harder to understand, debug, and maintain.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Over-engineered):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class User
|
|
16
|
+
[:name, :email, :phone].each do |attr|
|
|
17
|
+
define_method("display_#{attr}") do
|
|
18
|
+
send(attr) || "N/A"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (Explicit):**
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class User
|
|
28
|
+
def display_name
|
|
29
|
+
name || "N/A"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def display_email
|
|
33
|
+
email || "N/A"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# ...
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Tools:** RuboCop (`Style/MethodMissingSuper`)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use pluck for selecting specific columns
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improve query performance by selecting only needed columns.
|
|
5
|
+
tags: rails, performance, database, active-record
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use pluck for selecting specific columns
|
|
9
|
+
|
|
10
|
+
Improve query performance by selecting only needed columns. Use `pluck` to select columns from multiple records. Use `pick` for a single value from a single record.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Loads whole User objects into memory just to get ids
|
|
16
|
+
User.all.map(&:id)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct:**
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Only selects 'id' in SQL
|
|
23
|
+
User.pluck(:id)
|
|
24
|
+
|
|
25
|
+
# Even better for IDs specifically
|
|
26
|
+
User.ids
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Tools:** RuboCop (`Rails/Pluck`, `Rails/PluckId`)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use size over count or length
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Optimize performance by using the most efficient method.
|
|
5
|
+
tags: rails, performance, active-record, database
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use size over count or length
|
|
9
|
+
|
|
10
|
+
Optimize performance by using the most efficient method. Use `size` instead of `count` or `length` for ActiveRecord collections. `size` intelligently chooses between `count` (SQL query if not loaded) and `length` (array size if already loaded).
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
# Always triggers a SELECT COUNT(*) query even if data is already loaded
|
|
16
|
+
users = User.all.to_a
|
|
17
|
+
puts users.count
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Uses the array size if already loaded, otherwise queries DB
|
|
24
|
+
puts User.all.size
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Tools:** RuboCop (`Rails/SkipsModelValidations`)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Order by timestamp columns, not id
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Ensure consistent ordering across database systems.
|
|
5
|
+
tags: database, rails, active-record, ordering
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Order by timestamp columns, not id
|
|
9
|
+
|
|
10
|
+
Ensure consistent ordering across database systems. Order records chronologically by timestamp columns (`created_at`, `updated_at`) instead of `id`. IDs may not be sequential in distributed systems (e.g., UUIDs).
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
scope :recent, -> { order(id: :desc) }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct:**
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Tools:** Manual Review
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use where.missing for Rails 6.1+
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Use modern Rails APIs for finding records with missing associations.
|
|
5
|
+
tags: rails, active-record, simplicity, associations
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use where.missing for Rails 6.1+
|
|
9
|
+
|
|
10
|
+
Use modern Rails APIs for finding records with missing associations. Use `where.missing(:association)` instead of left joins with null checks. This is more readable and expressive.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
Post.left_outer_joins(:comments).where(comments: { id: nil })
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct:**
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
Post.where.missing(:comments)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Tools:** RuboCop (`Rails/WhereMissing`)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Keep methods under 10 lines
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improve code readability and maintainability.
|
|
5
|
+
tags: ruby, quality, metrics, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Keep methods under 10 lines
|
|
9
|
+
|
|
10
|
+
Improve code readability and maintainability. Limit methods to 10 lines of code (excluding blank lines and comments). Extract complex logic into smaller, well-named private methods.
|
|
11
|
+
|
|
12
|
+
**Incorrect (Too long):**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def process_order(order)
|
|
16
|
+
validate_order(order)
|
|
17
|
+
apply_discount(order)
|
|
18
|
+
notify_user(order)
|
|
19
|
+
update_inventory(order)
|
|
20
|
+
generate_invoice(order)
|
|
21
|
+
archive_order(order)
|
|
22
|
+
# ... many more lines
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct:**
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
def process_order(order)
|
|
30
|
+
prepare_order(order)
|
|
31
|
+
finalize_order(order)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def prepare_order(order)
|
|
37
|
+
# sub-logic < 10 lines
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** RuboCop (`Metrics/MethodLength`)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Limit method parameters to 4
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Reduce method complexity and improve readability.
|
|
5
|
+
tags: ruby, quality, design, parameters
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Limit method parameters to 4
|
|
9
|
+
|
|
10
|
+
Reduce method complexity and improve readability. Limit methods to 4 parameters maximum. If more parameters are needed, consider using a hash or a specific parameter object.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def send_email(to, from, subject, body, cc, bcc, attachments)
|
|
16
|
+
# logic
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
def send_email(to:, from:, subject:, body:, options: {})
|
|
24
|
+
# logic
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** RuboCop (`Metrics/ParameterLists`)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Avoid deep nesting (max 3 levels)
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improve code readability and reduce cyclomatic complexity.
|
|
5
|
+
tags: ruby, quality, metrics, nesting
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Avoid deep nesting (max 3 levels)
|
|
9
|
+
|
|
10
|
+
Improve code readability and reduce cyclomatic complexity. Limit nesting (if/else, blocks) to 3 levels maximum. Use guard clauses, early returns, or extract methods to flatten code.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def update_user
|
|
16
|
+
if logged_in?
|
|
17
|
+
if admin?
|
|
18
|
+
if params[:id].present?
|
|
19
|
+
# 4th level nesting
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Correct:**
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
def update_user
|
|
30
|
+
return unless logged_in?
|
|
31
|
+
return unless admin?
|
|
32
|
+
return if params[:id].blank?
|
|
33
|
+
|
|
34
|
+
# Logic at level 1
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Tools:** RuboCop (`Metrics/BlockNesting`)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use guard clauses for early returns
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Reduce nesting and improve code clarity.
|
|
5
|
+
tags: ruby, quality, style, clean-code
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use guard clauses for early returns
|
|
9
|
+
|
|
10
|
+
Reduce nesting and improve code clarity. Use guard clauses to handle edge cases or invalid states early and return. This keeps the main happy path at the top level of the method.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def notify(user)
|
|
16
|
+
if user.active?
|
|
17
|
+
if user.email_notifications?
|
|
18
|
+
# Main logic here
|
|
19
|
+
UserMailer.notification(user).deliver_now
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Correct:**
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
def notify(user)
|
|
29
|
+
return unless user.active?
|
|
30
|
+
return unless user.email_notifications?
|
|
31
|
+
|
|
32
|
+
# Main logic here
|
|
33
|
+
UserMailer.notification(user).deliver_now
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Tools:** RuboCop (`Style/GuardClause`)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Keep classes under 100 lines
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Maintain single responsibility and improve maintainability.
|
|
5
|
+
tags: ruby, quality, design, architecture
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Keep classes under 100 lines
|
|
9
|
+
|
|
10
|
+
Maintain single responsibility and improve maintainability. Limit classes to 100 lines of code (excluding blank lines and comments). Extract responsibilities into separate classes or modules if a class grows too large.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
class BigManager
|
|
16
|
+
# 200 lines of mixed logic
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct:**
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class OrderProcessor
|
|
24
|
+
# focused logic < 100 lines
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class OrderNotifier
|
|
28
|
+
# focused logic < 100 lines
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** RuboCop (`Metrics/ClassLength`)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use meaningful variable and method names
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improve code self-documentation and readability.
|
|
5
|
+
tags: ruby, naming, readability, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use meaningful variable and method names
|
|
9
|
+
|
|
10
|
+
Improve code self-documentation and readability. Use descriptive names that clearly convey purpose. Avoid single-letter variables except for common iterators (i, j, k).
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def calc(a, b)
|
|
16
|
+
r = a * b
|
|
17
|
+
return r
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct:**
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
def calculate_area(width, height)
|
|
25
|
+
area = width * height
|
|
26
|
+
return area
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** RuboCop (`Naming/VariableName`)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Don't Repeat Yourself (DRY)
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Reduce code duplication and improve maintainability.
|
|
5
|
+
tags: ruby, abstraction, maintenance, quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Don't Repeat Yourself (DRY)
|
|
9
|
+
|
|
10
|
+
Reduce code duplication and improve maintainability. Extract repeated code into methods, helpers, concerns, or partials. Use inheritance or modules to share behavior instead of copy-pasting code blocks.
|
|
11
|
+
|
|
12
|
+
**Incorrect:**
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
def create_admin
|
|
16
|
+
user = User.new(admin: true)
|
|
17
|
+
user.save!
|
|
18
|
+
Log.create(action: "admin created")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_user
|
|
22
|
+
user = User.new(admin: false)
|
|
23
|
+
user.save!
|
|
24
|
+
Log.create(action: "user created")
|
|
25
|
+
end
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Correct:**
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
def create_account(admin: false)
|
|
32
|
+
user = User.create!(admin: admin)
|
|
33
|
+
Log.create(action: "#{admin ? 'admin' : 'user'} created")
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Tools:** Reek, RuboCop (`Style/IdenticalConditionalBranches`)
|