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.
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