知识点一:Binding类
Binding通过创建绑定对象来捕获并带走当前的作用域,通过Kernel#binding方法进行实现
class MyClass
def my_method
@x = 1
binding
end
end
#b就是就是返回的绑定对象,因为方法my_method返回了binding方法,就是返回了绑定对象。
b = MyClass.new.my_method
#eval传递一个Binding对象作为额外的参数,代码就可以在这个Binding对象所携带的作用域中执行
eval ("@x", b) #=>1
#Ruby还提供了TOPLEVEL_BINDING的预定义常量,它表示顶级作用域的Binding对象:
class AnotherClass
def my_method
eval("self", TOPLEVEL_BINDING )
end
end
puts AnotherClass.new.my_method #=>main
知识点二:eval执行代码字符串
代码字符串:字符串中包含ruby代码
array = [10, 20]
element = 30
puts eval("array << element") #=>[10, 20, 30]
#eval方法有一个必要参数还有三个可选参数:
eval(statements, @binding, file, line)
#一行字符串形式的ruby代码,@binding表示绑定对象,file表示文件名,line表示行数
#instance_eval()方法也可以执行代码字符串
array = ["a", "b", "c"]
x = "d"
array.instance_eval "self[1] = x"
puts array #=>["a","d","c"]
#eval的代码注入
def explore_array(method)
code = "['a','b','c'].#{method}"
puts "evaluating: #{code}"
eval code
end
loop {p explore_array(gets())}
#如果在终端执行上面的代码,回车输入如下的方法:
length();Dir.glob(*)
#这段代码不仅会显示这个数组的长度,也会显示代码所示文件及所在文件夹的文件
#可以通过动态派发方式替换eval方法:
def explore_array(method, *arguments)
['a', 'b', 'c'].send(method, *arguments)
end
explore_array(:length)
#更为合适的方式是使用污染对象和安全级别:
user_input = "User input: #{gets()}"
puts user_input.tainted?
#在如上的代码中,user_input是一个受污染对象。安全级别是通过使用$SAFE局部变量来进行设定的
#如果安全级别大于0,Ruby都会拒绝执行污染的字符串:
$SAFE = 1
user_input = "User input: #{gets()}"
eval user_input
执行结果:insecury operation SecurityError
知识点三:钩子方法
1.钩子方法:当执行或者调用某个方法的时候,和这个方法相关联的方法会被执行。
module M
def self.included(othermod)
puts "#{self} was mixed into #{othermod}"
end
end
class C
include M #=> M was mixed into C
end
2.类扩展:在该类的eigenclass中include模块,使得模块中的实例方法成为类的类方法。
module M
def demo_method
puts "this is the demo method in module"
end
end
class DemoClass
extend M
end
DemoClass.demo_method #=> this is the demo method in module
3.类扩展混入:钩子方法和类扩展结合。
- 定义一个模块,比如MyMixin
- 在MyMixin中定义一个内部模块,这些方法最终会成为类方法。
- 覆写MyMixin#included()方法来用ClassMethods扩展包含者(使用extend()方法)
module MyMixin
def self.included(base)
base.extend(ClassMethods)
#Object#extend()只是接受者eigenclass中包含模块的快捷方式。
end
module ClassMethods
def demo_method
puts "this is the method in the module"
end
end
end
class DemoClass
include MyMixin
end
DemoClass.demo_method #=> this is the method in the module
#如果不需要为包含着提供实例方法,可以没有必要提供内部模块,直接在MyMixin中定义方法即可,这个方法会成为被包含着的类方法:
module MyMixin
def self.included(base)
base.extend(self)
end
def demo_method
puts "this is the method in the module"
end
end
class DemoClass
include MyMixin
end
DemoClass.demo_method #=> this is the method in the module
案例
案例:
class Person
include CheckedAttributes
attr_checked :age do |v|
v >= 18
end
end
me = Person.new
me.age = 39
me.age = 12 #抛出异常
步骤:
1.使用eval方法编写add_checked_attribute的内核方法,为类添加一个简单的经过校验的属性
2.重构add_checked_attribute方法,去掉eval方法
3.通过代码块来校验属性
4.把add_checked_attribute方法修改为attr_checked的类宏,对所有类可用
第一步:写一个基于eval()的方法用来为类添加一个最简单的校验过的属性
require 'test/unit'
class Person; end
class TestCheckedAttribute < Test::Unit::TestCase
def setup
#必须是:age的形式
add_checked_attribute(Person, :age)
@bob = Person.new
end
def test_accepts_valid_values
@bob.age = 20
assert_equal 20, @bob.age
end
def test_refuses_nil_values
assert_raises RuntimeError, "Invalid attribute" do
@bob.age = nil
end
end
def test_refuses_false_values
assert_raises RuntimeError, "Invalid attribute" do
@bob.age = false
end
end
end
实现结果如下:
def add_checked_attribute(clazz, attribute)
eval "
class #{clazz}
def #{attribute}=(value)
raise 'Invalid attribute' unless value
@#{attribute} = value
end
def #{attribute}()
@#{attribute}
end
end
"
end
第二步:去掉eval,使用打开类技术
def add_checked_attribute(clazz, attribute)
clazz.class_eval do
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless value
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
第三步:通过代码块来校验属性
require "test/unit"
class Person
end
class TestCheckedAttribute < Test::Unit::TestCase
def setup
add_checked_attribute(Person, :age) { |v| v >= 18 }
@bob = Person.new
end
def test_accepts_valid_values
@bob.age = 20
assert_equal 20, @bob.age
end
def test_refuses_nil_values
assert_raises RuntimeError, "Invalid attribute" do
@bob.age = 17
end
end
end
#实现结果
def add_checked_attribute(clazz, attribute, &validation)
clazz.class_eval do
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
第四步:把内核方法改造成类宏,对所有的类都可用
require "test/unit"
class Person
attr_checked :age do |v|
v >= 18
end
end
class TestCheckedAttribute < Test::Unit::TestCase
def setup
@bob = Person.new
end
def test_accepts_valid_values
@bob.age = 20
assert_equal 20, @bob.age
end
def test_refuses_nil_values
assert_raises RuntimeError, "Invalid attribute" do
@bob.age = 17
end
end
end
#实现方式
class Class
def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
第五步:使用类扩展混入技术,包含CheckedAttributes模块可用
require "test/unit"
class Person
include CheckedAttributes
attr_checked :age do |v|
v >= 18
end
end
class TestCheckedAttribute < Test::Unit::TestCase
def setup
@bob = Person.new
end
def test_accepts_valid_values
@bob.age = 20
assert_equal 20, @bob.age
end
def test_refuses_nil_values
assert_raises RuntimeError, "Invalid attribute" do
@bob.age = 17
end
end
end
#运行结果
module CheckedAttributes
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
end