mikyung 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,17 @@
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
+
data/README.textile ADDED
@@ -0,0 +1,149 @@
1
+ h2. The need for a Rest client
2
+
3
+ *Let media types and relations* drive your client, as REST is.
4
+
5
+ But, Http, Verbs, Media types, Representations, Error codes, retrials, conditional requests, there are so many things we should do to implement a REST system.
6
+
7
+ Built upon Restfulie, *Mikyung* provides a layer of client generalization that gives you enough room to configure how your client should behave in different situations and make them work with different servers.
8
+
9
+ The following example can be executed from the example directory, just *clone and rake spec* it.
10
+
11
+ h2. Simple example
12
+
13
+ In order to create a REST client, anyone needs to have a goal and a sequence of steps that we can follow to pursue this goal. Mikyung implements a simple framework so you can define your goal and steps, and let everything else happen.
14
+
15
+ As a simple example, let's try to buy something in *any* REST server:
16
+
17
+ <pre>Mikyung.new(Buy.new).run('http://openbuystore.caelumobjects.com:/')</pre>
18
+
19
+ This is how you awake a REST client, that will start executing a series of requests until its objective is achieved.
20
+
21
+ If we want to achieve the same goal in another system, Mikyung and Restfulie will handle it all, just change the entry point URI. All its required is that your client understands the server's media types.
22
+
23
+ And now, our goal is to have a payment that is paid:
24
+
25
+ <pre>
26
+ class Buy < RelationDrivenObjective
27
+
28
+ def completed?(resource)
29
+ resource.respond_to?(:status) && resource.status == "paid"
30
+ end
31
+
32
+ executes FinishesOrder, :when => has_relation?(:payment)
33
+ executes PickProduct, :when => has_relation?(:basket)
34
+ executes SearchProducts, :when => has_relation?(:search)
35
+
36
+ end
37
+ </pre>
38
+
39
+ If there is a search relation, let's search for our items:
40
+
41
+ <pre>
42
+ class SearchProducts
43
+ def execute(entry)
44
+ entry.search.get("rest")
45
+ end
46
+ end
47
+ </pre>
48
+
49
+ How will it work? Restfulie will content negotiate and discover that your server understands, lets say, opensearch. Using an OpenSearch media type handler, it will serialize your object to execute the query.
50
+
51
+ If your server understood Yahoo's YQL and provided an YQL specification handler, Restfulie would execute the query using YQL.
52
+
53
+ The next step is to choose the cheapest and the first product on the list:
54
+
55
+ <pre>
56
+ class PickProduct
57
+
58
+ def execute(list)
59
+
60
+ cheapest = list.entries...
61
+
62
+ # adds the cheapest and the first product
63
+ basket = [{:id => cheapest.id, :quantity => 1}, {:id => list.entries[0].id, :quantity => 1}]
64
+
65
+ list.basket.post!(basket)
66
+ end
67
+ end
68
+ </pre>
69
+
70
+ Again, if your server implements a well known e-commerce media type, Restfulie will serialize and handle everything for you. If your server uses a custom one, you can create your own handler and contribute with our project (although REST systems should, as much as possible, stick to well known media types).
71
+
72
+ Let's buy our products:
73
+
74
+ <pre>
75
+ class FinishesOrder
76
+ def execute(basket)
77
+ payment = {:creditcard_number => "4850000000000001", :creditcard_holder => "guilherme silveira", :creditcard_expires => "10/2020", :creditcard_code => "123", :amount => basket.price}
78
+ basket.payment.post!(payment)
79
+ end
80
+ end
81
+ </pre>
82
+
83
+ Again, media type handling, content negotiation, retrials, everything is handled by Mikyung and Restfulie.
84
+
85
+ Now you can rest... or keep reading
86
+
87
+ h2. The power
88
+
89
+ The above example should suffice to buy something in *any* REST system that contains the basket and payment process.
90
+
91
+ h2. How?
92
+
93
+ All you need to do is understand some media type that your server does. If the server understands well-known ones (as per the REST definition), contribute to Restfulie with your media type implementation.
94
+
95
+ If the server understand a custom media type, it should be their job to provide a media type handler, as they did not follow any standard.
96
+
97
+ Note that providing a media type handler does not mean that your client will follow some specific steps, but whenever it follow some steps, it will it as the media type described.
98
+
99
+ h2. Team and Support
100
+
101
+ Guilherme Silveira is behind Mikyung and support can be received through Restfulie's mailing lists.
102
+
103
+ h2. Registering media types and much more
104
+
105
+ Mikyung uses Restfulie so everything related with content type negotiation should be configured there.
106
+
107
+ h2. Objective and Steps
108
+
109
+ Everything related to objective, steps, error code handling, execution postponing can be configured or hacked into Mikyung.
110
+
111
+ h2. Objective examples
112
+
113
+ You can create your own objective builders, with the required conditions that your goal achiever requires.
114
+ Mikyung provides one objective builder called *RelationDrivenObjective* to describe how to use it. The following example shows how to achieve a buying objective:
115
+
116
+ [pre]
117
+ class Buy < RelationDrivenObjective
118
+
119
+ executes FinishesOrder, :when => has_relation(:payment)
120
+ executes PickProduct, :when => has_relation(:basket)
121
+ executes SearchProducts, :when => has_relation(:search)
122
+
123
+ def completed?(resource)
124
+ resource.respond_to?(:status) && resource.status == "paid"
125
+ end
126
+
127
+ end
128
+ [/pre]
129
+
130
+
131
+ A less declarative approach is to implement both the completed? and *next_step* methods:
132
+
133
+ [pre]
134
+ class Buy
135
+
136
+ def completed?(resource)
137
+ resource.respond_to?(:status) && resource.status == "paid"
138
+ end
139
+
140
+ def next_step(resource)
141
+ options = [ ["payment", FinishesOrder], ["basket", PickProduct], ["search", SearchProducts]]
142
+ step = options.find do |k|
143
+ resource.respond_to?(k.first)
144
+ end
145
+ step ? step.last : nil
146
+ end
147
+
148
+ end
149
+ [/pre]
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'rubygems/specification'
3
+ require 'rake'
4
+ require 'rake/gempackagetask'
5
+ require 'spec/rake/spectask'
6
+ require 'rake/rdoctask'
7
+
8
+ GEM = "mikyung"
9
+ GEM_VERSION = "0.7.0"
10
+ SUMMARY = "Mikyung is a restful client built on top of Restfulie that allows real rest clients to be built."
11
+ AUTHOR = "Guilherme Silveira"
12
+ EMAIL = "guilherme.silveira@caelum.com.br"
13
+ HOMEPAGE = "http://restfulie.caelumobjects.com"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = GEM
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.summary = SUMMARY
20
+ s.require_paths = ['lib']
21
+ s.files = FileList['lib/**/*.rb', '[A-Z]*'].to_a
22
+ s.add_dependency("restfulie", [">= 0.7.0"])
23
+
24
+ s.author = AUTHOR
25
+ s.email = EMAIL
26
+ s.homepage = HOMEPAGE
27
+ end
28
+
29
+ Rake::GemPackageTask.new(spec) do |pkg|
30
+ pkg.gem_spec = spec
31
+ end
32
+
33
+ Rake::RDocTask.new("rdoc") do |rdoc|
34
+ rdoc.options << '--line-numbers' << '--inline-source'
35
+ # rdoc.rdoc_files.include('lib/**/**/*.rb')
36
+ end
37
+
38
+ begin
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new do |t|
41
+ t.files = ['lib/restfulie/**/*.rb', 'README.textile'] # optional
42
+ # t.options = ['--any', '--extra', '--opts'] # optional
43
+ end
44
+ rescue; end
45
+
46
+ desc "Install the gem locally"
47
+ task :install => [:package] do
48
+ sh %{gem install pkg/#{GEM}-#{GEM_VERSION} -l}
49
+ end
50
+
51
+ desc "Create a gemspec file"
52
+ task :make_spec do
53
+ File.open("#{GEM}.gemspec", "w") do |file|
54
+ file.puts spec.to_ruby
55
+ end
56
+ end
57
+
58
+ desc "Builds the project"
59
+ task :build => :install
60
+
61
+ desc "Default build will run specs"
62
+ task :default => :install
63
+
data/lib/mikyung.rb ADDED
@@ -0,0 +1,84 @@
1
+ class HasRelation
2
+ def initialize(rel)
3
+ @rel = rel
4
+ end
5
+
6
+ def matches?(resource)
7
+ resource.respond_to?(@rel)
8
+ end
9
+ end
10
+
11
+ class RelationDrivenObjective
12
+
13
+ def self.executes(what, options = {})
14
+ internals << [options[:when], what]
15
+ end
16
+
17
+ def self.has_relation?(rel)
18
+ HasRelation.new(rel)
19
+ end
20
+
21
+ def internals
22
+ self.class.internals
23
+ end
24
+
25
+ def next_step(resource)
26
+ step = internals.find do |k|
27
+ k.first.matches?(resource)
28
+ end
29
+ step ? step.last : nil
30
+ end
31
+
32
+ private
33
+
34
+ def self.internals
35
+ @steps ||= []
36
+ end
37
+
38
+ end
39
+
40
+
41
+
42
+ # iterates following a series of steps provided a goal and a starting uri.
43
+ #
44
+ # Mikyung.new(objective).run(uri)
45
+ class Mikyung
46
+
47
+ # initializes with a goal in mind
48
+ def initialize(goal)
49
+ @goal = goal
50
+ end
51
+
52
+ # keeps changing from a steady state to another until its goal has been achieved
53
+ def run(uri)
54
+
55
+ current = Restfulie.at(uri).get!
56
+ Restfulie::Common::Logger.logger.debug current.response.body
57
+
58
+ while(!@goal.completed?(current))
59
+ step = @goal.next_step(current)
60
+ raise "No step was found for #{current} with links #{current.links}" unless step
61
+ Restfulie::Common::Logger.logger.debug "Mikyung > next step will be #{step}"
62
+ current = try_to_execute(step.new, current, 3)
63
+ end
64
+
65
+ end
66
+
67
+ private
68
+
69
+ def try_to_execute(step, current, max_attempts)
70
+ raise "Unable to proceed when trying to #{step}" if max_attempts == 0
71
+
72
+ resource = step.execute(current)
73
+ if resource.response.code != 200
74
+ try_to_execute(step, max_attempts - 1)
75
+ else
76
+ Restfulie::Common::Logger.logger.debug resource.response.body
77
+ resource
78
+ end
79
+ end
80
+
81
+ def direct_execute(step, current, max_attempts)
82
+ step.execute(current)
83
+ end
84
+ end
@@ -0,0 +1,54 @@
1
+ class OpenSearchEngine
2
+
3
+ def initialize(uri)
4
+ @description = Restfulie.accepts("application/opensearchdescription+xml").at(uri).get!
5
+ end
6
+
7
+ def post!(terms)
8
+ urls = @description["OpenSearchDescription"]["Url"]
9
+ uri = urls["template"].gsub("{searchTerms}", terms).gsub("{startPage?}","1")
10
+ type = urls["type"]
11
+ Restfulie.at(uri).accepts(type).get!
12
+ end
13
+
14
+ include Restfulie::Client::HTTP::RequestMarshaller
15
+
16
+ end
17
+
18
+
19
+ module Restfulie::Common::Representation
20
+
21
+ class OpenSearch
22
+
23
+ cattr_reader :media_type_name
24
+ @@media_type_name = 'application/opensearchdescription+xml'
25
+
26
+ cattr_reader :headers
27
+ @@headers = {
28
+ :get => { 'Accept' => media_type_name },
29
+ :post => { }
30
+ }
31
+
32
+ #Convert raw string to rAtom instances
33
+ def unmarshal(content)
34
+ Hash.from_xml(content)
35
+ end
36
+
37
+ def marshal(content)
38
+ content
39
+ end
40
+
41
+ # prepares a link request element based on a relation.
42
+ def prepare_link_for(link)
43
+ if link.rel=="search"
44
+ OpenSearchEngine.new(link.href)
45
+ else
46
+ link
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ Restfulie::Client::HTTP::RequestMarshaller.register_representation("application/opensearchdescription+xml", Restfulie::Common::Representation::OpenSearch)
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mikyung
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ platform: ruby
6
+ authors:
7
+ - Guilherme Silveira
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-04-08 00:00:00 -03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: restfulie
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.7.0
24
+ version:
25
+ description:
26
+ email: guilherme.silveira@caelum.com.br
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/mikyung.rb
35
+ - lib/opensearch_mediatype.rb
36
+ - LICENSE
37
+ - Rakefile
38
+ - README.textile
39
+ has_rdoc: true
40
+ homepage: http://restfulie.caelumobjects.com
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options: []
45
+
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Mikyung is a restful client built on top of Restfulie that allows real rest clients to be built.
67
+ test_files: []
68
+