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


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('app/views', {:formats => :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 "*/*"
        if values == [:js]
          values << :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.









On Sep 15, 2:25 pm, John Merlino <stoici...@aol.com> wrote:
> 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?

-- 
You received this message because you are subscribed to the Google Groups "Ruby 
on Rails: Talk" group.
To post to this group, send email to rubyonrails-talk@googlegroups.com.
To unsubscribe from this group, send email to 
rubyonrails-talk+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to