class Detector::Ruby::Rails

Defined in:

detector/detectors/ruby/rails.cr

Constant Summary

RAILS_CODE_MARKERS = [/(^|\W)[A-Za-z_:][\w:]*\.routes\.draw\b/, /(^|\W)Rails\.application\b/, /(^|\W)Rails::Application\b/, /(^|\s)require\s+["']rails\/all["']/]

Rails APIs that cannot appear in a project that is not Rails, so a single .rb or .ru file is enough to place the whole tree.

The dependency declaration is the better marker when it is there, but it is only there when the scan reaches the manifest. Point noir at a Rails app's config/ directory, at an engine whose gemspec does not name the framework, or at a service inside a monorepo scanned app-by-app, and there is no Gemfile in the walk — the app detects as nothing, the Rails analyzer never runs, and every route the app declares is lost. That is the same gap the Django detector closes for DRF-only apps, and it is worth closing here because the file the analyzer actually reads, config/routes.rb, is itself proof: .routes.draw is the Rails routing DSL and belongs to no other framework.

RAILS_GEMS = ["rails", "railties"]

Modern Rails apps frequently skip the umbrella rails dependency and pull the individual frameworks they actually use (railties + actionpack + activerecord + ...). Treat railties as a unique marker — it has no standalone use outside Rails — so those apps are still detected. Multi-engine projects (Spree, Solidus) push their Gemfile to gemspec and declare the dependency inside <gem>.gemspec instead; the tolerant matchers accept the s.add_dependency('rails', ...) parenthesized call form those gemspecs commonly use.

Class Method Summary

Instance Method Summary

Instance methods inherited from class Detector

applicable?(filename : String) : Bool applicable?, base_relative_path(filename : String) : String base_relative_path, content_matches?(file_contents : String, markers : Regex) : Bool content_matches?, detect(filename : String, file_contents : String) : Bool detect, gemfile_dependency?(file_contents : String, gem_name : String) : Bool gemfile_dependency?, gemspec_dependency?(file_contents : String, gem_name : String) : Bool gemspec_dependency?, idempotent? : Bool idempotent?, logger : NoirLogger logger, name : String name, path_sensitive? : Bool path_sensitive?, record_unparsable_document(filename : String, error : Exception) : Nil record_unparsable_document

Constructor methods inherited from class Detector

new(options : Hash(String, YAML::Any)) new

Macros inherited from class Detector

detector_for(tech, extensions = nil, basenames = nil, path_segments = nil, idempotent = nil) detector_for

Class Method Detail

def self.tech_name : String #

The tech name without needing an instance, so the registry can be read off the classes themselves rather than from a parallel list.


[View source]

Instance Method Detail

def applicable?(filename : String) : Bool #
Description copied from class Detector

Cheap filename-only filter the detector pass uses to skip #detect on files the detector cannot possibly match. The default true preserves prior behavior (every detector runs on every file). Override with the same predicate the body of #detect starts with — e.g., filename.ends_with?(".py") for a Python framework detector — so the detector loop avoids the #detect dispatch on files outside the detector's language.

On large codebases (saleor's 4255 .py files) this lifts ~100 virtual #detect calls per file out of the hot loop because most detectors' inner first-line is exactly this kind of cheap filename check.


[View source]
def detect(filename : String, file_contents : String) : Bool #

[View source]
def set_name #

[View source]