url 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/url.rb CHANGED
@@ -3,11 +3,15 @@ require "net/https"
3
3
  require 'uri'
4
4
  require 'cgi'
5
5
  require 'forwardable'
6
+ require "delegate"
6
7
 
7
-
8
- files = Dir.glob(File.join(File.dirname(__FILE__),'url','*.rb'))
9
- files.delete_if {|f| f =~ /url\/(classer)\.rb/}
10
- files.each { |f| require f }
8
+ files = %w{
9
+ version
10
+ helper_classes
11
+ handlers
12
+ response
13
+ }
14
+ files.each { |f| require File.join(File.dirname(__FILE__),'url',f) }
11
15
 
12
16
  # Main class for managing urls
13
17
  # url = URL.new('https://mail.google.com/mail/?shva=1#mbox')
@@ -29,6 +33,14 @@ class URL
29
33
  # The params for the request
30
34
  # @returns [URL::ParamsHash]
31
35
  attr_reader :params
36
+
37
+ # Set the params for the request
38
+ # Allows for url.params |= {:foo => 'bar'}
39
+ # @returns [URL::ParamsHash]
40
+ def params= p
41
+ raise ArgumentError, 'Params must be a URL::ParamsHash' unless p.is_a?(ParamsHash)
42
+ @params = p
43
+ end
32
44
 
33
45
  # Attributes of the URL which are editable
34
46
  # @returns [String]
@@ -46,6 +58,15 @@ class URL
46
58
 
47
59
  @path = str
48
60
  end
