url 0.2.2 → 0.3.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/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