restfulie 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,35 +1,88 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ module Restfulie::Server::Cache
18
+
19
+ class Config
20
+
21
+ def allow(seconds)
22
+ @max_age = seconds
23
+ self
24
+ end
25
+
26
+ def max_age
27
+ @max_age ||= 0
28
+ end
29
+ end
30
+ end
31
+
1
32
  module ActionController
2
33
  class Base
3
-
34
+
35
+ def self.cache
36
+ @cache_config ||= Restfulie::Server::Cache::Config.new
37
+ end
38
+
4
39
  # renders an specific resource to xml
5
40
  # using any extra options to render it (invoke to_xml).
6
41
  def render_resource(resource, options = {}, render_options = {})
7
- cache_info = {:etag => resource}
8
- cache_info[:last_modified] = resource.updated_at.utc if resource.respond_to? :updated_at
9
- if stale? cache_info
10
- options[:controller] = self
11
-
12
- respond_to do |format|
13
- add_media_responses(format, resource, options, render_options)
42
+
43
+ response.headers['Cache-control'] = "max-age=#{self.class.cache.max_age}"
44
+ return nil unless stale? resource.cache_info
45
+
46
+ return render(render_options) if render_options[:text]
47
+
48
+ options[:controller] = self
49
+ respond_to do |format|
50
+ add_media_responses(format, resource, options, render_options)
51
+ end
52
+
53
+ end
54
+
55
+ # renders a resource collection, making full use of atom support
56
+ def render_collection(collection, &block)
57
+ if block
58
+ content = collection.to_atom(:title =>collection_name, :controller => self) do |item|
59
+ block.call item
14
60
  end
61
+ else
62
+ content = collection.to_atom(:title => collection_name, :controller => self)
15
63
  end
64
+ render_resource collection, nil, {:content_type => 'application/atom+xml', :text => content}
16
65
  end
17
66
 
18
-
19
- def add_media_responses(format, resource, options, render_options)
20
- types = Restfulie::MediaType.default_types
21
- types = resource.class.media_types if resource.class.respond_to? :media_types
22
- types.each do |media_type|
23
- add_media_response(format, resource, media_type, options, render_options)
67
+ # returns the name of this controllers collection
68
+ def collection_name
69
+ self.class.name[/(.*)Controller/,1]
24
70
  end
25
- end
26
71
 
27
- def add_media_response(format, resource, media_type, options, render_options)
28
- controller = self
29
- format.send media_type.short_name.to_sym do
30
- media_type.execute_for(controller, resource, options, render_options)
72
+ def add_media_responses(format, resource, options, render_options)
73
+ types = Restfulie::MediaType.default_types
74
+ types = resource.class.media_types if resource.class.respond_to? :media_types
75
+ types.each do |media_type|
76
+ add_media_response(format, resource, media_type, options, render_options)
77
+ end
78
+ end
79
+
80
+ def add_media_response(format, resource, media_type, options, render_options)
81
+ controller = self
82
+ format.send media_type.short_name.to_sym do
83
+ media_type.execute_for(controller, resource, options, render_options)
84
+ end
31
85
  end
32
- end
33
86
 
34
87
  # adds support to rendering resources, i.e.:
35
88
  # render :resource => @order, :with => { :except => [:paid_at] }
@@ -1,50 +1,102 @@
1
- module Restfulie
2
- module Server
3
- module Instance
4
-
5
- # returns a list of available transitions for this objects state
6
- # TODO rename because it should never be used by the client...
7
- def available_transitions
8
- status_available = respond_to?(:status) && status!=nil
9
- return {:allow => []} unless status_available
10
- self.class.states[self.status.to_sym] || {:allow => []}
11
- end
12
-
13
- # returns a list containing all available transitions for this object's state
14
- def all_following_transitions
15
- all = [] + available_transitions[:allow]
16
- following_transitions.each do |t|
17
- t = Restfulie::Server::Transition.new(t[0], t[1], t[2], nil) if t.kind_of? Array
18
- all << t
19
- end
20
- all
21
- end
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
22
17
 
