Fix nested belongs_to scope when resource uses an `as:` alias
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.