简体   繁体   中英

Is it possible to access the object delegated by in Ruby on Rails?

The meow method is delegated to the Cat. Is it possible to access the Person?

class Person
  attr_reader :cat

  delegate :meow, to: :cat

  def initialize
    @cat = Cat.new
  end
end

class Cat
  def meow
    # How to access Person who called meow?
  end
end


Person.new.meow

As BroiSatse already said, the example doesn't really make sense. However, you could eg pass in the person to the Cat.

class Person
  attr_reader :cat

  delegate :meow, to: :cat

  def initialize
    @cat = Cat.new(self)
  end
end

class Cat
  def initialize(owner)
    @owner = owner
  end

  def meow
    puts "#{@owner} - meow"
  end
end


Person.new.meow

Edit

Another approach could be to use SimpleDelegator instead.

class User
  def born_on
    Date.new(1989, 9, 10)
  end
end

class UserDecorator < SimpleDelegator
  def birth_year
    born_on.year
  end
end

And then you can access the receiver with super or __getobj__()

https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html

I think this is not really possible, or at least not in a way it is requested. Haing an access to an object invoking a method would create an absolutely fascinating type of coupling where receiver is coupled to the the caller.

That being said, there are some ways to get around this. As @ChristianBruckMayes already answered, your Cat class could accept extra owner . But this might not work, if the generic concept of Cat is quite owner-less or when one Cat might be an attribute of multiple Person .

One common pattern I use is a inner proxy object:

class Person
  attr_reader :cat

  delegate :meow, to: :cat

  def initialize(cat = Cat.new)
    @cat = OwnedCat.new(cat, owner: self)
  end

  class OwnedCat
    def initialize(cat, owner:)
      @cat = cat
      @owner = owner
    end

    def meow
      # access to cat and owner here
    end

    # delegate everything else to cat object
    def method_missing(name, *args, &block)
      return super unless @cat.respond_to?(name)
      @cat.send(name, *args, &block)
    end

    def respond_to_missing?(name, private = false)
      @cat.respond_to?(name, private)
    end
  end
end

Alternatively you could just pass self to the method invcation:

class Cat
  def meow(owner: nil)
    if owner
       ...
    else
       ...
    end
  end
end

def Person
  def initialize
    @cat = Cat.new
  end

  def meow
    @cat.meow(owner: self)
  end
end

my solution: i create a module that will override delegate method and add some code that will setup cat.delegator to the caller person each time he delegate meo to his cat

module ForFunDelegator
  def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
    super

    location = caller_locations(1, 1).first
    file, line = location.path, location.lineno
    methods.map do |method|
      definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block"
      method_def = [
        "alias old_delegate_#{method} #{method}",
        "def #{method}(#{definition})",
        "def #{to}.delegator=(delegator)",
        "@delegator = delegator",
        "end",
        "def #{to}.delegator",
        "@delegator",
        "end",
        "#{to}.delegator = self",
        "old_delegate_#{method}(#{definition})",
        "end"
      ].join ";"
      module_eval(method_def, file, line)
    end
  end
end

demo

class Person
  extend ForFunDelegator

  attr_reader :cat
  delegate :meow, to: :cat

  def initialize
    @cat = Cat.new
  end
end

class Cat
  def meow
    puts delegator if respond_to?(:delegator) # Person:0x00007fa158868098
  end
end

Person.new.meow

now the Cat or anything else no need to concern about the self which is the delegator, the delegator module will do on behalf of them.

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