Skip to content

Modernize for Ruby 3.1+, Rails 6.1+, Faraday 2.x#554

Open
voltechs wants to merge 31 commits intoremi:masterfrom
TwilightCoders:master
Open

Modernize for Ruby 3.1+, Rails 6.1+, Faraday 2.x#554
voltechs wants to merge 31 commits intoremi:masterfrom
TwilightCoders:master

Conversation

@voltechs
Copy link
Copy Markdown

Summary

Comprehensive modernization of Her for current Ruby/Rails ecosystem, with 30+ bug fixes from open issues.

  • Ruby >= 3.1, ActiveModel >= 6.1, Faraday >= 2.x
  • GitHub Actions CI replacing Travis (Ruby 3.1/3.2/3.3 × ActiveModel 6.1–8.0)
  • 400 tests, all green across the full matrix
  • Dropped multi_json in favor of stdlib JSON
  • Removed long-deprecated methods (data, assign_data, update_attributes, etc.)
  • Replaced RuboCop 0.54 with Qlty

Bug fixes

Features

Cleanup

  • Fixed typos throughout (embeded_paramsembedded_params, etc.) (Fix typos #551)
  • Replaced (class << self; self; end) with singleton_class
  • Removed January 2020 TODO and deprecated options hash for custom requests
  • Extracted Association#foreign_key helper

Test plan

  • bundle exec rake spec passes (400 examples, 0 failures)
  • Matrix passes: ActiveModel 6.1, 7.0, 7.1, 7.2, 8.0
  • Matrix passes: Ruby 3.1, 3.2, 3.3
  • GitHub Actions CI workflow runs on push/PR

voltechs added 30 commits March 30, 2026 21:30
- Require Ruby >= 3.1, ActiveModel >= 6.1, Faraday >= 2.0
- Replace Travis CI with GitHub Actions (Ruby 3.1/3.2/3.3 × AM 6.1–8.0)
- Remove multi_json dependency in favor of stdlib JSON
- Drop ancient dev deps (json ~> 1.8, rake ~> 10.0)
- Clean up Gemfile Ruby 1.9.3 conditional logic
- AssociationProxy: inherit from BasicObject instead of removed
  ActiveSupport::ProxyObject/BasicObject, add explicit proxy
  methods for ==, class, inspect, nil?, etc.
- ParseJSON middleware: Faraday::Response::Middleware → Faraday::Middleware
- Replace MultiJson.load/dump with stdlib JSON.parse/generate
Ref: remi#551

- functionnality → functionality
- corrsponding → corresponding
- substracts → subtracts
- embeded_params → embedded_params
- immediatly → immediately
Ref: remi#383
Closes: remi#382

Override ActiveModel::Dirty's attribute_changed_in_place? to use
Her's change tracking, enabling validates_numericality_of and other
validators that depend on in-place change detection.
Ref: remi#372
Closes: remi#371

Prevents assign_attributes(class: 'value') from defining a getter
that overrides Object#class on the model instance.
Ref: remi#540

Route has_many fetches through get_collection instead of get, and
guard instantiate_collection against blank response data. This
prevents crashes when an API returns 204 No Content for an
association endpoint.
Ref: remi#292

When fetching has_many associations, set the inverse parent through
the association's cached_result rather than injecting it directly
into the child's attributes hash. This prevents the parent object
from appearing as a serializable attribute on child records.
Ref: remi#473

Drop the memoized setter_method_names Set in favor of a
respond_to_without_missing? method that checks for real setter
methods without triggering method_missing. This avoids stale cache
issues and simplifies the setter detection logic.
Ref: remi#412

Short-circuit to an empty Her::Collection when any query parameter
is an empty array, avoiding a pointless HTTP request that would
return all records instead of none.
Ref: remi#539

The infinite loop reported in remi#539 is already prevented by caching
the inverse association through the proxy (commit 74bc996) rather
than injecting it into the attributes hash. These tests confirm the
fix holds for to_params with bidirectional associations.
Ref: remi#360
Closes: remi#353

Resolve JSON API relationships against included resources in the
JsonApiParser middleware. When a response contains both
`relationships` and `included` keys, the parser matches resource
linkages by type+id and merges the resolved data into each
resource's attributes, enabling has_many/belongs_to associations
to be populated without additional HTTP requests.
Closes: remi#355

Add initialize_copy to deep-dup the attributes hash, metadata, and
response_errors so that modifying a dup'd or clone'd model does not
mutate the original.
Ref: remi#315, remi#187

AssociationProxy delegates nil? and blank? correctly for nil
associations. Ruby truthiness (if proxy) cannot be overridden by
the proxy pattern — use .nil? or .blank? instead.
Closes: remi#494

Replace the id.nil? check in new?/persisted? with an explicit
@_her_new_record flag. This correctly handles models initialized
with a pre-assigned id (e.g. User.new(id: 5)) which should still
be considered new/unpersisted until saved.
Closes: remi#176

When saving a new record, use the collection path instead of the
resource path so that Model.create(id: '1234') correctly POSTs to
/models rather than /models/1234. This was made possible by the
explicit new record tracking from the previous commit.
Closes: remi#288

has_many#build now respects the foreign_key option instead of always
inferring it from the parent's class name. This fixes cases like
has_many :articles, foreign_key: :creator_id where the foreign key
differs from convention.
Closes: remi#145

When API response data contains a key named "attributes", it
collides with ActiveModel's internal attributes method, causing a
crash during initialization. Filter reserved keys before calling
respond_to_without_missing? to prevent the collision.
Centralizes the foreign key lookup (respecting :foreign_key option
with convention fallback) in Association#foreign_key, replacing
duplicated inline logic in has_many and has_one build methods.
Closes: remi#396

Symbolize keys on parsed_data when it enters the model layer so
that custom middleware or caching layers (e.g. faraday-http-cache)
that return string-keyed hashes work correctly.
Closes: remi#517

Remove duplicate run_callbacks :find in Relation#find since
instantiate_record already triggers the :find callback. This was
causing after_find hooks to execute twice per find.
Closes: remi#406

Use thread-local tracking in inspect to detect and truncate cyclic
references with "..." instead of infinitely recursing through
bidirectional associations.
Closes: remi#212, remi#469

In active_model_serializers format, only treat the response as a
collection when the parsed data contains the pluralized root key.
This prevents has_one and belongs_to associations from being
incorrectly parsed as collections, which returned [] instead of
the expected single resource.
Closes: remi#286

When creating a resource through a has_many association that already
has fetched data, append to the cached collection rather than only
checking @parent.attributes. This ensures the new resource is
visible immediately without a refetch.
Closes: remi#272

When send_only_modified_attributes is enabled and to_params is
called without changes (e.g. custom_post actions), don't strip all
attributes. Only apply the filter when there are actual tracked
changes to slice by.
- Remove DeprecatedMethods module (data/data=/assign_data/has_data?/
  get_data/has_relationship?/get_relationship/relationships/her_api)
  — deprecated for 10+ years with no removal timeline
- Remove deprecated options Hash support for custom_get/custom_post
  etc. (TODO said "remove after January 2020")
- Replace (class << self; self; end) with singleton_class
- Remove stale TODO comment from has_many#build
Closes: remi#287

has_many#build now appends the new resource to the cached collection
so it's immediately visible via the association. If create fails
(save returns false), the resource is removed from the collection.
Closes: remi#278

Override select, reject, map, and other Array methods on
Her::Collection to return Collection instances that retain metadata
and errors, instead of plain Arrays that lose this context.
Closes: remi#380, remi#331

extract_array now checks parse_root_in_json? in addition to the
AMS and JSON API format flags, so APIs that wrap collections in a
root key (e.g. {"users": [...]}) work correctly with just
parse_root_in_json true — no need to also set AMS format.
- Remove dead badges (Travis, Gemnasium, CodeClimate, Gitter)
- Update Faraday examples for 2.x (adapter syntax, middleware class)
- Replace MultiJson with JSON in custom parser example
- Update before_filter to before_action
- Update BasicAuthentication to Faraday 2.x request syntax
- Remove deprecated update_attributes from examples
- Remove dead links (her-rb.org, rdoc.info, her-example repo)
- Remove stale project list, history, and contributors sections
- Update copyright years and add contributor copyright
Remove RuboCop 0.54 config (2018) and todo file in favor of Qlty
with actionlint, markdownlint, trivy, trufflehog, and yamllint.
@voltechs
Copy link
Copy Markdown
Author

Hey @remi, hope you're well. I took the liberty of addressing the majority of the issues. I see the project has been mostly stagnant for over five years now...

Let me know if you want to discuss ownership/maintenance transfer if you're burnt out or no longer interested in the project. I've personally been a huge fan of it for about a decade now.

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