motion-resource 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +8 -0
  3. data/README.md +53 -0
  4. data/Rakefile +14 -0
  5. data/app/app_delegate.rb +7 -0
  6. data/examples/Colr/.gitignore +13 -0
  7. data/examples/Colr/Rakefile +10 -0
  8. data/examples/Colr/app/app_delegate.rb +10 -0
  9. data/examples/Colr/app/color.rb +15 -0
  10. data/examples/Colr/app/color_view_controller.rb +46 -0
  11. data/examples/Colr/app/initializer.rb +2 -0
  12. data/examples/Colr/app/main_navigation_controller.rb +6 -0
  13. data/examples/Colr/app/tag.rb +5 -0
  14. data/examples/Colr/app/tags_view_controller.rb +23 -0
  15. data/lib/motion-resource.rb +5 -0
  16. data/lib/motion-resource/associations.rb +94 -0
  17. data/lib/motion-resource/attributes.rb +37 -0
  18. data/lib/motion-resource/base.rb +51 -0
  19. data/lib/motion-resource/crud.rb +33 -0
  20. data/lib/motion-resource/find.rb +67 -0
  21. data/lib/motion-resource/requests.rb +64 -0
  22. data/lib/motion-resource/string.rb +23 -0
  23. data/lib/motion-resource/urls.rb +29 -0
  24. data/lib/motion-resource/version.rb +3 -0
  25. data/motion-resource.gemspec +20 -0
  26. data/spec/env.rb +41 -0
  27. data/spec/motion-resource/associations/belongs_to_spec.rb +116 -0
  28. data/spec/motion-resource/associations/has_many_spec.rb +128 -0
  29. data/spec/motion-resource/associations/has_one_spec.rb +69 -0
  30. data/spec/motion-resource/associations/scope_spec.rb +21 -0
  31. data/spec/motion-resource/attributes_spec.rb +64 -0
  32. data/spec/motion-resource/base_spec.rb +54 -0
  33. data/spec/motion-resource/crud_spec.rb +141 -0
  34. data/spec/motion-resource/find_spec.rb +90 -0
  35. data/spec/motion-resource/requests_spec.rb +119 -0
  36. data/spec/motion-resource/string_spec.rb +26 -0
  37. data/spec/motion-resource/urls_spec.rb +52 -0
  38. metadata +140 -0
@@ -0,0 +1,9 @@
1
+ .repl_history
2
+ build
3
+ resources/*.nib
4
+ resources/*.momd
5
+ resources/*.storyboardc
6
+ *.gem
7
+ .bundle
8
+ Gemfile.lock
9
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "bubble-wrap"
4
+ gem "motion-support"
5
+ gem "webstub", :git => 'git://github.com/mattgreen/webstub.git'
6
+
7
+ # Specify your gem's dependencies in motion-support.gemspec
8
+ gemspec
@@ -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!
@@ -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
@@ -0,0 +1,7 @@
1
+ class AppDelegate
2
+ def application(application, didFinishLaunchingWithOptions:launchOptions)
3
+ @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
4
+ @window.rootViewController = UIViewController.alloc.init
5
+ true
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ .repl_history
2
+ build
3
+ tags
4
+ resources/*.nib
5
+ resources/*.momd
6
+ resources/*.storyboardc
7
+ .DS_Store
8
+ nbproject
9
+ .redcar
10
+ #*#
11
+ *~
12
+ *.sw[po]
13
+ .eprj
@@ -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,2 @@
1
+ MotionResource::Base.root_url = 'http://www.colr.org/'
2
+ MotionResource::Base.extension = ''
@@ -0,0 +1,6 @@
1
+ class MainNavigationController < UINavigationController
2
+ def init
3
+ initWithRootViewController(ColorViewController.alloc.init)
4
+ self
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class Tag < MotionResource::Base
2
+ attr_accessor :name
3
+
4
+ belongs_to :color
5
+ 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,5 @@
1
+ require "motion-resource/version"
2
+ require 'bubble-wrap'
3
+ Dir.glob(File.join(File.dirname(__FILE__), 'motion-resource/*.rb')).each do |file|
4
+ BW.require file
5
+ 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