61
+
62
+ def add_to_path val
63
+ unless @path[-1] == 47 # '/'
64
+ @path << '/'
65
+ end
66
+
67
+ @path << val.sub(/^\//,'')
68
+ @path
69
+ end
49
70
 
50
71
  # Returns array of subdomains
51
72
  # @returns [URL::Subdomain]
@@ -107,6 +128,11 @@ class URL
107
128
  def host
108
129
  [@subdomain,@domain].flatten.compact.join('.')
109
130
  end
131
+
132
+ # Messed up host/hostname issue :(
133
+ def host_with_port
134
+ host<<':'<<port.to_s
135
+ end
110
136
 
111
137
  # Outputs the full current url
112
138
  # @param [Hash] ops Prevent certain parts of the object from being shown by setting `:scheme`,`:port`,`:path`,`:params`, or `:hash` to `false`
@@ -189,6 +215,12 @@ class URL
189
215
  def delete(*args)
190
216
  req_handler.delete(*args)
191
217
  end
218
+
219
+ # Performs a put request for the current URL
220
+ # @return [URL::Response] A subclass of string which also repsonds to a few added mthods storing more information
221
+ def put(*args)
222
+ req_handler.delete(*args)
223
+ end
192
224
 
193
225
  def inspect
194
226
  "#<#{self.class} #{to_s}>"
@@ -0,0 +1,39 @@
1
+ class URL
2
+ class Service
3
+ module AcceptsEndpoint
4
+
5
+ # creates the endpoint for which ever object it's imported into
6
+ def endpoint arg, &blk
7
+ endpoint = if arg.is_a?(Hash)
8
+ f = arg.first
9
+ name = f.shift
10
+ f.shift
11
+ else
12
+ name = arg
13
+ end
14
+
15
+ eigenclass.send :attr_reader, "#{name}_endpoint"
16
+ instance_eval <<-RUBY, __FILE__, __LINE__
17
+ def #{name} params=nil, args={}
18
+ if params.nil?
19
+ @#{name}_endpoint
20
+ else
21
+ @#{name}_endpoint.find(params,args)
22
+ end
23
+ end
24
+ RUBY
25
+
26
+ builder = EndpointBuilder.new(@base_url,endpoint,&blk)
27
+ e = builder._endpoint
28
+
29
+ e.inflate_into ||= @inflate_into if @inflate_into
30
+ instance_variable_set "@#{name}_endpoint",e
31
+ end
32
+
33
+ # allows access to the eigenclass
34
+ def eigenclass
35
+ class << self; self; end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,88 @@
1
+ class URL
2
+ class Service
3
+ class Endpoint
4
+ attr_accessor :inflate_into
5
+ include AcceptsEndpoint
6
+
7
+ # Storage for what each endpoint should inflate into
8
+ def method_inflate_into
9
+ @method_inflate_into ||= {}
10
+ end
11
+
12
+ # Define the built in methods and their fetcher aliases
13
+ BUILT_IN_MAP = {
14
+ :get => %w{find},
15
+ :post => %w{create},
16
+ :put => %w{update},
17
+ :delete => %w{destroy}
18
+ }.each do |method,aliases|
19
+ class_eval <<-RUBY, __FILE__, __LINE__
20
+ def #{method} params = {}, opts = {}
21
+ u = @url.dup
22
+ u.params.merge! params
23
+
24
+ u.#{method} opts
25
+ end
26
+ RUBY
27
+ aliases.each do |al|
28
+ class_eval <<-RUBY, __FILE__, __LINE__
29
+ def #{al} params={}, opts={}
30
+ transform_response #{method}(params, opts)
31
+ end
32
+ RUBY
33
+ end
34
+ end.freeze
35
+
36
+ BUILT_IN_METHODS = BUILT_IN_MAP.collect do |k,v|
37
+ v.collect{|vv| vv.to_sym}+[k.to_sym]
38
+ end.flatten.freeze
39
+
40
+ def initialize url
41
+ @base_url = @url = url
42
+ end
43
+
44
+ # Expose class eval externally
45
+ def class_eval *args,&blk
46
+ eigenclass.class_eval *args,&blk
47
+ end
48
+
49
+ def eigenclass
50
+ class << self; self; end
51
+ end
52
+
53
+ def inspect
54
+ %Q{#<#{self.class} #{@url.to_s}>}
55
+ end
56
+
57
+ private
58
+
59
+ def transform_response resp, into=nil
60
+ if resp.connection_refused
61
+ raise EndpointNotResponding, resp.url_obj.host_with_port
62
+ end
63
+
64
+ if resp && !resp.empty?
65
+ begin
66
+ resp = resp.json
67
+ rescue Exception => e
68
+ warn "The response #{resp} couldn't be parsed"
69
+ raise e
70
+ end
71
+ end
72
+
73
+ if into ||= inflate_into && resp.is_a?(Hash)
74
+ into.call(resp)
75
+ else
76
+ resp
77
+ end
78
+ end
79
+
80
+ class << self
81
+
82
+ end
83
+
84
+ class RequiredParameter < RuntimeError; end
85
+ class EndpointNotResponding < Errno::ECONNREFUSED; end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,78 @@
1
+ class URL
2
+ class Service
3
+ class EndpointBuilder
4
+ attr_reader :_endpoint
5
+ extend Forwardable
6
+
7
+ # Create a method missing enironment to build out all the methods on the
8
+ # endpoint object
9
+ def initialize base_url, endpoint, params={}, &blk
10
+ @url = base_url.dup
11
+ @url.add_to_path endpoint.to_s
12
+ @url.params.merge!(params)
13
+ @_endpoint = Endpoint.new(@url)
14
+
15
+ instance_eval &blk if block_given?
16
+ end
17
+
18
+ # Allow nesting of endpoints
19
+ def_delegators :@_endpoint, :endpoint
20
+
21
+ def returns arg=nil, &blk
22
+ blk = arg unless block_given?
23
+ @_endpoint.inflate_into = blk
24
+ end
25
+
26
+ # Build any additional methods sent
27
+ def method_missing *args, &blk
28
+ if m = caller.first.match(/^(#{__FILE__}:\d+):in `method_missing'$/) # protect against a typo within this function creating a stack overflow
29
+ raise "Method missing calling itself with #{args.first} in #{m[1]}"
30
+ end
31
+ is_built_in = false
32
+ # If you define a method named get,post,create,etc don't require the method type
33
+ if Endpoint::BUILT_IN_METHODS.include?(args.first) && !Endpoint::BUILT_IN_METHODS.include?(args[1])
34
+ name = args.shift
35
+
36
+ if Endpoint::BUILT_IN_MAP.has_key?(name.to_sym)
37
+ method = name.to_sym
38
+ is_built_in = true
39
+ else
40
+ method = Endpoint::BULT_IN_MAP.find do |meth,aliases|
41
+ aliases.include?(name)
42
+ end
43
+
44
+ method = method[0] if method
45
+ end
46
+ else
47
+ name = args.shift
48
+ method = args.shift if args.first.is_a?(Symbol)
49
+ end
50
+ name = name.to_sym
51
+
52
+ method ||= :get
53
+
54
+ options = args.shift||{}
55
+ options[:requires] ||= []
56
+ options[:requires] = [options[:requires]] unless options[:requires].is_a?(Array)
57
+ options[:into] ||= blk if block_given?
58
+ @_endpoint.method_inflate_into[name] = options[:into]
59
+
60
+ @_endpoint.class_eval <<-RUBY, __FILE__, __LINE__
61
+ def #{name} force_params={}, args={}
62
+ params = #{(options[:default]||{}).inspect}.merge(force_params)
63
+ #{(options[:requires]||[]).inspect}.each do |req|
64
+ raise RequiredParameter, "#{name} endpoint requires the "<<req<<" paramerter" unless params.include?(req.to_sym) || params.include?(req.to_s)
65
+ end
66
+
67
+ if #{is_built_in.inspect}
68
+ super(params,args)
69
+ else
70
+ transform_response(#{method}(params,args),method_inflate_into[#{name.inspect}])
71
+ end
72
+ end
73
+ RUBY
74
+ end
75
+
76
+ end
77
+ end
78
+ end
data/lib/url/handlers.rb CHANGED
@@ -30,6 +30,10 @@ class URL
30
30
  def delete(args={})
31
31
  raise Exception, "You need to implement #{self.class}#delete"
32
32
  end
33
+
34
+ def put(args={})
35
+ raise Exception, "You need to implement #{self.class}#put"
36
+ end
33
37
  end
34
38
 
35
39
  class JSONHandler
@@ -7,6 +7,8 @@ class URL
7
7
  t = Time.now
8
8
  resp = http.request(request)
9
9
  make_str(resp,Time.now-t)
10
+ rescue Errno::ECONNREFUSED => e
11
+ make_error
10
12
  end
11
13
 
12
14
  def post(args={})
@@ -16,6 +18,8 @@ class URL
16
18
  t = Time.now
17
19
  resp = http.request(request)
18
20
  make_str(resp,Time.now-t)
21
+ rescue Errno::ECONNREFUSED => e
22
+ make_error
19
23
  end
20
24
 
21
25
  def delete(args={})
@@ -24,6 +28,19 @@ class URL
24
28
  t = Time.now
25
29
  resp = http.request(request)
26
30
  make_str(resp,Time.now-t)
31
+ rescue Errno::ECONNREFUSED => e
32
+ make_error
33
+ end
34
+
35
+ def put(args={})
36
+ http = http_obj
37
+ request = Net::HTTP::Put.new(make_path)
38
+ request.body = url.params.to_s(false)
39
+ t = Time.now
40
+ resp = http.request(request)
41
+ make_str(resp,Time.now-t)
42
+ rescue Errno::ECONNREFUSED => e
43
+ make_error
27
44
  end
28
45
 
29
46
  private
@@ -31,6 +48,17 @@ class URL
31
48
  def make_path
32
49
  url.path
33
50
  end
51
+
52
+ def make_error
53
+ hsh = {
54
+ :code => 0,
55
+ :url => url.to_s,
56
+ :url_obj => url,
57
+ :connection_refused => true
58
+ }
59
+
60
+ Response.new('',hsh)
61
+ end
34
62
 
35
63
  def make_str(resp,time)
36
64
  hsh = {
@@ -38,7 +66,8 @@ class URL
38
66
  :time => time,
39
67
  :body => resp.body,
40
68
  :response => resp,
41
- :url => url.to_s
69
+ :url => url.to_s,
70
+ :url_obj => url
42
71
  }
43
72
 
44
73
  Response.new(hsh)
@@ -18,6 +18,16 @@ class URL
18
18
  resp = Typhoeus::Request.delete(url.to_s)
19
19
  make_str(resp)
20
20
  end
21
+
22
+ def put(args={})
23
+ resp = Typhoeus::Request.put(url.to_s, :body => url.params.to_s(false))
24
+ make_str(resp)
25
+ end
26
+
27
+ def head(args={})
28
+ resp = Typhoesu::Request.head(url.to_s)
29
+ make_str(resp)
30
+ end
21
31
 
22
32
  private
23
33
 
@@ -27,7 +37,8 @@ class URL
27
37
  :time => resp.time,
28
38
  :body => resp.body,
29
39
  :response => resp,
30
- :url => url.to_s
40
+ :url => url.to_s,
41
+ :url_obj => url
31
42
  }
32
43
 
33
44
  Response.new(hsh)
@@ -16,11 +16,23 @@ class URL
16
16
  end
17
17
 
18
18
  class ParamsHash < Mash
19
-
19
+ def | other
20
+ unless other.is_a? ParamsHash
21
+ other = other.to_hash if other.respond_to?(:to_hash)
22
+ other = ParamsHash[other]
23
+ end
24
+ other.merge(self)
25
+ end
26
+
27
+ def reverse_merge! other
28
+ replace self|other
29
+ end
30
+
20
31
  # Merges the array into a parameter string of the form <tt>?key=value&foo=bar</tt>
21
- def to_s
32
+ def to_s(questionmark=true)
22
33
  return '' if empty?
23
- '?' + to_a.inject(Array.new) do |ret,param|
34
+ str = questionmark ? '?' : ''
35
+ str << to_a.inject(Array.new) do |ret,param|
24
36
  key = param[0].to_s
25
37
  val = param[1]
26
38
 
@@ -50,6 +62,7 @@ class URL
50
62
  end
51
63
  ret
52
64
  end.join('&')
65
+ str
53
66
  end
54
67
 
55
68
  class << self
data/lib/url/response.rb CHANGED
@@ -1,5 +1,3 @@
1
- require "delegate"
2
-
3
1
  class URL
4
2
 
5
3
  # The Response class is a deleegate to string which also contains metadata about the request.
@@ -26,6 +24,16 @@ class URL
26
24
  # The url which generated this response
27
25
  # @returns [String]
28
26
  attr_reader :url
27
+
28
+ # The url object used to create the response
29
+ # @returns [URL]
30
+ attr_reader :url_obj
31
+
32
+ # This is set to true if the target server was not reachable
33
+ # @returns [Boolean]
34
+ def connection_refused
35
+ @connection_refused || code == 0
36
+ end
29
37
 
30
38
  # @param [String] body The body of the response object, main string
31
39
  # @param [Hash] args Additional arguments: :time,:code,:response,:url
@@ -35,7 +43,7 @@ class URL
35
43
  str = args[:body]
36
44
  end
37
45
 
38
- raise unless str
46
+ raise ArgumentError, "No string provided" unless str
39
47
  super(str)
40
48
  args.each do |key, value|
41
49
  instance_variable_set "@#{key}", value
@@ -0,0 +1,54 @@
1
+ %w{
2
+ accepts_endpoint
3
+ endpoint
4
+ endpoint_builder
5
+ }.each { |f| require File.join(File.dirname(__FILE__),f) }
6
+
7
+ class URL::Service
8
+ INHERITED_INSTANCE_VARIABLES = {:@base_url=>:dup}
9
+ extend AcceptsEndpoint
10
+ class << self
11
+ attr_accessor :config
12
+ def set_url url
13
+ unless url.is_a?(URL)
14
+ url = URL.new(url)
15
+ end
16
+ @base_url = url
17
+ self
18
+ end
19
+
20
+ def inherited(subclass)
21
+ super
22
+
23
+
24
+ if defined?(Rails) && File.exist?(Rails.root+'config/services.yml')
25
+ self.config ||= YAML.load_file(Rails.root+'config/services.yml')[Rails.env] rescue nil
26
+
27
+ if config
28
+ target_name = subclass.to_s.demodulize.underscore
29
+ service_url = config[target_name]||config[target_name.sub(/_service$/,'')]
30
+ end
31
+ end
32
+
33
+ if service_url
34
+ subclass.set_url service_url
35
+ end
36
+
37
+ ivs = subclass.instance_variables.collect{|x| x.to_s}
38
+ INHERITED_INSTANCE_VARIABLES.each do |iv,dup|
39
+ next if ivs.include?(iv.to_s)
40
+ sup_class_value = instance_variable_get(iv)
41
+ sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
42
+ subclass.instance_variable_set(iv, sup_class_value)
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ class << URL
51
+ def Service url
52
+ Class.new(URL::Service).set_url(url)
53
+ end
54
+ end
data/lib/url/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class URL
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/spec/handler_spec.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  shared_examples_for "all requests" do
4
+ before do
5
+ @resp = @url.send(@method)
6
+ end
4
7
  it "should work" do
5
8
  @resp.should be_success
6
9
  end
@@ -14,6 +17,19 @@ shared_examples_for "all requests" do
14
17
  @resp.code.should be_a(Integer)
15
18
  @resp.url.should be_a(String)
16
19
  end
20
+
21
+ it "should not error when making request" do
22
+ test_connection_issue(@method)
23
+ end
24
+
25
+ end
26
+
27
+ def test_connection_issue(sym)
28
+ u = URL.new('http://localhost:5280')
29
+ resp = nil
30
+ expect {resp = u.send(sym)}.to_not raise_error
31
+ resp.should_not be_successful
32
+ resp.connection_refused.should be true
17
33
  end
18
34
 
19
35
  shared_examples_for "all builds" do
@@ -25,21 +41,22 @@ shared_examples_for "all builds" do
25
41
 
26
42
  describe "#get" do
27
43
  before do
28
- @resp = @url.get
44
+ @method = :get
29
45
  end
46
+
30
47
  it_should_behave_like "all requests"
31
48
  end
32
49
 
33
50
  describe "#post" do
34
51
  before do
35
- @resp = @url.post
52
+ @method = :post
36
53
  end
37
54
  it_should_behave_like "all requests"
38
55
  end
39
56
 
40
57
  describe "#delete" do
41
58
  before do
42
- @resp = @url.delete
59
+ @method = :delete
43
60
  end
44
61
  it_should_behave_like "all requests"
45
62
  end
@@ -0,0 +1,2 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
9
- - 2
10
- version: 0.2.2
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Tal Atlas
@@ -15,8 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-07-25 00:00:00 -04:00
19
- default_executable:
18
+ date: 2011-07-25 00:00:00 Z
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
21
  name: rspec
@@ -58,7 +57,10 @@ extra_rdoc_files:
58
57
  - LICENSE
59
58
  - README.rdoc
60
59
  files:
60
+ - lib/url/accepts_endpoint.rb
61
61
  - lib/url/classer.rb
62
+ - lib/url/endpoint.rb
63
+ - lib/url/endpoint_builder.rb
62
64
  - lib/url/handlers/as_json_handler.rb
63
65
  - lib/url/handlers/base_json_handler.rb
64
66
  - lib/url/handlers/net_handler.rb
@@ -67,18 +69,19 @@ files:
67
69
  - lib/url/handlers.rb
68
70
  - lib/url/helper_classes.rb
69
71
  - lib/url/response.rb
72
+ - lib/url/service.rb
70
73
  - lib/url/version.rb
71
74
  - lib/url.rb
72
75
  - LICENSE
73
76
  - spec/classer_spec.rb
74
77
  - spec/handler_spec.rb
75
78
  - spec/json_spec.rb
79
+ - spec/service_spec.rb
76
80
  - spec/spec.opts
77
81
  - spec/spec_helper.rb
78
82
  - spec/url_spec.rb
79
83
  - README.rdoc
80
84
  - Rakefile
81
- has_rdoc: true
82
85
  homepage: http://github.com/talby/url
83
86
  licenses: []
84
87
 
@@ -108,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
111
  requirements: []
109
112
 
110
113
  rubyforge_project:
111
- rubygems_version: 1.6.2
114
+ rubygems_version: 1.8.8
112
115
  signing_key:
113
116
  specification_version: 3
114
117
  summary: A URL object