Skip to content

Non-invasive performance improvements and route helper optimizations - #18

Merged
jlsync merged 6 commits into
masterfrom
performance-improvements
Oct 9, 2026
Merged

jlsync merged 6 commits into
masterfrom
performance-improvements

Conversation

@jlsync

@jlsync jlsync commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Summary of Improvements

This PR introduces several non-invasive, backward-compatible performance optimizations across routing helpers, request introspection, and controller lifecycle methods:

  1. Routing Helpers (NamedRouteHelper):

    • Replaced array concatenation args + [options] with in-place append args << options unless options.empty?.
    • Replaced args = [resource] + args with in-place prepend args.unshift(resource).
    • Replaced temporary array concatenation *(enclosing_collection_resources + args) with Ruby multi-splat argument dispatch *enclosing_collection_resources, *args.
    • Replaced resource_method.match?(/enclosing_resource/) with resource_method.include?('enclosing_resource').
    • Optimized suffix stripping (delete_suffix) and route mapping lookups.
  2. Request Path Introspection (RequestPathIntrospection):

    • Cached compiled lookahead regular expressions in remove_current_segment via SEGMENT_REGEXPS, eliminating runtime regex compilation on each request.
    • Refactored param_keys to a single-pass params.each_key.filter_map to eliminate intermediate arrays.
    • Replaced path[1..-1].to_s.split('/') with path.delete_prefix('/').split('/').
    • Used delete_suffix('_id') in segment_for_key instead of range slicing 0..-4.
  3. Instance Variable Access & Controller Lifecycle (ResourcesController & Specification):

    • Precomputed ivar Symbols (@ivar_name = :"@#{name}", @collection_ivar_name = :"@#{plural}") in Specification#initialize, avoiding string interpolation and symbol interning on every call to resource, resource=, resources, and resources=.
    • Modernized hand-rolled accessors resource_service= and enclosing_resource_name to attr_writer and attr_reader for C-level VM dispatch and YJIT inline caching.
    • Replaced /\A\?(.*)/ regex matching in load_enclosing_resources with fast prefix check spec.is_a?(String) && spec.start_with?('?').
    • Replaced temporary Hash allocation in load_wildcards_from with a block check.
    • Used symbol :@enclosing_resource_name in instance_variable_set.
  4. Parameters & Form Helpers (ResourceMethods & Helper):

    • In resource_params, used params.fetch(resource_name) { {} } to avoid allocating unused Hash instances, and extracted frozen constant EXCLUDED_PARAM_COLUMNS = %w[updated_at created_at].freeze.
    • In Helper#form_for_resource_options, replaced resource_path(*([resource] + args)) with direct argument passing resource_path(resource, *args).

Test Suite Verification

All 658 examples pass with 0 failures:

$ bundle exec rspec
Finished in 3.34 seconds (files took 1.15 seconds to load)
658 examples, 0 failures

Co-authored-by: Antigravity antigravity@google.com

jlsync and others added 2 commits October 9, 2026 09:15
- Cache compiled regular expressions in RequestPathIntrospection#remove_current_segment
- Single-pass parameter filtering via filter_map in param_keys
- Use precomputed ivar Symbols for resource getters and setters
- Modernize accessors with attr_writer and attr_reader
- Use in-place argument manipulation and multi-splat in generated route helpers
- Avoid intermediate array allocations in form_for_resource_options and resource_params

Co-authored-by: Antigravity <antigravity@google.com>
@jlsync

jlsync commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

jlsync and others added 4 commits October 9, 2026 10:35
…x state

* RequestPathIntrospection#remove_current_segment no longer keeps a process
  wide, never invalidated cache of compiled regexps.  The pattern is built at
  most once per request (#nesting_request_path is memoised), so the cache only
  added shared mutable state for no measurable gain.  The current segment is
  now Regexp.escape'd, so it is matched literally, as #remove_namespace already
  does.
* #update_name_prefix always leaves @name_prefix as a String, so a controller
  whose enclosing resources all have an empty (:name_prefix => false) prefix no
  longer leaves the raw ivar as nil.
* Use Symbol instance variable names consistently in #add_enclosing_resource.
* Document that the #resource / #resources ivar names come from the resource
  specification, and note the #resource_name override change in the CHANGELOG.
* Add regression specs for the literal segment matching and for the
  #update_name_prefix invariant.
* Replace .travis.yml with a GitHub Actions workflow that tests Ruby 3.3, 3.4
  and 4.0 against Rails 8.0 and 8.1, driven by an optional RAILS_VERSION in the
  Gemfile, and refresh the README badge and development notes.
* Expand the 5.0.0 CHANGELOG entry to cover the spec suite, development setup
  and CI changes made on this branch.

Co-authored-by: DeepSeek <agent@deepseek.com>
…kaging

* ResourceService#respond_to? always returned false for the proxy's own methods: it
  called super, but BasicObject has no #respond_to?, so Ruby fell through to
  ResourceService#method_missing and asked the model instead.  The proxy now lists
  its own methods (PROXY_METHODS) and asks the service about everything else.
* NamedRouteHelper and Helper expose their generated route helpers through
  respond_to_missing? rather than by overriding respond_to?, which is the modern
  hook and also makes controller.method(:resource_path) work.
* Memoise route_name.pluralize: inflection was the most expensive operation on the
  named route helper path (about 4us per call).
* lib is now frozen_string_literal: true.  The two places where an empty literal
  prefix escapes to callers (#name_prefix, and Specification#name_prefix when
  :name_prefix => false) use +'' so that they stay mutable.
* Removed the dead Rails 2 find_filter branch and its specs, the dead
  spec/verify_rcov.rb, the Rakefile's Rake 13 era rake/rdoctask fallback, and the
  spec:make_test_app / spec:rspec_generated_specs tasks: they generated a Rails 2-5
  era app and could no longer run.  The README no longer advertises them.
* Packaging: added the MIT license and gem metadata, dropped the stale
  rubygems_version (which made gem build warn), and s.files now lists lib and the
  docs explicitly instead of shelling out to git ls-files, so the gem no longer
  ships the specs, CI config, Gemfile and other development files.
* Added .rspec (--require spec_helper).

Co-authored-by: DeepSeek <agent@deepseek.com>
@jlsync
jlsync merged commit 815250a into master Oct 9, 2026
6 checks passed
@jlsync
jlsync deleted the performance-improvements branch October 9, 2026 09:44
@jlsync
jlsync restored the performance-improvements branch October 9, 2026 13:45
@jlsync
jlsync deleted the performance-improvements branch October 9, 2026 13:47
jlsync added a commit that referenced this pull request Oct 10, 2026
v5.0.0 is tagged at the PR #18 merge, so the two changes that landed on master
after the tag get their own section: this request_path fix, and the README.rdoc to
README.md conversion (which was pushed in 1db30bb, after the tag).

Bumps VERSION to 5.0.1 so that version.rb, the gemspec and the CHANGELOG agree.

Co-authored-by: DeepSeek <agent@deepseek.com>
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.

1 participant