Skip to content

feat: ruby (Rails + Pundit + CanCanCan + Devise) structural support#96

Merged
boorad merged 2 commits into
mainfrom
feat/85-ruby-structural
May 29, 2026
Merged

feat: ruby (Rails + Pundit + CanCanCan + Devise) structural support#96
boorad merged 2 commits into
mainfrom
feat/85-ruby-structural

Conversation

@boorad
Copy link
Copy Markdown
Collaborator

@boorad boorad commented May 29, 2026

Summary

Structural scanner support for Ruby — wires up tree-sitter-ruby and ships nine TOML rules covering Rails, Pundit, CanCanCan, Devise, and the inline RBAC idioms. Calibrated against discourse/discourse (10K Ruby files, 339 findings) and the varvet/pundit test fixtures.

Changes

  • Grammar wiringtree-sitter-ruby added to Cargo.toml; .rb / .rake mapped to Language::Ruby in src/scanner/discovery.rs and src/scanner/parser.rs; PHP becomes the new structural-canary in parse_source tests.
  • Nine rules under rules/ruby/:
    • pundit-authorizeauthorize @post / authorize! :update, @article
    • pundit-policy-methodpolicy(@post).update?
    • pundit-policy-classclass FooPolicy < ApplicationPolicy
    • cancancan-can-declarationcan :read, Article
    • cancancan-can-checkcurrent_user.can?(:update, @post) (receiver pinned to principal-shaped names)
    • rails-before-action-filterbefore_action :require_admin / skip_before_action ... with an authz-name predicate
    • role-equals-checkuser.role == "admin" / :admin
    • role-collection-includecurrent_user.roles.include?(:manager)
    • current-user-role-predicate — Devise-style current_user.admin?
  • Tests — 22 Rust matcher tests + 33 inline TOML rule tests (covers positive matches plus the high-value FP cases: chained obj.authorize, non-Policy classes, before_action :load_post, widget.can?, unrelated tags.include?, non-role predicates).
  • Corpusdocs/corpus/ruby.md captures the Discourse run with rule-by-rule and category breakdowns, top filter names, the policy-impl-path-skip note, and the Guardian.*? follow-up gap.
  • DocsAGENTS.md / README.md / docs/DESIGN.md move Ruby from "planned" to v0.2 shipped; src/deep/candidate.rs and src/scanner/discovery.rs drop Ruby from the "no structural support" comments.

Testing

  • cargo check — clean
  • cargo clippy --all-features -- -D warnings — clean
  • cargo fmt --check — clean
  • cargo test --all-features — 466 unit + 18 + 6 + 6 + 10 integration tests pass
  • cargo run -- rules test — 461 inline rule tests pass

Known gaps

  • Guardian.*? call-site shape (Discourse-specific but generalizable) — tracked in the corpus doc.
  • before_action filter-name regex breadth catches Rails framework filters like check_xhr and verify_authenticity_token; deliberate for now, narrower variant deferred until a second corpus target's filter inventory.
  • Policy-implementation path skip is broader than ideal on Discourse (63 files); shared heuristic, tracked as a future rule-engine tweak.

Closes #85

Summary by CodeRabbit

  • New Features

    • Structural scanning support for Ruby (v0.2) and language detection for Ruby projects
    • Added detection for multiple Ruby authorization patterns: Pundit (policy/authorize), CanCanCan (ability/can?/cannot?), Rails before_action filters, Devise/current-user role predicates, role collection includes, and role-equals checks
  • Documentation

    • Added Ruby corpus report with findings and gaps
    • Updated README and design docs to reflect Ruby v0.2 support and roadmap adjustments

Review Change Stack

Wires up the `tree-sitter-ruby` grammar and ships nine TOML rules covering
the dominant Ruby authz idioms: Rails `before_action` / `skip_before_action`
filters, Pundit (`authorize`, `policy(...).<action>?`, `*Policy <
ApplicationPolicy`), CanCanCan (`can :read, Article`, `user.can?(:x, ...)`),
Devise-style role predicates (`current_user.admin?`), and inline RBAC
shapes (`user.role == "admin"`/`:admin`, `user.roles.include?(...)`).