23
- # checks if its possible to execute such transition and, if it is, executes it
24
- def move_to(name)
25
- raise "Current state #{status} is invalid in order to execute #{name}. It must be one of #{transitions}" unless available_transitions[:allow].include? name
26
- self.class.transitions[name].execute_at self
27
- end
28
-
29
- # gets all the links for each transition
30
- def links(controller)
31
- links = []
32
- unless controller.nil?
33
- all_following_transitions.each do |transition|
34
- rel, uri = link_for(transition, controller)
35
- links << {:rel => rel, :uri => uri}
36
- end
37
- end
38
- links
39
- end
18
+ module Restfulie::Server::Cache
19
+
20
+ # returns specific cache information for this resource
21
+ # you may customize it and return a custom etag and last_modified fields
22
+ def cache_info
23
+ info = {:etag => self}
24
+ info[:etag] = self.etag if self.respond_to? :etag
25
+ info[:last_modified] = self.updated_at.utc if self.respond_to? :updated_at
26
+ info
27
+ end
28
+
29
+ end
30
+
31
+ # acts_as_restfulie instances implement this module
32
+ module Restfulie::Server::Instance
33
+
34
+ include Restfulie::Server::Cache
35
+
36
+ # checks whether this resource can execute a transition or has such a relation
37
+ def can?(what)
38
+ what = what.to_sym if what.kind_of? String
39
+ all_following_transitions.each do |t|
40
+ return true if t.name == what
41
+ end
42
+ false
43
+ end
44
+
45
+ # returns a list of available transitions for this objects state
46
+ # TODO rename because it should never be used by the client...
47
+ def available_transitions
48
+ status_available = respond_to?(:status) && status!=nil
49
+ return {:allow => []} unless status_available
50
+ self.class.states[self.status.to_sym] || {:allow => []}
51
+ end
52
+
53
+ # returns a list containing all available transitions for this object's state
54
+ def all_following_transitions
55
+ all = [] + available_transitions[:allow]
56
+ following_transitions.each do |t|
57
+ t = Restfulie::Server::Transition.new(t) if t.kind_of? Array
58
+ all << t
59
+ end
60
+ all
61
+ end
40
62
 
41
- private
42
- # gets a link for this transition
43
- def link_for(transition, controller)
44
- transition = self.class.existing_transition(transition.to_sym) unless transition.kind_of? Restfulie::Server::Transition
45
- transition.link_for(self, controller)
63
+ # checks if its possible to execute such transition and, if it is, executes it
64
+ def move_to(name)
65
+ raise "Current state #{status} is invalid in order to execute #{name}. It must be one of #{transitions}" unless available_transitions[:allow].include? name
66
+ self.class.transitions[name].execute_at self
67
+ end
68
+
69
+ # gets all the links for each transition
70
+ def links(controller)
71
+ links = []
72
+ unless controller.nil?
73
+ all_following_transitions.each do |transition|
74
+ rel, uri = link_for(transition, controller)
75
+ links << {:rel => rel, :uri => uri}
46
76
  end
77
+ end
78
+ links
79
+ end
80
+
81
+ private
82
+ # gets a link for this transition
83
+ def link_for(transition, controller)
84
+ transition = self.class.existing_transition(transition.to_sym) unless transition.kind_of? Restfulie::Server::Transition
85
+ transition.link_for(self, controller)
86
+ end
87
+
88
+ end
89
+
90
+ class Array
91
+
92
+ include Restfulie::Server::Cache
47
93
 
94
+ def updated_at
95
+ last = nil
96
+ each do |item|
97
+ last = item.updated_at if item.respond_to?(:updated_at) && (last.nil? || item.updated_at > last)
48
98
  end
99
+ last || Time.now
49
100
  end
50
- end
101
+
102
+ end
@@ -1,3 +1,19 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
1
17
 
2
18
  module Restfulie
3
19
 
@@ -0,0 +1,54 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Restfulie
19
+ module OpenSearch
20
+
21
+ class Description
22
+
23
+ def self.define(attribute, xml_attribute)
24
+
25
+ define_method("#{attribute.to_s}=") do |new_value|
26
+ self.instance_variable_set(xml_attribute, new_value)
27
+ end
28
+
29
+ define_method(attribute.to_s) do |new_value|
30
+ self.instance_variable_get xml_attribute
31
+ end
32
+
33
+ end
34
+
35
+ define(:short_name, :@ShortName)
36
+ define(:description, :@Description)
37
+ define(:tags, :@Tags)
38
+ define(:contact, :@Contact)
39
+
40
+ def initialize(name)
41
+ short_name = name
42
+ description = name
43
+ @types = []
44
+ end
45
+
46
+ def accepts(content_type)
47
+ @types << content_type
48
+ self
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'restfulie/server/opensearch/description'
@@ -1,3 +1,20 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
1
18
  module Restfulie
2
19
 
3
20
  module Server
@@ -39,14 +56,40 @@ module Restfulie
39
56
  head :ok
40
57
  end
41
58
 
