接下来介绍的章节,会使用到instance_eval
, class_eval
,加上我们已经在 Day12 提到的MetaClass
和 Singleton
的概念。因此在使用前,今天会先介绍instance_eval
, class_eval
的使用方式,以及相对应的使用情境。
先讲对同一 class
使用 instance_eval
, class_eval
所产生的结果
class_eval
➡️ 新增实体方法instance_eval
➡️ 新增类别方法class A
end
# 新增实体方法
A.class_eval do
def foo
'foo'
end
end
# 新增类别方法
A.instance_eval do
def bar
'bar'
end
end
A.bar #=> 'bar'
A.new.foo #=> 'foo'
class_eval
的概念很简单,即为在class
新增方法,而class
内部的方法为实体方法。我们举个例子:
#======= 正规写法
class Apple
def color
'red'
end
end
apple = Apple.new
apple.color #=> 'red'
#======= 使用class_eval: 对 class 新增方法
class Apple
end
Apple.class_eval do
def color
'red'
end
end
apple = Apple.new
apple.color #=> 'red'
使用class_eval
为Apple
新增了名叫color
的方法,此方法与实体方法无异。
首先,我们可以对一个实体,透过instance_eval
定义新方法。
my_string = "String"
my_string.instance_eval do
def new_method
self.reverse
end
end
my_string.new_method => "gnirtS"
我们对my_string
新增方法,就如同 Day12 提到的MetaClass
的观念一样。我们为my_string
新增了单体方法singleton
,所有用字串String
创建的物件里面,只有my_string
有new_method
这个独特技能!
order = Order.first
order.instance_eval do
def foo
'bar'
end
def info
{id: id, price: price}
end
end
order.foo #=> "bar"
order.info #=> {:id=>1, :price=>18100}
如上所示,我们可以为特地的订单新增方法!不过照上面的逻辑,如果有100张订单,我们就要创建100个foo
, info
共200个方法。不如在每个order
的原型 Order
,所以以上的情况,我们会使用 class_eval
直接在Order
定义实体方法。
对单体新增方法,只有在比较特殊的情境会用到!目前汉汉老师还没有碰过这种需求
之前我们提到,在Ruby的程序语言中,任何东西都是物件,包括使用Apple
新增的apple
物件,以及Apple
本身也是物件。
# 物件
apple = Apple.new
# 也是物件
Apple
既然是物件,我们可以对apple
使用instance_eval
,没有道理不能对Apple
也使用instance_eval
。
Day12 我们提到将所有的猩猩加护,所有的猩猩都能够使用bar
技能,而bar
为类别方法
class Orangutan
end
def Orangutan.bar
'bar'
end
Orangutan.bar #=> bar
同样,我们可以透过instance_eval
新增类别方法bar
,替所有的猩猩都增加同样的技能。
class Orangutan
end
Orangutan.instance_eval do
def bar
'bar'
end
end
Orangutan.bar #=> bar
换个角度来思考,我们使用instance_eval
,或使用 <<
新增方法,都是为物件新增一个单例方法,只不过当我们使用在apple
, orangutan
,成为了apple
, orangutan
的独特行为,而若使用在Apple
, Orangutan
则为类别方法。
我们使用singleton_methods
可以找到 Orangutan
的单体方法 bar
Orangutan.singleton_methods #=> [:bar]
此外,我们也可以透过instance_eval
取得实体变数 & 和私有方法
class Apple
def initialize(color = 'red')
@color = color
end
private
attr_accessor :color
def private_foo
'foo'
end
end
apple = Apple.new
#=> #<Apple:0x00007fd8888b9d28 @color="red">
apple.color
# NoMethodError (private method `color' called for #<Apple:0x00007fd8848203e8 @color="red">)
apple.private_foo
# NoMethodError (private method `private_foo' called for #<Apple:0x00007fd8848203e8 @color="red">)
# 取得实体变数 & 和私有方法
apple.instance_eval {color} #=> "red"
apple.instance_eval {private_foo} #=> "foo"
最重要的,还是如何在实际应用中使用class_eval
, instance_eval
,以下举例5种可以使用的情境。
#included
继承链namespace :data do
desc '同步资料'
task sync_data: :environment do
Order.class_eval do
scope :bar, -> { 'bar' }
scope :reviewed, -> { ... }
scope :yet_not_sync, -> { ... }
scope :week_ago, -> { where('done_at < ?', Rails.env.production? ? 1.day.ago : 1.minute.ago) }
# class_methods
class << self
define_method :foo, -> { 'foo' }
end
end
Order.yet_not_sync.week_ago.reviewed.each do |order|
begin
...
rescue
p "同步失败"
end
end
end
end
作为 react_component
的传递资料使用,我们可以使用 class_eval
替原本的model
产生更多方法
# app/helpers/member_helper.rb
module MemberHelper
# react component with props data
def member_helper
react_component("MemberOrder", props: react_store_member_params)
end
# params props in react
def react_store_member_params
Order.class_eval do
define_method :foo, -> { 'foo' }
def bar
'bar'
end
end
{ orders: @orders.order('created_at desc').includes(:product).as_json(methods: [:foo, :bar]) }
end
end
在画面中呼叫member_helper
,即可使用 react
元件。
= member_helper
输出表单前,我们需要整理资料。整理多量model资料的工作可以交给class_eval
去做
# orders_helper.rb
module Admin::OrdersHelper
extend ActiveSupport::Concern
# 写进Excel表单的内容
def write_sheet(wb, sheet_name, orders)
wb.add_worksheet(name: sheet_name) do |sheet|
...
# 订单资料
orders.each do |order|
...
order.order_items.each_with_index do |item, index|
_data = []
_data << order.export_shipping_type
_data << item.export_sku
_data << item.export_preorder
...
end
end
end
end
included do
Order.class_eval do
def export_shipping_type
shipping_type.present? ? I18n.t("order.shipping_type.#{shipping_type}") : ''
end
end
OrderItem.class_eval do
def export_sku
variant.sku
end
def export_preorder
is_preorder ? 'v' : ''
end
end
end
end
下列的程序码中,can_view?(:user)
是Rails helper
定义的方法。在 Day9 提到过,do end
包起来的部分为独立的领域,外面的变数无法和里面交流,因此在 ReturnOrder, SubOrder 的 instance_eval
(or class_eval
) 的 block
内部看不懂can_view?(:user)
,因此造成错误
# 错误使用方法: can_view?(:user)
module Admin::SidebarHelper
[SubOrder, ReturnOrder].each do |name_model|
name_model.instance_eval do
def with_permission
if can_view?(:user)
user_brand_ids = User.first.brands.pluck(:id)
name_model.where(brand_id: user_brand_ids)
else
name_model.where(store_id: current_user.store_id)
end
end
end
end
end
图解领域所造成的影响,会造成 instance_eval 包覆内的区块看不懂 can_view?(:user)
我们可以用以下 2 种写法改写
# 方案1: 不用 instance_eval
module Admin::SidebarHelper
def with_permission(name_model)
if can_view?(:user)
user_brand_ids = User.first.brands.pluck(:id)
name_model.where(brand_id: user_brand_ids)
else
name_model.where(store_id: current_user.store_id)
end
end
end
With_permission(ReturnOrder)
# 方案2: 传参数进去
module Admin::SidebarHelper
[SubOrder, ReturnOrder].each do |name_model|
name_model.instance_eval do
def with_permission(permission)
if permission
user_brand_ids = User.first.brands.pluck(:id)
name_model.where(brand_id: user_brand_ids)
else
name_model.where(store_id: current_user.store_id)
end
end
end
end
end
ReturnOrder.with_permission(can_view?(:user))
在Day14, Day15我们会讲到的继承所搭配的4个hook
,适合搭配instance_eval
, class_eval
module#included
,module#prepended
,module#extended
,class#inherited
module Notification::Helper
def self.included(base)
base.class_eval do
# ...
end
base.instance_eval do
# ...
end
end
end
Day12和今天所介绍的内容为OOP的其中一种设计流程 ➡️ Singleton pattern
class Apple
attr_accessor :color
def initialize(color = 'red')
@color = color
end
def description
[color, 'Apple'].join(' ')
end
end
apple = Apple.new
another = Apple.new
# 称为 Eigenclass, Singleton class
def another.description
'金苹果'
end
apple.description #=> "red Apple"
another.description #=> '金苹果'
apple.singleton_methods #=> []
another.singleton_methods #=> [:description]
类别方法也是一种单体方法
class Apple
def self.ad
'大家来买苹果'
end
end
Apple.singleton_methods.include?(:ad) #=> true
Apple.instance_methods.include?(:ad) #=> false
我们为class_eval
, instance_eval
来做总结
class_eval
作用於 class,并可以用来定义instance_methods
instance_eval
作用於 instance,并可以用来定义 singleton_methods
如果是对 class
做instance_eval
,等同类别方法。
<<: D7 allauth 采坑日记 Extending & Substituting User model (2)
>>: Day 0xC UVa10170 The Hotel with Infinite Rooms
面对面实体销售,我们能从聊天问答、肢体行为初步了解客户需求,但──在网路世界,如果你想知道客户喜欢什...
Leetcode #102. Binary Tree Level Order Traversal 简...
前言 终於进入新的篇章06-Wireless Attacks,但由於先前的Kali虚拟机环境无法进行...
Hello 大家, 明明是普通的周末, 不知为何这周一堆人出去玩@@ 我错过了甚麽吗? 今天来讲媒体...
这一个月的不间断发文,会将大二下学期必修的资料库概论浓缩成30篇文,并将当中所学,纪录的课程笔记再整...