Interpreting Rails-Attribute methods

Source: Internet
Author: User
Tags extend hash regular expression sprintf

This article translated from reading Rails-attribute Methods, limited to my level, translation is inappropriate, please advise.

In our previous discussion, we've seen the property methods rails uses to track property changes (attribute methods). There are three types of attribute methods: prefix (prefix), suffix (suffix), and fixed-affix (affix). To be concise, we will focus only on Postfix attribute methods like Attribute_method_suffix, and pay particular attention to how it can help us to implement model properties like name and the corresponding generated name_changed-like methods.

If you need to follow my steps, please use Qwandry to open each of the relevant code libraries, or view the source directly from GitHub. statement (Declarations)

The attribute method is one of many examples of the use of meta-programming techniques in rails. In metaprogramming, we write code that can write code. For example, the Attribute_method_suffix suffix method is a method that defines a helper method for each attribute. In the previous discussion, Activemodel used this approach to define a _changed for each of your properties: Method ( Tip : Type QW activemodel View code on the command line).

Module Dirty
  extend Activesupport::concern
  include Activemodel::attributemethods

  included
    do Attribute_method_suffix ' _changed? ', ' _change ', ' _will_change! ', ' _was '
    # ...

Let's open the Attribute_methods.rb file in the Activemodel library and see what's going on.

def attribute_method_suffix (*suffixes)
  self.attribute_method_matchers + = suffixes.map! do |suffix|
    Attributemethodmatcher.new suffix:suffix
  End
  # ...
End

When you call the Attribute_method_suffix method, each suffix is converted to a Attributemethodmatcher object by the map! method. These objects are stored in the attribute_method_matchers. If you look at the top of the module again, you will find that Attribute_method_matchers is using the Class_attribute definition in each class that contains this module:

Module Attributemethods
  extend Activesupport::concern

  included do
    class_attribute:attribute_aliases,
                    : Attribute_method_matchers,
                    instance_writer:false
    # ...

The Class_attribute method helps you define properties on the class. You can use this in your own code like this:

Class Person
  class_attribute:d atabase
  #
... End

class Employee < person
end

person.database = sql.new (:host=> ' localhost ')
Employee.database #=> <sql:host= ' localhost ' >

There is no built-in implementation of Class_attribute in Ruby, it is the method defined in Activesupport ( hint : type QW activesupport View code on the command line). If you are curious about this, you can simply look at the ATTRIBUTE.RB

Now let's take a look at Attributemethodmatcher.

