メタプログラミングRubyを読んでいます。完全自分用のメモ記事です。
第5章の「クラス定義」で特に参考になった部分を中心に書いています。
🚜 6章クラス定義
オブジェクトの汚染
外部から来た安全ではないをブジェクを「オブジェクトの汚染」という。
この汚染を判定するメソッドが#tainted?
である。
user_input = "User input: #{gets()}" puts "user_input.tainted? => #{ user_input.tainted? }"
only_code_object = 1 puts "only_code_object.tainted? => #{ only_code_object.tainted? }"
|
ERB内のRubyコードの評価メソッド
ERBの中でRubyのコードを書くとそのコードがeval
で評価される。
class ERB def result(b=new_toplevel) if @safe_level proc { $SAFE = @safe_level eval(@src, b, (@filename || '(erb)'), 0) }.call else eval(@scr, b, (@filename || '(erb)'), 0) end end end
|
ユーザーが@safe_level
を設定していれば、サンドボックスの中で、コードを評価する。
また、$SAFE
はprocの中だけで有効になっており、全体の設定を変更しないようにしている。
🚕 フックメソッド
Rubyにはいくつかのイベントが発生した時にフックするメソッドが存在する。
継承にフック
class String def self.inherited(subclass) puts "#{self} は #{subclass} に継承されたよ!" end end
class MyString < String; end
|
includeにフック
module M1 def self.included(othermod) puts "#{self} は #{othermod} にincludeされたよ!" end end
class C include M1 end
|
prependにフック
module M2 def self.prepended(othermod) puts "#{self} は #{othermod} にprependされたよ!" end end
class C prepend M2 end
|
そのほか以下のようなメソッドもある。
Module#method_added - メソッドを追加した時に呼ばれる
Module#method_removed - メソッドがModule#remove_method により削除された時に呼ばれる
Module#method_undefined - メソッドがModule#undef_method によって削除されるか、 undef 文により未定義になったら呼ばれる
# 特異メソッドのイベントをキャッチする
Kernel#singleton_method_added - 特異メソッドが追加された時に呼ばれる
Kernel#singleton_method_removed - 特異メソッドが削除された時に呼ばれる
Kernel#singleton_method_undefined - 特異メソッドがundefinedになった時に呼ばれる
😸 属性のチェック
全Classで attr_checked
を使えるようにする
classやモジュールの属性をチェックするようなDSL attr_checked
を追加する例。
require 'test/unit'
class Class def attr_checked(attribute, &validation) define_method "#{attribute}=" do |value| raise 'Invalid attribute' if value.nil? || !value || !validation.call(value) instance_variable_set("@#{attribute}", value) end
define_method attribute do instance_variable_get("@#{attribute}") end end end
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 = 18 assert_equal 18, @bob.age end
def test_refuses_invalid_values assert_raises RuntimeError, 'Invalid attribute' do @bob.age = 17 end 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
|
なるほど、これできるのかとちょっと感激!
includeした時だけ使えるようにする
require 'test/unit'
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' if value.nil? || !value || !validation.call(value) instance_variable_set("@#{attribute}", value) end
define_method attribute do instance_variable_get("@#{attribute}") end end end end
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 = 18 assert_equal 18, @bob.age end
def test_refuses_invalid_values assert_raises RuntimeError, 'Invalid attribute' do @bob.age = 17 end 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
|
🏀 サンプルソース
何かの役に立つこともあるかもですので、リポジトリも公開しておきます。
morizyun/meta_programming_ruby2 - GitHub
🖥 VULTRおすすめ
「VULTR」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。
最近はVULTRのヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!