diff options
Diffstat (limited to 'doc/syntax/refinements.rdoc')
| -rw-r--r-- | doc/syntax/refinements.rdoc | 174 |
1 files changed, 106 insertions, 68 deletions
diff --git a/doc/syntax/refinements.rdoc b/doc/syntax/refinements.rdoc index 3ba03f6d58..80595eb445 100644 --- a/doc/syntax/refinements.rdoc +++ b/doc/syntax/refinements.rdoc @@ -7,7 +7,7 @@ changes. This can cause unintended side-effects or breakage of programs. Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a -class locally. +class locally. Refinements can modify both classes and modules. Here is a basic refinement: @@ -26,8 +26,7 @@ Here is a basic refinement: end First, a class +C+ is defined. Next a refinement for +C+ is created using -Module#refine. Refinements only modify classes, not modules so the argument -must be a class. +Module#refine. Module#refine creates an anonymous module that contains the changes or refinements to the class (+C+ in the example). +self+ in the refine block is @@ -43,15 +42,21 @@ Activate the refinement with #using: == Scope -You may only activate refinements at top-level, not inside any class, module -or method scope. You may activate refinements in a string passed to -Kernel#eval that is evaluated at top-level. Refinements are active until the -end of the file or the end of the eval string, respectively. +You may activate refinements at top-level, and inside classes and modules. +You may not activate refinements in method scope. Refinements are activated +until the end of the current class or module definition, or until the end of +the current file if used at the top-level. -Refinements are lexical in scope. When control is transferred outside the -scope the refinement is deactivated. This means that if you require or load a -file or call a method that is defined outside the current scope the refinement -will be deactivated: +You may activate refinements in a string passed to Kernel#eval. Refinements +are active until the end of the eval string. + +Refinements are lexical in scope. Refinements are only active within a scope +after the call to +using+. Any code before the +using+ statement will not have the +refinement activated. + +When control is transferred outside the scope, the refinement is deactivated. +This means that if you require or load a file or call a method that is defined +outside the current scope the refinement will be deactivated: class C end @@ -74,7 +79,7 @@ will be deactivated: x.foo # prints "C#foo in M" call_foo(x) #=> raises NoMethodError -If a method is defined in a scope where a refinement is active the refinement +If a method is defined in a scope where a refinement is active, the refinement will be active when the method is called. This example spans multiple files: c.rb: @@ -136,6 +141,26 @@ In a file: end # activated here +In a class: + + # not activated here + class Foo + # not activated here + def foo + # not activated here + end + using M + # activated here + def bar + # activated here + end + # activated here + end + # not activated here + +Note that the refinements in +M+ are *not* activated automatically if the class ++Foo+ is reopened later. + In eval: # not activated here @@ -154,9 +179,9 @@ When not evaluated: end # not activated here -When defining multiple refinements in the same module, inside a refine block -all refinements from the same module are active when a refined method is -called: +When defining multiple refinements in the same module inside multiple +refine+ blocks, +all refinements from the same module are active when a refined method +(any of the +to_json+ methods from the example below) is called: module ToJSON refine Integer do @@ -182,80 +207,93 @@ called: p [{1=>2}, {3=>4}].to_json # prints "[{\"1\":2},{\"3\":4}]" -You may also activate refinements in a class or module definition, in which -case the refinements are activated from the point where using is called to -the end of the class or module definition: - # not activated here - class Foo - # not activated here - using M - # activated here - def foo - # activated here - end - # activated here - end - # not activated here +== Method Lookup -Note that the refinements in M are not activated automatically even if the class -Foo is reopened later. +Method lookup in Ruby is based on the ancestor chain. You can see the +ancestor chain for any object in Ruby by doing: -== Method Lookup + object.singleton_class.ancestors + # or, if the object does not support a singleton class: + object.class.ancestors + +The ancestor chain is constructed as follows: -When looking up a method for an instance of class +C+ Ruby checks: +* Subclasses are before superclasses in the ancestor chain +* Prepended modules are before the class they prepend in the ancestor + chain, in reverse order in which they were prepended. +* Included modules are after the class they are included in in the + ancestor chain, in reverse order in which they were included. -* If refinements are active for +C+, in the reverse order they were activated: - * The prepended modules from the refinement for +C+ - * The refinement for +C+ - * The included modules from the refinement for +C+ -* The prepended modules of +C+ -* +C+ -* The included modules of +C+ +When looking up a method for an object, Ruby goes through each ancestor: -If no method was found at any point this repeats with the superclass of +C+. +* If the class/module has been refined, Ruby will consider the refinements + activated at the point the method was called, in reverse order of + activation. +* Otherwise, Ruby will check the methods of the class/module itself. -Note that methods in a subclass have priority over refinements in a -superclass. For example, if the method <code>/</code> is defined in a -refinement for Integer <code>1 / 2</code> invokes the original Fixnum#/ -because Fixnum is a subclass of Integer and is searched before the refinements -for the superclass Integer. +If no method was found at either point this repeats with the next +ancestor. -If a method +foo+ is defined on Integer in a refinement, <code>1.foo</code> -invokes that method since +foo+ does not exist on Fixnum. +Note that methods in a earlier ancestor have priority over refinements in a +later ancestor. For example, if the method <code>/</code> is defined in a +refinement for Numeric <code>1 / 2</code> invokes the original Integer#/ +because Integer is a comes before Numeric in the ancestor chain. However, +if a method +foo+ is defined on Numeric in a refinement, <code>1.foo</code> +invokes that method since +foo+ does not exist on Integer. == +super+ -When +super+ is invoked method lookup checks: +When +super+ is invoked, method lookup starts: -* The included modules of the current class. Note that the current class may - be a refinement. -* If the current class is a refinement, the method lookup proceeds as in the - Method Lookup section above. -* If the current class has a direct superclass, the method proceeds as in the - Method Lookup section above using the superclass. +* If the method is in a refinement, at the refined class or module +* Otherwise, at the next ancestor -Note that +super+ in a method of a refinement invokes the method in the -refined class even if there is another refinement which has been activated in -the same context. +Method lookup then proceeds as described in the Method Lookup section +above. -== Indirect Method Calls +Refinements activated at the call site of a refinement method do not +affect +super+ inside that method. Only refinements activated at the +point +super+ was called affect method lookup for that +super+ call. +You cannot use refinements to insert into the middle of a method +lookup chain, only to insert at the start of a method lookup chain, +unless you control the +super+ call sites. -When using indirect method access such as Kernel#send, Kernel#method or -Kernel#respond_to? refinements are not honored for the caller context during -method lookup. +Note that if you refine a module, the refinement method can call +super+ +to call the method in the module, but the method in the module cannot +call +super+ to continue the method lookup process to further ancestors. + +== Methods Introspection + +When using introspection methods such as Kernel#method or Kernel#methods refinements are not honored. This behavior may be changed in the future. -== Refinements and module inclusion +== Refinement inheritance by Module#include + +When a module X is included into a module Y, Y inherits refinements from X. + +For example, C inherits refinements from A and B in the following code: + + module A + refine X do ... end + refine Y do ... end + end + module B + refine Z do ... end + end + module C + include A + include B + end -Refinements are inherited by module inclusion. That is, using activates all -refinements in the ancestors of the specified module. Refinements in a -descendant have priority over refinements in an ancestor. + using C + # Refinements in A and B are activated here. + +Refinements in descendants have higher precedence than those of ancestors. == Further Reading -See http://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec for the +See https://github.com/ruby/ruby/wiki/Refinements-Spec for the current specification for implementing refinements. The specification also contains more details. - |
