databaseとの通信
class Entity
attr_reader :table, :ident
def initialize(table,ident)
@table = table
@ident = ident
Database.sql "INSERT INTO #{table} (id) VALUES (#{@ident})"
end
def set(col, val)
Database.sql "UPDATE #{table) SET #{col}='#{val}' WHERE id = #{@ident}"
end
def get(col)
Database.sql("SELECT #{col} FROM #{@table} WHERE id =#{@ident}")[0][0]
end
end
このクラスはrubyでdatabaseに対して値を入れたり、出しているコード。
attr readerでインスタンス変数の、値を参照することを許している。
initializeメソッドはnewで新しくインスタンスができた際に呼ばれるので、新しくentity classのobjectができた時点で,指定されたtableに新しいidを作る。
インスタンス変数に格納することでインスタンス毎にtable名とそのidが保存されているので、getやsetでデータを更新したり、データを参照したいときはそのインスタンス変数を使うことで、自身の列を返すsqlを叩くことができる。
class Movie < Entity
def initialize(ident)
super "movies", ident
end
def title
get "title"
end
def title=(value)
set "title:, value"
end
def director
set "director"
end
def director=(value)
set "director", value
end
end
次のこれはEntity classのサブクラスのMovie classで、movies tableのtitleを参照したり、更新したりするためのメソッドがある。
一つ一つのメソッドを実行しながら、解説していくと、
movie = Movie.new(1)
movie.title = "star wars"
movie.director = "takahiro"
まず、Movie.new(1)でMovie classのinitializeを呼び出す、そうするとsuperで一つ上の階層のinitializeを引数("movies",1)で呼び出す。
tableはmoviesでidは1のものが作られる。table名とidはインスタンス変数として、保存されているので、movie.title = でEntity classのsetを呼び出して、title名を更新したり、deirector名を更新したりできる。
このようにrubyでdatabaseの更新をしたり、値を参照しようとしたとき、まず、Entityのようにsqlを叩く動作を集約したものを作って、table毎にそのサブクラスを作る。自分の使いたいカラムに対して読み取りと書き込み用に一つずつメソッドを定義していかなくちゃいけない。
ActiveRecord
railsはとても大雑把にいうと、databaseの内容をhtmlに差し込んで動的にページを生成してくれるというものなのでdatabaseとのやりとりが主な機能になる。ActiveRecordというライブラリはdatabaseの参照や書き込みをしてくれるもので、まさにrailsの根幹を担うライブラリだということができる。
例えば先のMovies classを作ろうと思ったら、
class Movie < ActiveRecord::Base
end
とするだけで,
movie = Movie.new(1)
movie.title = "star wars"
movie.title #=> "takahiro"
といった動作をすることができる。
ただ、ここにはtable名の定義もカラム名の定義もされておらず、なんなら、ActiveRecordないを探索しても、先ほどのコードのようにtitleを直接指定して値を参照したり、取ってきたりするコードはない。
table名に関しては単純で、movieからメソッドを呼ぶ際に自分自身のclass名を参照することによって、使うtable名を決定してくれる。
titleやdirectorについては、カラムを一つ一つ参照するためのメソッドがどこにも書いていないのに、実行時に正常に呼び出される。ActiveRecordではdbからカラムなどの情報を読み取って、それを元に動的にメソッドを作っている。(railsでいうschema.rb)
つまり、からむに合わせてtitle,title=などのメソッドを動的に定義してくれているということになる。そして、そのような動的にメソッドを追加することを可能にしてくれているのがrubyの黒魔術と呼ばれるメタプログラミングである。
メタプログラミングの定義を本から引っ張ってくると、
言語要素を実行時に操作するコードを記述すること
らしい。
自分で解釈して言葉を付け加えるとコード上の言語要素を実行時に操作して新たなコードを記述することだと思う。
漫画に例えると、実際に読まれる時にその時の状況に応じて漫画内で新しく次のコマを生成したり変更したりすることなのかなと。(適切なたとえではない自覚はある)
ActiveRecordの設計
ここからは実際にActiveRecord内の設計について触れていこうと思う。
ActiveRecordは実は内部でActiveModelとActiveRecordに別れている。
ActiveRecordは実際にdbの書き換えや読み込みをしたり、する部分。
ActiveModelはオブジェクトのアトリビュートを保持したり、妥当性を追跡したりする部分。
例えば、
class Duck < ActiveRecord::Base
validate do
errors.add(:base, "Illegal duck name.") unless name[0] == 'D'
end
end
とすることで、
my_duck = Duck.new
my_duck.name = "Donald"
my_duck.valid? #=> true
といったようにvalid?メソッドを使うことができる。
validメソッドの定義を探すためにActiveRecord::Baseに行くとしょっぱなで
require 'acitve_support'
require 'active_model'
でactive_modelを読んでいる。
ActiveRecord::Baseはたくさんのmoduleをincludeしているだけのclassで、その中からActiveRecord::Validationsを呼び出しているところが見つかる。
module Validationsないでdef valid?というのも見つかり、定義を発見することができた。ただ、module Validations内でActiveModel::Validationsをincludeしている部分が見つかる。似たようなmoduleが定義されているのには理由がある。それがまさに、上で書いたようにdbに手を加える部分と、アトリビュートの追跡で分けるためである。
このようにActiveRedordは様々なmoduleを呼び出すことで、出来上がっているということがわかる。