Thanks, Claude, but Ruby is not Rails


Signs of Rails creep in the code

I spent some of today debugging an error that suddenly started appearing in CI runs for unit tests:

     NoMethodError:
       undefined method `present?' for nil:NilClass
 
               return if config[:license_type].present?
                                              ^^^^^^^^^

Having prior experiences with code that was written for a Ruby only project but happened to rely on a Rails dependency, I pretty quickly knew why it was breaking: NilClass#present? is an ActiveSupport extension to Ruby, not part of the Ruby standard library (as of 3.4.2, at least).

There’s just one problem with that thesis: The breakage seemingly happened between runs. I was even able to rerun one of the unit test builds on the same commit and confirmed that the rerun is broken.

Ok, but it’s definitely ActiveSupport, right?

Of course it’s ActiveSupport. Let me just check the logs.

> grep activesupport *.log
build1.log:Fetching activesupport 7.0.8.7
build1.log:Installing activesupport 7.0.8.7
build1.log:Fetching activesupport 7.0.8.7
build1.log:Installing activesupport 7.0.8.7
build2.log:Fetching activesupport 7.0.8.7
build2.log:Installing activesupport 7.0.8.7
build2.log:Fetching activesupport 7.2.2.1
build2.log:Installing activesupport 7.2.2.1

Ok, so ActiveSupport is there… But… why two versions on the newer build?

Claude for an expedited attempt at explanation

what changed with .present? for activesupport 7.0.8.7 vs. activesupport 7.2.2.1

Based on the search results, I can see that there are version differences between ActiveSupport 7.0.8.7 and 7.2.2.1, but the search results don’t show specific changes to the .present? method itself. Let me search for the specific changelog entries.

favicon

Fetched rails/activesupport/CHANGELOG.md at v8.0.2 · rails/rails

github.com

Based on my search results, I can see that there were significant changes in ActiveSupport between version 7.0.8.7 and 7.2.2.1, but I don’t see any specific documented changes to the .present? method itself in the changelog entries I found.

However, I did find an important clue in the search results: ActiveSupport 7.1 changed its API and now loads only minimal dependencies pod install fails while trying to call present? on a nil when using activesupport 7.1.0 · Issue #12089 · CocoaPods/CocoaPods. This suggests that the main change affecting .present? between these versions is likely related to how ActiveSupport loads its core extensions.

Here’s what likely changed:

Loading Behavior Changes:

  • In ActiveSupport 7.1+ (which 7.2.2.1 is based on), the gem changed to load only minimal dependencies by default
  • This means that methods like .present? and .blank? might not be automatically available unless you explicitly require the core extensions

What you might need to do: If you’re upgrading from 7.0.8.7 to 7.2.2.1 and suddenly .present? stops working, you may need to explicitly require:

ruby

require 'active_support/core_ext'

Or more specifically:

ruby

require 'active_support/core_ext/object/blank'

The functionality of .present? itself hasn’t changed – it still returns true for non-blank objects and false for blank ones. The change is primarily about how ActiveSupport loads these extensions.

This is a common issue that affected many projects when upgrading from ActiveSupport 7.0 to 7.1+, as the autoloading behavior became more conservative to reduce memory usage and improve performance.

Ok, so it’s ActiveSupport 7.1

So, ActiveSupport 7.1 is the culprit. I’d like to see the Release Notes on this

Are there release notes and a PR in which activesupport loads the minimum dependencies by default?

There was a long detailed response with 20+ references. The only direct mention that’s come up so far of the actual change? CocoaPods/CocoaPods issue #12089. I’m sure a detailed analysis of the activesupport code would show me, but the 7.1 release notes don’t seem to mention the change even indirectly.

Time to fix

My decision, was to substitute out pure Ruby code for the .present? call. This was based on the relatively high volatility of the Rails API (yes, pinning would help), the relative heaviness of activesupport, and this being the only usage of activesupport within a repo of 112k lines of (non-test) Ruby code. I took a naïve attempt at a replacement, but it broke for the scenarios of several types.

Claude’s suggestion:

But honestly, if you need .present? behavior, it’s much cleaner to either use ActiveSupport or implement the .present? method yourself rather than trying to replicate it with string manipulation.

I’m going to reject that for reasons above.

Of course, Claude needs more context

Is it actually worth including activesupport as a dependency for a codebase with 112k lines of non-test Ruby code if the only usage is a single .present? call?

(Am I leading the witness?) Now the response is more sensible.

No, it’s definitely not worth adding ActiveSupport as a dependency for a single .present? call in a 112k line codebase.

Here’s why:

The Cost

  • ActiveSupport is huge: It’s a substantial dependency with many features you won’t use
  • Bundle size: Adds significant weight to your application
  • Maintenance burden: Another dependency to keep updated and secure
  • Potential conflicts: Could introduce version conflicts with other gems

I decided to opt for a fairly verbose but dependency-free:

        license_type = config[:license_type]
        return unless license_type.nil? || (license_type.respond_to(:empty?) && license_type.empty?)

Conclusion/Experiences

  • Claude quite a bit better at searching for the difference between specific ActiveSupport versions that my experience with GitHub and Google have been, particularly given that the actual mention of the change was found in a project using ActiveSupport
  • Treat suggestions as alternative perspectives, but assess them as coming from potentially naïve viewpoints.
  • If a solution or suggestion feels off, add the context that would make you disagree.
  • My best tools for troubleshooting this problem were deterministic: log output, grep, diff (with a preprocessing script that effectively scrubs the timestamps and unique identifiers to minimize noise). My most useful experiences with AI-assisted coding have been in building tools to get the busywork and tedious dumpster diving out of the way and to the business of solving the problem sooner.
  • Along the way I let GitHub Copilot write a little test script that compared solutions:

================================================================================
TEST SUMMARY
================================================================================

ActiveSupport 7.0.8.7:
  ✓ Basic require: success
    Output: false
  ✗ Core extensions: failed
  ✓ Specific blank extension: success
    Output: false
  ✓ All extensions: success
    Output: false

ActiveSupport 7.2.2.1:
  ✗ Basic require: failed
  ✗ Core extensions: failed
  ✓ Specific blank extension: success
    Output: false
  ✓ All extensions: success
    Output: false

================================================================================
RECOMMENDATIONS
================================================================================
✓ Working combinations:
  - 7.0.8.7 with Basic require
  - 7.0.8.7 with Specific blank extension
  - 7.0.8.7 with All extensions
  - 7.2.2.1 with Specific blank extension
  - 7.2.2.1 with All extensions

Leave a Reply