formats in action view base

When you invoke ActionView::Base, it in turn invokes a LookupContext object. The LookupContext class object has some class level macros that builds some instance methods corresponding to a format:

    register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } def self.register_detail(name, options = {}, &block)       self.registered_details << name       initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }

      Accessors.send :define_method, :"default_#{name}", &block       Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1         def #{name}           @details.fetch(:#{name}, )         end

        def #{name}=(value)           value = value.present? ? Array(value) : default_#{name}           _set_detail(:#{name}, value) if value != @details[:#{name}]         end

        remove_possible_method :initialize_details         def initialize_details(details)           #{initialize.join("\n")}         end       METHOD     end

So these methods are included as instance methods via the Accessors module. Later the LookupContext class override the formats method with its own definition:

    def formats=(values)       if values         values.concat(default_formats) if values.delete "*/*"         if values == [:js]           values << :html           @html_fallback_for_js = true         end       end       super(values)     end

1) So what was the purpose of adding a formats method to the Accessors module and then include it into LookupContext, if it will always be overriden by LookupContext's own implementation?

2) What is meant by expand ["*/*"] values in the comment "# Override formats= to expand ["*/*"] values " which is directly above the formats implementation on LookupContext?

Let's say I call LookupContext with a details hash consisitng of a format. Here's the sequence as I understand it:

1) First lookup_context.rb is loaded, which means the macros are called immediately. The register_detail macro, accepts a symbol and block. We pass it a symbol :formats and a block whose return value is an array of symbols representing a variety of formats:

register\_detail\(:formats\) \{ ActionView::Base\.default\_formats ||

[:html, :text, :js, :css, :xml, :json] }

2) We declare a getter/setter registered_details at module level (which will make it available at class and module level (e.g. LookupContext.registered_details or Accessors.class.registered_details - where class refers to Module)). We initialize it as an array in LookupContext class context. When register_detail class method is invoked, we append the :formats symbol to that array. We overwrite the assignment of initialize by iterating through that class array and indexing strings that we will later evaluate. The strings substitute n for :formats. In other words, when the string is evaluated in ruby, it will check if a user passed a :formats key in the details hash passed to the initialize method, and if not, then it will default to the implementation of default_formats. Then the result is assigned to the @details instance variable hash. That default implementation is defined using :define_method and sending the message to the Accessors module, which is included in LookupContext, and, thus, default_formats is available to LookupContext objects as public instance methods. We declare a setter/getter formats method via the Accessors module. And then we declare an initialize_details method, which will be evaluated when the constructor is invoked during the instantiation of LookupContext. At this point, we just did some dynamic definitions via declarative macro-level class methods.

mattr\_accessor :registered\_details
self\.registered\_details = \[\]

def self\.register\_detail\(name, options = \{\}, &amp;block\)
  self\.registered\_details &lt;&lt; name
  initialize = registered\_details\.map \{ |n| &quot;@details\[:\#\{n\}\] =

details[:#{n}] || default_#{n}" }

  Accessors\.send :define\_method, :&quot;default\_\#\{name\}&quot;, &amp;block
  Accessors\.module\_eval &lt;&lt;\-METHOD, \_\_FILE\_\_, \_\_LINE\_\_ \+ 1
    def \#\{name\}
      @details\.fetch\(:\#\{name\}, \[\]\)
    end

    def \#\{name\}=\(value\)
      value = value\.present? ? Array\(value\) : default\_\#\{name\}
      \_set\_detail\(:\#\{name\}, value\) if value \!= @details\[:\#\{name\}\]
    end

    remove\_possible\_method :initialize\_details
    def initialize\_details\(details\)
      \#\{initialize\.join\(&quot;\\n&quot;\)\}
    end
  METHOD
end

include Accessors

3) Now that our LookupContext class has a template we can use, we instantiate a LookupContext object, passing in a hash with a formats key:

LookupContext\.new\(&#39;app/views&#39;, \{:formats =&gt; :abc\}\)

4) The constructor method is called. We pass in our hash as a local variable called details. We initialize our LookupContext @details instance variable to be an empty hash. Hence, now we have a @details instance variable available to the instance methods of the LookupContext object instances. We then call initialize_details method, passing in our details hash, which comprises of the hash we passed as the second argument during initialization of the object. Remember our initialize local variable is an array of strings. In this case, it's an array of three strings, since we call register_detail three times, passing each time a symbol and block. Now the array of strings get evaluated and we add a nonbreaking space so the ruby interpreter doesn't raise any kind of syntax exception during the evaluation. We check if the details parameter contains a :formats key and in our case, since we passed a :formats hash to the constructor it does, so we assign the return value (:abc) to the :formats key of the instance variable @details hash. Note if we did not pass an argument to the constructor, then default_formats would be invoked, and remember we used define_method on Accessors module to build the defaults_formats method and assign it a default block, which returns an array of defaults for formats. So now we have our @details instance variable that we initialized with the constructor, we then assigned it a key/value pair value (e.g. :formats => :abc). So by invoking the getter formats method we dynamically created, @details.fetch(:formats) will return :abc.

class LookupContext
def initialize\(view\_paths, details = \{\}, prefixes = \[\]\)
  @details, @details\_key = \{\}, nil
  @skip\_default\_locale = false
  @cache = true
  @prefixes = prefixes
  @rendered\_format = nil

  self\.view\_paths = view\_paths
  initialize\_details\(details\)
end

5) Then we decide to set a format, for example, when we instantiate ActionView::Base, passing a format as the fourth parameter to it:

lookup\_context\.formats  = formats if formats

6) We may think that this would in turn invoke the formats method that we included from our Accessors module to the LookupContext class. However, the way the call chain works is that the class context is looked up prior to the modules included into that class context. In the class context, we actually do define the formats method, so this gets invoked instead of the one defined in the Accessors module. Let's say the format value we passed it was the symbol :js, representing the ubiquitous javascript scripting language. I think "*/*" refers to all possible formats (please help, I'm not sure about that...). So if values array contains the string "*/*" in its index, then we merge with it the returned array of default_formats, which is [:html, :text, :js, :css, :xml, :json]. We also check if the parameter is [:js]. If so, we add :html as fallback to :js. We also set the @html_fallback_for_js instance variable to true, just in case we ever need to check if we already set the html as a default to javascript.

def formats=\(values\)
  if values
    values\.concat\(default\_formats\) if values\.delete &quot;\*/\*&quot;
    if values == \[:js\]
      values &lt;&lt; :html
      @html\_fallback\_for\_js = true
    end
  end
  super\(values\)
