简体   繁体   中英

Delegate all method calls on a model to an association

I am having an ActiveRecord model with a polymorphic association like this:

class Reach < ActiveRecord::Base
  belongs_to :reachable, :polymorphic => true
end

This model acts like a proxy. What I need to do is to forward all method calls on that object to the associated object :reachable . I think delegate won't help here because I have to explicitly name all the methods I need to delegate. I need something like delegate :all to delegate all methods (not all method).

There are two things you can do here:

  1. The slower (performance-wise) but easier method is to use method_missing:

     class Reach < ActiveRecord::Base def method_missing(method, *args) return reachable.send(method, *args) if reachable.respond_to?(method) super end end 
  2. The faster performing method would be to define each method dynamically that you want to delegate:

     class Reach < ActiveRecord::Base [:all, :my, :methods, :here].each do |m| define_method(m) do |*args| reachable.send(m, *args) end end end 

You could even use that method in a more dynamic manner, if you wanted, by taking the Reach class, finding the methods that are defined on it and it alone, and defining only those on Reachable. I would do it by hand though because there are some you probably won't want to include.

Since Rails 5.1+ you can delegate everything not implemented with delegate_missing_to :reachable

Basically, do what you expect. You could read more on the Api Doc

If you are stuck in a previous version then just recommend using the method_missing from @Veraticus answer , is less performance-wise as mentioned but I think is the more flexible approach.

For Rails, I did the following:

class User < ApplicationRecord
  has_one :member
  delegate (Member.new.attributes.keys - User.new.attributes.keys), to: :member
end

the - User.new... is to not override existing attributes on User (eg, created_at )

I'm not sure how this approach would work with polymorphism, however.

I have found a neat way to approach the problem using refinements. There already is a class in the standard library that allows the delegation of every method call to a target object. Delegator and by extension SimpleDelegator Now there is a way to insert SimpleDelegator into your inheritance chain without inheriting from it directly using refinements:

def self.include_delegator
  mod = Module.new do
    include refine(SimpleDelegator) { yield if block_given? }
  end
  self.send :include, mod
end
include_delegator

Now in order to take advantage of SimpleDelegator set the delegation target in an after initialize callback like so:

after_initialize do |instance|
  __setobj__(instance.reachable)
end

This is equivalent to inheriting directly from SimpleDelegator and setting the delegation in the construction, there is no manual housekeeping of methods to delegate and you can avoid using method missing.

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