Rails Rails 5.1 add delegate_missing_to

zhangbin-github for 时空 · 发布于 2017年06月02日 · 最后由 imwildcat 回复于 2017年06月15日 · 1420 次阅读
28684
本帖已被设为精华帖!

http://blog.bigbinary.com/2017/05/30/rails-5-1-adds-delegate-missing-to.html

rails 5.1 添加了delegate_missing_to方法

实验如下:

rails g model user usename:string password:string email:string
rails g model book title:string user:references

rails  db:migrate

生成如下model

class User < ApplicationRecord
    has_many :books
end

class Book < ApplicationRecord
    belongs_to :user
end

在console中实验

User.create(username:'test', password:'123456', email:'t@test.com')
Book.create(user:User.last, title:'this is a book')
irb(main):017:0> Book.last.user
  Book Load (0.3ms)  SELECT  `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
  User Load (0.2ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> #<User id: 1, username: "test", password: "123456", email: "test@test.com", created_at: "2017-06-02 01:55:54", updated_at: "2017-06-02 01:55:54">

irb(main):018:0> Book.last.username
  Book Load (0.4ms)  SELECT  `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
NoMethodError: undefined method `username' for #<Book >
    from (irb):18

可以看到直接访问username会报method missing

在book中添加 delegate_missing_to

class Book < ApplicationRecord
  belongs_to :user
  delegate_missing_to :user
end

try again

irb(main):020:0> Book.last.username
  Book Load (0.2ms)  SELECT  `books`.* FROM `books` ORDER BY `books`.`id` DESC LIMIT 1
  User Load (0.3ms)  SELECT  `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
=> "test"

看实现 pr: https://github.com/rails/rails/pull/23930/files#r64312481

# The target can be anything callable withing the object. E.g. instance
# variables, methods, constants ant the likes.
def delegate_missing_to(target)
  target = target.to_s
  target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)

  module_eval <<-RUBY, __FILE__, __LINE__ + 1
    def respond_to_missing?(name, include_private = false)
      #{target}.respond_to?(name, include_private)
    end
    def method_missing(method, *args, &block)
      #{target}.send(method, *args, &block)
    end
  RUBY
end

好处:1:可以少写一点代码,在比较多的delegate指向同一个asocciation的时候;2:可以向其他非动态语言炫技

共收到 20 条回复
1107 jasl 将本帖设为了精华贴 06月02日 11:23
1232

这种功能就是坑。。 之后各种性能问题

1107
1232tony612 回复

比如?还是你再说如果不清楚实现细节会有无谓的查询?

我之前在做扩展字段(基于serialize字段),如果要做到透明就是自己实现了这个功能

1232
1107jasl 回复

人人都喜欢省事,但程序开发并不是可以偷懒的,有时 explicit 反而是更好的选择。当你调用一个方法,都很可能意识不到它做了 DB 查询时,最终就拖慢了你的程序。

1232

不过我看了那个 PR,它给的 example 倒没有这个问题。

class Partition
  def initialize(first_event)
    @events = [ first_event ]
  end

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

    def method_missing(method, *args, &block)
      @events.send(method, *args, &block)
    end
end
=>
class Partition
  delegate_missing_to :@events

  def initialize(first_event)
    @events = [ first_event ]
  end
end

只是既然楼主会想到这种用法,就难免会有很多人会这样用

1107
1232tony612 回复

这个确实,不过问题在于限制 delegate_missing_to 的使用,其实主贴说的有点不太对,看指出的源码,这里的实现只是一个增强版的 delegate 而已,而 delegate 也有滥用会无意识增加额外查询的问题

1232
1107jasl 回复

👍 我也刚刚看到

28684

是我的用法有问题😂 请不要模仿

28684

我有点不明白做这个delegate_missing_to的目的了,难道不是为了简化代码?毕竟pr里给出的例子,还是摆脱不了一次数据库查询啊

27

同意 @tony612 的看法,这种 feature 简直是毒瘤!

28684
27numbcoder 回复

这个要看怎么用吧,如果在遍历集合的时候使用delegate,肯定会产生性能问题(N+1)

6764
  1. 显式地 delegate 方法给特定的对象
  2. 借助 delegate_missing_to 一揽子地把公开方法 delegate 给特定对象, 但并非是透明代理 ( transparent proxy )
  3. 借助 SimpleDelegator 来实现透明代理
  4. 借助 Forwardable 实现类似 delegate 的功能.

Pattern 有点多, LZ 来讲讲

28684
Book.includes(:user).references(:user).last.username

可解决n+1的问题

我理解delegate_missing_to 就是增强版的delegate,将原本

delegate :username, to: :user
delegate :email, to: :user

简化,通过method_missing来send,这个和rails本身的delegate实现还是有区别的

9800

你这例子想坑死多少了。。。。book.username你真的不怕维护着拿刀砍你吗?

1107
9800pynix 回复

其实这东西适用场景挺多的,可以把组合模式透明化啊,不过把方法代理到另一个 AR 实体确实有坑... 而且 AR 没办法自动 eager loading

27

不光是性能的问题,项目做到很大,协作人员多了之后,后面接手项目的人会很想砍人

Bb9eb9

实现用到的method_missing本身就会拖慢性能,它会先找该对象的方法,再找所有父类,没有该方法再实现。元编程里也是尽量不用的

8744

还是不能因为自己不会用就说新方法不好

8744
27numbcoder 回复

是项目做到很臃肿吧

8744
Bb9eb9huibin 回复

真的已经到了要抠一个method_missing的性能问题的地步吗?直接写汇编最快执行效率最高

14174
8744lithium4010 回复

N+1 query 啊

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册