59
+ # updates a resouce
60
+ def update
61
+ type = model_type
62
+
63
+ @loaded = type.find(params[:id])
64
+ return head(:status => 405) unless @loaded.can? :update
65
+
66
+ return head(415) unless fits_content(type, request.headers['CONTENT_TYPE'])
67
+
68
+ @model = Hash.from_xml(request.body.string)[model_name]
69
+ pre_update(@model) if self.respond_to?(:pre_update)
70
+
71
+ if @loaded.update_attributes(@model)
72
+ render_resource @loaded
73
+ else
74
+ render :xml => @loaded.errors, :status => :unprocessable_entity
75
+ end
76
+ end
77
+
42
78
  # returns the model for this controller
43
79
  def model_type
44
80
  self.class.name[/(.*)Controller/,1].singularize.constantize
45
81
  end
82
+
83
+ # retrieves the model name
84
+ def model_name
85
+ self.class.name[/(.*)Controller/,1].singularize.underscore
86
+ end
46
87
 
47
88
  def fits_content(type, content_type)
48
89
  Restfulie::MediaType.supports?(content_type) &&
49
- type.media_type_representations.include?(content_type)
90
+ (type.respond_to?(:media_type_representations) ?
91
+ type.media_type_representations.include?(content_type) :
92
+ Restfulie::MediaType.default_representations.include?(content_type))
50
93
  end
51
94
 
52
95
  end
@@ -1,3 +1,20 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
1
18
  module Restfulie
2
19
 
3
20
  module Server
@@ -8,13 +25,27 @@ module Restfulie
8
25
  attr_writer :options
9
26
  attr_accessor :result
10
27
 
11
- def initialize(name, options = {}, result = nil, body = nil)
28
+ def initialize(*args)
29
+ args[0].kind_of?(Array) ? from_array(*args) : from(*args)
30
+ end
31
+
32
+ private
33
+ def from_array(array)
34
+ @name = array[0]
35
+ @options = array.length>0 ? array[1] : nil
36
+ @result = array.length>1 ? array[2] : nil
37
+ @body = nil
38
+ end
39
+
40
+ def from(name, options = {}, result = nil, body = nil)
12
41
  @name = name
13
42
  @options = options
14
43
  @result = result
15
44
  @body = body
16
45
  end
17
46
 
47
+ public
48
+
18
49
  def action
19
50
  @options || {}
20
51
  end
@@ -1,15 +1,31 @@
1
- # module Hashi
2
- # class CustomHash
3
- # # uses_restfulie
4
- # def initialize(h)
5
- # @hash = h
6
- # link = h['link']
7
- # add_transitions([link]) if link.kind_of? Hash
8
- # add_transitions(link) if link.kind_of? Array
9
- # end
10
- # end
11
- # end
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
12
4
  #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ module Hashi
19
+ class CustomHash
20
+ def initialize(hash = {})
21
+ @internal_hash = hash
22
+ link = hash['link'] if hash.kind_of? Hash
23
+ add_transitions([link]) if link.kind_of? Hash
24
+ add_transitions(link) if link.kind_of? Array
25
+ end
26
+ end
27
+ end
28
+
13
29
  # module Jeokkarak
14
30
  # module Base
15
31
  # alias_method :old_from_hash_parse, :from_hash_parse
@@ -27,48 +43,63 @@ module Restfulie
27
43
  # basic code from Matt Pulver
28
44
  # found at http://www.xcombinator.com/2008/08/11/activerecord-from_xml-and-from_json-part-2/
29
45
  # addapted to support links
46
+
30
47
  def from_hash( hash )
31
48
  h = hash ? hash.dup : {}
32
49
  links = nil
33
- h.each do |key,value|
34
- case value.class.to_s
35
- when 'Array'
36
- if key=="link"
37
- links = h[key]
38
- h.delete("link")
39
- else
40
- h[key].map! { |e| reflect_on_association(key.to_sym ).klass.from_hash e }
41
- end
42
- when /\AHash(WithIndifferentAccess)?\Z/
43
- if key=="link"
44
- links = [h[key]]
45
- h.delete("link")
50
+ h.each do |key, value|
51
+ if key == "link"
52
+ links = value.instance_of?(Array) ? h[key] : [h[key]]
53
+ elsif [Array, Hash].include? value.class
54
+ klazz = get_the_class(key)
55
+ if value.instance_of?(Array)
56
+ h[key].map! { |e| (klazz || Hashi::CustomHash).send(:from_hash, e) }
57
+ elsif value.instance_of?(Hash)
58
+ h[key] = klazz.array_from_hash(value)
46
59
  else
47
- h[key] = reflect_on_association(key.to_sym ).klass.from_hash value
60
+ h[key] = klazz.from_hash value
48
61
  end
49
62
  end
