restfulie 0.5.0 → 0.6.0

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