ここでは具体的な応用方法ではなく、単にModelを定義する方法を取り扱います。

class Article < Retriever::Model
  field.string :title,        required: true
  field.string :description
  field.time   :created
  field.int    :viewcount
  field.bool   :publish
  field.has    :author, User, required: true
  field.has    :category, Category
  field.has    :tags, [Tag]
end

フィールドの定義

上の例では、新しく定義したArticle Modelに、幾つかのフィールドを追加しています。 field.の直後に書いてあるstringなどがフィールドの型で、その後の :title などがフィールドの名前、その後にオプション値と続きます。

フィールドの型の種類

string

文字列です。記事のタイトルや本文、ユーザの名前など、あらゆるテキストに適しています。 Rubyコードでは、 String として表現されます。

どんなオブジェクトを入れても、 Object#to_s された結果が格納され、必ず String になります(1 のような数字を格納すると文字列の"1"になる)。

time

時刻です。投稿日時、変更日時といった、タイムスタンプに適しています。 Rubyコードでは、 Time として表現されます。

このフィールドにStringが渡された場合、その文字列は Time.parse の第一引数に渡され、その結果に置き換えられます。 また、 Integer が渡された場合は、 Time.at に渡され、その結果が利用されます。

int

整数です。数値IDなどを表すのに適しています。例では閲覧された回数を保持するのに利用しています。 Rubyコードでは、 Integer で表現されます。

どんなオブジェクトを入れても、 Object.to_i された結果が格納され、必ず Integer になります("1"のような文字列を格納すると数値の1になる)。

bool

真偽値です。フラグなどを表すのに適しています。 Rubyコードでは true(TrueClass) または false(FalseClass)で表現されます。

Rubyで真として扱われる値は true に、 偽として扱われる値は false に変換されます。 ただし、文字列の "false" だけは、偽として扱われます。これは、その昔Twitterが、真理値を返すべきフィールドに、falseという文字列を入れていたことがあったためです。 この変換が問題になるようなフィールドでは、!!valueのように、論理否定を2回行って、強制的に truefalse に変換したうえで、格納してください。

has(特殊な値)

Hasは特殊なキーワードで、第二引数に Retriever::Model のサブクラスを渡すことで、フィールドの値として別のModelをもたせることができます。

Retriever::Modelを持つ

例えばTwitterでは、全てのツイートは、投稿したツイッターユーザが必ず存在します。 こんな風に、Modelは異なるまたは同じModelに関係を持つことがあります。そういう場合には、このhasキーワードを使って次のように書きます。

field.has :user, User

これで、userフィールドにUserのインスタンスを格納することができるようになります。

複数の値を持つ

なかには、ひとつのフィールドに2つ以上の値をもたせたいこともあるでしょう。 例えば、Twitterのリストを表すUserListは、リストのメンバーとして複数のUserを持っています。

こういう場合には、次のように書きます。

field.has :member, [User]

hasキーワードを使い、第二引数の型を、[]で囲みます。こうすることで、その型の値の配列を格納するようになります。

必須フィールド

以上で定義したフィールドには、どれも nil を格納したり、そもそも値を格納しないということができてしまいます。 これは、例えば string と定義したにも関わらず、文字列でない可能性があるということです。

絶対に存在すべきフィールドは、次のように書くと、Modelが作られたときに例外 Retriever::InvalidTypeError が発生するようになります。

field.string :title, required: true

名前付き引数 required に真を渡します。

実際に扱う

初期化する

article = Article.new(
  title: "この松屋の玉子が浮いているように見える人は疲れているので寝ましょう",
  description: "ごにょごにょ",
  created: "2016-08-31 00:00:00 +09:00",
  viewcount: 114514,
  publish: true,
  author: toshi_a,
  category: category_test,
  tags: [tag_a, tag_b, tag_c]
)

コンストラクタに、Hashで値を渡します。キーはSymbolでフィールドの名前を、値はそれに対応する値です。

ここで、Modelを定義したときに宣言しなかったフィールドを指定しても、それは受け付けられ、記録されます。フィールドが定義されていないなどと、エラーになるわけではありません。

しかし、宣言されていないフィールドは、バリデーションや変換もされませんし、何よりアクセスするときに制限を受けます。

値を取り出す

格納した値をそのまま取得する

article[:title]

[]を使うと、格納したときの値をそのまま取得できます。引数には、Symbolでフィールド名を渡します。

例えばintのフィールドなら、どんな値を格納しても数値に変換することを試みると書きましたが、この方法を使って取得すれば、変換前の値を取得することができます。

また、存在しないフィールド名を渡した場合、単にnilが返り、例外が発生しません。

コンバートされる前の値が返ってくること、タイプ数が増えること、スペルミスに気づきにくいことから、この方法を使うことは原則おすすめできません。

一方で、これは宣言されていないフィールドにアクセスする唯一の方法でもあります。次に説明する方法では、未定義のフィールドにはアクセスできません。

変換後の値を取得する

article.title
article.get(:title)

フィールドの名前と同じ名前のメソッドが自動的に追加されているので、それを使ってアクセスすると、先に説明したような変換がされた値を取得できます。

また、取得するフィールドの名前が変数になっているようなケースに対応するために、getメソッドが用意されています。基本的に意味は同じですが、特に理由がない限り、最初の方法でアクセスすることをおすすめします。

この方法では、存在しないまたは未定義のフィールドにアクセスしようとすると、例外NoMethodErrorが発生します。

真理値を取得する

article.publish?

Rubyでは、truefalseを返すメソッドは、末尾を?にする習慣があります。Modelの定義された全てのフィールドについてもこのメソッドが用意され、値がセットされており、それが真であればtrueを返します。

値を格納する

article[:title] = "タイトル"
article.title = "タイトル"

それぞれ、値を取得するときのコードに、=演算子で値を代入することで、値をセットできます。 どちらの方法で値を格納しても結果に変わりはありません。

しかし、後者の方法では、定義していないフィールドに値を代入しようとするとNoMethodErrorが発生します。

当然、おすすめの書き方は後者のarticle.title=のスタイルです。

コーディングスタイル

例のコードでは、例えば field.time のあとのフィールド名が、他の行と同じ場所から始まるようにスペースをわざわざ入れています。 これは単に例を見やすくするためにやっていることで、実際にコードを書くときには必ずしも必要ではありません。 フィールドが多くなると見るだけでも大変になってくるので、実際のコードでもこのようなコーディングをするのも良いでしょう。