50
- h.delete("xmlns") if key=="xmlns"
51
- end
52
- result = self.new h
53
- if !(links.nil?) && self.include?(Restfulie::Client::Instance)
54
- result.add_transitions(links)
55
- end
56
- result
57
- end
58
- end
59
- end
60
-
61
- module ActiveRecord
62
- class Base
63
- extend Restfulie::Unmarshalling
63
+ h.delete key if ["xmlns", "link"].include?(key)
64
+ end
65
+
66
+ result = make_new_object h
67
+ result.add_transitions(links) if !(links.nil?) && self.include?(Restfulie::Client::Instance)
68
+ result
69
+ end
70
+
71
+ # creates an array of this type based on a hash created using rails Hash.from_xml method
72
+ # because Hash.from_xml returns some no-cute hash structure when dealing with aligned elements
73
+ # we had to support it in a variety of ways.
74
+ # usage example:
75
+ #
76
+ # hash = {"player" => {"name" => "guilherme silveira"}}
77
+ # player = Player.array_from_hash(hash)
78
+ # puts player[0].name
79
+ #
80
+ # hash = {"player" => [{"name" => "guilherme silveira"}, {"name" => "andre de santi"}]}
81
+ # player = Player.array_from_hash(hash)
82
+ # puts player[0].name
83
+ # puts player[1].name
84
+ def array_from_hash(hash)
85
+
86
+ return self.from_hash(hash) unless (hash.size == 1 && hash.keys.first == self.to_s.underscore)
87
+
88
+ children = hash.values.first
89
+
90
+ return [self.from_hash(children)] unless children.instance_of?(Array)
64
91
 
65
- def self.from_json(json)
92
+ children.map { |child| self.from_hash(child) }
93
+
94
+ end
95
+
96
+ def from_json(json)
66
97
  from_hash(safe_json_decode(json).values.first)
67
98
  end
68
99
 
69
100
  # The xml has a surrounding class tag (e.g. ship-to),
70
101
  # but the hash has no counterpart (e.g. 'ship_to' => {} )
71
- def self.from_xml(xml)
102
+ def from_xml(xml)
72
103
  hash = Hash.from_xml xml
73
104
  head = hash[self.to_s.underscore]
74
105
  result = self.from_hash head
@@ -77,9 +108,20 @@ module ActiveRecord
77
108
  result
78
109
  end
79
110
 
111
+ private
112
+ def make_new_object(hash = {})
113
+ obj = self.new
114
+ hash.keys.each { |key| obj.send("#{key}=", hash[key]) }
115
+ obj
116
+ end
117
+
118
+ def get_the_class(name)
119
+ respond_to?(:reflect_on_association) ? reflect_on_association(name.to_sym).klass.to_s.constantize : nil
120
+ end
80
121
  end
81
- end
122
+ end
82
123
 
124
+ private
83
125
  def safe_json_decode(json)
84
126
  return {} if !json
85
127
  begin
data/lib/restfulie.rb CHANGED
@@ -1,3 +1,22 @@
1
+ #
2
+ # Copyright (c) 2009 Caelum - www.caelum.com.br/opensource
3
+ # All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'restfulie/logger'
19
+
1
20
  require 'net/http'
2
21
  require 'uri'
3
22
  require 'vendor/jeokkarak/jeokkarak'
@@ -13,9 +32,13 @@ require 'restfulie/server/marshalling'
13
32
  require 'restfulie/server/transition'
14
33
  require 'restfulie/server/restfulie_controller'
15
34
  require 'restfulie/server/atom_media_type'
35
+ require 'restfulie/unmarshalling'
16
36
 
17
- module Restfulie
37
+ # Author:: Guilherme Silveira (mailto:guilherme.silveira@caelum.com.br)
18
38
 
39
+ # This module controls global options for the Restfulie framework.
40
+ module Restfulie
41
+
19
42
  # Sets this class as a restfulie class.
20
43
  # You may pass a block defining your own transitions.
21
44
  #
@@ -37,6 +60,8 @@ module Restfulie
37
60
  extend Restfulie::MediaTypeControl
38
61
  include Restfulie::Server::Instance
39
62
  include Restfulie::Server::Marshalling
63
+ extend Restfulie::Unmarshalling #TODO still needs testing. e.g. server model has from_xml
64
+ #TODO and need to test (de)serialization with both AR and no-AR
40
65
 
41
66
  self.send :define_method, :following_transitions do
42
67
  transitions = []
@@ -49,4 +74,4 @@ end
49
74
 
50
75
  Object.extend Restfulie
51
76
 
52
- require 'restfulie/unmarshalling'
77
+ include ActiveSupport::CoreExtensions::Hash