Class Attributemethodmatcher #:nodoc:
  attr_reader:p refix,: suffix,: method_missing_target

  def initialize ( options = {})
    # ...
    @prefix, @suffix = Options.fetch (:p refix, '), Options.fetch (: suffix, ')
    @regex =/^ (?: #{regexp.escape (@prefix)}) (.*) (?: #{regexp.escape (@suffix)}) $/
    @method_missing_target = "#{@prefix}attribute#{@suffix}"
    @method_name = "# {Prefix}%s#{suffix} "
  end

The prefix and suffix in the code are extracted through the Hash#fetch method. This returns the value of a corresponding key, or a default value. If the method is called without providing a default value, the Hash#fetch method throws an exception that indicates that the specified key does not exist. This is a good model for options processing, especially for Boolean data:

options = {: name = "Mortimer",: imaginary = false}
# Don ' t do this:
Options[:imaginary] | | true     #=& Gt True
# do this:
Options.fetch (: Imaginary, true) #=> false

For our attribute_method_suffix ' _changed ' example, Attributemethodmatcher will have the following instance variables:

@prefix                #=> ""
@suffix                #=> "_changed?"
@regex                 #=>/^ (?:) (.*) (?: _changed\?) $/
@method_missing_target #=> "attribute_changed?"
@method_name           #=> "%s_changed?"

You must want to know what the%s in%s_changed is for. This is a formatted string (format string). You can use the sprintf method to insert values into it, or use the abbreviation (shortcut)%:

sprintf ("%s_changed?", "name") #=> "named_changed?"
%s_changed? "%" Age "          #=>" age_changed? "

The second interesting place is the way the regular expression is created. Note the use of Regexp.escape when creating @regex variables. If the suffix is not escape, the symbol with a special meaning in the regular expression will be interpreted incorrectly (misinterpreted):

# Don ' t do this!
Regex =/^ (?: #{@prefix}) (. *) (?: #{@suffix}) $/#=>/^ (?:) (.*) (?: _changed?) $/
regex.match ("Name_changed?")                 #=> Nil
regex.match ("Name_change")                   #=> #<matchdata "Name_change" 1: "Name" >

# do this:
@ Regex =/^ (?: #{regexp.escape (@prefix)}) (. *) (?: #{regexp.escape (@suffix)}) $/
regex.match ("Name_changed?")                 #=> #<matchdata "Name_changed?" 1: "Name" >
regex.match ("Name_change")                   #=> Nil

Keep in mind that regex and method_name, which can be used to match and generate property methods, will continue to be used later.

We have now figured out how property methods are declared, but in practice, how does rails use them? Call via Method Missing (invocation with method Missing)

When we call an undefined method, rails will call the object's Method_missing method before throwing an exception. Let's see how rails uses this technique to invoke property methods:

Def method_missing (method, *args, &block)
  if respond_to_without_attributes? ( method, True)
    Super
  else
    match = Match_attribute_method? ( method.to_s)
    match attribute_missing (match, *args, &block): Super
  End
End

The first parameter passed to the Method_missing method is a method name represented by the symbol type, for example, our: name_changed?. *args is the (undefined) method that is called when all parameters are passed in, and &block is an optional block of code. Rails first checks to see if there are any other methods that can correspond to this call by calling the Respond_to_without_attributes method. If other methods can handle this call, control is transferred through the Super method. If no other method can be found to handle the current call, Activemodel passes the Match_attribute_method? method to check whether the currently called method is a property method. If it is, it will then call the Attribute_missing method.

The Match_attribute_method method takes advantage of previously declared Attributemethodmatcher objects:

def Match_attribute_method? (method_name)
  Match = Self.class.send (: Attribute_method_matcher, method_name)
  match if match && Attribute_method? ( Match.attr_name)
End

Two things have happened in this method. First, Rails finds a match (Matcher) and checks if this is really a property. To tell the truth, I am also more confused, why Match_attribute_method? Method calls are Self.class.send (: Attribute_method_matcher, Method_name), Rather than Self.attribute_method_matcher (method_name), but we can still assume that their effect is the same.

If we look at Attribute_method_matcher again, we'll see that its core code is just a scan that matches the Attributemethodmatcher instance, and what it does is compare the regular expression of the object itself with the current method name:

def attribute_method_matcher (method_name)
  # ...
  Attribute_method_matchers.detect {|method| method.match (method_name)}
  # ...
End

If rails finds a property that matches the method that is currently called, then all parameters are passed to the Attribute_missing method:

def attribute_missing (Match, *args, &block)
  __send__ (Match.target, Match.attr_name, *args, &block)
End

This method proxies the matching property name and any passed arguments or code blocks to the Match.target. Looking back at our instance variables, Match.target would be attribute_changed, and Match.attr_name would be "name". The __send__ method will call the Attribute_changed method, or any special property method you define. Meta Programming (metaprogramming)

There are many ways to distribute a call to a method (dispatch), and if this method is often called, then implementing a name_changed method will be more efficient. Rails uses the Define_attribute_methods method to automatically define such attribute methods:

def define_attribute_methods (*attr_names)
  Attr_names.flatten.each {|attr_name| Define_attribute_method (attr_ Name)}
end

def define_attribute_method (attr_name)
  Attribute_method_matchers.each do |matcher|
    Method_name = Matcher.method_name (attr_name)

    define_proxy_call True,
                      generated_attribute_methods,
                      Method_name,
                      matcher.method_missing_target,
                      attr_name.to_s
  end
End

Matcher.method_name used the formatted string we saw earlier and inserted the Attr_name. In our case, "%s_changed?" into a "name_changed?". Now we are ready to understand meta-programming in Define_proxy_call. Here's how this method has been removed from the code in some special scenarios, and you can learn more about the code yourself after reading this article.

def define_proxy_call (include_private, mod, name, send, *extra)
  defn = "Def #{name} (*args)"
  extra = (extra.map! ( &:inspect) << "*args"). Join (",")
  target = "#{send} (#{extra})"

  Mod.module_eval <<-ruby, __ file__, __line__ + 1
    #{defn}
      #{target}
    end
  RUBY
End

This defines a new method for us. Name is the name of the method that is being defined, and send is the processor (handler), and the other extra is the property name. The MoD parameter is a special module that rails generates using the Generated_attribute_methods method, which is embedded (mixin) into our class. Now let's look at the Module_eval method more. Here are three interesting things that have happened.

The first thing that Heredoc is used as a parameter is passed to a method. This is a bit difficult to understand, but it's very useful for some scenarios. For example, imagine that we have a way to embed JavaScript code in a server response (response):

Include_js (<<-JS,: Minify = True)
  $ (' #logo '). Show ();
  App.refresh ();
Js

This will put the string "$ (' #logo '). Show (); App.refresh (); " As the first parameter passed in when calling Include_js, instead: Minify = True as the second argument. This is a very useful technique when you need to generate code in Ruby. Happily, an editor such as TextMate can recognize this pattern and correctly highlight the string. Even if you don't need to generate code, Heredoc is useful for multiple lines of strings.

Now we know what <<-ruby did, but __file__ and __line__ + 1. __FILE__ returns the (relative) path of the current file, and __line__ returns the line number of the current code. Module_eval receives these parameters and determines the location of the new code definition "looks" in the file. This is especially useful for stack traces (stack traces).

Finally, let's look at some of the code that actually executes in module_eval. Can we replace the values with our name_changed?:

Mod.module_eval <<-ruby, __file__, __line__ + 1
  def name_changed? ( *args)
    attribute_changed? (" Name ", *args)
  end
RUBY

Now Name_changed is a real method, which is much less expensive than relying on the implementation of the Method_missing method. Summary (RECAP)

We found that calling the Attribute_method_suffix method saves a well-configured object for one of two meta-programming methods in rails. Regardless of whether method_missing is used or if a new method is defined through Module_eval, the invocation of the method is always passed to such attribute_changed? (attr) on such a method.

Through this relatively broad journey, we have also learned some useful tricks: you must use Hash#fetch to read the parameters from the options, especially for the Boolean type parameter. A formatted string such as "%s_changed" can be used for simple templates. You can use the Regexp.escapeescape regular expression. When you try to invoke an undefined method, Ruby calls the Method_missing method. Heredocs can be used in method parameters, or it can be used to define multiple lines of string. __file__ and __line__ point to the current file and line number. You can use Module_eval to dynamically generate code.

Stick to the source code of rails and you'll always find the treasures you didn't know. love this article.

Read the other 8 articles in "Reading rails".

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.