RemoteModel

JSON API <-> NSObject in one line. Powered by RubyMotion and BubbleWrap.

Example

Let's say we have some User and Question objects retrievable via our API. We can do fun stuff like:

# GET http://ourapi.com/users/1.json -> {:user => {id: 1}}
user = User.find(1) do |user|
  # async
  # GET http://ourapi.com/users/1/questions.json -> {:questions => [...]}
  Question.find_all(user_id: user.id) do |questions|
    # async
    puts questions
  end
end

# Later...
=> [#<Question @user=#<User>, 
    #<Question @user=#<User>]

Here's what our files look like:

./app/models/user

class User < RemoteModule::RemoteModel
  attr_accessor :id

  has_many :questions

  collection_url "users"
  member_url "users/:id"
end

./app/models/question.rb

class Question < RemoteModule::RemoteModel
  attr_accessor :id, :question, :is_active

  belongs_to :user

  collection_url "users/:user_id/questions"
  member_url "users/:user_id/questions/:id"

  custom_urls :active_url => member_url + "/make_active"

  # The urls substitute params based on a passed hash and/or object's methods,
  # so we define user_id to use for the collection/member urls
  def user_id
    user && user.id
  end

  # An example of how we can use custom URLs to make custom nice(r) methods
  # EX
  # a_question.make_active(false) do |question|
  #   p question.is_active
  # end
  def make_active(active)
    post(self.active_url, payload: {active: active}) do |response, json|
      self.is_active = json[:question][:is_active]
      if block_given?
        yield self
      end
    end
  end
end

Installation

gem install remote_model

And now in your Rakefile, require remote_model:

$:.unshift("/Library/RubyMotion/lib")
require 'motion/project'
require 'remote_model'

Motion::Project::App.setup do |app|
  ...
end

Setup

Add an initialization file somewhere, like ./app/initializers/remote_model.rb. This is where we put the API specifications:

module RemoteModule
  class RemoteModel
    # The default URL for our requests.
    # Overrideable per model subclass
    self.root_url = "http://localhost:5000/"

    # Options attached to every request
    # Appendable per model subclass
    # See BubbleWrap docs on what can be passed in BubbleWrap::HTTP.<method>(url, options)
    self.default_url_options = {
        :headers => {
          "x-api-token" => "some_token",
          "Accept" => "application/json"
        }
      }
  end
end

How?

RemoteModel is designed for JSON APIs which return structures with "nice" properties.

When you make a request with a RemoteModel (self.get/put/post/delete), the result is always parsed as JSON. The ActiveRecord-esque methods take this JSON and create objects out of it. It's clever and creates the proper associations (belongs_to/has_one/has_many) within the objects, as defined in the models.

FormatableString

The AR methods also use the member/collection defined URLs to make requests. These URLs are a string which you can use :symbols to input dynamic values. These strings can be formatted using a hash and/or using an object (it will look to see if the object responds to these symbols and call the method if applicable):

>> s = RemoteModule::FormatableString.new("url/:param")
=> "url/:param"
>> s.format({param: 6})
=> "url/6"
>> obj = Struct.new("Paramer", :param).new(param: 100)
=> ...
>> s.format({}, obj)
=>  "url/100"

RemoteModels can define custom urls and call those as methods (see question.rb above).

Todo

  • More tests
  • CoreData integration