shushu 0.1.16 → 1.0.0rc1

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.
@@ -2,12 +2,6 @@ module Shushu
2
2
  module HttpHelpers
3
3
  extend self
4
4
 
5
- def build_q_params(hash)
6
- "?" + hash.map do |k, v|
7
- CGI.escape("#{k}=#{v}")
8
- end.join("&")
9
- end
10
-
11
5
  def headers
12
6
  {
13
7
  :content_type => :json,
@@ -1,30 +1,25 @@
1
1
  module Shushu
2
- module BEvent
3
- extend self
2
+ class BillableEvent < Client
4
3
 
5
4
  OPEN = "open"
6
5
  CLOSE = "close"
7
6
 
8
- def all
9
- Shushu.handle_req {RestClient.get([Shushu.url, "/billable_events"].join, Shushu.headers)}
10
- end
11
-
12
7
  def open(args)
13
8
  hid = args.delete(:hid) || args.delete("hid")
14
9
  entity_id = args.delete(:entity_id) || args.delete("entity_id")
15
10
  args[:state] = OPEN
16
- Shushu.handle_req {RestClient.put(events_url(hid, entity_id), args, Shushu.headers)}
11
+ handle_req {RestClient.put(events_url(hid, entity_id), args, headers)}
17
12
  end
18
13
 
19
14
  def close(args)
20
15
  hid = args.delete(:hid) || args.delete("hid")
21
16
  entity_id = args.delete(:entity_id) || args.delete("entity_id")
22
17
  args[:state] = CLOSE
23
- Shushu.handle_req {RestClient.put(events_url(hid, entity_id), args, Shushu.headers)}
18
+ handle_req {RestClient.put(events_url(hid, entity_id), args, headers)}
24
19
  end
25
20
 
26
21
  def events_url(hid, entity_id)
27
- [Shushu.url, "/resources/#{hid}/billable_events/#{entity_id}"].join
22
+ [url, "/resources/#{hid}/billable_events/#{entity_id}"].join
28
23
  end
29
24
 
30
25
  end
@@ -0,0 +1,20 @@
1
+ module Shushu
2
+ class RateCode < Client
3
+ def create(args)
4
+ if args[:slug].nil?
5
+ handle_req do
6
+ RestClient.post(rate_codes_url, args, headers)
7
+ end
8
+ else
9
+ handle_req do
10
+ url = [rate_codes_url, args.delete(:slug)].join("/")
11
+ RestClient.put(url, args, headers)
12
+ end
13
+ end
14
+ end
15
+
16
+ def rate_codes_url
17
+ [url, "/rate_codes"].join
18
+ end
19
+ end
20
+ end
data/lib/models/report.rb CHANGED
@@ -1,68 +1,50 @@
1
+ require 'ostruct'
2
+
1
3
  module Shushu
2
- class Report
4
+ class Report < Client
3
5
  attr_accessor(
4
6
  :from,
5
7
  :to,
6
- :billable_units
8
+ :billable_units,
9
+ :report
7
10
  )
8
11
 
9
- def fetch
10
- url = [Shushu.url, resource].join
11
- Shushu.handle_req do
12
- RestClient.get(url, {:params => {
13
- :from => from.utc.to_s,
14
- :to => to.utc.to_s
15
- }})
12
+ def fetch(report_url, from, to)
13
+ @report = handle_req do
14
+ response_found = false
15
+ while !response_found
16
+ response = RestClient::Resource.new(report_url).get({:params => {
17
+ :from => from.utc.to_s,
18
+ :to => to.utc.to_s
19
+ }})
20
+
21
+ if response.code == 202
22
+ sleep 0.5
23
+ else
24
+ response_found = true
25
+ end
26
+ end
27
+ response
16
28
  end
29
+ self
17
30
  end
18
31
 
19
32
  def billable_units
20
- @billable_units ||= report["billable_units"].map {|buh| BillableUnit.new(buh)}
33
+ @billable_units ||= report["billable_units"].map do |unit_hash|
34
+ OpenStruct.new(unit_hash).tap { |o| o.extend Totalable }
35
+ end
21
36
  end
22
37
 
23
38
  def total
24
39
  report["total"]
25
40
  end
26
-
27
- def report
28
- @report ||= fetch
29
- end
30
- end
31
-
32
- class UsageReport < Report
33
- attr_accessor :account_id
34
-
35
- def initialize(account_id, from, to)
36
- @account_id, @from, @to = account_id, from, to
37
- end
38
-
39
- def resource
40
- "/accounts/#{@account_id}/usage_reports"
41
- end
42
- end
43
-
44
- class Invoice < Report
45
- attr_accessor :payment_method_id
46
-
47
- def initialize(payment_method_id, from, to)
48
- @payment_method_id, @from, @to = payment_method_id, from, to
49
- end
50
-
51
- def resource
52
- "/payment_methods/#{payment_method_id}/invoices"
53
- end
54
41
  end
55
42
 
56
43
  class RateCodeReport < Report
57
- attr_accessor :rate_code_slug
58
-
59
- def initialize(rate_code_slug, from, to)
60
- @rate_code_slug, @from, @to = rate_code_slug, from, to
61
- end
44
+ attr_accessor :slug
62
45
 
63
- def resource
64
- "/rate_codes/#{rate_code_slug}/report"
46
+ def fetch(slug, from, to)
47
+ super(self.url + "/rate_codes/#{slug}", from, to)
65
48
  end
66
49
  end
67
-
68
50
  end
File without changes
data/lib/shushu.rb CHANGED
@@ -11,60 +11,34 @@ module Inflector
11
11
  end
12
12
 
13
13
  module Shushu
14
- extend self
15
- extend HttpHelpers
16
-
17
14
  ShushuException = Class.new(StandardError)
18
15
 
19
- def url
20
- Client.url
21
- end
22
-
23
- def url=(url)
24
- Client.url = url
25
- end
26
-
27
16
  class Client
28
- def self.url=(url)
29
- @@url = url
30
- end
17
+ include HttpHelpers
31
18
 
32
- def self.url
33
- if defined?(@@url)
34
- @@url
35
- else
36
- ENV["SHUSHU_URL"]
37
- end
38
- end
19
+ attr_accessor :url
39
20
 
40
- def initialize(url)
41
- self.class.url = url
21
+ def initialize(url = ENV["SHUSHU_URL"])
22
+ @url = url
42
23
  end
43
24
 
44
- def [](api)
45
- Shushu.const_get(Inflector.camelize(api))
25
+ def method_missing(method, *args, &block)
26
+ begin
27
+ klass = method.to_s.gsub(/s$/,'')
28
+ Shushu.const_get(Inflector.camelize(klass)).new(@url)
29
+ rescue NameError => e
30
+ raise NameError, "'#{method}' not a local variable, method, or Shushu classname"
31
+ end
46
32
  end
47
33
  end
48
34
  end
49
35
 
36
+ # for ease of typing
37
+ ShuShu = Shushu
50
38
 
51
- require "models/account"
52
- require "models/billable_unit"
39
+ require "totalable"
40
+ require "models/billable_event"
41
+ require "models/resource_ownership"
42
+ require "models/rate_code"
53
43
  require "models/report"
54
- require "models/unit_group"
55
- require "models/line_item"
56
- require "models/b_event"
57
- require "models/res_own"
58
- require "models/r_code"
59
- require "models/rev_rep"
60
44
  require "models/heart_beat"
61
- require "models/p_method"
62
- require "models/acct_own"
63
-
64
- require "services/line_item_builder"
65
-
66
- require "presenters/base_presenter"
67
- require "presenters/unit_group_presenter"
68
- require "presenters/unit_presenter"
69
- require "presenters/line_item_presenter"
70
- require "presenters/report_presenter"
data/lib/totalable.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Shushu
2
+ module Totalable
3
+ def total
4
+ rate.to_f * qty.to_f
5
+ end
6
+ end
7
+ end
data/readme.md CHANGED
@@ -13,109 +13,13 @@ $ export SHUSHU_URL=https://123:secret@shushu.heroku.com
13
13
 
14
14
  ## Usage
15
15
 
16
- There are two ways to interact with Shushu APIs using this client. The first
17
- approach will rely on the credentials in `SHUSHU_URL`.
16
+ Instantiate a client to interact with the Shushu API.
18
17
 
19
- ```ruby
20
- Shushu::BEvent.open({})
21
- ```
22
-
23
- The second approach allows for credentials other than the ones stored in the
24
- `SHUSHU_URL`.
18
+ `Client::new` defaults to using `ENV['SHUHSU_URL']` if one is not provided.
25
19
 
26
20
  ```ruby
27
21
  shushu = Shushu::Client.new("https://other_provider_id:other_provider_token@shushu.heroku.com")
28
- shushu[:b_event].open({})
29
- ```
30
-
31
- ### PaymentMethod
32
-
33
- This API deals primarily with credit cards. PaymentMethods can be created
34
- indipendintly of Accounts. You will need a payment_method to generate an
35
- invoice.
36
-
37
- Reference: [PaymentMethod API](https://github.com/heroku/shushud/blob/master/doc/payment_methods_api.md)
38
-
39
- Create a new payment method with encrypted values:
40
-
41
- ```ruby
42
- PMethod.create(
43
- :card_num => "some encrypted value",
44
- :card_exp_year => "2012",
45
- :card_exp_month => "12"
46
- )
47
- #=> {:id => "12345", :card_token => "abc123", :card_type => "visa", :card_last4 => "1111"}
48
- ```
49
-
50
- Update a payment_method with a new credit card:
51
-
52
- ```ruby
53
- PMethod.update(
54
- :id => "001",
55
- :card_num => "some encrypted value",
56
- :card_exp_year => "2012",
57
- :card_exp_month => "12"
58
- )
59
- #=> {:id => "001", :card_token => "abc123", :card_type => "visa", :card_last4 => "1111"}
60
- ```
61
-
62
- Create a new payment_method with a token. Note, this endpoint is
63
- idempotent with respect to the id and token.
64
-
65
- ```ruby
66
- PMethod.update(:id => "my-custom-id", :card_token => "abc123")
67
- #=> {:id => "my-custom-id", :card_token => "abc123"}
68
- ```
69
-
70
- To mark a payment_method in such a way that credit card processing will be
71
- disabled:
72
-
73
- ```ruby
74
- PMethod.update(:id => "001", :non_receivable => true)
75
- #=> {:id => "001", :non_receivable => true}
76
- ```
77
-
78
- ### Account
79
-
80
- This API deals with accounts which is a primitive for grouping resources. You
81
- will need an account to generate a usage report.
82
-
83
- ```ruby
84
- Shushu::Account.create
85
- #=> {:id => "001"}
86
- ```
87
-
88
- ### AccountOwnership
89
-
90
- Use this API when you want to setup associations between Shushu accounts and
91
- Shushu payment_methods.
92
-
93
- Reference: [AccountOwnerships API](https://github.com/heroku/shushud/blob/master/doc/account_ownership_api.md)
94
-
95
- To associate an account with a payment_method:
96
-
97
- ```ruby
98
- Shushu::AcctOwn.act(
99
- :account_id => vault_account_id,
100
- :payment_method_id => payment_method_id,
101
- :entity_id => entity_id,
102
- :time => time
103
- )
104
- #=> {"payment_method_id"=>"123", "account_id"=>"1", "entity_id"=>"entity123"}
105
- ```
106
-
107
- If you need to move one account to another payment_method, you will need to
108
- deactivate the previous relationship and activate a new one. To deactivate
109
- call this method:
110
-
111
- ```ruby
112
- Shushu::AcctOwn.deact(
113
- :account_id => vault_account_id,
114
- :payment_method_id => payment_method_id,
115
- :entity_id => entity_id,
116
- :time => time
117
- )
118
- #=> {"payment_method_id"=>"123", "account_id"=>"1", "entity_id"=>"entity123"}
22
+ shushu.billable_events.open({})
119
23
  ```
120
24
 
121
25
  ### RateCode
@@ -126,7 +30,7 @@ RateCode. You can provide your own slug or a UUID will be generated for you. If
126
30
  Reference: [RateCode API](https://github.com/heroku/shushud/blob/master/doc/rate_code_api.md)
127
31
 
128
32
  ```ruby
129
- Shushu::RCode.create(
33
+ client.rate_codes.create(
130
34
  :slug => some_record.uuid,
131
35
  :rate => 5,
132
36
  :group => "addon",
@@ -148,7 +52,7 @@ Let Shushu generate a UUID for the slug.
148
52
  Remember to save the slug somewhere so that you can present it with billable_events.
149
53
 
150
54
  ```ruby
151
- Shushu::RCode.create(
55
+ client.rate_codes.create(
152
56
  :rate => 5,
153
57
  :group => "addon",
154
58
  :period => "month",
@@ -176,7 +80,7 @@ When the app is created or it is joining a new account, activate the the
176
80
  resource_ownership record.
177
81
 
178
82
  ```ruby
179
- Shushu::ResOwn.act(
83
+ client.resource_ownerships.act(
180
84
  :resource_id => resource_id,
181
85
  :entity_id => entity_id,
182
86
  :account_id => vault_account_id,
@@ -189,7 +93,7 @@ When an app is destroyed, or the app is moving to a new account_id,
189
93
  deactivate the resource_ownership record.
190
94
 
191
95
  ```ruby
192
- Shushu::ResOwn.deact(
96
+ client.resource_ownerships.deact(
193
97
  :resource_id => resource_id,
194
98
  :entity_id => entity_id,
195
99
  :account_id => vault_account_id,
@@ -210,7 +114,7 @@ Reference: [BillableEvent API](https://github.com/heroku/shushud/blob/master/doc
210
114
  Open an event when you would like to start billing.
211
115
 
212
116
  ```ruby
213
- Shushu::BEvent.open(
117
+ client.billable_events.open(
214
118
  :entity_id => entity_id,
215
119
  :hid => hid,
216
120
  :time => time,
@@ -224,7 +128,7 @@ Shushu::BEvent.open(
224
128
  Don't forget to close it.
225
129
 
226
130
  ```ruby
227
- Shushu::BEvent.close(
131
+ client.billable_events.close(
228
132
  :entity_id => entity_id,
229
133
  :hid => hid,
230
134
  :rate_code => rate_code,
@@ -232,88 +136,10 @@ Shushu::BEvent.close(
232
136
  )
233
137
  ```
234
138
 
235
- ### Invoice
236
-
237
- ```ruby
238
- invoice = Shushu::Invoice.new(payment_method_id, from, to)
239
- invoice.billable_units
240
- ```
241
- ### UsageReport
242
-
243
- ```ruby
244
- report = Shushu::UsageReport.new(account_id, from, to)
245
- report.billable_units
246
- ```
247
-
248
139
  ### RateCodeReport
249
140
 
250
141
  ```ruby
251
- report = Shushu::RateCodeReport.new(rate_code_slug, from, to)
142
+ report = client.rate_code_reports.fetch(rate_code_slug, from, to)
252
143
  report.total
253
144
  report.billable_units
254
145
  ```
255
-
256
-
257
- ### Report Generation
258
-
259
- Invoices and UsageReports can be used for report generation. Basically, the
260
- report generation code expects a collection of BillableUnits. BillableUnits are
261
- returned from both the Invoice API and the UsageReport API. However, the details
262
- of the billable_units may be different with respect to the type of the report.
263
-
264
- #### Presenters
265
-
266
- Clients of this library will want to generate some sort of view for the reports, the
267
- presenter objects were created to aid with that effort. You should only need to
268
- use the presenters while building views. Each view wraps a simple model object.
269
- All of the models and presenters are derived from the billable_unit which is
270
- retreived from the remote API.
271
-
272
- Report --> LineItem --> UnitGroup --> Billable Unit
273
-
274
- #### ReportPresenter
275
-
276
- The report presenter is how you will kick off the process of generating a
277
- report. You hand it a report object, either a UsageReport or and Invoice, and
278
- using the reports billable_units, it will build the line_items and a set of
279
- line_item_presenters for the line_items.
280
-
281
- #### LineItemBuilder
282
-
283
- The ReportPresenter will create a set of line_items based upon the
284
- billable_units in the report. The default is to group things by HID. Thus there
285
- will be a line_item for each distinct HID in the set of billable_units. The
286
- builder will also give a collection of unit_groups to the line_item. By default,
287
- the unit_groups will be partitioned by the product_group. (i.e. dyno, addon)
288
-
289
- You can customize the LineItemBuilder by creating a new class that responds to
290
- build and passing it to the ReportPresenter.
291
-
292
- ```ruby
293
- ReportPresenter.new(report, CustomLineItemBuilder)
294
- ```
295
-
296
- #### LineItemPresenter
297
-
298
- The LineItemPresenter is responsible for handling the total and names of the
299
- line_items. It also manages the set of unit_groups. Since the default
300
- LineItemBuilder partitioned unit_groups based upon product_group, you can ask
301
- the LineItemPresenter for infomation about subsets of unit_groups. For instance:
302
-
303
- ```ruby
304
- line_item_presenter.unit_group_presenters("dyno")
305
- line_item_presenter.unit_group_total("dyno")
306
- line_item_presenter.unit_group_qty("dyno")
307
- ```
308
- #### UnitGroupPresenter
309
-
310
- UnitGroups are collections of billable_units that are partitioned by
311
- product_name. So if there exists a set of billable_units that all have
312
- product_group = "dyno" and product_name= "worker", then this presenter
313
- will give you information about that group.
314
-
315
-
316
- #### UnitPresenter
317
-
318
- Finally, the UnitPresenter wraps a billable_unit and exposes methods to show
319
- totals, quantities and other meta-data.
@@ -0,0 +1,41 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class BillableEventTest < ShushuTest
4
+ def setup
5
+ @client = Shushu::Client.new "http://example.com"
6
+ super
7
+ end
8
+
9
+ def test_open
10
+ api_resp_body = Shushu::HttpHelpers.enc_json(our_params)
11
+ FakeWeb.register_uri(:put,
12
+ "http://example.com/resources/app123/billable_events/1",
13
+ :body => api_resp_body
14
+ )
15
+ event = @client.billable_events.open(our_params)
16
+ assert_equal(1, event["entity_id"])
17
+ end
18
+
19
+ def test_close
20
+ api_resp_body = Shushu::HttpHelpers.enc_json(our_params)
21
+ FakeWeb.register_uri(:put,
22
+ "http://example.com/resources/app123/billable_events/1",
23
+ :body => api_resp_body
24
+ )
25
+ event = @client.billable_events.close(our_params)
26
+ assert_equal(1, event["entity_id"])
27
+ end
28
+
29
+ def our_params
30
+ {
31
+ :hid => "app123",
32
+ :entity_id => 1,
33
+ :rate_code => "RT01",
34
+ :time => Time.utc(2012,1).to_s,
35
+ :product_name => "web",
36
+ :description => "some command",
37
+ :qty => 1
38
+ }
39
+ end
40
+ end
41
+
@@ -1,6 +1,12 @@
1
1
  require File.expand_path('../../test_helper', __FILE__)
2
2
 
3
3
  class RCodeTest < ShushuTest
4
+ def setup
5
+ @url = "http://provider:password@shushu.heroku.com"
6
+ @client = Shushu::Client.new(@url)
7
+ super
8
+ end
9
+
4
10
  def test_create_without_slug
5
11
  our_params = {
6
12
  :rate => 5,
@@ -10,12 +16,11 @@ class RCodeTest < ShushuTest
10
16
  api_resp_body = Shushu::HttpHelpers.enc_json(
11
17
  our_params.merge(:slug => "AO01")
12
18
  )
13
- Shushu.url = "http://provider:password@shushu.heroku.com"
14
19
  FakeWeb.register_uri(:post,
15
- (Shushu.url + "/rate_codes"),
20
+ (@url + "/rate_codes"),
16
21
  :body => api_resp_body
17
22
  )
18
- rate_code = Shushu::RCode.create(our_params)
23
+ rate_code = @client.rate_codes.create(our_params)
19
24
  assert_equal("AO01", rate_code["slug"])
20
25
  end
21
26
 
@@ -30,14 +35,12 @@ class RCodeTest < ShushuTest
30
35
  api_resp_body = Shushu::HttpHelpers.enc_json(
31
36
  our_params.merge(:slug => slug)
32
37
  )
33
- Shushu.url = "http://provider:password@shushu.heroku.com"
34
38
  FakeWeb.register_uri(:put,
35
- (Shushu.url + "/rate_codes/#{slug}"),
39
+ (@url + "/rate_codes/#{slug}"),
36
40
  :body => api_resp_body
37
41
  )
38
- rate_code = Shushu::RCode.create(our_params)
42
+ rate_code = @client.rate_codes.create(our_params)
39
43
  assert_equal(slug, rate_code["slug"])
40
-
41
44
  end
42
45
  end
43
46
 
@@ -0,0 +1,19 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class RateCodeReportTest < ShushuTest
4
+ def setup
5
+ @url = "http://provider:password@shushu.heroku.com"
6
+ @client = Shushu::Client.new(@url)
7
+ super
8
+ end
9
+
10
+ def test_rate_code_report
11
+ FakeWeb.register_uri(:get,
12
+ Regexp.new(Regexp.escape(@url + "/rate_codes/GUID")),
13
+ :body => Shushu::HttpHelpers.enc_json({:billable_units => [{"rate" => 10, "qty" => 5}]})
14
+ )
15
+
16
+ report = @client.rate_code_reports.fetch("GUID", Time.mktime(2011,1,1), Time.mktime(2011,2,1))
17
+ assert_equal 50, report.billable_units.first.total
18
+ end
19
+ end