Ruby 重构代码,包括;“双管”;及;实例变量";使用元编程

Ruby 重构代码,包括;“双管”;及;实例变量";使用元编程,ruby,metaprogramming,Ruby,Metaprogramming,我有两种方法: def ios_ids @ios_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'ios', alive: true).try(:reg_id)) end def android_ids @android_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'android

我有两种方法:

def ios_ids
  @ios_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'ios', alive: true).try(:reg_id))       
end

def android_ids
  @android_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'android', alive: true).try(:reg_id)) 
end
我想把它们折射成下面这样的东西

%w(android ios).each do |os_type|
  define_method(:"#{os_type}_ids") { "@#{os_type}_ids" ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: os_type, alive: true).try(:reg_id))}
end
但它不起作用

有人有任何答案或更好的解决方案吗?

您想使用,并且:


使用这种元编程真的很麻烦,尤其是在这样操作实例变量时。通常情况下,当你沿着这条路走的时候,这是因为你有很多类似的情况需要清理

让我们用更小的步骤来解决这个问题:

def ios_ids
  @ios_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'ios', alive: true).try(:reg_id))       
end
这里发生了一些非常奇怪的事情,比如
“#{x}”
反模式,它引用了一个几乎总是毫无意义的值。在绝对需要字符串的情况下,使用
.to\u s
处理所讨论的值

这还会加载模型并尝试从中获取属性。那太浪费了。它还使用很少使用的不规则
数组(…)
符号将其打包<代码>[…]是首选

所以你可以稍微整理一下:

def ios_ids
  @ios_ids ||= GcmToken.where(
    users_id: @event.user_id,
    os_type: 'ios',
    alive: true
  ).pluck(:reg_id)
end
这就归结了很多。现在它只是从
GcmToken
模型中获取所有相关的
reg\u id
值。如果您有一个
用户拥有多个:gcm\u令牌
并且
事件属于:User
,这里给出的数据应该是这种情况,那么您可以进一步清理它:

def ios_ids
  @ios_ids ||= @event.user.gcm_tokens.where(
    os_type: 'ios',
    alive: true
  ).pluck(:reg_id)
end
您可以通过一个简单的
scope
声明来进一步清理这个问题:

scope :alive_for_os_type, -> (os_type) {
  where(os_type: os_type, alive: true)
}
然后它变得更小:

def ios_ids
  @ios_ids ||= @event.user.gcm_tokens.alive_for_os_type('ios').pluck(:reg_id)
end
它变得越来越小了。在这一点上,使用
define_method
减少这一点是过分的,但如果您真的想这样做,请执行以下操作:

OS_TYPES = %w[ android ios ].freeze

OS_TYPES.each do |os_type|
  method_name = "#{os_type}_ids".to_sym
  instance_var = "@#{method_name}".to_sym

  define_method(method_name) do
    instance_variable_get(instance_var) or
      instance_variable_set(
        instance_var,
        @event.user.gcm_tokens.where(
          os_type: 'ios',
          alive: true
        ).pluck(:reg_id)
      )
  end
end
这比将每一个的实现简化为更简单的形式要糟糕得多,代码也要复杂得多。如果你有几十种类型,也许你会想这样做,但老实说,这个呢:

def platform_ids(os_type)
  @platform_ids ||= { }

  @platform_ids[os_type] ||= @event.user.gcm_tokens.alive_for_os_type(os_type).pluck(:reg_id)
end

有一种方法可以处理N种类型,您所要做的就是指定哪一种。有时特殊用途的方法不值得大惊小怪。

@Ilya typo,你当然是对的,谢谢你的关注!我应该删除我的答案吗?@Ilya在这种情况下(当你看到有人在他的答案中犯了一个小错误/风格错误时),你可能应该编辑已经给出的答案,而不是提供一个新的答案,只提供更好的代码表示。这是国际海事组织,我并不声称这是一条规则或任何东西,只是我对常识的看法:)看起来很相似,我同意,但方式完全不同。@Andrey Deineko你给出了正确的答案,谢谢你的帮助你给出了正确的答案,还有很多建议,谢谢你的帮助。我会认真研究你的建议。嗨,我读过一篇文章,说我们应该使用interpolate而不是to__s,你能解决我的困惑吗?我应该使用什么样的情况来使用to_s或interpolate?这篇文章是@tsao,用于在模式为
“…{x}…”的字符串中进行插值,但是隔离变量是没有意义的。例如,do
放置“Hello{name}”
,而不do
放置“{name}”
def platform_ids(os_type)
  @platform_ids ||= { }

  @platform_ids[os_type] ||= @event.user.gcm_tokens.alive_for_os_type(os_type).pluck(:reg_id)
end