Skip to content

Commit 5ec8f46

Browse files
committed
Starting to piece the gem together, extracting existing models and specs from the developer.wordnik.com rails app.
1 parent b2556fb commit 5ec8f46

22 files changed

+875
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pkg/*
2+
*.gem
3+
.bundle

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source "http://rubygems.org"
2+
3+
# Specify your gem's dependencies in wordnik.gemspec
4+
gemspec

Gemfile.lock

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
PATH
2+
remote: .
3+
specs:
4+
wordnik (0.0.1)
5+
activemodel (= 3.0.3)
6+
addressable (= 2.2.4)
7+
htmlentities (= 4.2.4)
8+
json (= 1.4.6)
9+
nokogiri (= 1.4.4)
10+
typhoeus (= 0.2.1)
11+
12+
GEM
13+
remote: http://rubygems.org/
14+
specs:
15+
activemodel (3.0.3)
16+
activesupport (= 3.0.3)
17+
builder (~> 2.1.2)
18+
i18n (~> 0.4)
19+
activesupport (3.0.3)
20+
addressable (2.2.4)
21+
builder (2.1.2)
22+
crack (0.1.8)
23+
diff-lcs (1.1.2)
24+
htmlentities (4.2.4)
25+
i18n (0.5.0)
26+
json (1.4.6)
27+
mime-types (1.16)
28+
nokogiri (1.4.4)
29+
rspec (2.4.0)
30+
rspec-core (~> 2.4.0)
31+
rspec-expectations (~> 2.4.0)
32+
rspec-mocks (~> 2.4.0)
33+
rspec-core (2.4.0)
34+
rspec-expectations (2.4.0)
35+
diff-lcs (~> 1.1.2)
36+
rspec-mocks (2.4.0)
37+
typhoeus (0.2.1)
38+
mime-types
39+
vcr (1.5.1)
40+
webmock (1.6.2)
41+
addressable (>= 2.2.2)
42+
crack (>= 0.1.7)
43+
44+
PLATFORMS
45+
ruby
46+
47+
DEPENDENCIES
48+
activemodel (= 3.0.3)
49+
addressable (= 2.2.4)
50+
htmlentities (= 4.2.4)
51+
json (= 1.4.6)
52+
nokogiri (= 1.4.4)
53+
rspec (= 2.4.0)
54+
typhoeus (= 0.2.1)
55+
vcr (= 1.5.1)
56+
webmock (= 1.6.2)
57+
wordnik!

Rakefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require 'bundler'
2+
Bundler::GemHelper.install_tasks
3+
4+
require 'rspec/core/rake_task'
5+
6+
RSpec::Core::RakeTask.new('spec')
7+
8+
# If you want to make this the default task
9+
task :default => :spec

lib/wordnik.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
require 'wordnik/endpoint'
2+
require 'wordnik/operation'
3+
require 'wordnik/operation_parameter'
4+
require 'wordnik/request'
5+
require 'wordnik/resource'
6+
require 'wordnik/response'
7+
8+
class Wordnik
9+
10+
# See http://tinyurl.com/5t3rhy6 for info on setting up this configuration magic
11+
def self.configure
12+
yield configuration
13+
end
14+
15+
def self.configuration
16+
@configuration ||= Configuration.new
17+
end
18+
19+
class Configuration
20+
attr_accessor :api_key
21+
end
22+
23+
end

lib/wordnik/endpoint.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# To jog the memory: Resource > Endpoint > Operation > OperationParameter
2+
require 'active_model'
3+
class Endpoint
4+
5+
include ActiveModel::Validations
6+
include ActiveModel::Conversion
7+
extend ActiveModel::Naming
8+
9+
attr_accessor :path, :description, :operations
10+
11+
validates_presence_of :path, :description, :operations
12+
13+
def initialize(attributes = {})
14+
attributes.each do |name, value|
15+
send("#{name.to_s.underscore.to_sym}=", value)
16+
end
17+
18+
# Generate Operations instances from JSON
19+
if self.operations
20+
self.operations = self.operations.map do |operationData|
21+
Operation.new(operationData)
22+
end
23+
end
24+
end
25+
26+
# It's an ActiveModel thing..
27+
def persisted?
28+
false
29+
end
30+
31+
end

lib/wordnik/operation.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# To jog the memory: Resource > Endpoint > Operation > OperationParameter
2+
3+
class Operation
4+
include ActiveModel::Validations
5+
include ActiveModel::Conversion
6+
extend ActiveModel::Naming
7+
8+
attr_accessor :http_method, :summary, :notes, :parameters, :response, :open
9+
10+
validates_presence_of :http_method, :summary, :notes, :parameters, :response, :open
11+
12+
def initialize(attributes = {})
13+
attributes.each do |name, value|
14+
send("#{name.to_s.underscore.to_sym}=", value)
15+
end
16+
17+
self.http_method = self.http_method.to_s.downcase
18+
19+
# Generate OperationParameter instances from JSON
20+
if self.parameters
21+
self.parameters = self.parameters.map do |parameterData|
22+
OperationParameter.new(parameterData)
23+
end
24+
end
25+
26+
end
27+
28+
def get?
29+
self.http_method.downcase == "get"
30+
end
31+
32+
# Can this operation be run in the sandbox?
33+
def sandboxable?
34+
self.get?
35+
end
36+
37+
# It's an ActiveModel thing..
38+
def persisted?
39+
false
40+
end
41+
42+
end

lib/wordnik/operation_parameter.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# To jog the memory: Resource > Endpoint > Operation > OperationParameter
2+
3+
class OperationParameter
4+
include ActiveModel::Validations
5+
include ActiveModel::Conversion
6+
extend ActiveModel::Naming
7+
8+
attr_accessor :name, :description, :required, :param_type, :default_value, :allowable_values
9+
10+
validates_presence_of :name, :description, :required, :param_type, :default_value, :allowable_values
11+
12+
def initialize(attributes = {})
13+
attributes.each do |name, value|
14+
send("#{name.to_s.underscore.to_sym}=", value)
15+
end
16+
end
17+
18+
def human_name
19+
return "request body" if self.param_type == 'body'
20+
self.name
21+
end
22+
23+
def has_allowable_array?
24+
self.allowable_values.present? && self.allowable_values.include?(",")
25+
end
26+
27+
def required?
28+
self.required
29+
end
30+
31+
# It's an ActiveModel thing..
32+
def persisted?
33+
false
34+
end
35+
36+
end

lib/wordnik/request.rb

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
class Request
2+
require 'uri'
3+
require 'addressable/uri'
4+
include ActiveModel::Validations
5+
include ActiveModel::Conversion
6+
extend ActiveModel::Naming
7+
8+
attr_accessor :host, :port, :path, :format, :params, :body, :http_method, :headers
9+
10+
validates_presence_of :host, :path, :format, :http_method
11+
12+
def initialize(http_method, path, attributes={})
13+
attributes[:format] ||= "json"
14+
attributes[:host] ||= configatron.api.base_uri
15+
attributes[:params] ||= {}
16+
17+
# Set default headers, but allow them to be overridden
18+
default_headers = {
19+
'Content-Type' => "application/#{attributes[:format].downcase}",
20+
}
21+
attributes[:headers] = default_headers.merge(attributes[:headers] || {})
22+
23+
self.http_method = http_method.to_sym
24+
self.path = path
25+
attributes.each do |name, value|
26+
send("#{name.to_s.underscore.to_sym}=", value)
27+
end
28+
end
29+
30+
# Construct a base URL
31+
def url
32+
u = Addressable::URI.new
33+
u.host = self.host.sub(/\/$/, '')
34+
u.port = self.port if self.port.present?
35+
u.path = self.interpreted_path
36+
u.scheme = "http" # For some reason this must be set _after_ host, otherwise Addressable gets upset
37+
u.to_s
38+
end
39+
40+
# Iterate over the params hash, injecting any path values into the path string
41+
# e.g. /word.{format}/{word}/entries => /word.json/cat/entries
42+
def interpreted_path
43+
p = self.path
44+
self.params.each_pair do |key, value|
45+
p = p.gsub("{#{key}}", value.to_s)
46+
end
47+
48+
# Stick a .{format} placeholder into the path if there isn't
49+
# one already or an actual format like json or xml
50+
# e.g. /words/blah => /words.{format}/blah
51+
unless ['.json', '.xml', '{format}'].any? {|s| p.downcase.include? s }
52+
p = p.sub(/^(\/?\w+)/, "\\1.#{format}")
53+
end
54+
55+
p = p.sub("{format}", self.format)
56+
URI.encode(p)
57+
end
58+
59+
def interpreted_body
60+
return unless self.body.present?
61+
return self.body.to_json if self.body.is_a?(Hash)
62+
self.body
63+
end
64+
65+
# Iterate over all params,
66+
# .. removing the ones that are part of the path itself.
67+
# .. stringifying values so Addressable doesn't blow up.
68+
# .. obfuscating the API key if needed.
69+
def query_string_params(obfuscated=false)
70+
qsp = {}
71+
self.params.each_pair do |key, value|
72+
next if self.path.include? "{#{key}}"
73+
next if value.blank?
74+
value = "YOUR_API_KEY" if key.to_sym == :api_key && obfuscated
75+
qsp[key] = value.to_s
76+
end
77+
qsp
78+
end
79+
80+
# Construct a query string from the query-string-type params
81+
def query_string(options={})
82+
83+
# We don't want to end up with '?' as our query string
84+
# if there aren't really any params
85+
return "" if query_string_params.blank?
86+
87+
default_options = {:obfuscated => false}
88+
options = default_options.merge(options)
89+
90+
qs = Addressable::URI.new
91+
qs.query_values = self.query_string_params(options[:obfuscated])
92+
qs.to_s
93+
end
94+
95+
# Returns full request URL with query string included
96+
def url_with_query_string(options={})
97+
default_options = {:obfuscated => false}
98+
options = default_options.merge(options)
99+
100+
[url, query_string(options)].join('')
101+
end
102+
103+
def make
104+
response = case self.http_method.to_sym
105+
when :get
106+
Typhoeus::Request.get(
107+
self.url_with_query_string,
108+
:headers => self.headers.stringify_keys
109+
)
110+
111+
when :post
112+
Typhoeus::Request.post(
113+
self.url_with_query_string,
114+
:body => self.interpreted_body,
115+
:headers => self.headers.stringify_keys
116+
)
117+
118+
when :put
119+
Typhoeus::Request.put(
120+
self.url_with_query_string,
121+
:body => self.interpreted_body,
122+
:headers => self.headers.stringify_keys
123+
)
124+
125+
when :delete
126+
Typhoeus::Request.delete(
127+
self.url_with_query_string,
128+
:body => self.interpreted_body,
129+
:headers => self.headers.stringify_keys
130+
)
131+
end
132+
133+
@response_obj = Response.new(response)
134+
end
135+
136+
# If the request has been made, return the existing response
137+
# If not, make the request and return the response
138+
def response
139+
@response_obj || self.make
140+
end
141+
142+
def response_code_pretty
143+
return unless @response.present?
144+
@response.code.to_s
145+
end
146+
147+
def response_headers_pretty
148+
return unless @response.present?
149+
# JSON.pretty_generate(@response.headers).gsub(/\n/, '<br/>').html_safe # <- This was for RestClient
150+
@response.headers.gsub(/\n/, '<br/>').html_safe # <- This is for Typhoeus
151+
end
152+
153+
# It's an ActiveModel thing..
154+
def persisted?
155+
false
156+
end
157+
158+
end

0 commit comments

Comments
 (0)