Skip to content

Rails 5 Undocumented Enumerable Methods Prioritized Over Scopes on Relations #26949

@matt-glover

Description

@matt-glover

Prior to Rails 5 applications were able to define a scope with a method name that happened to match many methods on Enumerable and treat it like any other scope. While attempting to upgrade an older application to Rails 5 I started receiving errors for calls to a legacy group_by scope that accepted a single parameter.

I think this commit introduced the change that triggered the error: b644964

Including the bug report components in case the maintainers want to treat this as a bug. I think it is also reasonable to treat this as an intended change considering that relations already had a few methods from Enumerable explicitly defined on them. If it is intended then I think it should be noted in the docs somewhere (maybe just the 4.2 -> 5.0 upgrade docs).

Steps to reproduce

  1. Create a scope on Rails 4.2 with a method name that matches a method on Enumerable but is not explicitly noted in the Rails guides.
  • Use a method like group_by rather than something like any? which has been noted in Rails docs for a while
  1. Construct and execute a query using that scope
  2. Observe that the scope is executed as expected
  3. Switch to Rails 5 and follow steps 1 and 2
begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"
  gem "rails", github: "rails/rails"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
   t.string :name
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class Post < ActiveRecord::Base
  has_many :comments

  scope :group_by, ->(value) { group(value) }
end

class Comment < ActiveRecord::Base
  belongs_to :post

  def self.group_by(value)
    group(value)
  end
end

class BugTest < Minitest::Test
  def test_group_by_with_class_method
    assert_nothing_raised(Comment.all.group_by('post_id'))
  end

  def test_group_by_with_scope
    assert_nothing_raised(Post.all.group_by('name'))
	end
end

Expected behavior

The scope is executed as expected, just as in Rails 4.2

Actual behavior

The scope is never called because the method from Enumerable is found and called first. If the arity of your scope is different from the arity of the method on Enumerable then an error is thrown.

System configuration

Rails version: 5.0.0.1 (although the bug demo script happens to reproduce the behavior against 5.1.0.alpha)

Ruby version: 2.3.1

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions