musashi 0.0.1.b1

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.
File without changes
data/LICENSE ADDED
File without changes
@@ -0,0 +1,87 @@
1
+ =Musashi
2
+
3
+ Musashi is a RestClient that follows relations or links.
4
+
5
+ =Example
6
+
7
+ ==Protocol
8
+
9
+ curl -i http://foo.com/customers/1
10
+ {
11
+ "id": "1",
12
+ "login": "foo",
13
+ "name": "Foo Bar",
14
+ "phones": [
15
+ {
16
+ "number": "51156782323",
17
+ "extension": ""
18
+ },
19
+ {
20
+ "number": "51156782323",
21
+ "extension": "2345"
22
+ }
23
+ ],
24
+ "emails": ["foo@gmail.com",
25
+ "foo.bar@gmail.com.br",
26
+ "foo+you@gmail.com.br"],
27
+ "address": {
28
+ "street": "No Where",
29
+ "number": "123",
30
+ "complement": "4-F",
31
+ "district": "Pinheiros",
32
+ "city": "São Paulo",
33
+ "state": "SP",
34
+ "country": "Brazil",
35
+ "postal_code": "000000"
36
+ },
37
+ "link": [
38
+ {
39
+ "rel": "items",
40
+ "href": "http://bar.com/customers/1/items"
41
+ }
42
+ ]
43
+ }
44
+
45
+ curl -i http://bar.com/customer/1/items
46
+ {"entries" :
47
+ [ "http://bazz.com/items/1", "http://bazz.com/items/2" ]
48
+ }
49
+
50
+ curl -i http://bazz.com/items/1
51
+ {"name":"bazz item"}
52
+
53
+ ==Code
54
+
55
+ ===With class
56
+
57
+ require 'musashi'
58
+ class Customer
59
+ include Musashi::Retriever
60
+ include Musashi::Resource::JSON
61
+ attr_accessor :id
62
+ endpoint = '"http://foo.com/customers/#{id}"'
63
+ end
64
+
65
+ customer.id = '1'
66
+ puts customer.login
67
+ puts customer.phones.first.number
68
+ puts customer.email.first
69
+ puts customer.address.number
70
+ customer.items.entries.each do |i|
71
+ puts i.name
72
+ end
73
+
74
+ ===Without class
75
+
76
+ require 'musashi'
77
+ customer = Musashi::Resource.new('http://foo.com/customer/1', :format => :json)
78
+
79
+ puts custome.id
80
+ puts customer.login
81
+ puts customer.phones.first.number
82
+ puts customer.email.first
83
+ puts customer.address.number
84
+ customer.items.entries.each do |i|
85
+ puts i.name
86
+ end
87
+
File without changes
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ require 'rake'
6
+ require 'rspec/core/rake_task'
7
+ require 'musashi/version'
8
+
9
+ desc 'Run tests'
10
+ RSpec::Core::RakeTask.new(:spec)
11
+
12
+ desc 'Run the gem build'
13
+ task :build do
14
+ system 'gem build musashi.gemspec'
15
+ end
16
+
17
+ desc 'Run the gem push'
18
+ task :release => [:spec, :build] do
19
+ system "gem push musashi-#{Musashi::VERSION}.gem"
20
+ end
21
+
22
+ desc 'Run the unit suite'
23
+ task :default => [:spec]
24
+
@@ -0,0 +1,41 @@
1
+ module Faraday
2
+ class Response::RaiseHttp4xx < Response::Middleware
3
+ def on_complete(env)
4
+ case env[:status].to_i
5
+ when 400
6
+ raise "BadRequest-#{error_message(env)}"
7
+ when 401
8
+ raise "Unauthorized-#{error_message(env)}"
9
+ when 403
10
+ raise "Forbidden-#{error_message(env)}"
11
+ when 404
12
+ raise "NotFound-#{error_message(env)}"
13
+ when 406
14
+ raise "NotAcceptable-#{error_message(env)}"
15
+ when 420
16
+ raise "EnhanceYourCalm-#{error_message(env)}"
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def error_message(env)
23
+ "#{env[:method].to_s.upcase} #{env[:url].to_s}: #{env[:status]}#{error_body(env[:body])}"
24
+ end
25
+
26
+ def error_body(body)
27
+ if body.nil?
28
+ nil
29
+ elsif body['error']
30
+ ": #{body['error']}"
31
+ elsif body['errors']
32
+ first = Array(body['errors']).first
33
+ if first.kind_of? Hash
34
+ ": #{first['message'].chomp}"
35
+ else
36
+ ": #{first.chomp}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ module Faraday
2
+
3
+ class Response::RaiseHttp5xx < Response::Middleware
4
+
5
+ def on_complete(env)
6
+ case env[:status].to_i
7
+ when 500
8
+ raise "InternalServerError-#{error_message(env)}"
9
+ when 502
10
+ raise "BadGateway-#{error_message(env)}"
11
+ when 503
12
+ raise "ServiceUnavailable-#{error_message(env)}"
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def error_message(env, body=nil)
19
+ "#{env[:method].to_s.upcase} #{env[:url].to_s}: #{[env[:status].to_s + ':', body].compact.join(' ')}"
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'active_support/core_ext/integer'
3
+ require 'active_support/core_ext/string'
4
+ require 'active_support/core_ext/hash'
5
+ require 'active_support/core_ext/module'
6
+ require 'faraday'
7
+ require 'faraday_middleware'
8
+ require 'yaml'
9
+
10
+ require 'faraday/response/raise_http_4xx'
11
+ require 'faraday/response/raise_http_5xx'
12
+ require 'musashi/version'
13
+ require 'musashi/connection'
14
+ require 'musashi/retriever'
15
+ require 'musashi/resource'
16
+
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Musashi::Connection
3
+
4
+ attr_accessor :endpoint
5
+ attr_accessor_with_default :format, :json
6
+
7
+ def options
8
+ return @options if @options
9
+ builder = ::Faraday::Builder.new
10
+ builder.use ::Faraday::Request::Multipart
11
+ builder.use ::Faraday::Request::UrlEncoded
12
+ builder.use ::Faraday::Response::RaiseHttp4xx
13
+ case format.to_s.downcase
14
+ when 'json'
15
+ builder.use ::Faraday::Response::Mashify
16
+ builder.use ::Faraday::Response::ParseJson
17
+ when 'xml'
18
+ builder.use ::Faraday::Response::Mashify
19
+ builder.use ::Faraday::Response::ParseXml
20
+ end
21
+ builder.use ::Faraday::Response::RaiseHttp5xx
22
+ builder.adapter :net_http
23
+ @options = {
24
+ :headers => {
25
+ 'Accept' => "application/#{format}",
26
+ 'User-Agent' => "Musashi Gem #{::Musashi::VERSION}"
27
+ },
28
+ :url => endpoint,
29
+ :ssl => {:verify => false},
30
+ :builder => builder
31
+ }
32
+ end
33
+
34
+ def connection
35
+ @connection ||= ::Faraday.new(options)
36
+ end
37
+
38
+ end
@@ -0,0 +1,61 @@
1
+ class Musashi::Resource
2
+
3
+ module Format
4
+
5
+ def self.formats
6
+ @@formats ||= {}
7
+ end
8
+
9
+ def retrieve
10
+ marshalled = super
11
+ resource_type = Format.formats[format]
12
+ marshalled.extend(resource_type) unless resource_type.nil?
13
+ marshalled
14
+ end
15
+
16
+ end
17
+
18
+ module JSON
19
+ include Format
20
+ Format.formats[:json] = JSON
21
+
22
+ def self.included(clazz)
23
+ super
24
+ clazz.extend ClassMethods
25
+ clazz.send :include, InstanceMethods
26
+ end
27
+
28
+ module ClassMethods
29
+ attr_accessor :endpoint
30
+ end
31
+
32
+ module InstanceMethods
33
+ def retrieve
34
+ self.endpoint = eval self.class.endpoint
35
+ super
36
+ end
37
+ end
38
+
39
+ def method_missing(sym,*args,&block)
40
+ super
41
+ rescue NoMethodError => error
42
+ raise error unless retrieved?
43
+ raise NoMethodError.new(sym) unless respond_to?(:link)
44
+ selected = link.select{|ln| ln.rel == sym.to_s}
45
+ raise NoMethodError.new(sym) if selected.nil? or selected.empty?
46
+ raise "More than one result for #{sym} #{selected.join(',')}" if selected.size > 1
47
+ selected.first.href
48
+ end
49
+
50
+ end
51
+
52
+ include ::Musashi::Retriever::DelegateHash
53
+ def initialize(url,options={})
54
+ raise ArgumentError.new('Undefined url') if url.nil? or url.empty?
55
+ resource_type = Format.formats[ options[:format] || :json ]
56
+ extend(resource_type) unless resource_type.nil?
57
+ self.endpoint = url
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,192 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Musashi::Retriever
3
+
4
+ module Base
5
+ include ::Musashi::Connection
6
+
7
+ attr_writer :path
8
+ def path
9
+ @path ||= connection.path_prefix
10
+ end
11
+
12
+ def retrieved?
13
+ @retrieved ||= false
14
+ end
15
+
16
+ def retrieve(header={})
17
+ response = connection.get(path, header)
18
+ @retrieved = true
19
+ response.body.extend(VisitorStrategist)
20
+ end
21
+
22
+ end
23
+
24
+ module Strategist
25
+
26
+ def self.extend_object(receiver)
27
+ self.define_by_behavior(receiver)
28
+ end
29
+
30
+ def self.retrievers_by_behavior
31
+ @@retrievers_by_behavior ||= {}
32
+ end
33
+
34
+ def self.retrievers_by_behavior=(retrievers)
35
+ @@retrievers_by_behavior = retrievers
36
+ end
37
+
38
+ def self.define_by_behavior(receiver)
39
+ @@retrievers_by_behavior.each do |retriever|
40
+ if retriever.extend?(receiver)
41
+ receiver.extend(retriever)
42
+ break
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.retrievers_by_name
48
+ @@retrievers_by_name ||= {}
49
+ end
50
+
51
+ def self.retrievers_by_name=(retrievers)
52
+ @@retrievers_by_name = retrievers
53
+ end
54
+
55
+ def self.define_by_name(receiver,name)
56
+ raise "Undefined receiver" if receiver.nil?
57
+ raise "Undefined name for #{receiver}" if name.nil?
58
+ retriever = @@retrievers_by_name[name]
59
+ raise "Undefined retriever for #{receiver} with name=#{name}" if retriever.nil?
60
+ receiver.extend retriever
61
+ end
62
+
63
+ def define(receiver,name=nil)
64
+ raise "Undefined receiver" if receiver.nil?
65
+ if name.nil?
66
+ Strategist.define_by_behavior receiver
67
+ else
68
+ retriever = Strategist.retrievers_by_name[name]
69
+ if retriever.nil?
70
+ Strategist.define_by_behavior receiver
71
+ else
72
+ receiver.extend retriever
73
+ end
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ module VisitorStrategist
80
+ include Base
81
+ include Strategist
82
+
83
+ def self.extend?(receiver)
84
+ !receiver.nil? && receiver.respond_to?(:keys) && receiver.respond_to?(:fetch)
85
+ end
86
+
87
+ def self.extend_object(receiver)
88
+ raise RuntimeError.new unless extend? receiver
89
+ super
90
+ end
91
+
92
+ def [](key)
93
+ return nil unless self.has_key?(key)
94
+ value = self.fetch(key.to_s) rescue self.fetch(key) rescue nil
95
+ return value if value.nil?
96
+ define value,key.to_sym
97
+ value
98
+ end
99
+
100
+ def has_key?(key)
101
+ self.key?(key.to_s) || self.key?(key)
102
+ end
103
+
104
+ def respond_to?(method)
105
+ self.has_key?(method) ? true : super
106
+ end
107
+
108
+ def method_missing(sym,*args,&block)
109
+ super unless args.empty?
110
+ super unless has_key?(sym)
111
+
112
+ value = self.fetch(sym.to_s) rescue self.fetch(sym) rescue nil
113
+ return value if value.nil?
114
+
115
+ define value, sym
116
+ value
117
+ end
118
+
119
+ end
120
+
121
+ module DelegateHash
122
+ include VisitorStrategist
123
+
124
+ attr_accessor :attrs
125
+
126
+ def [](key)
127
+ @attrs = retrieve if @attrs.nil?
128
+ super
129
+ end
130
+
131
+ def method_missing(sym,*args,&block)
132
+ @attrs = retrieve if @attrs.nil?
133
+ super
134
+ end
135
+
136
+ def keys
137
+ @attrs = retrieve if @attrs.nil?
138
+ @attrs.keys
139
+ end
140
+
141
+ def key?(key)
142
+ @attrs = retrieve if @attrs.nil?
143
+ @attrs.key?(key)
144
+ end
145
+
146
+ def fetch(*args)
147
+ @attrs = retrieve if @attrs.nil?
148
+ @attrs.fetch(*args)
149
+ end
150
+
151
+ end
152
+
153
+ module Content
154
+ include DelegateHash
155
+
156
+ def self.extend?(receiver)
157
+ !receiver.nil? && receiver.respond_to?(:start_with?) && receiver.start_with?('http')
158
+ end
159
+
160
+ def self.extend_object(receiver)
161
+ raise RuntimeError.new unless extend? receiver
162
+ super
163
+ end
164
+
165
+ def retrieve
166
+ self.endpoint = to_s
167
+ super
168
+ end
169
+
170
+ end
171
+
172
+ module Iterator
173
+ def self.extend?(receiver)
174
+ !receiver.nil? && receiver.is_a?(::Array)
175
+ end
176
+
177
+ def self.extend_object(receiver)
178
+ raise RuntimeError.new unless extend? receiver
179
+ receiver.each do |item|
180
+ item.extend VisitorStrategist
181
+ end
182
+ end
183
+ end
184
+
185
+ Strategist.retrievers_by_behavior = [
186
+ Content,
187
+ VisitorStrategist,
188
+ Iterator
189
+ ]
190
+
191
+ include DelegateHash
192
+ end
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Musashi
3
+ VERSION = "0.0.1.b1"
4
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'spec_helper'
3
+
4
+ describe Musashi::Resource do
5
+
6
+ before(:all) do
7
+ @customer = Musashi::Resource.new('http://localhost:4567/customers/1.json')
8
+ end
9
+
10
+ it 'should raise an error for undefined method' do
11
+ lambda{ @resource.foo }.should raise_error(NoMethodError)
12
+ end
13
+
14
+ it 'should retrieve a customer instance' do
15
+ @customer.id.should eq('1')
16
+ @customer.cpf.should eq('12345678912')
17
+ end
18
+
19
+ it 'should retrieve relationships' do
20
+ @customer.provisionings.entries.each{ |p| p.service_key.should_not be_nil }
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,6 @@
1
+ # -*- enconding: utf-8 -*-
2
+ require 'sinatra'
3
+
4
+ public_dir = File.expand_path('../public/',__FILE__)
5
+ set :public, public_dir
6
+
@@ -0,0 +1,65 @@
1
+ {
2
+ "id": "1",
3
+ "login": "foo",
4
+ "name": "Foo Bar",
5
+ "cpf": "12345678912",
6
+ "rg": "12345678912",
7
+ "emails": [
8
+ "foo@gmail.com"
9
+ ],
10
+ "phones": [
11
+ "11111111",
12
+ "22222222"
13
+ ],
14
+ "address": {
15
+ "street": "No where",
16
+ "number": "42",
17
+ "complement": "apt-42",
18
+ "district": "Place",
19
+ "city": "Townsville",
20
+ "state": "Batman",
21
+ "country": "BR",
22
+ "postal_code": "12121212"
23
+ },
24
+ "twitter": "foo",
25
+ "facebook": "foo",
26
+ "link": [
27
+ {
28
+ "rel": "contacts",
29
+ "href": "http://localhost:4567/customers/1/contacts"
30
+ },
31
+ {
32
+ "rel": "billing_info",
33
+ "href": "http://localhost:4567/customers/1/billing_info"
34
+ },
35
+ {
36
+ "rel": "billing_contact",
37
+ "href": "http://localhost:4567/customers/1/billing_contact"
38
+ },
39
+ {
40
+ "rel": "balance",
41
+ "href": "http://localhost:4567/customers/1/balance"
42
+ },
43
+ {
44
+ "rel": "credit_limit",
45
+ "href": "http://localhost:4567/customers/1/credit_limit"
46
+ },
47
+ {
48
+ "rel": "manager",
49
+ "href": "http://localhost:4567/customers/1/manager"
50
+ },
51
+ {
52
+ "rel": "provisionings",
53
+ "href": "http://localhost:4567/customers/1/provisionings"
54
+ },
55
+ {
56
+ "rel": "orders",
57
+ "href": "http://localhost:4567/customers/1/orders"
58
+ },
59
+ {
60
+ "rel": "bills",
61
+ "href": "http://localhost:4567/customers/1/bills"
62
+ }
63
+ ]
64
+ }
65
+
File without changes
File without changes
@@ -0,0 +1 @@
1
+ {"entries":[{"id":"1234567890","parent":"0","service_key":"SERHOST0001","created_at":"2011-09-23","active":true,"status":"active"}]}
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'rspec'
3
+ require 'musashi'
4
+
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: musashi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.b1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - pahagon
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-30 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: i18n
16
+ requirement: &8196940 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.6'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *8196940
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &8196300 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.1.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *8196300
36
+ - !ruby/object:Gem::Dependency
37
+ name: hashie
38
+ requirement: &8195460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *8195460
47
+ - !ruby/object:Gem::Dependency
48
+ name: faraday
49
+ requirement: &8194660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.7.4
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *8194660
58
+ - !ruby/object:Gem::Dependency
59
+ name: faraday_middleware
60
+ requirement: &8194040 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 0.7.0
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *8194040
69
+ - !ruby/object:Gem::Dependency
70
+ name: multi_json
71
+ requirement: &8193280 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.0.0
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *8193280
80
+ - !ruby/object:Gem::Dependency
81
+ name: multi_xml
82
+ requirement: &8192780 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: 0.4.0
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *8192780
91
+ - !ruby/object:Gem::Dependency
92
+ name: rspec
93
+ requirement: &8191840 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *8191840
102
+ - !ruby/object:Gem::Dependency
103
+ name: sinatra
104
+ requirement: &8188320 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *8188320
113
+ description: ! 'Musashi is a library for '
114
+ email: pahagon@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - Rakefile
120
+ - LICENSE
121
+ - README.rdoc
122
+ - ROADMAP.md
123
+ - CHANGELOG.md
124
+ - lib/musashi/resource.rb
125
+ - lib/musashi/connection.rb
126
+ - lib/musashi/retriever.rb
127
+ - lib/musashi/version.rb
128
+ - lib/musashi.rb
129
+ - lib/faraday/response/raise_http_5xx.rb
130
+ - lib/faraday/response/raise_http_4xx.rb
131
+ - spec/spec_helper.rb
132
+ - spec/resource_spec.rb
133
+ - spec/sinatra/public/customers/1.json
134
+ - spec/sinatra/public/customers/1/orders
135
+ - spec/sinatra/public/customers/1/contacts
136
+ - spec/sinatra/public/customers/1/billing_info
137
+ - spec/sinatra/public/customers/1/provisionings
138
+ - spec/sinatra/public/customers/1/bills
139
+ - spec/sinatra/public/customers/1/balance
140
+ - spec/sinatra/public/customers/1/credit_limit
141
+ - spec/sinatra/public/customers/1/billing_contact
142
+ - spec/sinatra/public/customers/1/manager
143
+ - spec/sinatra/app.rb
144
+ homepage: http://github.com/pahagon/musashi
145
+ licenses: []
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: 1.3.6
162
+ requirements: []
163
+ rubyforge_project: musashi
164
+ rubygems_version: 1.8.10
165
+ signing_key:
166
+ specification_version: 3
167
+ summary: Musashi is a library for
168
+ test_files:
169
+ - spec/spec_helper.rb
170
+ - spec/resource_spec.rb
171
+ - spec/sinatra/public/customers/1.json
172
+ - spec/sinatra/public/customers/1/orders
173
+ - spec/sinatra/public/customers/1/contacts
174
+ - spec/sinatra/public/customers/1/billing_info
175
+ - spec/sinatra/public/customers/1/provisionings
176
+ - spec/sinatra/public/customers/1/bills
177
+ - spec/sinatra/public/customers/1/balance
178
+ - spec/sinatra/public/customers/1/credit_limit
179
+ - spec/sinatra/public/customers/1/billing_contact
180
+ - spec/sinatra/public/customers/1/manager
181
+ - spec/sinatra/app.rb