rested 0.2.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.
- data/.gitignore +3 -0
- data/README.rdoc +96 -0
- data/Rakefile +28 -0
- data/VERSION +1 -0
- data/lib/rested/base.rb +97 -0
- data/lib/rested/debug.rb +59 -0
- data/lib/rested/entity.rb +247 -0
- data/lib/rested/error.rb +57 -0
- data/lib/rested/ext.rb +97 -0
- data/lib/rested/validations.rb +25 -0
- data/lib/rested.rb +7 -0
- metadata +109 -0
data/.gitignore
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
= rested
|
2
|
+
|
3
|
+
* http://github.com/chetan/rested
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Ruby library built on top of httpclient for working with RESTful APIs.
|
8
|
+
|
9
|
+
== DOCUMENTATION:
|
10
|
+
|
11
|
+
Documentation is available online at at rdoc.info[http://rdoc.info/projects/chetan/rested]
|
12
|
+
|
13
|
+
== FEATURES:
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
class Foo < Rested::Entity
|
18
|
+
base_url "http://api.example.com/entities/"
|
19
|
+
endpoint "foo"
|
20
|
+
user "user@example.com"
|
21
|
+
pass "foobar"
|
22
|
+
|
23
|
+
field :id, :baz, :bar
|
24
|
+
end
|
25
|
+
|
26
|
+
foo = Foo.find(1)
|
27
|
+
foo.baz = "frobnicator"
|
28
|
+
foo.save!
|
29
|
+
|
30
|
+
moe = Foo.new
|
31
|
+
moe.bar = "curly"
|
32
|
+
moe.save!
|
33
|
+
puts "new id is #{moe.id}"
|
34
|
+
|
35
|
+
# to upload a file
|
36
|
+
moe.add_file("param_name", "/path/to/file")
|
37
|
+
moe.save!
|
38
|
+
|
39
|
+
# non-entity based example
|
40
|
+
|
41
|
+
class Baz < Rested::Base
|
42
|
+
base_url "http://api.example.com/methods/"
|
43
|
+
user "user@example.com"
|
44
|
+
pass "foobar"
|
45
|
+
|
46
|
+
def self.search(terms)
|
47
|
+
params = {}
|
48
|
+
params["terms"] = terms
|
49
|
+
ret = post("/search", params)
|
50
|
+
ret["search_results"]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
== REQUIREMENTS:
|
57
|
+
|
58
|
+
* httpclient
|
59
|
+
* json
|
60
|
+
|
61
|
+
== INSTALL:
|
62
|
+
|
63
|
+
With gemcutter:
|
64
|
+
|
65
|
+
sudo gem install rested
|
66
|
+
|
67
|
+
Without gemcutter:
|
68
|
+
|
69
|
+
git clone git://github.com/chetan/rested.git
|
70
|
+
cd rested
|
71
|
+
sudo rake install
|
72
|
+
|
73
|
+
== LICENSE:
|
74
|
+
|
75
|
+
(The MIT License)
|
76
|
+
|
77
|
+
Copyright (c) 2010 Better Advertising, Inc.
|
78
|
+
|
79
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
80
|
+
a copy of this software and associated documentation files (the
|
81
|
+
'Software'), to deal in the Software without restriction, including
|
82
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
83
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
84
|
+
permit persons to whom the Software is furnished to do so, subject to
|
85
|
+
the following conditions:
|
86
|
+
|
87
|
+
The above copyright notice and this permission notice shall be
|
88
|
+
included in all copies or substantial portions of the Software.
|
89
|
+
|
90
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
91
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
92
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
93
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
94
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
95
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
96
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'rubygems'
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "rested"
|
7
|
+
gemspec.summary = "Ruby library for working with RESTful APIs"
|
8
|
+
gemspec.description = "Ruby library built on top of httpclient for working with RESTful APIs."
|
9
|
+
gemspec.email = "chetan@betteradvertising.com"
|
10
|
+
gemspec.homepage = ""
|
11
|
+
gemspec.authors = ["Chetan Sarva"]
|
12
|
+
gemspec.add_dependency('httpclient', '>= 2.1.5.2')
|
13
|
+
gemspec.add_dependency('json', '>= 1.4.2')
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require "rake/testtask"
|
21
|
+
desc "Run unit tests"
|
22
|
+
Rake::TestTask.new("test") { |t|
|
23
|
+
#t.libs << "test"
|
24
|
+
t.ruby_opts << "-rubygems"
|
25
|
+
t.pattern = "test/**/*_test.rb"
|
26
|
+
t.verbose = false
|
27
|
+
t.warning = false
|
28
|
+
}
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/lib/rested/base.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'httpclient'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Rested
|
7
|
+
|
8
|
+
module BaseMethods
|
9
|
+
|
10
|
+
# override this method to customize HTTPClient
|
11
|
+
def setup_client
|
12
|
+
@client = ::HTTPClient.new
|
13
|
+
if self.user and self.pass then
|
14
|
+
@client.set_auth(self.base_url, self.user, self.pass)
|
15
|
+
end
|
16
|
+
@client.debug_dev = STDOUT if Rested.debug
|
17
|
+
@client
|
18
|
+
end
|
19
|
+
|
20
|
+
def client
|
21
|
+
@client ||= setup_client()
|
22
|
+
end
|
23
|
+
|
24
|
+
def get(uri, params = nil)
|
25
|
+
url = self.base_url + uri
|
26
|
+
Rested.log_out{"GET #{url}"}
|
27
|
+
Rested.log_out{" params: " + params.inspect}
|
28
|
+
handle_response(self.client.get(url, params))
|
29
|
+
end
|
30
|
+
|
31
|
+
def post(uri, params = nil)
|
32
|
+
url = self.base_url + uri
|
33
|
+
Rested.log_out{"POST #{url}"}
|
34
|
+
Rested.log_out{" params: " + params.inspect}
|
35
|
+
handle_response(self.client.post(url, params))
|
36
|
+
end
|
37
|
+
|
38
|
+
def delete(uri)
|
39
|
+
url = self.base_url + uri
|
40
|
+
Rested.log_out{"DELETE #{url}"}
|
41
|
+
res = self.client.delete(url)
|
42
|
+
handle_error(res) if message.status >= 400
|
43
|
+
return res.status == 200
|
44
|
+
end
|
45
|
+
|
46
|
+
def handle_response(message)
|
47
|
+
handle_error(message) if message.status >= 400
|
48
|
+
Rested.log_in{ puts; "HTTP/#{message.version} #{message.status} #{message.reason}"}
|
49
|
+
Rested.log_in{ message.header.all.each{|h| Rested.log_in("#{h[0]}: #{h[1]}")}; puts }
|
50
|
+
decode_response(message)
|
51
|
+
end
|
52
|
+
|
53
|
+
def decode_response(message)
|
54
|
+
ct = message.contenttype
|
55
|
+
if ct =~ /json|javascript/ then
|
56
|
+
decode_json_response(message)
|
57
|
+
elsif ct =~ /xml/ then
|
58
|
+
decode_xml_response(message)
|
59
|
+
else
|
60
|
+
Rested.log_in { "\n" + message.content }
|
61
|
+
raise "Unknown response type"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def decode_json_response(message)
|
66
|
+
begin
|
67
|
+
JSON.load(message.content)
|
68
|
+
rescue => ex
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def decode_xml_response(message)
|
74
|
+
raise NotImplementedError # TODO
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_error(message)
|
78
|
+
Rested.log_in{ puts; "HTTP/#{message.version} #{message.status} #{message.reason}"}
|
79
|
+
Rested.log_in{ message.header.all.each{|h| Rested.log_in("#{h[0]}: #{h[1]}")}; puts }
|
80
|
+
raise Rested::Error.new(message)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class Base
|
86
|
+
|
87
|
+
rattr_accessor :base_url, :user, :pass
|
88
|
+
|
89
|
+
class << self
|
90
|
+
include BaseMethods
|
91
|
+
end
|
92
|
+
|
93
|
+
include BaseMethods
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
data/lib/rested/debug.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Rested
|
2
|
+
|
3
|
+
@debug = false
|
4
|
+
|
5
|
+
def self.debug(val=nil)
|
6
|
+
return @debug unless val
|
7
|
+
if val == true then
|
8
|
+
@debug = STDOUT
|
9
|
+
else
|
10
|
+
@debug = val
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.debug=(val)
|
15
|
+
self.debug(val)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.log(msg = nil, &block)
|
19
|
+
return unless @debug
|
20
|
+
if block_given? then
|
21
|
+
s = yield
|
22
|
+
return if not s.kind_of? String
|
23
|
+
@debug.puts(s)
|
24
|
+
elsif not msg.nil? then
|
25
|
+
@debug.puts(msg)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.log_do(msg = nil, &block)
|
30
|
+
if block_given? then
|
31
|
+
s = yield
|
32
|
+
return if not s.kind_of? String
|
33
|
+
log { "* " + s }
|
34
|
+
else
|
35
|
+
log("* #{msg}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.log_in(msg = nil, &block)
|
40
|
+
if block_given? then
|
41
|
+
s = yield
|
42
|
+
return if not s.kind_of? String
|
43
|
+
log { "< " + s }
|
44
|
+
else
|
45
|
+
log("< #{msg}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.log_out(msg = nil, &block)
|
50
|
+
if block_given? then
|
51
|
+
s = yield
|
52
|
+
return if not s.kind_of? String
|
53
|
+
log { "> " + s }
|
54
|
+
else
|
55
|
+
log("> #{msg}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/ext'
|
2
|
+
require File.dirname(__FILE__) + '/base'
|
3
|
+
|
4
|
+
module Rested
|
5
|
+
|
6
|
+
class Entity < Base
|
7
|
+
|
8
|
+
rattr_accessor :endpoint, :id_field
|
9
|
+
attr_accessor :errors
|
10
|
+
Entity.id_field(:id)
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
attr_reader :before_filters, :after_filters
|
15
|
+
|
16
|
+
def before_filters
|
17
|
+
@before_filters ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_filters
|
21
|
+
@after_filters ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def before_save(&block)
|
25
|
+
before_filters << block
|
26
|
+
end
|
27
|
+
|
28
|
+
def after_save(&block)
|
29
|
+
after_filters << block
|
30
|
+
end
|
31
|
+
|
32
|
+
def id_field
|
33
|
+
@id_field ||= inherit_static_from_super(:id_field) || nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def inherit_static_from_super(sym)
|
37
|
+
if superclass.respond_to? sym then
|
38
|
+
val = superclass.send(sym)
|
39
|
+
return val if val.kind_of? Symbol
|
40
|
+
return val.dup if not val.nil?
|
41
|
+
end
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def fields
|
46
|
+
@fields ||= inherit_static_from_super(:fields) || []
|
47
|
+
end
|
48
|
+
|
49
|
+
def field(*args)
|
50
|
+
args.each do |f|
|
51
|
+
add_field(f) unless fields.include? f
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def delimited_fields
|
56
|
+
@delimited_fields ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
def delimited_field(field, delimiter = ',')
|
60
|
+
unless fields.include? field
|
61
|
+
delimited_fields[field] = delimiter
|
62
|
+
add_field(field)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_field(field)
|
67
|
+
self.fields << field
|
68
|
+
attr_accessor field
|
69
|
+
end
|
70
|
+
|
71
|
+
def find(id = nil, masquerade = nil)
|
72
|
+
uri = self.endpoint
|
73
|
+
uri += "/#{id}" if not id.nil?
|
74
|
+
begin
|
75
|
+
json = get(uri, :masquerade => masquerade)
|
76
|
+
rescue Rested::Error => ex
|
77
|
+
if ex.message =~ /Invalid/ then
|
78
|
+
raise ObjectNotFound.new(ex.http_response)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
if id.nil? then
|
82
|
+
# return as list
|
83
|
+
return json.values.first.map { |j| new(j) }
|
84
|
+
end
|
85
|
+
return nil if json.values.empty?
|
86
|
+
return new(json.values.first)
|
87
|
+
end
|
88
|
+
|
89
|
+
def list
|
90
|
+
find()
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
def files
|
96
|
+
@files ||= {}
|
97
|
+
end
|
98
|
+
|
99
|
+
def add_file(name, file)
|
100
|
+
if file.kind_of? String then
|
101
|
+
raise IOError.new("File not found: #{file}") if not File.exists? file
|
102
|
+
file = File.new(file)
|
103
|
+
elsif file.is_a? Tempfile
|
104
|
+
file = File.new(file.path)
|
105
|
+
end
|
106
|
+
return if not file.kind_of? File
|
107
|
+
self.files[name] = file
|
108
|
+
end
|
109
|
+
|
110
|
+
def initialize(*args)
|
111
|
+
if args.kind_of? Hash then
|
112
|
+
h = args
|
113
|
+
elsif args.kind_of? Array and args.first.kind_of? Hash then
|
114
|
+
h = args.first
|
115
|
+
end
|
116
|
+
set_values(h) if h
|
117
|
+
self.errors = []
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_values(h)
|
121
|
+
h.each_pair do |name, value|
|
122
|
+
writer_method = "#{name}="
|
123
|
+
value = parse_value(name, value)
|
124
|
+
if respond_to?(writer_method)
|
125
|
+
send(writer_method, value)
|
126
|
+
else
|
127
|
+
self[name.to_s] = value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_value(name, value)
|
133
|
+
if !value.nil? then
|
134
|
+
if delimited_fields.include?(name.to_sym) then
|
135
|
+
value = value.split(delimited_fields[name.to_sym]) if value.is_a?(String)
|
136
|
+
value = value.map(&:to_i) if value.first.is_a?(String) && value.all?{ |v| v.to_i.to_s == v }
|
137
|
+
else
|
138
|
+
value = value.to_i if value.is_a?(String) && value.to_i.to_s == value
|
139
|
+
end
|
140
|
+
value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def id_val
|
145
|
+
self.send(id_field)
|
146
|
+
end
|
147
|
+
|
148
|
+
def id_val=(val)
|
149
|
+
self.send("#{id_field.to_s}=", val)
|
150
|
+
end
|
151
|
+
|
152
|
+
def new_record?
|
153
|
+
self.new?
|
154
|
+
end
|
155
|
+
|
156
|
+
def new?
|
157
|
+
self.id_val.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
def [](name)
|
161
|
+
begin
|
162
|
+
send(name)
|
163
|
+
rescue NoMethodError
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def []=(name, value)
|
169
|
+
begin
|
170
|
+
send("#{name}=", value)
|
171
|
+
rescue NoMethodError
|
172
|
+
self.class.add_field(name)
|
173
|
+
send("#{name}=", value)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_h
|
178
|
+
h = {}
|
179
|
+
fields.each do |f|
|
180
|
+
val = self.send(f)
|
181
|
+
h[f] = val && delimited_fields.include?(f) ? val.join(delimited_fields[f]) : val
|
182
|
+
end
|
183
|
+
h
|
184
|
+
end
|
185
|
+
|
186
|
+
def to_json
|
187
|
+
JSON.generate(to_h())
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_s
|
191
|
+
to_json()
|
192
|
+
end
|
193
|
+
|
194
|
+
def update_attributes(attributes = {})
|
195
|
+
attributes.each_pair do |field, value|
|
196
|
+
send("#{field}=", value)
|
197
|
+
end
|
198
|
+
save
|
199
|
+
end
|
200
|
+
|
201
|
+
def save
|
202
|
+
begin
|
203
|
+
save!
|
204
|
+
rescue Rested::Error => e
|
205
|
+
self.errors = e.validations
|
206
|
+
false
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def save!(params = nil)
|
211
|
+
self.class.before_filters.each do |f|
|
212
|
+
f.call(self)
|
213
|
+
end
|
214
|
+
uri = self.endpoint
|
215
|
+
uri += "/#{self.id_val}" unless new?
|
216
|
+
params = to_h() if not params
|
217
|
+
params.delete(self.id_field) if new?
|
218
|
+
unless self.files.empty?
|
219
|
+
params.merge!(self.files)
|
220
|
+
@files = {}
|
221
|
+
end
|
222
|
+
ret = self.post(uri, params)
|
223
|
+
if new? then
|
224
|
+
self.id_val = self.class.new(ret.values.first).id_val
|
225
|
+
end
|
226
|
+
self.class.after_filters.each do |f|
|
227
|
+
f.call(self)
|
228
|
+
end
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
232
|
+
def delete!
|
233
|
+
return if new?
|
234
|
+
uri = self.endpoint + "/#{self.id_val}"
|
235
|
+
self.delete(uri)
|
236
|
+
true
|
237
|
+
end
|
238
|
+
|
239
|
+
def fields
|
240
|
+
@fields ||= self.class.fields
|
241
|
+
end
|
242
|
+
|
243
|
+
def delimited_fields
|
244
|
+
@delimited_fields ||= self.class.delimited_fields
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
data/lib/rested/error.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
module Rested
|
3
|
+
|
4
|
+
class Error < ::Exception
|
5
|
+
|
6
|
+
attr_accessor :status, :reason, :http_response, :message, :validations
|
7
|
+
|
8
|
+
def initialize(res)
|
9
|
+
|
10
|
+
hash = Rested::Base.decode_response(res)
|
11
|
+
if hash.include? "error" then
|
12
|
+
self.message = hash["error"]
|
13
|
+
end
|
14
|
+
self.validations = Validations.new(hash['validations'])
|
15
|
+
self.status = res.status
|
16
|
+
self.reason = res.reason
|
17
|
+
self.http_response = res
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{self.status} #{self.reason}" + (self.message.nil? ? "" : ": #{self.message}")
|
23
|
+
end
|
24
|
+
|
25
|
+
# def self.handle(res)
|
26
|
+
# case res.status
|
27
|
+
# when 400
|
28
|
+
# raise "Bad request"
|
29
|
+
# when 401
|
30
|
+
# raise "401 Unauthorized"
|
31
|
+
# when 403
|
32
|
+
# raise "403 Forbidden"
|
33
|
+
# when 404
|
34
|
+
# raise "404 Not Found"
|
35
|
+
# when 405
|
36
|
+
# raise "405 Method Not Allowed"
|
37
|
+
# else
|
38
|
+
# raise "#{res.status} #{res.reason}" if res.status >= 400
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class ObjectNotFound < Error
|
45
|
+
attr_accessor :id
|
46
|
+
|
47
|
+
def initialize(res)
|
48
|
+
super(res)
|
49
|
+
self.id = $1 if res.content =~ /Invalid Object.*?(\d+)/
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
"Invalid Object ID '#{self.id}'"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/lib/rested/ext.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
|
2
|
+
# orginally cribbed from Rails edge. modified a bunch.
|
3
|
+
|
4
|
+
class Hash
|
5
|
+
# By default, only instances of Hash itself are extractable.
|
6
|
+
# Subclasses of Hash may implement this method and return
|
7
|
+
# true to declare themselves as extractable. If a Hash
|
8
|
+
# is extractable, Array#extract_options! pops it from
|
9
|
+
# the Array when it is the last element of the Array.
|
10
|
+
def extractable_options?
|
11
|
+
instance_of?(Hash)
|
12
|
+
end
|
13
|
+
end if not {}.respond_to? :extractable_options?
|
14
|
+
|
15
|
+
class Array
|
16
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
17
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
18
|
+
#
|
19
|
+
# def options(*args)
|
20
|
+
# args.extract_options!
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# options(1, 2) # => {}
|
24
|
+
# options(1, 2, :a => :b) # => {:a=>:b}
|
25
|
+
def extract_options!
|
26
|
+
if last.is_a?(Hash) && last.extractable_options?
|
27
|
+
pop
|
28
|
+
else
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end if not [].respond_to? :extract_options!
|
33
|
+
|
34
|
+
# Extends the class object with class and instance accessors for attributes,
|
35
|
+
# just like the native attr* accessors for instance attributes. Attributes can be
|
36
|
+
# set at the class level and overriden at the instance level as well.
|
37
|
+
#
|
38
|
+
# class Person
|
39
|
+
# cattr_accessor :hair_colors
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
43
|
+
class Class
|
44
|
+
def rattr_reader(*syms)
|
45
|
+
options = syms.extract_options!
|
46
|
+
syms.each do |sym|
|
47
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
48
|
+
unless defined? @#{sym}
|
49
|
+
@#{sym} = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.#{sym}(#{sym} = nil)
|
53
|
+
return @#{sym} unless #{sym}
|
54
|
+
@#{sym} = #{sym}
|
55
|
+
end
|
56
|
+
EOS
|
57
|
+
|
58
|
+
unless options[:instance_reader] == false
|
59
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
60
|
+
def #{sym}
|
61
|
+
return @#{sym} if @#{sym}
|
62
|
+
self.class.#{sym}
|
63
|
+
end
|
64
|
+
EOS
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def rattr_writer(*syms)
|
70
|
+
options = syms.extract_options!
|
71
|
+
syms.each do |sym|
|
72
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
73
|
+
unless defined? @#{sym}
|
74
|
+
@#{sym} = nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.#{sym}=(obj)
|
78
|
+
@#{sym} = obj
|
79
|
+
end
|
80
|
+
EOS
|
81
|
+
|
82
|
+
unless options[:instance_writer] == false
|
83
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
84
|
+
def #{sym}=(obj)
|
85
|
+
@#{sym} = obj
|
86
|
+
end
|
87
|
+
EOS
|
88
|
+
end
|
89
|
+
self.send("#{sym}=", yield) if block_given?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def rattr_accessor(*syms, &blk)
|
94
|
+
rattr_reader(*syms)
|
95
|
+
rattr_writer(*syms, &blk)
|
96
|
+
end
|
97
|
+
end if not Class.respond_to? :rattr_accessor
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rested
|
2
|
+
class Validations
|
3
|
+
def initialize(validations_hash)
|
4
|
+
self.validations_hash = validations_hash || {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def full_messages
|
8
|
+
validations_hash.inject([]) do |messages, validation|
|
9
|
+
messages << "#{humanize(validation.first)} #{validation.last}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def count
|
14
|
+
validations_hash.size
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def humanize(field)
|
20
|
+
field.gsub(/[A-Z]/){ " #{$&.downcase}"}.capitalize
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :validations_hash
|
24
|
+
end
|
25
|
+
end
|
data/lib/rested.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + '/rested/debug'
|
3
|
+
require File.dirname(__FILE__) + '/rested/ext'
|
4
|
+
require File.dirname(__FILE__) + '/rested/error'
|
5
|
+
require File.dirname(__FILE__) + '/rested/base'
|
6
|
+
require File.dirname(__FILE__) + '/rested/entity'
|
7
|
+
require File.dirname(__FILE__) + '/rested/validations'
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rested
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Chetan Sarva
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-07-21 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: httpclient
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 119
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 1
|
33
|
+
- 5
|
34
|
+
- 2
|
35
|
+
version: 2.1.5.2
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: json
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 4
|
50
|
+
- 2
|
51
|
+
version: 1.4.2
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
description: Ruby library built on top of httpclient for working with RESTful APIs.
|
55
|
+
email: chetan@betteradvertising.com
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.rdoc
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- README.rdoc
|
65
|
+
- Rakefile
|
66
|
+
- VERSION
|
67
|
+
- lib/rested.rb
|
68
|
+
- lib/rested/base.rb
|
69
|
+
- lib/rested/debug.rb
|
70
|
+
- lib/rested/entity.rb
|
71
|
+
- lib/rested/error.rb
|
72
|
+
- lib/rested/ext.rb
|
73
|
+
- lib/rested/validations.rb
|
74
|
+
has_rdoc: true
|
75
|
+
homepage: ""
|
76
|
+
licenses: []
|
77
|
+
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options:
|
80
|
+
- --charset=UTF-8
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
98
|
+
segments:
|
99
|
+
- 0
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.3.7
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Ruby library for working with RESTful APIs
|
108
|
+
test_files: []
|
109
|
+
|