Calibrated on `discourse/discourse` (10K Ruby files, 339 findings) — see
`docs/corpus/ruby.md` for the run, top filter names, and the known
`Guardian.*?` follow-up gap.
@boorad boorad self-assigned this May 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 123d1a3b-b721-4837-9f70-fb1316cf9a6d

📥 Commits

Reviewing files that changed from the base of the PR and between e506829 and f9a7fd5.

📒 Files selected for processing (1)
  • src/scanner/discovery.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/scanner/discovery.rs

📝 Walkthrough

Walkthrough

Adds tree-sitter-based structural Ruby support: dependency and parser wiring, nine TOML authorization rules (Pundit/CanCanCan/Rails/Devise/role patterns) with templates and tests, and documentation/corpus updates promoting Ruby to structural scanning (v0.2).

Changes

Ruby Structural Scanner Support

Layer / File(s) Summary
Language infrastructure and parser wiring
Cargo.toml, src/scanner/discovery.rs, src/scanner/parser.rs, src/rules/embedded.rs, src/deep/candidate.rs
Adds tree-sitter-ruby dependency; recognizes .rb/.rake and Rakefile; maps Language::Ruby to tree-sitter; registers embedded ruby-* rule TOMLs; updates cold-region comment.
Ruby authorization rule definitions
rules/ruby/*.toml
Adds nine TOML rules capturing Pundit (authorize, policy class/method), CanCanCan (can/cannot declarations and checks), Rails before_action/skip_before_action, and idiomatic role/permission patterns, each with Rego/Cedar templates and tests.
Ruby rule matching test coverage
src/scanner/matcher.rs
Adds parse-and-match helper and ~324 lines of tests exercising positive and negative cases across the new Ruby rules.
Language support documentation and corpus calibration
AGENTS.md, README.md, docs/DESIGN.md, docs/corpus/README.md, docs/corpus/ruby.md
Moves Ruby from "planned" to "yes" (v0.2) in docs, adds discourse/discourse corpus target, and includes a detailed calibration report with findings and coverage notes.

Sequence Diagram(s)

sequenceDiagram
  participant Cargo as Cargo.toml
  participant Discovery as src/scanner/discovery.rs
  participant Parser as src/scanner/parser.rs
  participant Embedded as src/rules/embedded.rs
  participant Matcher as src/scanner/matcher.rs
  Cargo->>Discovery: add tree-sitter-ruby dependency
  Discovery->>Parser: detect .rb/.rake/Rakefile -> Language::Ruby
  Parser->>Embedded: map Language::Ruby -> tree-sitter-ruby grammar
  Embedded->>Matcher: register ruby-*.toml embedded rules
  Matcher->>Matcher: run Ruby rule tests via parse_and_match_ruby
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • EnforceAuth/zift#29: Similar language-support plumbing changes (discovery/parser/embedded rules) applied for Python.
  • EnforceAuth/zift#17: Prior work modifying Rust language-detection logic; closely related to discovery/parser changes for language additions.
  • EnforceAuth/zift#50: Extended EMBEDDED_RULES to add other language rule TOMLs; similar embedding pattern.

Poem

🐇 I hopped through trees and Ruby files,
I parsed the rails and matched their styles,
Pundit, CanCanCan, filters too,
Nine small rules now guard the view.
A rabbit cheers — v0.2 says "woo!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the primary change: adding Ruby structural support for Rails, Pundit, CanCanCan, and Devise frameworks.
Linked Issues check ✅ Passed All acceptance criteria from issue #85 are met: tree-sitter-ruby integrated, Pundit/CanCanCan rules added, Rails before_action and idiomatic role-comparison rules present, corpus entry created, Ruby moved from planned to yes in documentation.
Out of Scope Changes check ✅ Passed All changes align with scope: Ruby structural support, framework-specific rules, documentation updates, and test coverage. No Sinatra/Hanami/Grape or ActiveRecord scope patterns included.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/85-ruby-structural

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@amazon-q-developer amazon-q-developer Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR successfully implements structural scanner support for Ruby with comprehensive testing and proper integration. The implementation follows established patterns from other language integrations and includes extensive test coverage (22 Rust matcher tests + 33 inline TOML rule tests). No blocking defects were identified that would prevent merging.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/scanner/discovery.rs (1)

17-31: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit support for extensionless Rakefile in Ruby language detection

detect_language is extension-based only (e.g., tests cover foo.rb and Rakefile.rake, but no logic exists to match a bare Rakefile filename). Extend detection (and add a test like detect_language(Path::new("Rakefile")) == Some((Language::Ruby, false))) to cover Rails’ common extensionless entrypoint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scanner/discovery.rs` around lines 17 - 31, detect_language currently
only checks extensions; add a special-case filename check so an extensionless
"Rakefile" is detected as Ruby. In function detect_language, before or alongside
the extension extraction, inspect the file name/stem (e.g., using
path.file_name()/to_str()) and if it equals "Rakefile" return
Some((Language::Ruby, false)); keep existing extension-based matching for "rake"
and "rb" intact and ensure the new check handles case-sensitivity if needed for
tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/scanner/discovery.rs`:
- Around line 17-31: detect_language currently only checks extensions; add a
special-case filename check so an extensionless "Rakefile" is detected as Ruby.
In function detect_language, before or alongside the extension extraction,
inspect the file name/stem (e.g., using path.file_name()/to_str()) and if it
equals "Rakefile" return Some((Language::Ruby, false)); keep existing
extension-based matching for "rake" and "rb" intact and ensure the new check
handles case-sensitivity if needed for tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46ae534c-dee8-4c36-a75b-5aedc992d606

📥 Commits

Reviewing files that changed from the base of the PR and between 4822e87 and e506829.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • AGENTS.md
  • Cargo.toml
  • README.md
  • docs/DESIGN.md
  • docs/corpus/README.md
  • docs/corpus/ruby.md
  • rules/ruby/cancancan-can-check.toml
  • rules/ruby/cancancan-can-declaration.toml
  • rules/ruby/current-user-role-predicate.toml
  • rules/ruby/pundit-authorize.toml
  • rules/ruby/pundit-policy-class.toml
  • rules/ruby/pundit-policy-method.toml
  • rules/ruby/rails-before-action-filter.toml
  • rules/ruby/role-collection-include.toml
  • rules/ruby/role-equals-check.toml
  • src/deep/candidate.rs
  • src/rules/embedded.rs
  • src/scanner/discovery.rs
  • src/scanner/matcher.rs
  • src/scanner/parser.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 29, 2026
Bare Rakefile is the common Rails entrypoint and was previously
ignored because detection was extension-only. Special-case the
filename in both detect_language and detect_language_for_deep.
@boorad
Copy link
Copy Markdown
Collaborator Author

boorad commented May 29, 2026

@coderabbitai Addressed in f9a7fd5:

  • Fixed: src/scanner/discovery.rs — added a filename special-case so a bare Rakefile is detected as Ruby in both detect_language and detect_language_for_deep. Added unit-test assertions for Rakefile and project/Rakefile.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Tip

For best results, initiate chat on the files or code changes.

@boorad: Thanks for the follow-up! The fix looks solid — handling the bare Rakefile filename as a special case in both detect_language and detect_language_for_deep ensures consistent Ruby detection regardless of whether the file has an extension or a path prefix. The added unit-test assertions for both Rakefile and project/Rakefile cover the key cases nicely.

@boorad boorad merged commit 2ac2292 into main May 29, 2026
2 checks passed
@boorad boorad deleted the feat/85-ruby-structural branch May 29, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Structural scanner support for Ruby

1 participant