RubyMotion で CoreData を使うなら MotionData を導入した方がいい

RubyMotion で CoreData が使えることは確認できた。

ただ、CoreData のエンティティ定義をコードで書くのは結構シンドイ。 NSManagedObjectContext の初期化やデータの操作を含めると、CoreData だけでかなりのコードを書くことになる。 正直、CoreData のラッパーを使ったほうがいい。

CoreData のラッパーで良さそうなのが、『MotionData』っていう gem。

Ruby の 有名 ORM 『DataMapper』風に定義できる。

MotionData は Bundler を使ってインストールできる。Gemfile に

gem 'motion_data', github:'alloy/MotionData'

を追加して bundle install 実行するだけ。 MotionData は RubyGems.org で公開されていないから、Github からダウンロードしないといけない。

前回作成した CoreData サンプルを、MotionData を使って書き変えてみたのがこちら。

# coding: utf-8

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    # CoreData を初期化
    path = File.join(NSHomeDirectory(), 'Documents', 'Guestbook.sqlite')
    MotionData.setupCoreDataStackWithSQLiteStore(path)

    nav = UINavigationController.alloc.initWithRootViewController(MasterController.alloc.init)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = nav
    @window.makeKeyAndVisible
    true
  end
end

# MotionData::ManagedObject を使えば
# DataMapper みたいにモデルを定義できる
class Entry < MotionData::ManagedObject
  property :title, String
  property :creation_date, Time
end

class MasterController < UITableViewController
  def viewDidLoad
    super

    navigationItem.title = 'Guestbook'
    navigationItem.leftBarButtonItem = editButtonItem
    navigationItem.rightBarButtonItem = UIBarButtonItem.alloc.initWithBarButtonSystemItem(
      UIBarButtonSystemItemAdd,
      target:self,
      action:'add_entry'
    )
  end

  def save
    MotionData::Context.main.saveChanges
    MotionData::Context.root.saveChanges
    @entries = nil
  end

  def add_entry
    # 追加
    Entry.new.tap do |entry|
      entry.title = 'foo'
      entry.creation_date = NSDate.date
    end
    save

    view.reloadData
  end

  # SQLite に保存したデータを取得
  def entries
    # 日付降順に並び替える
    @entries ||= Entry.all.sortBy(:creation_date, ascending:false).to_a
  end

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

  CellID = 'CellIdentifier'
  def tableView(tableView, cellForRowAtIndexPath:indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier(CellID) || UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:CellID)
    entry = entries[indexPath.row]

    @date_formatter ||= NSDateFormatter.alloc.init.tap do |df|
      df.timeStyle = NSDateFormatterMediumStyle
      df.dateStyle = NSDateFormatterMediumStyle
    end
    cell.textLabel.text = @date_formatter.stringFromDate(entry.creation_date)
    cell
  end

  def tableView(tableView, editingStyleForRowAtIndexPath:indexPath)
    UITableViewCellEditingStyleDelete
  end

  def tableView(tableView, commitEditingStyle:editingStyle, forRowAtIndexPath:indexPath)
    entry = entries[indexPath.row]

    # 削除
    MotionData::Context.main.deleteObject(entry)
    save

    tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation:UITableViewRowAnimationFade)
  end
end

エンティティの定義や NSManagedObjectContext の初期化といった 面倒事を MotionData がやってくれるから、コードがスッキリした。