How exactly does the rails routing DSL parsed and executed? What are the downsides to the current implementation and how to refactor it into a much more modular codebase?
a subclass of Railtie • Engines are subclasses of Railtie and responsible for routing and other request handling configuration • Rails apps are supercharged engines
the Rails.application.routes method • routes is an instance of ActionDispatch::RouteSet • routes.routes is an instance of Journey::Routes • routes.routes.routes is an array of Journey::Route
explicit scope • Scope is a set of properties that determine the default options of a route • Most essentially these are the path, controller and action properties
an instance of Mapper • Mapper holds global state e.g. current scope, declared concerns, nesting and the route set • Scope is a hash that is backed up before running new scopes and then restored
args.flatten.join('/') if args.any? options[:constraints] ||= {} unless nested_scope? options[:shallow_path] ||= options[:path] if options.key?(:path) options[:shallow_prefix] ||= options[:as] if options.key?(:as) end if options[:constraints].is_a?(Hash) defaults = options[:constraints].select do |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) end (options[:defaults] ||= {}).reverse_merge!(defaults) else block, options[:constraints] = options[:constraints], {} end SCOPE_OPTIONS.each do |option| if option == :blocks value = block elsif option == :options value = options else value = options.delete(option) end if value recover[option] = @scope[option] @scope[option] = send("merge_#{option}_scope", @scope[option], value) end end yield self ensure @scope.merge!(recover) end
generators • get/post/put/patch/delete are wrappers around match • match normalizes everything and uses Mapping to create a normalized route and adds it to the RouteSet • RouteSet further normalizes the routes and adds them to it’s own set (Journey::Router)
even for experienced developers to get there head around it • Aaron Patterson (author of Journey) calls Mapper the biggest pain point of ActionDispatch • Merging of scopes loses information that is useful when route is added to route set
have no information on whether a route is singular or not • Normalization is redundant because routes are unaware of controllers and actions • Paths are parsed multiple times in each route set
represents a resource. This has led to long standing problems • Problems like generating polymorphic urls with singular resources. See issue #1769 • Using hash to maintains scope makes the DSL hard to test and maintain
scope and have direct access to the scope properties within which they execute • AbstractScope has an ivar parent pointing to it’s parent scope or nil for root scopes
on the AbstractScope class instead of Mapper • Subclasses use the super implementation along with their own customizations • All properties on a scope are built by merging with from it's parent scope which does so by merging with it's own parent and up… • Global state like route set is accessed from parent unless specified during initialization