module ArchSpec::RailsReflector
Produces facts from real Active Record reflection. The parent command starts Rails explicitly; normal analysis never requires Active Record.
Constants
- MACROS
- Site
Public Instance Methods
# File lib/archspec/rails_reflector.rb, line 32 def capture(graph, models:, environment:, facts_path: 'archspec_facts') sites = association_sites(graph).group_by { |site| [site.macro, site.name] } references = [] methods = [] gaps = [] reflections = models.flat_map(&:reflect_on_all_associations) .uniq { |reflection| [reflection.active_record.name, reflection.name] } reflections.sort_by { |reflection| [reflection.active_record.name.to_s, reflection.name.to_s] }.each do |reflection| model = reflection.active_record next if model.name.nil? || graph.constants_named(model.name).empty? ancestors = graph.ancestor_names(model.name).first | Set[model.name] candidates = sites.fetch([reflection.macro, reflection.name.to_s], []).select { |site| ancestors.include?(site.owner) } source = source_node(graph, model, candidates) next unless source if candidates.size != 1 gaps << entry_for(graph, source, source.location).merge('source' => model.name, 'message' => "association #{model.name}.#{reflection.name}: no unique literal declaration") next end site = candidates.first entry = entry_for(graph, source, site.location) generated = model.generated_association_methods.instance_methods(false).map(&:to_s) names = [reflection.name.to_s, "#{reflection.name}="] & generated unless names.empty? methods << entry.merge('owner' => model.name, 'scope' => 'instance', 'names' => names.sort) end if reflection.polymorphic? gaps << entry.merge('source' => model.name, 'message' => "polymorphic association #{model.name}.#{reflection.name}") next end begin target = reflection.klass.name raise Error, 'association target is anonymous' if target.nil? || target.empty? references << entry.merge('source' => model.name, 'target' => target) rescue NameError, ArgumentError, ActiveRecord::ActiveRecordError, Error => error gaps << entry.merge('source' => model.name, 'message' => "unresolved association #{model.name}.#{reflection.name}: #{error.message.lines.first.strip}") end end { 'version' => Facts::VERSION, 'producer' => 'active_record', 'environment' => environment, 'snapshot' => Facts.snapshot(graph, excluding: facts_path), 'references' => references, 'methods' => methods, 'gaps' => gaps } end
# File lib/archspec/rails_reflector.rb, line 15 def run(config_path:, root:, output_path:, environment:) rails = File.join(root, 'bin/rails') raise Error, "no #{rails} found; reflection requires a Rails application" unless File.file?(rails) environment_variables = { 'ARCHSPEC_REFLECTION_CONFIG' => File.expand_path(config_path), 'ARCHSPEC_REFLECTION_OUTPUT' => output_path } stdout, stderr, status = Open3.capture3(environment_variables, RbConfig.ruby, rails, 'runner', '-e', environment, File.expand_path('reflect_runner.rb', __dir__), chdir: root) raise Error, "Rails reflection failed (exit #{status.exitstatus}):\n#{stderr}#{stdout}" unless status.success? stdout rescue SystemCallError => error raise Error, "could not start Rails reflection: #{error.message}" end