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?

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

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] }

  1. 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 = {}, &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

    include Accessors

  1. 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(‘app/views’, {:formats => :abc})

  1. 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

  1. 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

  1. 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 “/
if values == [:js]
values << :html
@html_fallback_for_js = true
end
end
super(values)
end

  1. 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.

Also,

p Dog.ancestors

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