motion-resource 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +9 -0
- data/Gemfile +8 -0
- data/README.md +53 -0
- data/Rakefile +14 -0
- data/app/app_delegate.rb +7 -0
- data/examples/Colr/.gitignore +13 -0
- data/examples/Colr/Rakefile +10 -0
- data/examples/Colr/app/app_delegate.rb +10 -0
- data/examples/Colr/app/color.rb +15 -0
- data/examples/Colr/app/color_view_controller.rb +46 -0
- data/examples/Colr/app/initializer.rb +2 -0
- data/examples/Colr/app/main_navigation_controller.rb +6 -0
- data/examples/Colr/app/tag.rb +5 -0
- data/examples/Colr/app/tags_view_controller.rb +23 -0
- data/lib/motion-resource.rb +5 -0
- data/lib/motion-resource/associations.rb +94 -0
- data/lib/motion-resource/attributes.rb +37 -0
- data/lib/motion-resource/base.rb +51 -0
- data/lib/motion-resource/crud.rb +33 -0
- data/lib/motion-resource/find.rb +67 -0
- data/lib/motion-resource/requests.rb +64 -0
- data/lib/motion-resource/string.rb +23 -0
- data/lib/motion-resource/urls.rb +29 -0
- data/lib/motion-resource/version.rb +3 -0
- data/motion-resource.gemspec +20 -0
- data/spec/env.rb +41 -0
- data/spec/motion-resource/associations/belongs_to_spec.rb +116 -0
- data/spec/motion-resource/associations/has_many_spec.rb +128 -0
- data/spec/motion-resource/associations/has_one_spec.rb +69 -0
- data/spec/motion-resource/associations/scope_spec.rb +21 -0
- data/spec/motion-resource/attributes_spec.rb +64 -0
- data/spec/motion-resource/base_spec.rb +54 -0
- data/spec/motion-resource/crud_spec.rb +141 -0
- data/spec/motion-resource/find_spec.rb +90 -0
- data/spec/motion-resource/requests_spec.rb +119 -0
- data/spec/motion-resource/string_spec.rb +26 -0
- data/spec/motion-resource/urls_spec.rb +52 -0
- metadata +140 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# MotionResource
|
2
|
+
|
3
|
+
This is a library for using JSON APIs in RubyMotion apps. It is based on [RemoteModel](https://github.com/clayallsopp/remote_model), however it is an almost complete rewrite. Also, it is inspired by ActiveResource.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add MotionResource to your Gemfile, like this:
|
8
|
+
|
9
|
+
gem "motion-resource"
|
10
|
+
|
11
|
+
## Example
|
12
|
+
|
13
|
+
Consider this example for a fictional blog API.
|
14
|
+
|
15
|
+
class User < RemoteModule::RemoteModel
|
16
|
+
attr_accessor :id
|
17
|
+
|
18
|
+
has_many :posts
|
19
|
+
|
20
|
+
collection_url "users"
|
21
|
+
member_url "users/:id"
|
22
|
+
end
|
23
|
+
|
24
|
+
class Post < RemoteModule::RemoteModel
|
25
|
+
attr_accessor :id, :user_id, :title, :text
|
26
|
+
|
27
|
+
belongs_to :user
|
28
|
+
|
29
|
+
collection_url "users/:user_id/posts"
|
30
|
+
member_url "users/:user_id/posts/:id"
|
31
|
+
end
|
32
|
+
|
33
|
+
Now, we can access a user's posts like that:
|
34
|
+
|
35
|
+
User.find(1) do |user|
|
36
|
+
user.posts do |posts|
|
37
|
+
puts posts.inspect
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Note that the blocks are called asynchronously.
|
42
|
+
|
43
|
+
## Setup
|
44
|
+
|
45
|
+
You can configure every model separately; however you will most likely want to configure things like the root_url the same for every model:
|
46
|
+
|
47
|
+
MotionResource::Base.root_url = "http://localhost:3000/"
|
48
|
+
|
49
|
+
Don't forget the trailing '/' here!
|
50
|
+
|
51
|
+
# Forking
|
52
|
+
|
53
|
+
Feel free to fork and submit pull requests!
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
$:.unshift("/Library/RubyMotion/lib")
|
3
|
+
require 'motion/project'
|
4
|
+
require "bundler/gem_tasks"
|
5
|
+
Bundler.setup
|
6
|
+
Bundler.require
|
7
|
+
|
8
|
+
$:.unshift("./lib/")
|
9
|
+
require './lib/motion-resource'
|
10
|
+
|
11
|
+
Motion::Project::App.setup do |app|
|
12
|
+
# Use `rake config' to see complete project settings.
|
13
|
+
app.name = 'MotionResource'
|
14
|
+
end
|
data/app/app_delegate.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
$:.unshift("/Library/RubyMotion/lib")
|
3
|
+
require 'motion/project'
|
4
|
+
require 'motion-support'
|
5
|
+
require 'motion-resource'
|
6
|
+
|
7
|
+
Motion::Project::App.setup do |app|
|
8
|
+
# Use `rake config' to see complete project settings.
|
9
|
+
app.name = 'Colr'
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class AppDelegate
|
2
|
+
attr_reader :window
|
3
|
+
|
4
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
5
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
6
|
+
@window.rootViewController = MainNavigationController.alloc.init
|
7
|
+
@window.makeKeyAndVisible
|
8
|
+
true
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Color < MotionResource::Base
|
2
|
+
attr_accessor :hex
|
3
|
+
|
4
|
+
has_many :tags
|
5
|
+
|
6
|
+
scope :random, :url => 'json/colors/random/7'
|
7
|
+
|
8
|
+
def ui_color
|
9
|
+
pointer = Pointer.new(:uint)
|
10
|
+
scanner = NSScanner.scannerWithString(self.hex)
|
11
|
+
scanner.scanHexInt(pointer)
|
12
|
+
rgbValue = pointer[0]
|
13
|
+
return UIColor.colorWithRed(((rgbValue & 0xFF0000) >> 16)/255.0, green:((rgbValue & 0xFF00) >> 8)/255.0, blue:(rgbValue & 0xFF)/255.0, alpha:1.0)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class ColorViewController < UITableViewController
|
2
|
+
attr_accessor :colors
|
3
|
+
|
4
|
+
def init
|
5
|
+
self.colors = []
|
6
|
+
self.title = "7 random colors"
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def viewDidLoad
|
11
|
+
load_data
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def tableView(tableView, numberOfRowsInSection:section)
|
16
|
+
self.colors.size
|
17
|
+
end
|
18
|
+
|
19
|
+
def tableView(tableView, cellForRowAtIndexPath:indexPath)
|
20
|
+
cell = tableView.dequeueReusableCellWithIdentifier('Cell')
|
21
|
+
cell ||= UITableViewCell.alloc.initWithStyle(UITableViewCellStyleSubtitle, reuseIdentifier:'Cell')
|
22
|
+
|
23
|
+
color = colors[indexPath.row]
|
24
|
+
cell.textLabel.text = color.hex
|
25
|
+
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator
|
26
|
+
cell
|
27
|
+
end
|
28
|
+
|
29
|
+
def tableView(tableView, willDisplayCell:cell, forRowAtIndexPath:indexPath)
|
30
|
+
color = colors[indexPath.row]
|
31
|
+
cell.backgroundColor = color.ui_color
|
32
|
+
end
|
33
|
+
|
34
|
+
def tableView(tableView, didSelectRowAtIndexPath:indexPath)
|
35
|
+
navigationController.pushViewController(TagsViewController.alloc.initWithColor(colors[indexPath.row]), animated:true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_data
|
39
|
+
Color.random do |results|
|
40
|
+
if results
|
41
|
+
self.colors = results
|
42
|
+
end
|
43
|
+
tableView.reloadData
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class TagsViewController < UITableViewController
|
2
|
+
attr_accessor :color
|
3
|
+
|
4
|
+
def initWithColor(color)
|
5
|
+
self.color = color
|
6
|
+
self.title = "Tags for color #{color.hex}"
|
7
|
+
init
|
8
|
+
end
|
9
|
+
|
10
|
+
def tableView(tableView, numberOfRowsInSection:section)
|
11
|
+
self.color.tags.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def tableView(tableView, cellForRowAtIndexPath:indexPath)
|
15
|
+
cell = tableView.dequeueReusableCellWithIdentifier('Cell')
|
16
|
+
cell ||= UITableViewCell.alloc.initWithStyle(UITableViewCellStyleSubtitle, reuseIdentifier:'Cell')
|
17
|
+
|
18
|
+
tag = color.tags[indexPath.row]
|
19
|
+
cell.textLabel.text = tag.name
|
20
|
+
cell.selectionStyle = UITableViewCellSelectionStyleNone
|
21
|
+
cell
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module MotionResource
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
def has_one(name)
|
5
|
+
define_method name do
|
6
|
+
instance_variable_get("@#{name}")
|
7
|
+
end
|
8
|
+
|
9
|
+
define_method "#{name}=" do |value|
|
10
|
+
klass = Object.const_get(name.to_s.classify)
|
11
|
+
value = klass.instantiate(value) if value.is_a?(Hash)
|
12
|
+
instance_variable_set("@#{name}", value)
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method "reset_#{name}" do
|
16
|
+
instance_variable_set("@#{name}", nil)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_many(name, params = lambda { |o| Hash.new })
|
21
|
+
backwards_association = self.name.underscore
|
22
|
+
|
23
|
+
define_method name do |&block|
|
24
|
+
if block.nil?
|
25
|
+
instance_variable_get("@#{name}") || []
|
26
|
+
else
|
27
|
+
cached = instance_variable_get("@#{name}")
|
28
|
+
block.call(cached) and return if cached
|
29
|
+
|
30
|
+
Object.const_get(name.to_s.classify).find_all(params.call(self)) do |results|
|
31
|
+
if results && results.first && results.first.respond_to?("#{backwards_association}=")
|
32
|
+
results.each do |result|
|
33
|
+
result.send("#{backwards_association}=", self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
instance_variable_set("@#{name}", results)
|
37
|
+
block.call(results)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
define_method "#{name}=" do |array|
|
43
|
+
klass = Object.const_get(name.to_s.classify)
|
44
|
+
instance_variable_set("@#{name}", [])
|
45
|
+
|
46
|
+
array.each do |value|
|
47
|
+
value = klass.instantiate(value) if value.is_a?(Hash)
|
48
|
+
instance_variable_get("@#{name}") << value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method "reset_#{name}" do
|
53
|
+
instance_variable_set("@#{name}", nil)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def belongs_to(name, params = lambda { |o| Hash.new })
|
58
|
+
define_method name do |&block|
|
59
|
+
if block.nil?
|
60
|
+
instance_variable_get("@#{name}")
|
61
|
+
else
|
62
|
+
cached = instance_variable_get("@#{name}")
|
63
|
+
block.call(cached) and return if cached
|
64
|
+
|
65
|
+
Object.const_get(name.to_s.classify).find(self.send("#{name}_id"), params.call(self)) do |result|
|
66
|
+
instance_variable_set("@#{name}", result)
|
67
|
+
block.call(result)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
define_method "#{name}=" do |value|
|
73
|
+
klass = Object.const_get(name.to_s.classify)
|
74
|
+
value = klass.instantiate(value) if value.is_a?(Hash)
|
75
|
+
instance_variable_set("@#{name}", value)
|
76
|
+
end
|
77
|
+
|
78
|
+
define_method "reset_#{name}" do
|
79
|
+
instance_variable_set("@#{name}", nil)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class << self
|
85
|
+
def scope(name, options = {})
|
86
|
+
custom_urls "#{name}_url" => options[:url] if options[:url]
|
87
|
+
|
88
|
+
metaclass.send(:define_method, name) do |&block|
|
89
|
+
fetch_collection(send("#{name}_url"), &block)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module MotionResource
|
2
|
+
class Base
|
3
|
+
class_inheritable_array :attributes
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def attribute(*fields)
|
7
|
+
attr_reader *fields
|
8
|
+
fields.each do |field|
|
9
|
+
define_method "#{field}=" do |value|
|
10
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
11
|
+
instance_variable_set("@#{field}", value.dup)
|
12
|
+
else
|
13
|
+
instance_variable_set("@#{field}", value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
self.attributes += fields
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_attributes(params = {})
|
22
|
+
attributes = self.methods - Object.methods
|
23
|
+
params.each do |key, value|
|
24
|
+
if attributes.member?((key.to_s + "=:").to_sym)
|
25
|
+
self.send((key.to_s + "=:").to_sym, value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes
|
31
|
+
self.class.attributes.inject({}) do |hash, attr|
|
32
|
+
hash[attr] = send(attr)
|
33
|
+
hash
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module MotionResource
|
2
|
+
class Base
|
3
|
+
attr_accessor :id
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
@new_record = true
|
7
|
+
update_attributes(params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_record?
|
11
|
+
@new_record
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def instantiate(json)
|
16
|
+
raise ArgumentError, "No :id parameter given for #{self.name}.instantiate" unless json[:id]
|
17
|
+
|
18
|
+
klass = if json[:type]
|
19
|
+
begin
|
20
|
+
Object.const_get(json[:type].to_s)
|
21
|
+
rescue NameError
|
22
|
+
self
|
23
|
+
end
|
24
|
+
else
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
if result = klass.recall(json[:id])
|
29
|
+
result.update_attributes(json)
|
30
|
+
else
|
31
|
+
result = klass.new(json)
|
32
|
+
klass.remember(result.id, result)
|
33
|
+
end
|
34
|
+
result.send(:instance_variable_set, "@new_record", false)
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
def identity_map
|
39
|
+
@identity_map ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def remember(id, value)
|
43
|
+
identity_map[id] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
def recall(id)
|
47
|
+
identity_map[id]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module MotionResource
|
2
|
+
class Base
|
3
|
+
def save(&block)
|
4
|
+
@new_record ? create(&block) : update(&block)
|
5
|
+
end
|
6
|
+
|
7
|
+
def update(&block)
|
8
|
+
self.class.put(member_url, :payload => { self.class.name.underscore => attributes }) do |response, json|
|
9
|
+
block.call json.blank? ? nil : self.class.instantiate(json) if block
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(&block)
|
14
|
+
# weird heisenbug: Specs crash without that line :(
|
15
|
+
dummy = self
|
16
|
+
self.class.post(collection_url, :payload => { self.class.name.underscore => attributes }) do |response, json|
|
17
|
+
block.call json.blank? ? nil : self.class.instantiate(json) if block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def destroy(&block)
|
22
|
+
self.class.delete(member_url) do |response, json|
|
23
|
+
block.call json.blank? ? nil : self.class.instantiate(json) if block
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload(&block)
|
28
|
+
self.class.get(member_url) do |response, json|
|
29
|
+
block.call json.blank? ? nil : self.class.instantiate(json) if block
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|