Fix nested belongs_to scope when resource uses an `as:` alias

activeadmin/activeadmin

ActiveAdmin now derives the parent association name from the model class instead of the as: alias, preventing NoMethodError on nested routes such as /admin/users/1/stories.

背景

When a resource is registered with an as: alias that differs from the actual model name, the nested‑route action raised a NoMethodError because the parent association was looked up using the alias rather than the real association name. The example of a User having many posts but registering Post as "Story" demonstrated this failure (undefined method 'stories' for #<User>). This behavior originated from Inherited Resources, which defaulted method_for_association_chain to resource_collection_name derived from controller_name (the alias). The bug surfaced only when the alias did not coincide with the association name.

The root cause was therefore a mismatch between the parent's has_many association (posts) and the child's controller name (stories). Without a fix, developers could not use as: aliases for nested resources, limiting ActiveAdmin's flexibility.

技術的な変更

A new method_for_association_chain implementation was added to app/controllers/active_admin/resource_controller/data_access.rb. It returns the pluralized element name of the resource's model class when the resource belongs to a parent, ensuring the correct association name is used regardless of the alias.

@@
   def scoped_collection
         end_of_association_chain
       end

+      # Derive the parent's `has_many` association name from the
+      # resource's model class so that resources registered with an
+      # `as:` alias still scope through the correct collection.
+      def method_for_association_chain
+        return super unless active_admin_config&.belongs_to?
+
+        active_admin_config.resource_class.model_name.element.pluralize.to_sym
+      end
+
       # Retrieve, memoize and authorize a resource based on params[:id]. The
       # actual work of finding the resource is done in #find_resource.
       #

The method checks active_admin_config.belongs_to? and, when true, builds the association symbol from resource_class.model_name.element.pluralize. This overrides the default behaviour without affecting non‑nested resources.

Supporting test changes verify the new logic. In spec/unit/belongs_to_spec.rb a scenario registers Post as "Story" and asserts that method_for_association_chain returns :posts.

+  describe "method_for_association_chain (with `as:` alias)" do
+    around do |example|
+      with_resources_during(example) do
+        ActiveAdmin.register User
+        ActiveAdmin.register(Post, as: "Story") { belongs_to :user, optional: true }
+      end
+    end
+
+    let(:post_config) { ActiveAdmin.application.namespaces[:admin].resources["Story"] }
+    let(:controller) { post_config.controller.new }
+
+    it "derives the parent's association name from the resource's model class" do
+      expect(controller.send(:method_for_association_chain)).to eq(:posts)
+    end
+  end

Feature tests were also extended. The new scenario in features/belongs_to.feature navigates to /admin/users/:id/stories and confirms the page renders correctly. The helper admin_user_stories_path was added to features/support/paths.rb to support the route.

@@
   when /^the last author's posts$/
     admin_user_posts_path(User.last)

+  when /^the last author's stories$/
+    admin_user_stories_path(User.last)
+
   when /^the last author's last post page$/
     admin_user_post_path(User.last, Post.where(author_id: User.last.id).last)

These additions collectively ensure that alias‑based resources participate in nested routing without manual overrides.

設計判断

Instead of introducing a new configuration key (e.g., belongs_to_association_name), the PR chooses to override the existing method_for_association_chain only for the specific case where a resource belongs to a parent. This keeps the public API unchanged and preserves backward compatibility for callers that rely on the default method.

The overridden method delegates to super for all non‑nested resources, limiting the scope of the change. By deriving the association name from resource_class.model_name, the implementation works for both conventional and unconventional association names, provided the developer explicitly defines the association when the default pluralization does not match.

This design respects ActiveAdmin's convention‑over‑configuration philosophy while offering a targeted fix that does not ripple through unrelated code paths.

まとめ

The added method_for_association_chain resolves the NoMethodError that occurred when a child resource used an as: alias, enabling correct scoping of nested routes. The approach modifies internal lookup logic rather than expanding the configuration surface, thereby maintaining API stability and adhering to ActiveAdmin's minimalist design.

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
116bdef6

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)、まとめの5要素がすべて揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きハイライトは正しい形式です。ただし PR リンクが `[PR #9030](URL)` となっており、要求された `[#9030](URL)` 形式と若干異なります。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門的なエンジニア向けの内容で、余計な基礎解説はなく適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成になっており、トピックセンテンスで始まり、1段落1トピック、段落長も適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは Diff の追加内容と完全に一致し、ファイル名も合致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている技術用語は PR と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明は Diff と PR の記述に基づき正確で、因果関係も論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の全主張が PR のタイトル・説明・Diff で裏付けられており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #9030 などの固有名詞は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトルと完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

外部知識(バージョンサポートやリリース日程等)は含まれておらず、PR だけで完結しています。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。