読者です 読者をやめる 読者になる 読者になる

CoreDataQuery は求めていた gem かもしれない

rubymotion ios

CoreDataQuery という CoreData ラッパー gem が公開された

開発元は RubyMotionQuery と同じ InfiniteRed。 RubyMotion ではラッパー gem じゃなくて、MagicalRecord や NLCoreData のような Objective-C のライブラリを使えばいいやって思いはじめていたとき、この gem を観測。 ざっと目を通したところ興味を引かれる gem だったので触ってみた。

CoreDataQuery の特徴

MagicalRecord や NLCoreData、MotionData や MotionDataWrapper と比べて、 CoreDataQuery が優れている点は次の2つ。

Rubyスキーマを定義できる

MagicalRecord や NLCoreData、あと MotionDataWrapper を使う場合、 Xcode Data Modeler でスキーマ定義していた。

でもせっかく RubyiOS アプリを開発しているんだから、 ActiveRecordマイグレーションファイルみたいに Rubyスキーマを定義したい。 CoreDataQuery はそんな望みを叶えてくれる。

Ruby で記述したスキーマから、CoreDataQuery が提供するタスクを使って xcdatamodeld ファイルを生成する。 実際にこの機能を提供しているのは CoreDataQuery ではなくて ruby-xcdm っていう gem だけど。

Named Scope をサポートしている

ActiveRecord と同じように

class Author < CDQManagedOjbect
  scope :a_authors, where(:name).begins_with('A')
  scope :prolific, where(:publish_count).gt(99)
end

という風に Named Scope を定義できる。 MotionDataWrapper は実装を試みているけどまだ完了していない(2014/01/15 時点)。

CoreDataQuery では where や sort_by が使えるけど、ActiveRecord とは使い方が少々異なっていた。

実際に CoreDataQuery を使ってみる

サンプルプロジェクトを作成
motion create CDQSample

でサンプルプロジェクトを作成。

Gemfile に

gem 'cdq'

を追加して

bundle install

を実行すればインストール完了。

あとは

bundle exec cdq init

を実行するとプロジェクトに対して下記の処理を行われる。

  • schemas ディレクトリが作成される
  • schemas/0001_initial.rb という最初のスキーマの雛形が作成される
  • build:simulator タスクが schema:build に依存する記述が Rakefile に追加される
スキーマの作成

cdq init で作成されたファイルにスキーマを定義する。

schema "0001 initial" do

  entity "Entry" do
    string :body
    datetime :created_at
    datetime :updated_at
  end

end

エンティティの属性には次の型が使える。

  • integer16
  • integer32
  • integer64
  • decimal
  • double
  • float
  • string
  • boolean
  • datetime
  • binary
  • transformable

スキーマを定義したら

bundle exec rake schema:build

を実行すれば xcdatamodeld ファイルが生成される。 ただ build:simulator タスクを実行すると schema:build も実行されるから、 このタスクをわざわざ実行しなくても大丈夫。

モデルの作成
bundle exec cdq create model entry

を実行すると

  • app/models/entry.rb
  • spec/models/entry.rb

が生成される。モデルのソースコードがこちら。

class Entry < CDQManagedObject

end

CDQManagedObject は NSManagedObject を継承して機能拡張したクラス。 実際には間に CoreDataQueryManagedObjectBase っていうクラスを挟んでいるけど。 CDQManagedObject には create や destroy なんかが定義されている。

CoreDataQuery を使ったサンプル

CoreDataQuery を使って Create・Read・Delete を行うサンプルは次の通り。 先ほど作成したスキーマとモデルをサンプルで使用している。

# coding: utf-8

class AppDelegate
  include CDQ

  def application(application, didFinishLaunchingWithOptions:launchOptions)
    # CoreDataQuery のセットアップ
    cdq.setup

    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = MainViewController.controllerWithNavigation
    @window.makeKeyAndVisible

    true
  end
end

class MainViewController < UITableViewController
  def self.controllerWithNavigation
    UINavigationController.alloc.initWithRootViewController(self.new)
  end

  def viewDidLoad
    super
    addButton = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
      UIBarButtonSystemItemAdd,
      target: self,
      action: "createEntry"
    )
    self.navigationItem.title = "CDQSample"
    self.navigationItem.leftBarButtonItem = self.editButtonItem
    self.navigationItem.rightBarButtonItem = addButton
  end

  def entries
    @entries ||= begin
      Entry.where(:body).
        contains("Te").
        sort_by(:created_at, :descending).
        array
    end
  end

  def createEntry
    Entry.create(
      body: "Test",
      created_at: NSDate.date,
      updated_at: NSDate.date
    )
    cdq.save

    reloadData
  end

  def reloadData
    @entries = nil
    self.tableView.reloadData
  end

  def tableView(tableView, numberOfRowsInSection:section)
    self.entries.size
  end

  def tableView(tableView, cellForRowAtIndexPath:indexPath)
    cell_id = "main-view-cell"
    entry = self.entries[indexPath.row]
    cell = tableView.dequeueReusableCellWithIdentifier(cell_id) ||
      UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:cell_id)
    cell.textLabel.text = entry.body
    cell
  end

  def tableView(tableView, commitEditingStyle:editingStyle, forRowAtIndexPath:indexPath)
    case editingStyle
    when UITableViewCellEditingStyleDelete
      entry = self.entries[indexPath.row]
      entry.destroy
      cdq.save

      reloadData
    end
  end
end

結局 CoreDataQuery ってどうなの?

自分が知る CoreData のラッパー gem の中では、今のところ最も完成度が高い。 ラッパー gem の中では CoreDataQuery 一択かも。

MagicalRecord や NLCoreData といった Objective-C ライブラリと比べてどちらが良いかは、 現段階ではなんとも言えない。 スキーママイグレーションをまだ試していないし、 スレッド毎に NSManagedObjectContext を管理するのも試していない。

RubyDSLスキーマを定義できたり、Named Scope が使えるのは個人的にかなりツボ。 今開発しているアプリに導入してみようと思う。