Ruby 环绕别名

torvaldsdb · January 19, 2018 · Last by torvaldsdb replied at January 19, 2018 · 2334 hits
  • 参看元编程的时候,发现环绕别名,感觉很新颖

自己在项目中测试了一下,却没有顺利执行,如下

class User < ApplicationRecord
  alias :real_avatar :avatar

  def avatar
    real_avatar.present? ? "real_avatar" : "default.png"
  end
end

控制器执行的结果: User 实例:

2.4.2 (main):0 > user = User.last
  User Load (0.3ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User:0x00007fde39a372c0
 id: 2,
 uid: "017526292153702",
 avatar: "http://img2.imgtn.bdimg.com/it/u=3563567972,3348399262&fm=27&gp=0.jpg",
 nickname: "Nick-member",
 created_at: Fri, 19 Jan 2018 00:26:16 CST +08:00,
 updated_at: Fri, 19 Jan 2018 00:26:16 CST +08:00>

调用的 atatar, 并没有展示我想要的 "real_avatar", 而是报了错误

user.avatar

=> NameError: undefined method `avatar' for class `#<Class:0x00007fe4db80b6d8>'

哪里出了错了吗?

顺序

def avatar
  real_avatar.present? ? "real_avatar" : "default.png"
end

alias :real_avatar :avatar
2 Floor has deleted

是这样调用的么?

user = User.last
user.avatar

user model:


class User < ApplicationRecord

  def avatar
    real_avatar.present? ? "real_avatar" : "default.png"
  end

  alias :real_avatar :avatar
end

执行结果:

2.4.2 (main):0 > user = User.last
  User Load (0.6ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User:0x00007fd3cc9330a8
 id: 2,
 uid: "017526292153702",
 avatar: "http://img2.imgtn.bdimg.com/it/u=3563567972,3348399262&fm=27&gp=0.jpg",
 nickname: "Nick-member",
 created_at: Fri, 19 Jan 2018 00:26:16 CST +08:00,
 updated_at: Fri, 19 Jan 2018 00:26:16 CST +08:00>
2.4.2 (main):0 > user.avatar
SystemStackError: stack level too deep
from /Users/dubing/workspace/vcooline/group_buying_front/app/models/user.rb:18:in `avatar'
2.4.2 (main):0 >

例子的关键点在于 length 是一个 String 的一个实例方法,和你代码里定义的 length 不一样

例子的意思是 length 本来 String 里面就有定义,书中的意思是你假如别名这个方法的话它还是会引用之前的那个。但这里 avatar 在 User 或 ApplicationRecord 里面本来是没有定义的,它调用了你后期定义导致了无限嵌套。

你要是想实现默认头像的效果可参考以下代码:

class User < ApplicationRecord

  attr_accessor :real_avatar

  def avatar
    real_avatar || "default.png"
  end
end

你的定义顺序没有错,但在 Rails 模型中,我认为,你应该用 alias_attribute

alias_attribute :real_avatar, :avatar

附,其实你完全可不必用 alias 的方法来重写 avatar,以达到有默认头像的功能。 你可以这样:

class User < ApplicationRecord
  def avatar
    read_attribute_before_type_cast["avatar"] || "default.png"
  end
end
Reply to n5ken

你这里只是使用了 attr_accessor, 接着... 这个是为了,有些人没有上传头像,才会调用默认头像, 可是你这种就会,一直使用默认头像,毕竟 real_avatar始终是空。

原因在于,ActiveRecord方法是动态加载的,它自己加上了很多元编程的东西,其实在你运行alias :real_avatar :avatar的时候,还不存在avatar这个实例方法,不信你试试看

class User < ApplicationRecord
  p instance_methods(false)
  alias :real_avatar :avatar

  def avatar
    real_avatar.present? ? "real_avatar" : "default.png"
  end
end

你看 Ruby 元编程的话,建议用原生 Ruby 来尝试例子,后面它会讲到 Rails 里面的元编程,你心里就有数了

再来一个例子

class Test
  p instance_methods(false)
  def t
  end
  p instance_methods(false)
  alias :new_t :t
  p instance_methods(false)                                                                                                                                    
end 
Reply to torvaldsdb

上传后 real_avatar 就会有值

Reply to torvaldsdb

一般插件里面也会有默认选项,如 Peperclip:

has_attached_file :avatar, :styles => { :thumb => "60x60#", :small  => "120x120>",
      :medium => "180x180" },
      # default avatar goes here
      :default_url => '/system/default/user_avatar.png',
      :path => ...,
      :url  => ...

书上的例子是打开了String类,打开之前,length方法已经被定义了,所以可以alias,而User中的 avatar 方法是动态定义的,好像是你第一次调用的时候才定义的。

像大家说的 avatar 是动态定义的,很难保证执行顺序,所以现在一般用 prepend 取代 alias 的作用

class User < ApplicationRecord
  module AvatarWithDefaultValue
    def avatar
      super || 'default value'
    end
  end
  prepend AvatarWithDefaultValue
end
Reply to swordray

我在处理的时候是这样写的

class User < ApplicationRecord
 avatar
    super || 'default value'
  end 
end

你的写法,比我的优雅了太多了。相形见绌。

学习到了,你好认真,灰常谢谢你提供的详细的解释。学习了。👏

You need to Sign in before reply, if you don't have an account, please Sign up first.