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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Search Discussions

  • John Merlino at Sep 16, 2012 at 2:59 am
    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 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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • 7stud -- at Sep 16, 2012 at 4:13 am
    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?

    --
    Posted via http://www.ruby-forum.com/.

    --
    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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • 7stud -- at Sep 16, 2012 at 5:26 am

    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

    --
    Posted via http://www.ruby-forum.com/.

    --
    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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.
  • 7stud -- at Sep 16, 2012 at 3:48 pm
    Also,


    p Dog.ancestors

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

    --
    Posted via http://www.ruby-forum.com/.

    --
    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+unsubscribe@googlegroups.com.
    For more options, visit https://groups.google.com/groups/opt_out.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouprubyonrails-talk @
categoriesrubyonrails
postedSep 15, '12 at 6:25p
activeSep 16, '12 at 3:48p
posts5
users2
websiterubyonrails.org
irc#RubyOnRails

2 users in discussion

7stud --: 3 posts John Merlino: 2 posts

People

Translate

site design / logo © 2022 Grokbase