简体   繁体   中英

Ruby: Singleton method of an instance variable inside a class

I am taking ruby-kickstart (Josh Cheek) challenges and even though I managed to pass all the test there is one thing I cannot understand.

In the exercise you are being asked to override the << method for an instance variable. Specifically here is what you have to do:

In Ruby on Rails, when a person goes to a URL on your site, your application looks at the url, and maps it to a controller method to handle the request

My boss wanted to be able to specify what CSS class the body of the HTML output should have, based on which controller method was being accessed. It fell to me to provide a method, which, when invoked, would return a String that could handle the request There are a few nuances, though. The String you return must be retained during the object's entire life The method must be able to be called multiple times The String you return should know how to add new classes: each class is separated by a space The only method you need to worry about being invoked is the << method. (plus a few other irrelevant things) EXAMPLE:

controller = ApplicationController.new   
controller.body_class                  
#=> ""    
controller.body_class << 'admin'    
controller.body_class     
#=> "admin"   
controller.body_class << 'category'    
controller.body_class        
#=> "admin category"    
controller.body_class << 'page' << 'order'    
controller.body_class                  
#=> "admin category page order"

My working solution:

class ApplicationController 

  def initialize
    @body_class = ""
  end

  def body_class
    def @body_class.<<(str)
      puts "self is:"+self
      return self if self=~/\b#{Regexp.escape(str)}\b/
      if self==""
        self.concat(str)
      else
        self.concat(" ")
        self.concat(str)
      end
    end

    return @body_class
  end
end

Everything works perfectly fine. But an earlier solution I gave (and it didn't work) was the following

class ApplicationController 
  attr_accessor :body_class
  def initialize
    @body_class = ""
  end

  def @body_class.<<(str)
    puts "self is:"+self
    return self if self=~/\b#{Regexp.escape(str)}\b/


    if self==""
      self.concat(str)
    else
      self.concat(" ")
      self.concat(str)
    end

  end

  def body_class                #Even this for the way I work it out on my mind is unnecessary 
    return @body_class
  end



end

When someone runs on the second not-working sollution the following

obj = ApplicationController.new
obj.body_class << "Hi"

The << method is not being overridden by the object's singleton. I do not understand why I have to wrap the singleton methods inside the body_class method. (Mind that in the second solution there is an attr_accessor.

Could anyone enlighten me please! Thanks!

I do not understand why I have to wrap the singleton methods inside the body_class method.

To access the correct instance variable. When you attempt to override it outside of method, you're in the class context. This code runs at class load time. No instances have been created yet. (And even if instances did exist at this point, @body_class instance variable belongs to class ApplicationController , which is itself an instance of class Class ).

You need instance context.


Also I am pretty sure that this problem can be solved without any method patching voodoo. Just provide a different object (conforming to the same API. This is called "duck typing").

class ApplicationController

  def body_class
    @body_class ||= CompositeClass.new
  end

  class CompositeClass
    def initialize
      @classes = []
    end

    def <<(new_class)
      @classes << new_class
    end

    # I imagine, this is what is ultimately called on ApplicationController#body_class,
    # when it's time to render the class in the html.
    def to_s
      @classes.join(' ')
    end
  end
end

Didn't test this code, naturally.

BTW, the proper way to do it is to explicitly extend the instance variable:

class A
  attr_reader :body_class
  def initialize
    @body_class = "".extend(Module.new do
      def <<(other)
        return self if self[/\b#{Regexp.escape(other)}\b/]
        concat(' ') unless empty?
        concat(other)
      end
    end)    
  end  
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM