restfulie 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/README.textile +83 -7
  2. data/Rakefile +98 -13
  3. data/lib/restfulie/client/base.rb +48 -53
  4. data/lib/restfulie/client/configuration.rb +69 -0
  5. data/lib/restfulie/client/http/adapter.rb +487 -0
  6. data/lib/restfulie/client/http/atom_ext.rb +87 -0
  7. data/lib/restfulie/client/http/cache.rb +28 -0
  8. data/lib/restfulie/client/http/error.rb +80 -0
  9. data/lib/restfulie/client/http/marshal.rb +147 -0
  10. data/lib/restfulie/client/http.rb +13 -0
  11. data/lib/restfulie/client.rb +8 -56
  12. data/lib/restfulie/common/builder/builder_base.rb +58 -0
  13. data/lib/restfulie/common/builder/helpers.rb +22 -0
  14. data/lib/restfulie/common/builder/marshalling/atom.rb +197 -0
  15. data/lib/restfulie/common/builder/marshalling/base.rb +12 -0
  16. data/lib/restfulie/common/builder/marshalling/json.rb +2 -0
  17. data/lib/restfulie/common/builder/marshalling.rb +16 -0
  18. data/lib/restfulie/common/builder/rules/collection_rule.rb +10 -0
  19. data/lib/restfulie/common/builder/rules/link.rb +20 -0
  20. data/lib/restfulie/common/builder/rules/links.rb +9 -0
  21. data/lib/restfulie/common/builder/rules/member_rule.rb +8 -0
  22. data/lib/restfulie/common/builder/rules/namespace.rb +25 -0
  23. data/lib/restfulie/common/builder/rules/rules_base.rb +76 -0
  24. data/lib/restfulie/common/builder.rb +16 -0
  25. data/lib/restfulie/common/errors.rb +9 -0
  26. data/lib/restfulie/{logger.rb → common/logger.rb} +3 -5
  27. data/lib/restfulie/common/representation/atom.rb +48 -0
  28. data/lib/restfulie/common/representation/generic.rb +33 -0
  29. data/lib/restfulie/common/representation/xml.rb +24 -0
  30. data/lib/restfulie/common/representation.rb +10 -0
  31. data/lib/restfulie/common.rb +23 -0
  32. data/lib/restfulie/server/action_controller/base.rb +31 -0
  33. data/lib/restfulie/server/action_controller/params_parser.rb +62 -0
  34. data/lib/restfulie/server/action_controller/restful_responder.rb +39 -0
  35. data/lib/restfulie/server/action_controller/routing/restful_route.rb +14 -0
  36. data/lib/restfulie/server/action_controller/routing.rb +12 -0
  37. data/lib/restfulie/server/action_controller.rb +15 -0
  38. data/lib/restfulie/server/action_view/helpers.rb +45 -0
  39. data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +15 -0
  40. data/lib/restfulie/server/action_view/template_handlers.rb +13 -0
  41. data/lib/restfulie/server/action_view.rb +8 -0
  42. data/lib/restfulie/server/configuration.rb +21 -0
  43. data/lib/restfulie/server/core_ext/array.rb +45 -0
  44. data/lib/restfulie/server/core_ext.rb +1 -0
  45. data/lib/restfulie/server/restfulie_controller.rb +1 -17
  46. data/lib/restfulie/server.rb +15 -0
  47. data/lib/restfulie.rb +4 -72
  48. data/lib/vendor/atom/configuration.rb +24 -0
  49. data/lib/vendor/atom/pub.rb +250 -0
  50. data/lib/vendor/atom/xml/parser.rb +373 -0
  51. data/lib/vendor/atom.rb +771 -0
  52. metadata +94 -33
  53. data/lib/restfulie/client/atom_media_type.rb +0 -75
  54. data/lib/restfulie/client/cache.rb +0 -103
  55. data/lib/restfulie/client/entry_point.rb +0 -94
  56. data/lib/restfulie/client/extensions/http.rb +0 -116
  57. data/lib/restfulie/client/helper.rb +0 -28
  58. data/lib/restfulie/client/instance.rb +0 -158
  59. data/lib/restfulie/client/request_execution.rb +0 -321
  60. data/lib/restfulie/client/state.rb +0 -36
  61. data/lib/restfulie/media_type.rb +0 -143
  62. data/lib/restfulie/media_type_control.rb +0 -115
  63. data/lib/restfulie/media_type_defaults.rb +0 -51
  64. data/lib/restfulie/server/atom_media_type.rb +0 -115
  65. data/lib/restfulie/server/base.rb +0 -91
  66. data/lib/restfulie/server/controller.rb +0 -122
  67. data/lib/restfulie/server/instance.rb +0 -102
  68. data/lib/restfulie/server/marshalling.rb +0 -47
  69. data/lib/restfulie/server/opensearch/description.rb +0 -54
  70. data/lib/restfulie/server/opensearch.rb +0 -18
  71. data/lib/restfulie/server/transition.rb +0 -93
  72. data/lib/restfulie/unmarshalling.rb +0 -131
  73. data/lib/vendor/jeokkarak/hashi.rb +0 -65
  74. data/lib/vendor/jeokkarak/jeokkarak.rb +0 -81
data/README.textile CHANGED
@@ -1,21 +1,97 @@
1
1
  h1. Web site
2
2
 
3
- Restulie's website can be found at "http://restfulie.caelum.com.br":http://restfulie.caelum.com.br
3
+ Restfulie's website can be found at "http://restfulie.caelum.com.br":http://restfulie.caelum.com.br
4
+ Mikyung's, a REST agent framework built on top of Restfulie can be seen at "http://github.com/caelum/mikyung":http://github.com/caelum/mikyung.
4
5
 
5
6
  h1. Quit pretending
6
7
 
7
- CRUD through HTTP is a good step forward to using resources and becoming RESTful, another step further into it is to make use of hypermedia based services and this gem allows you to do it really fast.
8
+ CRUD through HTTP is a good step forward to using resources and becoming RESTful, another step further into it is to make use of hypermedia and semantic meaningful media types: this gem allows you to do it really fast.
8
9
 
9
10
  You can read the "article on using the web for real":http://guilhermesilveira.wordpress.com/2009/11/03/quit-pretending-use-the-web-for-real-restfulie/ which gives an introduction to hypermedia/resources/services.
10
11
 
11
12
  h2. Why would I use restfulie?
12
13
 
13
- 1. Easy --> writing hypermedia aware resource based clients
14
- 2. Easy --> hypermedia aware resource based services
15
- 3. Small -> it's not a bloated solution with a huge list of APIs
16
- 4. HATEOAS --> clients you are unaware of will not bother if you change your URIs
17
- 5. HATEOAS --> services that you consume will not affect your software whenever they change part of their flow or URIs
14
+ # Easy --> writing hypermedia and semantic meaningful media type aware clients
15
+ # Small -> it's not a bloated solution with a huge list of APIs
16
+ # HATEOAS --> clients you are unaware of will not bother if you change your URIs
17
+ # HATEOAS --> resources that you consume will not affect your software whenever they change part of their flow or URIs
18
+ # Adaptability --> clients are able to adapt to your changes
19
+
20
+ h2. Installing
18
21
 
22
+ For client side usage, execute:
23
+
24
+ <pre>
25
+ gem install activesupport
26
+ gem install restfulie
27
+ </pre>
28
+
29
+ For server side usage, execute:
30
+
31
+ <pre>
32
+ gem install restfulie
33
+ </pre>
34
+
35
+ h2. Simple server example
36
+
37
+ In the server side, all you need to do is notify inherited_resources which media types you are able to represent your resource:
38
+
39
+ <pre>
40
+ class OrdersController < ApplicationController
41
+ include Restfulie::Server::ActionController::Base
42
+
43
+ respond_to :atom, :html, :xml, :commerce, :opensearch
44
+
45
+ def index
46
+ respond_with @orders = Order.all
47
+ end
48
+
49
+ def show
50
+ respond_with @order = Order.find(params[:id])
51
+ end
52
+
53
+ end
54
+ </pre>
55
+
56
+ That's it. Restfulie will take care of rendering a valid representation according to content negotiation. You can configure the rendering process through a custom tokamak view:
57
+
58
+ <pre>
59
+ describe_collection(@orders) do |collection|
60
+ collection.id = orders_url
61
+ collection.links << link( :rel => :create, :href => orders_url )
62
+ collection.describe_members
63
+ end
64
+ </pre>
65
+
66
+ You can go through an entire application by "watching this video":http://guilhermesilveira.wordpress.com or "downloading an example application":http://github.com/caelum/mikyung.
67
+
68
+ h2. Simple client example
69
+
70
+ The following example is a partial REST client implementation that navigates through some relations:
71
+
72
+ <pre>
73
+ order = Restfulie.at('http://localhost:3000/orders/1').get!
74
+ order.items.each { |item| puts items }
75
+ receipt = order.payment.post! { :amount => 500 }
76
+ puts receipt.id
77
+ </pre>
78
+
79
+ In order to create a full REST client, "watch this video":http://guilhermesilveira.wordpress.com.
80
+
81
+ h2. Download source example
82
+
83
+ You can view an entire application running Restfulie under *spec/integration/order/server* and *spec/integration/order/client* in this git repository.
84
+
85
+ "You can also download a full example of a REST based agent and server":http://github.com/caelum/mikyung using Restfulie and Mikyung, according to the Rest Architecture Maturity Model.
86
+
87
+ h2. Building the project
88
+
89
+ If you want to build the project and run its tests, remember to install all (client and server) required gems and:
90
+
91
+ <pre>
92
+ gem install rack-conneg
93
+ gem install responders_backport
94
+ </pre>
19
95
 
20
96
  <script type="text/javascript">
21
97
  var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
data/Rakefile CHANGED
@@ -3,12 +3,13 @@ require 'rubygems/specification'
3
3
  require 'rake'
4
4
  require 'rake/gempackagetask'
5
5
  require 'spec/rake/spectask'
6
+ require 'rake/rdoctask'
6
7
 
7
8
  GEM = "restfulie"
8
- GEM_VERSION = "0.6.0"
9
- SUMMARY = "Hypermedia aware resource based library in ruby (client side) and ruby on rails (server side)."
10
- AUTHOR = "Guilherme Silveira, Caue Guerra"
11
- EMAIL = "guilherme.silveira@caelum.com.br"
9
+ GEM_VERSION = "0.7.0"
10
+ SUMMARY = "Hypermedia aware resource based library in ruby (client side) and ruby on rails (server side)."
11
+ AUTHOR = "Guilherme Silveira, Caue Guerra"
12
+ EMAIL = "guilherme.silveira@caelum.com.br"
12
13
  HOMEPAGE = "http://restfulie.caelumobjects.com"
13
14
 
14
15
  spec = Gem::Specification.new do |s|
@@ -18,28 +19,111 @@ spec = Gem::Specification.new do |s|
18
19
  s.summary = SUMMARY
19
20
  s.require_paths = ['lib']
20
21
  s.files = FileList['lib/**/*.rb', '[A-Z]*'].to_a
21
- s.add_dependency("ratom", [">= 0.6.3"])
22
- # s.add_dependency("jeokkarak", [">= 1.0.3"])
23
-
24
- # s.add_dependency(%q<rubigen>, [">= 1.3.4"])
22
+ s.add_dependency("actionpack", [">= 2.3.2"])
23
+ s.add_dependency("activesupport", [">= 2.3.2"])
24
+ s.add_dependency("responders_backport", ["~> 0.1.0"])
25
25
 
26
26
  s.author = AUTHOR
27
27
  s.email = EMAIL
28
28
  s.homepage = HOMEPAGE
29
29
  end
30
30
 
31
- Spec::Rake::SpecTask.new do |t|
32
- t.spec_files = FileList['spec/**/*_spec.rb']
33
- t.spec_opts = %w(-fs -fh:doc/specs.html --color)
31
+ namespace :test do
32
+ def execute_process(name)
33
+ sh "ruby ./spec/units/client/#{name}.rb &"
34
+ %x(ps -ef | grep #{name}).split[1]
35
+ end
36
+ def process(name)
37
+ %x(ps -ef | grep #{name} | grep -v grep).split[1] || execute_process(name)
38
+ end
39
+ def start_server_and_invoke_test(task_name)
40
+ pid = process "fake_server"
41
+ puts "fake_server pid >>>> #{pid}"
42
+ Rake::Task[task_name].invoke
43
+ sh "kill -9 #{pid}"
44
+ end
45
+
46
+ desc "Execute integration Order tests"
47
+ task :integration do
48
+ integration_path = "spec/integration/order/server"
49
+
50
+ Dir.chdir(File.join(File.dirname(__FILE__), integration_path)) do
51
+ system('rake db:drop db:create db:migrate')
52
+ system('rake')
53
+ end
54
+ end
55
+
56
+ namespace :spec do
57
+ spec_opts = ['--options', File.join(File.dirname(__FILE__) , 'spec', 'units', 'spec.opts')]
58
+ Spec::Rake::SpecTask.new(:all) do |t|
59
+ t.spec_files = FileList['spec/units/**/*_spec.rb']
60
+ t.spec_opts = spec_opts
61
+ end
62
+ Spec::Rake::SpecTask.new(:common) do |t|
63
+ t.spec_files = FileList['spec/common/**/*_spec.rb']
64
+ t.spec_opts = spec_opts
65
+ end
66
+ Spec::Rake::SpecTask.new(:client) do |t|
67
+ t.spec_files = FileList['spec/units/client/**/*_spec.rb']
68
+ t.spec_opts = spec_opts
69
+ end
70
+ Spec::Rake::SpecTask.new(:server) do |t|
71
+ t.spec_files = FileList['spec/units/server/**/*_spec.rb']
72
+ t.spec_opts = spec_opts
73
+ end
74
+ end
75
+
76
+ namespace :rcov do
77
+ Spec::Rake::SpecTask.new('rcov') do |t|
78
+ options_file = File.expand_path('spec/units/spec.opts')
79
+ t.spec_opts = %w(-fs -fh:doc/specs.html --color)
80
+ t.spec_files = FileList['spec/units/**/*_spec.rb']
81
+ t.rcov = true
82
+ t.rcov_opts = ["-e", "/Library*", "-e", "~/.rvm", "-e", "spec", "-i", "bin"]
83
+ end
84
+ end
85
+
86
+ namespace :run do
87
+ task :all do
88
+ start_server_and_invoke_test('test:spec:all')
89
+ puts "Execution integration tests... (task test:integration)"
90
+ Rake::Task["test:integration"].invoke()
91
+ end
92
+ task :common do
93
+ start_server_and_invoke_test('test:spec:common')
94
+ end
95
+ task :client do
96
+ start_server_and_invoke_test('test:spec:client')
97
+ end
98
+ task :server do
99
+ start_server_and_invoke_test('test:spec:server')
100
+ end
101
+ task :rcov do
102
+ start_server_and_invoke_test('test:rcov:rcov')
103
+ end
104
+ end
34
105
  end
35
106
 
36
107
  Rake::GemPackageTask.new(spec) do |pkg|
37
108
  pkg.gem_spec = spec
38
109
  end
39
110
 
111
+ Rake::RDocTask.new("rdoc") do |rdoc|
112
+ rdoc.options << '--line-numbers' << '--inline-source'
113
+ # rdoc.rdoc_files.include('lib/**/**/*.rb')
114
+ end
115
+
116
+ begin
117
+ require 'yard'
118
+ YARD::Rake::YardocTask.new do |t|
119
+ t.files = ['lib/restfulie/**/*.rb', 'README.textile'] # optional
120
+ # t.options = ['--any', '--extra', '--opts'] # optional
121
+ end
122
+ rescue; end
123
+
40
124
  desc "Install the gem locally"
41
125
  task :install => [:package] do
42
- sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION} -l}
126
+ sh %{gem install pkg/#{GEM}-#{GEM_VERSION} -l}
43
127
  end
44
128
 
45
129
  desc "Create a gemspec file"
@@ -53,4 +137,5 @@ desc "Builds the project"
53
137
  task :build => :spec
54
138
 
55
139
  desc "Default build will run specs"
56
- task :default => :spec
140
+ task :default => ['test:run:all']
141
+
@@ -1,65 +1,60 @@
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
+ module Restfulie::Client#:nodoc
17
2
 
18
- class Hash
19
- def to_object(body)
20
- if keys.length>1
21
- raise "unable to parse an xml with more than one root element"
22
- elsif keys.length == 0
23
- self
24
- else
25
- type = keys[0].camelize.constantize
26
- type.from_xml(body)
3
+ module EntryPoint
4
+ include HTTP::RequestMarshaller
5
+ extend self
6
+
7
+ @resources_configurations = {}
8
+ def configuration_of(resource_name)
9
+ @resources_configurations[resource_name]
27
10
  end
28
- end
29
- end
30
11
 
31
- module Restfulie
12
+ def configuration_for(resource_name,configuration = Configuration.new)
13
+ yield configuration if block_given?
14
+ @resources_configurations[resource_name] = configuration
15
+ end
16
+
17
+ def retrieve(resource_name)
18
+ returning Object.new do |resource|
19
+ restore.extend(Base)
20
+ resource.configure
21
+ end
22
+ end
32
23
 
33
- # will execute some action in a specific URI
34
- def self.at(uri)
35
- Client::RequestExecution.new(nil, nil).at uri
36
24
  end
37
-
38
- module Client
39
- module Config
40
- BASIC_MAPPING = { :delete => Net::HTTP::Delete, :put => Net::HTTP::Put, :get => Net::HTTP::Get, :post => Net::HTTP::Post}
41
- DEFAULTS = { :destroy => Net::HTTP::Delete, :delete => Net::HTTP::Delete, :cancel => Net::HTTP::Delete,
42
- :refresh => Net::HTTP::Get, :reload => Net::HTTP::Get, :show => Net::HTTP::Get, :latest => Net::HTTP::Get, :self => Net::HTTP::Get}
43
25
 
44
- def self.self_retrieval
45
- [:latest, :refresh, :reload, :self]
46
- end
47
-
48
- def self.requisition_method_for(overriden_option,name)
49
- return BASIC_MAPPING[overriden_option.to_sym] if overriden_option
50
- DEFAULTS[name.to_sym] || Net::HTTP::Post
51
- end
52
-
26
+ module Base
27
+ include HTTP::RequestMarshaller
28
+
29
+ def self.included(base)#:nodoc
30
+ base.extend(self)
53
31
  end
54
-
55
- module Base
56
32
 
57
- def is_self_retrieval?(name)
58
- name = name.to_sym if name.kind_of? String
59
- Restfulie::Client::Config.self_retrieval.include? name
33
+ def uses_restfulie(configuration = Configuration.new,&block)
34
+ EntryPoint.configuration_for(resource_name,configuration,&block)
35
+ configure
36
+ end
37
+
38
+ def configure
39
+ configuration = EntryPoint.configuration_of(resource_name)
40
+ raise "Undefined configuration for #{resource_name}" unless configuration
41
+ at(configuration.entry_point)
42
+ configuration.representations.each do |representation_name,representation|
43
+ register_representation(representation_name,representation)
60
44
  end
61
-
62
45
  end
63
-
46
+
47
+ def resource_name
48
+ @resource_name || @resource_name = self.class.to_s.to_sym
49
+ end
50
+
64
51
  end
52
+
65
53
  end
54
+
55
+ # Shortcut to Restfulie::Client::EntryPoint
56
+ module Restfulie
57
+ include Client::EntryPoint
58
+ extend self
59
+ end
60
+
@@ -0,0 +1,69 @@
1
+ module Restfulie::Client #:nodoc:
2
+
3
+ # Use this class to configure the entry point and other relevant behaviors related to accessing or interacting with resources
4
+ #
5
+ # The available options are:
6
+ #
7
+ # * <tt>:entry_point</tt> - The URI for an entry point, such as http://resource.entrypoint.com/post
8
+ # * <tt>:representations</tt> - Representations.
9
+ #
10
+ # You can also store any other custom configuration.
11
+ #
12
+ # ==== Example
13
+ #
14
+ # configuration = Configuration.new
15
+ # configuration[:entry_point] = 'http://resource.entrypoint.com/post'
16
+ # configuration[:entry_point] # => 'http://resource.entrypoint.com/post'
17
+ #
18
+ # or you can use:
19
+ #
20
+ # configuration.entry_point = 'http://resource.entrypoint.com/post'
21
+ # configuration.entry_point # => 'http://resource.entrypoint.com/post'
22
+ class Configuration < ::Hash
23
+
24
+ # the current environment
25
+ attr_reader :environment
26
+
27
+ @@default_configuration = {
28
+ :entry_point => '',
29
+ :representations => {}
30
+ }
31
+
32
+ def initialize
33
+ super
34
+ self.environment = :development
35
+ end
36
+
37
+ # this will store a new configuration (based on the default) for the environment passed by value.
38
+ def environment=(value)
39
+ @environment = value
40
+ unless has_key?(@environment)
41
+ dee_clone = Marshal::load(Marshal::dump(@@default_configuration))
42
+ store(@environment,dee_clone)
43
+ end
44
+ @environment
45
+ end
46
+
47
+ # access (key) configuration value
48
+ def [](key)
49
+ fetch(@environment)[key]
50
+ end
51
+
52
+ # store on (key) configuration the value
53
+ def []=(key,value)
54
+ fetch(@environment)[key] = value
55
+ end
56
+
57
+ def method_missing(name, *args, &block)
58
+ method_name = name.to_s
59
+ if method_name.last == '='
60
+ fetch(environment)[method_name.chop.to_sym] = args[0]
61
+ else
62
+ value = fetch(environment)[name]
63
+ value ? value : super
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ end