LoginSignup
3
1

More than 5 years have passed since last update.

rails内の探検

Last updated at Posted at 2018-04-10

databaseとの通信

sample.rb
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を叩くことができる。

sample.rb
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を参照したり、更新したりするためのメソッドがある。
一つ一つのメソッドを実行しながら、解説していくと、

sample.rb
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を作ろうと思ったら、

sample.rb
class Movie < ActiveRecord::Base
end

とするだけで,

sample.rb
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はオブジェクトのアトリビュートを保持したり、妥当性を追跡したりする部分。
例えば、

sample.rb
class Duck < ActiveRecord::Base
  validate do
    errors.add(:base, "Illegal duck name.") unless name[0] == 'D'
  end
end

とすることで、

sample.rb
my_duck = Duck.new
my_duck.name = "Donald"
my_duck.valid? #=> true

といったようにvalid?メソッドを使うことができる。
validメソッドの定義を探すためにActiveRecord::Baseに行くとしょっぱなで

sample.rb
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を呼び出すことで、出来上がっているということがわかる。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1