torben-toepper
torben-toepper
Blog von Torben Toepper
1 post
Mehr über mich auf torbentoepper.de
Don't wanna be here? Send us removal request.
torben-toepper · 11 years ago
Text
RubyMotion on Rails (Part 1)
Bereits seit über einem Jahr code ich mit RubyMotion. Und ich muss sagen, ich bin immer noch sehr davon begeistert. Speziell die Möglichkeit der Blöcke und der asynchronen Programmierung (Dispatch) finde ich sehr angenehm. Ich finde durch die zwei Möglichkeiten kommt Ruby erst so richtig zur Geltung. Außerdem ist ein Ruby Block einfach viel besser als ein Objective-C Block...
Ein kleines Beispiel:
def do_it # Some really useful things... after 1.0 do puts "A block was called!" end # Some really useful things... end ... def after(time, &block) # block.weak! queue = Dispatch::Queue.current timer = Dispatch::Source.timer(time, Dispatch::TIME_FOREVER, 0.0, queue) do |src| begin block.call ensure src.cancel! end end end
Auch bin ich ein großer Fan von MotionModel. Es ist zwar noch nicht komplett fertig entwickelt, aber die flexible Möglichkeit der Datenablage finde ich super. Allerdings fehlte mir hierbei die Anbindung an eine API. Deshalb habe ich für das Gem eine Erweiterung geschrieben: MotionModelResource. Folgende Ziele hatte ich dabei:
so "leicht" wie möglich
RESTful
ActiveResource ähnlich
Aber nun zum Thema. Bei meinen Apps habe ich sehr häufig eine API dahinter. Rails hat uns vorgemacht, wie einfach und strukturiert wir APIs bauen können. Also sollte der Client (RubyMotion) auch so einfach wie möglich an die Daten ran kommen können. Durch die genannte Asynchronität und die Blöcke ist das auch sehr einfach.
# RubyMotion Task.fetch do |tasks| tasks.each do |task| puts task.name end end
So habe ich mir das vorgestellt und in meinem Gem umgesetzt. Somit hat man 100% Flexibilität und kann zB. ein Loading Grafik anzeigen, während geladen wird.
  Relations
Auch wichtig ist es Relationen abrufen zu können. ZB. hat ein User mehrere Tasks:
# Rails class User < ActiveRecord::Base has_many :projects end
# RubyMotion user = User.find(1) user.fetch do user.tasks.present? # Yes! end
Die URL wird im Model hinterlegt. Es gibt eine Klassen- und Instanzmethode „url“.
# RubyMotion class Task belongs_to :user ... def self.url "#{App.info_plist['host']}/tasks" end def url "#{App.info_plist['host']}/tasks/#{id}" end end
Die Felder von der API und dem lokalen System können über eine Mapping Tabelle zusammengeführt werden. Somit kann man auch Felder verbinden, die zum Beispiel im CamelCase oder im Underscore sind.
# RubyMotion class Task ... def self.wrapper @wrapper ||= { fields: { id: :id, name: :name, due_at: :due_at }, relations: [:user] } end end
Die wrapper Methode ist so aufgebaut, dass man in „fields“ die Felder verknüpft. Der Hash-Key ist der Remote-Name und die Hash-Value ist der lokale Name. In einer nächsten Version werde ich das optional machen. Der zweite Wert ist die Relations-Hash. Hier kann man alle verknüpften Models angeben. Wenn diese in der JSON Antwort enthalten sind, werden die Models auch angelegt und aktualisiert.
# JSON { "id": 1, "name": "Peter", "tasks": [{ "name": "Do it!" "due_date": null }] } …
  "Aaaand Action"
Ok soweit so gut. Schauen wir uns ein Beispiel an. Wir haben eine ToDo App und sind auf einem Eingabeformular, das beim abschicken einen neuen „Task“ anlegen soll. Diese soll auf dem Device und auf dem Server gespeichert werden. Der Server hat eine Authentifizierung über einen „API-Key“.
# RubyMotion def create_task(sender) # Called from UIButton down # TODO: Show loading indicator task = Task.new(name: input_field.text) # input_field is your UITextField task.save(params: params[:payload]) do |t| App.alert t.present? ? "Success!" : "Fail" # TODO: handle UI after save end end def params { payload: { api_key: current_user.try(:api_key) # User model } } end def current_user @current_user ||= User.find(App::Persistence["current_user_id"]) end
Super, wir haben einen ersten Eintrag. Auf der Rails Seite wird ein POST Request erstellt und der Task wird wie gewohnt als "task[name]" übergeben. Wichtig für den Wrapper ist, dass alle Actions von der Rails Seite, ein JSON Objekt mit den gewünschten Models zurückgeben. Wenn das nicht der Fall ist, wird es wie ein Fehler behandelt. Nun wollen wir alle Tasks in einer TableView anzeigen. Dafür habe ich ein ganz simples Beispiel gebaut, wie wir auch hier unsere Remote-Daten verwenden können:
# RubyMotion class TasksViewController < UITableViewController def viewDidLoad super tableView.registerClass(UITableViewCell, forCellReuseIdentifier:"Cell") fetch_tasks end def fetch_tasks # TODO: Show loading indicator Task.fetch(nil, params) do |tasks| # Loads the remote tasks @tasks = nil # emtpy to get a fresh result of tasks tableView.reloadData # reloads the table view end end def tasks @tasks ||= Task.all # First time it will be a [] end def numberOfSectionsInTableView(tableView) 1 end def tableView(tableView, numberOfRowsInSection:section) tasks.length end def tableView(tableView, cellForRowAtIndexPath:indexPath) task = tasks[indexPath.row] # gets the current task cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath) cell.textLabel.text = task.name cell end def params { payload: { api_key: current_user.try(:api_key) # User model } } end
  Installation
Die Installation und Konfiguration ist auf der github Seite beschrieben.
Der nächste Teil wird sein, die Models mit einem Observer auszustatten, der unsere Views bei Änderungen automatisch updated.
0 notes