|
|
||
特定のオブジェクトにメソッドを追加できる。
str = 'hello world' def str.title? self.upcase == self end p str.title? p str.methods.grep(/hello/) p str.singleton_methods
class MyClass < Array def my_method 'Hello' end end
これと同じ動きをするものを、classキーワードを用いずに書く。
class Class.new(Array) def my_method 'Hello' end end
class Loan def initialize(book) @book = book @time = Time.now end def to_s "#{@book.upcase} loaded on #{@time}" end end
次の用にコードを帰る
class Loan def initialize(book) @book = book @time = Loan.time_class.now end def self.time_class @time_class || Time end def to_s "#{@book.upcase} loaded on #{@time}" end end
実際に使う際には、@time_classは常にnilになるので、Timeが呼ばれる。テストコードでは、@time_classにダミーの時間を挿入してテストすると良い。以下、テストコード
require 'test/unit' class FakeTime def self.now '2011-06-21 21:14:44 +0900' end end class Test_Loan < Test::Unit::TestCase def test_conversion_to_string Loan.instance_eval { @time_class = FakeTime } loan = Loan.new('War and Peace') assert_equal "WAR AND PEACE loaded on #{FakeTime.now}", loan.to_s end end
Module#class_evalを使用する
def add_method(a_class) a_class.class_eval do def m 'Hello' end end end add_method String 'a'.m # => 'Hello'
復習
blockは中括弧か、do endで定義
ブロックを定義できるのは、メソッドを呼び出す時だけ。
ブロックはメソッドにそのままわ達され、メソッド側でyieldを使ってブロックをコールバックする。
def a_method(a, b) a + yield(a, b) end a_method(1, 2) {|x, y| (x + y) * 3} # => 10
メソッドの内部からblockが渡されているか確認する方法。
def a_method return yield if block_given? 'no block' end a_method { 'block' } # =>'block' a_method # =>'no block'
class MyClass define_method :my_method do |my_arg| my_arg * 3 end end obj = MyClass.new obj.my_method(2) # => 6
動的メソッドと呼ばれる
class Foo private def print(text) p text end end Foo.new.send(:print, 'hoge') # => "hoge" Foo.new.public_send(:print, 'hoge') # => error
呼び出せてしまう。public_sendを用いることでpublicのメソッドのみ読み出せるので通常はこちらをつかったほうがよさそう。ただ、privateなメソッドのテストを行うときにはsend使うと便利かも
アプリケーションの設定情報をYAMLで保存している
admin : Bill title : Rubyland topic : Ruby and more
これらの設定をconfオブジェクトに格納している。普通に書いたら下記のようになる。
conf.admin = 'Bill' conf.title = 'Rubyland' conf.topic = 'Ruby and more'
ただ、Campingのコードには下記のようなソースはない。
YAML.load_file(conf.rc).each do |k, v| conf.send("#{k}=", v) end
class MyClass def my_method(my_arg) my_arg * 2 end end obj = MyClass.new p obj.my_method(3) p obj.send(:my_method, 3)
後者のsendを使えばメソッドを動的に呼び出せる。この技術を動的ディスパッチと呼ぶ。
module Printable def print; end end module Document def print; end end class Book include Document end class Book2 include Document include Printable end Book.ancestors # => [Book, Document, Object, Kernel, BasicObject] Book2.ancestors # => [Book2, Printable, Document, Object, Kernel, BasicObject]
include Documentした際にはBook2の上にDocumentの継承チェーンを作る。include Printableの時にBook2の上にPrintableが配置され、Documentはひとつ上に押し上げられる。なので、例えばBook2でprintメソッドを呼び出した場合はPrintableモジュールのprintが実行される。
testing_selfを呼び出すとobjがselfになる。@varはobjのインスタンス変数になる。
class MyClass def testing_self @var = 10 my_method() self end def my_method @var = @var + 1 end end obj = MyClass.new p obj.testing_self # => #<MyClass:0x0000010092e180 @var=11>
常にselfのオブジェクトが何かを意識すること。
methodを呼び出さないとき、selfはどうなっているのか。
ruby-head :011 > self => main ruby-head :012 > self.class => Object
ruby-head :013 > class X; self; end => X
モジュールやクラスの定義のなかでは、selfはモジュールやクラスになる。
class C def public_method self.private_method end private def private_method; end end C.new.public_method
これはもちろん実行できない。privateキーワードは明示的なレシーバーをつけてプライベートメソッドを呼び出すことはできない。暗黙的なレシーバーselfに対するものでなければならない。よって、上記のコードはselfを削除すれば動く
print()みたいなどこからでも呼び出せるメソッドはKernelモジュールのプライベートインスタンスメソッド。
ObjectクラスがKernelモジュールをincludeしている
ruby-head :010 > Kernel.private_instance_methods.grep(/^pr/) => [:printf, :print, :proc]
このメソッドで継承チェーンを知ることが可能となる
class MyClass def my_method 'my_method()' end end class MySubclass < MyClass end obj = MySubclass.new p obj.my_method() p MySubclass.ancestors # => [MySubclass, MyClass, Object, Kernel, BasicObject]
my_method()を読みだすとRubyはレシーバであるobjからMySubcclassを検索。MySbuclassクラスにもメソッドがないので、さらに上位クラスのMyclassを探す。
class MyClass def my_method 'my_method()' end end class MySubclass < MyClass end obj = MySubclass.new p obj.my_method()
module Rake class Task …
Taskクラスの完全な名前はRake::Taskになった。これで名前が衝突することがなくなる。Rakeのような定数をまとめただけのmoduleをネームスペースと呼ぶ
現在のスコープにある全ての定数を返す
module M Y = '他の定数' class C ::M::Y end end p M.constants # => [:Y, :C]
クラス名Cも定数。
Module.constants()は現在のプログラムのトップレベルにある定数を返す。
ruby-head :008 > Module.constants => [:Object, :Module, :Class, :Kernel, :NilClass, :NIL, :Data, :TrueClass, :TRUE, :FalseClass, :FALSE, :Encoding, :Comparable, :Enumerable, :String, :Symbol, :Exception, :SystemExit, :SignalException, :Interrupt, :StandardError, :TypeError, :ArgumentError, :IndexError, :KeyError, :RangeError, :ScriptError, :SyntaxError, :LoadError, :NotImplementedError, :NameError, :NoMethodError, :RuntimeError, :SecurityError, :NoMemoryError, :EncodingError, :SystemCallError, :Errno, :ZeroDivisionError, :FloatDomainError, :Numeric, :Integer, :Fixnum, :Float, :Bignum, :Array, :Hash, :ENV, :Struct, :RegexpError, :Regexp, :MatchData, :Marshal, :Range, :IOError, :EOFError, :IO, :STDIN, :STDOUT, :STDERR, :ARGF, :FileTest, :File, :Dir, :Time, :Random, :Signal, :Process, :Proc, :LocalJumpError, :SystemStackError, :Method, :UnboundMethod, :Binding, :Math, :GC, :ObjectSpace, :Enumerator, :StopIteration, :RubyVM, :Thread, :TOPLEVEL_BINDING, :ThreadGroup, :Mutex, :ThreadError, :Fiber, :FiberError, :Rational, :Complex, :RUBY_VERSION, :RUBY_RELEASE_DATE, :RUBY_PLATFORM, :RUBY_PATCHLEVEL, :RUBY_REVISION, :RUBY_DESCRIPTION, :RUBY_COPYRIGHT, :RUBY_ENGINE, :ARGV, :Gem, :TSort, :RbConfig, :Config, :CROSS_COMPILING, :Exception2MessageMapper, :IRB, :RubyToken, :RubyLex, :Readline, :Date, :BasicObject]
現在のパスが必要ならばModule.nesting()を使う
module M class C module M2 p Module.nesting # => [M::C::M2, M::C, M] end end end
::で書き始めて外部の定数を絶対パスで指定する。
module M Y = '他の定数' class C ::M::Y end end ||<< * [meta] classのクラス >|ruby| ruby-head :003 > String.class => Class ruby-head :004 > Class.class => Class ruby-head :005 > Class.superclass => Module ruby-head :006 > Module.superclass => Object ruby-head :007 > String.superclass => Object
メタプログラミングとは、言語要素を実行時に操作するコードを記述すること
# -*- coding: utf-8 -*- require 'active_record' class Movie < ActiveRecord::Base end movie = Movie.create movie.title = "博士の異常な愛情" p movie.title
クラスの属性ごとにアクセサメソッドを書くのではなく、ActiveRecord::Baseを継承するだけで実行時にアクセサメソッドが定義されるようなコードを書いてある。