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,67 @@
1
+ module MotionResource
2
+ class Base
3
+ class << self
4
+ def find(id, params = {}, &block)
5
+ fetch_member(member_url.fill_url_params(params.merge(id: id)), &block)
6
+ end
7
+
8
+ def find_all(params = {}, &block)
9
+ fetch_collection(collection_url.fill_url_params(params), &block)
10
+ end
11
+
12
+ def fetch_member(url, &block)
13
+ get(url) do |response, json|
14
+ if response.ok?
15
+ obj = instantiate(json)
16
+ request_block_call(block, obj, response)
17
+ else
18
+ request_block_call(block, nil, response)
19
+ end
20
+ end
21
+ end
22
+
23
+ def fetch_collection(url, &block)
24
+ get(url) do |response, json|
25
+ if response.ok?
26
+ objs = []
27
+ arr_rep = nil
28
+ if json.class == Array
29
+ arr_rep = json
30
+ elsif json.class == Hash
31
+ plural = self.name.underscore.pluralize
32
+ if json.has_key?(plural) || json.has_key?(plural.to_sym)
33
+ arr_rep = json[plural] || json[plural.to_sym]
34
+ end
35
+ else
36
+ # the returned data was something else
37
+ # ie a string, number
38
+ request_block_call(block, nil, response)
39
+ return
40
+ end
41
+ arr_rep.each { |one_obj_hash|
42
+ objs << instantiate(one_obj_hash)
43
+ }
44
+ request_block_call(block, objs, response)
45
+ else
46
+ request_block_call(block, nil, response)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+ def request_block_call(block, default_arg, extra_arg)
53
+ if block
54
+ if block.arity == 1
55
+ block.call default_arg
56
+ elsif block.arity == 2
57
+ block.call default_arg, extra_arg
58
+ else
59
+ raise "Not enough arguments to block"
60
+ end
61
+ else
62
+ raise "No block given"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ module MotionResource
2
+ class Base
3
+ HTTP_METHODS = [:get, :post, :put, :delete]
4
+
5
+ HTTP_METHODS.each do |method|
6
+ define_method method do |*args, &block|
7
+ self.class.send(method, *args, &block)
8
+ end
9
+ end
10
+
11
+ class << self
12
+ def get(url, params = {}, &block)
13
+ http_call(:get, url, params, &block)
14
+ end
15
+
16
+ def post(url, params = {}, &block)
17
+ http_call(:post, url, params, &block)
18
+ end
19
+
20
+ def put(url, params = {}, &block)
21
+ http_call(:put, url, params, &block)
22
+ end
23
+
24
+ def delete(url, params = {}, &block)
25
+ http_call(:delete, url, params, &block)
26
+ end
27
+
28
+ private
29
+ def complete_url(fragment)
30
+ if fragment[0..3] == "http"
31
+ return fragment
32
+ end
33
+ (self.root_url || MotionResource::Base.root_url) + fragment
34
+ end
35
+
36
+ def http_call(method, url, call_options = {}, &block)
37
+ options = call_options
38
+ options.merge!(MotionResource::Base.default_url_options || {})
39
+ url += self.extension
40
+ if query = options.delete(:query)
41
+ if url.index("?").nil?
42
+ url += "?"
43
+ end
44
+ url += query.map{|k,v| "#{k}=#{v}"}.join('&')
45
+ end
46
+ if self.default_url_options
47
+ options.merge!(self.default_url_options)
48
+ end
49
+ BubbleWrap::HTTP.send(method, complete_url(url), options) do |response|
50
+ if response.ok?
51
+ body = response.body.to_str.strip rescue nil
52
+ if body.blank?
53
+ block.call(response, {})
54
+ else
55
+ block.call response, BubbleWrap::JSON.parse(body)
56
+ end
57
+ else
58
+ block.call response, nil
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,23 @@
1
+ class String
2
+ # Takes in a hash and spits out the formatted string
3
+ # Checks the delegate first
4
+ def fill_url_params(params = {}, delegate = nil)
5
+ params ||= {}
6
+ split = self.split '/'
7
+ split.collect { |path|
8
+ ret = path
9
+ if path[0] == ':'
10
+ path_sym = path[1..-1].to_sym
11
+
12
+ curr = nil
13
+ if delegate && delegate.respond_to?(path_sym)
14
+ curr = delegate.send(path_sym)
15
+ end
16
+
17
+ ret = (curr || params[path_sym] || path).to_s
18
+ end
19
+
20
+ ret
21
+ }.join '/'
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module MotionResource
2
+ class Base
3
+ class_inheritable_accessor :collection_url, :member_url
4
+ class_inheritable_accessor :root_url, :default_url_options
5
+ class_inheritable_accessor :extension
6
+ self.extension = '.json'
7
+
8
+ class << self
9
+ def custom_urls(params = {})
10
+ params.each do |name, url_format|
11
+ define_method name do |params = {}|
12
+ url_format.fill_url_params(params, self)
13
+ end
14
+ define_singleton_method name do
15
+ url_format
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def collection_url(params = {})
22
+ self.class.collection_url.fill_url_params(params, self)
23
+ end
24
+
25
+ def member_url(params = {})
26
+ self.class.member_url.fill_url_params(params, self)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module MotionResource
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/motion-resource/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "motion-resource"
6
+ s.version = MotionResource::VERSION
7
+ s.authors = ["Thomas Kadauke"]
8
+ s.email = ["thomas.kadauke@googlemail.com"]
9
+ s.homepage = "https://github.com/tkadauke/motion-resource"
10
+ s.summary = "Access RESTful resources from your iOS app"
11
+ s.description = "Access RESTful resources from your iOS app. Inspired by ActiveResource."
12
+
13
+ s.files = `git ls-files`.split($\)
14
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
15
+ s.require_paths = ["lib"]
16
+
17
+ s.add_dependency 'bubble-wrap'
18
+ s.add_dependency 'motion-support'
19
+ s.add_development_dependency 'rake'
20
+ end
@@ -0,0 +1,41 @@
1
+ MotionResource::Base.root_url = 'http://example.com/'
2
+
3
+ class Post < MotionResource::Base
4
+ attr_accessor :text
5
+
6
+ self.member_url = 'posts/:id'
7
+
8
+ has_many :comments
9
+ end
10
+
11
+ class Comment < MotionResource::Base
12
+ attr_accessor :post_id, :text
13
+
14
+ self.member_url = 'comments/:id'
15
+ self.collection_url = 'comments'
16
+
17
+ belongs_to :post
18
+
19
+ scope :recent, :url => 'comments/recent'
20
+
21
+ custom_urls :by_user_url => 'comments/by_user/:name'
22
+ end
23
+
24
+ class User < MotionResource::Base
25
+ self.member_url = 'users/:id'
26
+
27
+ has_one :profile
28
+ end
29
+
30
+ class Profile < MotionResource::Base
31
+ attr_accessor :name, :email
32
+ end
33
+
34
+ class Shape < MotionResource::Base
35
+ attribute :contents, :position
36
+ attr_accessor :created_at
37
+ end
38
+
39
+ class Rectangle < Shape
40
+ attribute :size
41
+ end
@@ -0,0 +1,116 @@
1
+ describe "belongs_to" do
2
+ extend WebStub::SpecHelpers
3
+
4
+ it "should define reader" do
5
+ Comment.new.should.respond_to :post
6
+ end
7
+
8
+ it "should define writer" do
9
+ Comment.new.should.respond_to :post=
10
+ end
11
+
12
+ it "should define reset method" do
13
+ Comment.new.should.respond_to :reset_post
14
+ end
15
+
16
+ it "should reset" do
17
+ comment = Comment.new
18
+ comment.post = Post.new
19
+ comment.reset_post
20
+ comment.post.should.be.nil
21
+ end
22
+
23
+ describe "reader" do
24
+ extend WebStub::SpecHelpers
25
+
26
+ before do
27
+ stub_request(:get, "http://example.com/posts/1.json").to_return(json: { id: 1, text: 'Hello' })
28
+ end
29
+
30
+ it "should by default return nil when called without a block" do
31
+ Comment.new.post.should.be.nil
32
+ end
33
+
34
+ it "should return any cached values when called without a block" do
35
+ comment = Comment.new
36
+ post = Post.new
37
+ comment.post = post
38
+ comment.post.should == post
39
+ end
40
+
41
+ it "should fetch resource when called with a block" do
42
+ @comment = Comment.new(:post_id => 1)
43
+ @comment.post do |result|
44
+ @result = result
45
+ resume
46
+ end
47
+
48
+ wait_max 1.0 do
49
+ @result.text.should == 'Hello'
50
+ end
51
+ end
52
+
53
+ it "should return cached resource immediately if exist when called with a block" do
54
+ post = Post.new
55
+ @comment = Comment.new
56
+ @comment.post = post
57
+ @comment.post do |result|
58
+ result.should == post
59
+ end
60
+ end
61
+
62
+ it "should cache resource after fetching" do
63
+ @comment = Comment.new(:post_id => 1)
64
+ @comment.post do |result|
65
+ @result = result
66
+ resume
67
+ end
68
+
69
+ wait_max 1.0 do
70
+ @comment.post.should == @result
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "writer" do
76
+ it "should create model when assigned with hash" do
77
+ comment = Comment.new
78
+ comment.post = { :id => 1, :text => 'Hello' }
79
+ comment.post.should.is_a Post
80
+ end
81
+
82
+ it "should set attributes when assigned with hash" do
83
+ comment = Comment.new
84
+ comment.post = { :id => 1, :text => 'Hello' }
85
+ comment.post.text.should == 'Hello'
86
+ end
87
+
88
+ it "should use identity map when assigned with hash" do
89
+ post = Post.instantiate(:id => 10)
90
+ comment = Comment.new
91
+ comment.post = { :id => 10 }
92
+ comment.post.should == post
93
+ end
94
+
95
+ it "should act like a regular setter when assigned with object" do
96
+ post = Post.new
97
+ comment = Comment.new
98
+ comment.post = post
99
+ comment.post.should == post
100
+ end
101
+ end
102
+
103
+ describe "piggybacking" do
104
+ it "should set association when returned with model" do
105
+ stub_request(:get, "http://example.com/comments/1.json").to_return(json: { id: 1, post: { id: 2, text: 'Hello' } })
106
+ Comment.find(1) do |result|
107
+ @result = result
108
+ resume
109
+ end
110
+
111
+ wait_max 1.0 do
112
+ @result.post.text.should == 'Hello'
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,128 @@
1
+ describe "has_many" do
2
+ extend WebStub::SpecHelpers
3
+
4
+ it "should define reader" do
5
+ Post.new.should.respond_to :comments
6
+ end
7
+
8
+ it "should define writer" do
9
+ Post.new.should.respond_to :comments=
10
+ end
11
+
12
+ it "should define reset method" do
13
+ Post.new.should.respond_to :reset_comments
14
+ end
15
+
16
+ it "should reset" do
17
+ post = Post.new
18
+ post.comments = [Comment.new]
19
+ post.reset_comments
20
+ post.comments.should == []
21
+ end
22
+
23
+ describe "reader" do
24
+ extend WebStub::SpecHelpers
25
+
26
+ before do
27
+ stub_request(:get, "http://example.com/comments.json").to_return(json: [{ id: 1, text: 'Whats up?' }])
28
+ end
29
+
30
+ it "should by default return an empty array when called without a block" do
31
+ Post.new.comments.should == []
32
+ end
33
+
34
+ it "should return any cached values when called without a block" do
35
+ comment = Comment.new
36
+ post = Post.new
37
+ post.comments = [comment]
38
+ post.comments.should == [comment]
39
+ end
40
+
41
+ it "should fetch resources when called with a block" do
42
+ @post = Post.new
43
+ @post.comments do |results|
44
+ @results = results
45
+ resume
46
+ end
47
+
48
+ wait_max 1.0 do
49
+ @results.size.should == 1
50
+ @results.first.text.should == 'Whats up?'
51
+ end
52
+ end
53
+
54
+ it "should return cached resources immediately if exist when called with a block" do
55
+ comment = Comment.new
56
+ @post = Post.new
57
+ @post.comments = [comment]
58
+ @post.comments do |results|
59
+ results.should == [comment]
60
+ end
61
+ end
62
+
63
+ it "should assign backward associations when fetching resources" do
64
+ @post = Post.new
65
+ @post.comments do |results|
66
+ @results = results
67
+ resume
68
+ end
69
+
70
+ wait_max 1.0 do
71
+ @results.first.post.should == @post
72
+ end
73
+ end
74
+
75
+ it "should cache resources after fetching" do
76
+ @post = Post.new
77
+ @post.comments do |results|
78
+ @results = results
79
+ resume
80
+ end
81
+
82
+ wait_max 1.0 do
83
+ @post.comments.should == @results
84
+ end
85
+ end
86
+ end
87
+
88
+ describe "writer" do
89
+ it "should create model when assigned with hash" do
90
+ post = Post.new
91
+ post.comments = [{ :id => 1, :text => 'Whats up?' }]
92
+ post.comments.first.should.is_a Comment
93
+ end
94
+
95
+ it "should set attributes when assigned with hash" do
96
+ post = Post.new
97
+ post.comments = [{ :id => 1, :text => 'Whats up?' }]
98
+ post.comments.first.text.should == 'Whats up?'
99
+ end
100
+
101
+ it "should use identity map when assigned with hash" do
102
+ comment = Comment.instantiate(:id => 10)
103
+ post = Post.new
104
+ post.comments = [{ :id => 10 }]
105
+ post.comments.first.should == comment
106
+ end
107
+
108
+ it "should act like a regular setter when assigned with array" do
109
+ post = Post.new
110
+ post.comments = [Comment.new]
111
+ post.comments.size.should == 1
112
+ end
113
+ end
114
+
115
+ describe "piggybacking" do
116
+ it "should set association when returned with model" do
117
+ stub_request(:get, "http://example.com/posts/1.json").to_return(json: { id: 1, comments: [{ id: 2, text: 'Whats up?' }] })
118
+ Post.find(1) do |result|
119
+ @result = result
120
+ resume
121
+ end
122
+
123
+ wait_max 1.0 do
124
+ @result.comments.first.text.should == 'Whats up?'
125
+ end
126
+ end
127
+ end
128
+ end