-
Notifications
You must be signed in to change notification settings - Fork 21.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Raise an error if scope
or enum
is about to add a conflicting method to the class
#13450
Conversation
@chancancode did you consider 813c8c0 ? What can be the sign of a mistake is a feature in other situations. As an illustration: module Publishable
extend ActiveSupport::Concern
included do
enum status: [:proposed, :written, :published]
end
def published!
super
"do publish work..."
end
end |
@senny we could do some metaprogramming to avoid that scenario using class ActiveRecord; end
class ActiveRecord::Base
class << self
def singleton_method_added(method_name)
puts "We could check '#{method_name}' as it is added to #{self}"
super
end
end
end
class Barz < ActiveRecord::Base
def self.any_method_you_want
end
end
# => "We could check 'any_method_you_want' as it is added to Barz" Though this only works if the |
@senny, good catch, will update to make this order independent |
@senny updated! |
Tangent: It would have been easier if there was an accessor method/object. Model.enum.shipped? Instead of the method going straight on the class: Model.shipped? — On Tue, Dec 24, 2013 at 6:22 AM, Godfrey Chan notifications@github.com
|
Seems good to me |
@@ -139,6 +139,12 @@ def scope_attributes? # :nodoc: | |||
# Article.published.featured.latest_article | |||
# Article.featured.titles | |||
def scope(name, body, &block) | |||
if respond_to?(name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this handle private methods too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed via in chancancode@f350402, thanks 😁
Update: some open points...
|
@rafaelfranca @senny Sorry for the hold up, I re-implemented this just now. The 'dangerous' logic is basically the same as attribute method (defined on This catches the most "dangerous" scenarios (conflict with AR internals) but still allows the programmer to do some potentially dangerous stuff such as re-defining a scope/enum in a subclass or override an existing user-defined class method with a scope. I also thought about extracting some of these into Active Support, but the logic we use here is actually quite specific to AR, so I don't know how useful that would be. If you still think that's helpful I can give it a shot. |
The extraction would look roughly like this: class Module
def method_defined_within?(name, sup = Module)
if method_defined?(name) || private_method_defined?(name)
if sup.method_defined?(name) || sup.private_method_defined?(name)
instance_method(name).owner != sup.instance_method(name).owner
else
true
end
else
false
end
end
def class_method_defined_within?(name, sup = Module)
if respond_to?(name, true)
if sup.respond_to?(name, true)
method(name).owner != sup.method(name).owner
else
true
end
else
false
end
end
private
def safe_define_method(name, base = self, sup = Module, &block)
unless base.method_defined_within?(name, sup)
define_method(name, &block)
end
end
def safe_define_method!(name, base = self, sup = Module, &block)
safe_define_method(name, base, sup, &block) ||
raise ArgumentError, "Attempted to redefine the method #{name} " \
"when it is already defined by #{base.name}."
end
def safe_define_class_method(name, base = self, sup = Module, &block)
unless base.class_method_defined_within?(name, sup)
base.singleton_class.send(:define_method, name, &block)
end
end
def safe_define_class_method!(name, base = self, sup = Module, &block)
safe_define_class_method(name, base, sup, block) ||
raise ArgumentError, "Attempted to redefine the class method #{name} " \
"when it is already defined by #{base.name}."
end
end
class Class
def method_defined_within?(name, sup = self.superclass)
super(name, sup)
end
def class_method_defined_within?(name, sup = self.superclass)
super(name, sup)
end
private
def safe_define_method(name, base = self, sup = base.superclass, &block)
super(name, base, sup, &block)
end
def safe_define_method!(name, base = self, sup = base.superclass, &block)
super(name, base, sup, &block)
end
def safe_define_class_method(name, base = self, sup = base.superclass, &block)
super(name, base, sup, &block)
end
def safe_define_class_method!(name, base = self, sup = base.superclass, &block)
super(name, base, sup, &block)
end
end |
Before: >> ActiveRecord::Base.respond_to?(:find_by_something) NoMethodError: undefined method `abstract_class?' for Object:Class After: >> ActiveRecord::Base.respond_to?(:find_by_something) => false
Similar to dangerous attribute methods, a scope name conflict is dangerous if it conflicts with an existing class method defined within `ActiveRecord::Base` but not its ancestors. See also rails#13389. *Godfrey Chan*, *Philippe Creux*
Dangerous name conflicts includes instance or class method conflicts with methods defined within `ActiveRecord::Base` but not its ancestors, as well as conflicts with methods generated by other enums on the same class. Fixes rails#13389.
Raise an error if `scope` or `enum` is about to add a conflicting method to the class Fixed #13389
With this change, tables that use uuid as id and redefine :first and :last are broken now. |
Redefine how? |
Like this |
why would you do this? But anyway, you still can have the same behavior. def self.first
order('created_at').first
end
def self.last
order('created_at DESC').first
end |
In general you should not use scopes to return AR objects, scopes should return other relations. In any case you can still use class methods for that, as @rafaelfranca just pointed out. |
Thanks you @rafaelfranca |
…ls 4.1 forbids that name The "distinct" method is available since Rails 4.0.0 [1] (and was available as "uniq" since Rails 3.2.0 [2]), so we no longer need to define our own "distinct" scope. And as of Rails 4.1 it is forbidden to define a scope with a conflicting name[3], so we have to remove our "distinct" scope. [1] rails/rails@a1bb6c8 [2] rails/rails@562583c [3] rails/rails#13450 Co-authored-by: Tom Levy <tomlevy93@gmail.com>
See #13389 for details. I left out case 4, because that does not particularly concerns me, and I don't think checking
klass.new.respond_to?(...)
is a good idea.cc @senny @dhh