end

7) This is where I am really unsure. We then call the same setter method of the super class passing in our values array. The LookupContext class does not inherit from any other class. However, the Accessors module is included in it, which does define a formats setter method so I think this is what gets called next. If this is the case, then what happens next is the setter method we dynamically created when the class was loaded gets called (which we included from the Accessors module), and we encapsulate the value into an array. We check if @details instance variable :formats key already has that value, and if not then we make a copy of @details and set the value to the :formats key. (Another question why do we make a copy?)

protected

  def \_set\_detail\(key, value\)
    @details = @details\.dup if @details\_key
    @details\_key = nil
    @details\[key\] = value
  end

This is my assessment of the sequence of actions. My main question is if it's true that a call to super in an instance method will call the method in an included module of the same name, as shown in the example provided above, with detailed descriptions.

class Animal   def greet     puts 'hi'   end end

class Dog < Animal   def greet     super   end end

d = Dog.new d.greet

--output:-- hi

module Animal   def greet     puts 'hi'   end end

class Dog   include Animal

  def greet     super   end end

d = Dog.new d.greet

--output:-- hi

Modules that are included are inserted into the inheritance chain. Where in the chain?

module Cat   def greet     puts 'meow'   end end

class Animal   def greet     puts 'hi'   end end

class Dog < Animal   include Cat

  def greet     super   end end

--output:-- hi

You tell me?

7stud -- wrote in post #1076203:

Modules that are included are inserted into the inheritance chain. Where in the chain?

module Cat   def greet     puts 'meow'   end end

class Animal   def greet     puts 'hi'   end end

class Dog < Animal   include Cat

  def greet     super   end end

--output:-- hi

Whoops. That should be:

--output:-- meow

Also,

p Dog.ancestors

--output:-- [Dog, Cat, Animal, Object, Kernel, BasicObject]