使用元编程重构包含 "double pipe" 和 "instance variable" 的代码
Refactor code that includes "double pipe" and "instance variable" using 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', 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
但是没用
有人有任何答案或更好的解决方案吗?
您想使用 Object#instance_variable_set
and Object#instance_variable_get
:
%w(android ios).each do |os_type|
define_method "#{os_type}_ids" do
instance_variable_get("@#{os_type}_ids") ||
instance_variable_set("@#{os_type}_ids", Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: os_type, alive: true).try(:reg_id)))
end
end
使用那种元编程确实是 heavy-handed,尤其是在像那样操作实例变量时。通常情况下,当您沿着这条路走下去时,是因为您遇到了 很多 需要整理的情况。
让我们分步解决这个问题:
def ios_ids
@ios_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'ios', alive: true).try(:reg_id))
end
这里发生了一些非常奇怪的事情,比如 "#{x}"
anti-pattern,它引用了一个几乎总是毫无意义的单一值。如果您绝对需要字符串,请对相关值使用 .to_s
。
这还会加载模型并尝试从中获取属性。这是非常浪费的。它还使用很少使用的不规则 Array(...)
符号对其进行打包。 [ ... ]
优先。
所以稍微清理一下你会得到:
def ios_ids
@ios_ids ||= GcmToken.where(
users_id: @event.user_id,
os_type: 'ios',
alive: true
).pluck(:reg_id)
end
这归结为很多。现在它只是从 GcmToken
模型中获取所有关联的 reg_id
值。如果你有 User has_many :gcm_tokens
和 Event belongs_to :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
减少它是 over-kill,但如果你真的想要,那么这样做:
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种类型,你只需要指定是哪一种即可。有时 special-purpose 方法不值得大惊小怪。
我有两个方法:
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
但是没用
有人有任何答案或更好的解决方案吗?
您想使用 Object#instance_variable_set
and Object#instance_variable_get
:
%w(android ios).each do |os_type|
define_method "#{os_type}_ids" do
instance_variable_get("@#{os_type}_ids") ||
instance_variable_set("@#{os_type}_ids", Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: os_type, alive: true).try(:reg_id)))
end
end
使用那种元编程确实是 heavy-handed,尤其是在像那样操作实例变量时。通常情况下,当您沿着这条路走下去时,是因为您遇到了 很多 需要整理的情况。
让我们分步解决这个问题:
def ios_ids
@ios_ids ||= Array(GcmToken.find_by(users_id: "#{@event.user_id}", os_type: 'ios', alive: true).try(:reg_id))
end
这里发生了一些非常奇怪的事情,比如 "#{x}"
anti-pattern,它引用了一个几乎总是毫无意义的单一值。如果您绝对需要字符串,请对相关值使用 .to_s
。
这还会加载模型并尝试从中获取属性。这是非常浪费的。它还使用很少使用的不规则 Array(...)
符号对其进行打包。 [ ... ]
优先。
所以稍微清理一下你会得到:
def ios_ids
@ios_ids ||= GcmToken.where(
users_id: @event.user_id,
os_type: 'ios',
alive: true
).pluck(:reg_id)
end
这归结为很多。现在它只是从 GcmToken
模型中获取所有关联的 reg_id
值。如果你有 User has_many :gcm_tokens
和 Event belongs_to :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
减少它是 over-kill,但如果你真的想要,那么这样做:
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种类型,你只需要指定是哪一种即可。有时 special-purpose 方法不值得大惊小怪。