shushu 0.1.5
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/helpers/http_helpers.rb +46 -0
- data/lib/models/account.rb +13 -0
- data/lib/models/acct_own.rb +31 -0
- data/lib/models/b_event.rb +31 -0
- data/lib/models/billable_unit.rb +35 -0
- data/lib/models/heart_beat.rb +10 -0
- data/lib/models/line_item.rb +19 -0
- data/lib/models/r_code.rb +14 -0
- data/lib/models/report.rb +51 -0
- data/lib/models/res_own.rb +31 -0
- data/lib/models/rev_rep.rb +13 -0
- data/lib/models/unit_group.rb +59 -0
- data/lib/presenters/base_presenter.rb +25 -0
- data/lib/presenters/line_item_presenter.rb +49 -0
- data/lib/presenters/report_presenter.rb +23 -0
- data/lib/presenters/unit_group_presenter.rb +63 -0
- data/lib/presenters/unit_presenter.rb +34 -0
- data/lib/services/line_item_builder.rb +17 -0
- data/lib/shushu.rb +64 -0
- data/readme.md +252 -0
- data/test/fixtures.rb +29 -0
- data/test/models/b_event_test.rb +20 -0
- data/test/models/billable_unit_test.rb +8 -0
- data/test/models/line_item_test.rb +10 -0
- data/test/models/r_code_test.rb +18 -0
- data/test/models/rev_rep_test.rb +12 -0
- data/test/models/unit_group_test.rb +17 -0
- data/test/presenters/line_item_presenter_test.rb +36 -0
- data/test/presenters/report_presenter_test.rb +22 -0
- data/test/services/line_item_builder_test.rb +30 -0
- data/test/test_helper.rb +19 -0
- metadata +117 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module Shushu
|
2
|
+
module HttpHelpers
|
3
|
+
extend self
|
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
|
+
def headers
|
12
|
+
{:content_type => :json, :accept => :json}
|
13
|
+
end
|
14
|
+
|
15
|
+
def enc_json(hash)
|
16
|
+
Yajl::Encoder.encode(hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def dec_json(json)
|
20
|
+
Yajl::Parser.parse(json)
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_req(&blk)
|
24
|
+
begin
|
25
|
+
resp = yield
|
26
|
+
dec_json(resp.body)
|
27
|
+
rescue RestClient::Exception => e
|
28
|
+
body = dec_json(e.http_body)
|
29
|
+
case e.http_code
|
30
|
+
when 404
|
31
|
+
when 401
|
32
|
+
raise(AuthorizationError, "Response: #{body.inspect}")
|
33
|
+
when 403
|
34
|
+
when 409
|
35
|
+
when 500
|
36
|
+
raise(UnexpectedError, "Response: #{body.inspect}")
|
37
|
+
when 503
|
38
|
+
raise(ServiceDownError, "#{Shushu.url} is down for maintenance.")
|
39
|
+
else
|
40
|
+
raise(UnexpectedError, "Response: #{body.inspect}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shushu
|
2
|
+
module AcctOwn
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def act(args)
|
6
|
+
pmid, eid = args.delete(:payment_method_id), args.delete(:entity_id)
|
7
|
+
Shushu.handle_req {RestClient.post(acct_own_url(pmid, eid), args, Shushu.headers)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def xfr(args)
|
11
|
+
prev_pmid, prev_eid = args.delete(:prev_payment_method_id), args.delete(:prev_entity_id)
|
12
|
+
Shushu.handle_req {RestClient.put(acct_own_url(prev_pmid, prev_eid), args, Shushu.headers)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def deact(args)
|
16
|
+
pmid = args.delete(:payment_method_id)
|
17
|
+
eid = args.delete(:entity_id)
|
18
|
+
account_id = args[:account_id]
|
19
|
+
time = args[:time]
|
20
|
+
Shushu.handle_req {RestClient.delete([acct_own_url(pmid, eid), CGI.encode("?account_id=#{account_id}&time=#{time}")].join)}
|
21
|
+
end
|
22
|
+
|
23
|
+
def query(args)
|
24
|
+
Shushu.handle_req {RestClient.get([Shushu.url, "/accounts/#{args[:account_id]}/resource_ownerships"].join)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def acct_own_url(payment_method_id, entity_id)
|
28
|
+
[Shushu.url, "/payment_methods/#{payment_method_id}/account_ownerships/#{entity_id}"].join
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shushu
|
2
|
+
module BEvent
|
3
|
+
extend self
|
4
|
+
|
5
|
+
OPEN = "open"
|
6
|
+
CLOSE = "close"
|
7
|
+
|
8
|
+
def all
|
9
|
+
Shushu.handle_req {RestClient.get([Shushu.url, "/billable_events"].join, Shushu.headers)}
|
10
|
+
end
|
11
|
+
|
12
|
+
def open(args)
|
13
|
+
hid = args.delete(:hid)
|
14
|
+
entity_id = args.delete(:entity_id)
|
15
|
+
args[:state] = OPEN
|
16
|
+
Shushu.handle_req {RestClient.put(events_url(hid, entity_id), args, Shushu.headers)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def close(args)
|
20
|
+
hid = args.delete(:hid)
|
21
|
+
entity_id = args.delete(:entity_id)
|
22
|
+
args[:state] = CLOSE
|
23
|
+
Shushu.handle_req {RestClient.put(events_url(hid, entity_id), args, Shushu.headers)}
|
24
|
+
end
|
25
|
+
|
26
|
+
def events_url(hid, entity_id)
|
27
|
+
[Shushu.url, "/resources/#{hid}/billable_events/#{entity_id}"].join
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Shushu
|
2
|
+
class BillableUnit
|
3
|
+
attr_accessor(
|
4
|
+
:hid,
|
5
|
+
:from,
|
6
|
+
:to,
|
7
|
+
:qty,
|
8
|
+
:rate,
|
9
|
+
:rate_period,
|
10
|
+
:product_group,
|
11
|
+
:product_name
|
12
|
+
)
|
13
|
+
|
14
|
+
def initialize(args_hash)
|
15
|
+
@hid = args_hash["hid"]
|
16
|
+
@from = args_hash["from"]
|
17
|
+
@to = args_hash["to"]
|
18
|
+
@qty = args_hash["qty"].to_f
|
19
|
+
@rate = args_hash["rate"].to_i
|
20
|
+
@rate_period = args_hash["rate_period"]
|
21
|
+
@product_name = args_hash["product_name"]
|
22
|
+
@product_group = args_hash["product_group"]
|
23
|
+
end
|
24
|
+
|
25
|
+
def id
|
26
|
+
#TODO remove stub
|
27
|
+
Time.now.to_i
|
28
|
+
end
|
29
|
+
|
30
|
+
def total
|
31
|
+
@rate * @qty
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Shushu
|
2
|
+
class LineItem
|
3
|
+
|
4
|
+
attr_reader :unit_groups
|
5
|
+
|
6
|
+
def initialize(unit_groups)
|
7
|
+
@unit_groups = unit_groups
|
8
|
+
end
|
9
|
+
|
10
|
+
def app_name
|
11
|
+
@unit_groups.first.hid
|
12
|
+
end
|
13
|
+
|
14
|
+
def total
|
15
|
+
@unit_groups.map(&:total).reduce(:+)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Shushu
|
2
|
+
class Report
|
3
|
+
attr_accessor(
|
4
|
+
:from,
|
5
|
+
:to,
|
6
|
+
:billable_units
|
7
|
+
)
|
8
|
+
|
9
|
+
def fetch
|
10
|
+
Shushu.handle_req do
|
11
|
+
RestClient.get([Shushu.url, resource].join, {:params => {:from => from.utc.to_s, :to => to.utc.to_s}})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def billable_units
|
16
|
+
@billable_units ||= report["billable_units"].map {|buh| BillableUnit.new(buh)}
|
17
|
+
end
|
18
|
+
|
19
|
+
def total
|
20
|
+
report["total"]
|
21
|
+
end
|
22
|
+
|
23
|
+
def report
|
24
|
+
@report ||= fetch
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class UsageReport < Report
|
29
|
+
attr_accessor :account_id
|
30
|
+
|
31
|
+
def initialize(account_id, from, to)
|
32
|
+
@account_id, @from, @to = account_id, from, to
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource
|
36
|
+
"/accounts/#{@account_id}/usage_reports"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Invoice < Report
|
41
|
+
attr_accessor :payment_method_id
|
42
|
+
|
43
|
+
def initialize(payment_method_id, from, to)
|
44
|
+
@payment_method_id, @from, @to = payment_method_id, from, to
|
45
|
+
end
|
46
|
+
|
47
|
+
def resource
|
48
|
+
"/payment_methods/#{payment_method_id}/invoices"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shushu
|
2
|
+
module ResOwn
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def act(args)
|
6
|
+
aid, eid = args.delete(:account_id), args.delete(:entity_id)
|
7
|
+
Shushu.handle_req {RestClient.post(res_own_url(aid, eid), args, Shushu.headers)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def xfr(args)
|
11
|
+
prev_aid, prev_eid = args.delete(:prev_account_id), args.delete(:prev_entity_id)
|
12
|
+
Shushu.handle_req {RestClient.put(res_own_url(prev_aid, prev_eid), args, Shushu.headers)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def deact(args)
|
16
|
+
aid = args.delete(:account_id)
|
17
|
+
eid = args.delete(:entity_id)
|
18
|
+
hid = args[:hid]
|
19
|
+
time = args[:time]
|
20
|
+
Shushu.handle_req {RestClient.delete([res_own_url(aid, eid), CGI.encode("?hid=#{hid}&time=#{time}")].join)}
|
21
|
+
end
|
22
|
+
|
23
|
+
def query(args)
|
24
|
+
Shushu.handle_req {RestClient.get([Shushu.url, "/accounts/#{args[:account_id]}/resource_ownerships"].join)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def res_own_url(account_id, entity_id)
|
28
|
+
[Shushu.url, "/accounts/#{account_id}/resource_ownerships/#{entity_id}"].join
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Shushu
|
2
|
+
class UnitGroup
|
3
|
+
InvalidUnitGroup = Class.new(ShushuException)
|
4
|
+
|
5
|
+
attr_reader :units
|
6
|
+
|
7
|
+
def initialize(units)
|
8
|
+
@units = units
|
9
|
+
check_rates
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_rates
|
13
|
+
if @units.map(&:rate).uniq.length > 1
|
14
|
+
raise(InvalidUnitGroup, "Rates must be homogenius.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def total
|
19
|
+
rate * qty
|
20
|
+
end
|
21
|
+
|
22
|
+
def qty
|
23
|
+
@units.map(&:qty).reduce(:+)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rate
|
27
|
+
sample_unit.rate
|
28
|
+
end
|
29
|
+
|
30
|
+
def rate_period
|
31
|
+
sample_unit.rate_period
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
sample_unit.product_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def hid
|
39
|
+
sample_unit.hid
|
40
|
+
end
|
41
|
+
|
42
|
+
def product_name
|
43
|
+
sample_unit.product_name
|
44
|
+
end
|
45
|
+
|
46
|
+
def product_group
|
47
|
+
sample_unit.product_group
|
48
|
+
end
|
49
|
+
|
50
|
+
def description
|
51
|
+
sample_unit.product_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def sample_unit
|
55
|
+
@sample_unit ||= @units.sample
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Shushu
|
2
|
+
class BasePresenter
|
3
|
+
|
4
|
+
def money(cents)
|
5
|
+
add_commas(pennies_to_dollar(cents.to_i))
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_commas(str)
|
9
|
+
str.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
|
10
|
+
end
|
11
|
+
|
12
|
+
def pennies_to_dollar(qty)
|
13
|
+
sprintf("%.2f", qty / 100.0)
|
14
|
+
end
|
15
|
+
|
16
|
+
def trunc_hours(hrs)
|
17
|
+
sprintf("%.3f", hrs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def date_range(s, f)
|
21
|
+
s.strftime('%d %b %H:%M ') + " - " + f.strftime('%d %b %H:%M ')
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Shushu
|
2
|
+
class LineItemPresenter < BasePresenter
|
3
|
+
|
4
|
+
def initialize(line_item)
|
5
|
+
@line_item = line_item
|
6
|
+
@unit_groups = @line_item.unit_groups
|
7
|
+
@unit_group_presenters = @unit_groups.map {|ug| UnitGroupPresenter.new(ug)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def total
|
11
|
+
money(@line_item.total)
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
@line_item.app_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def unit_group_rate(product_group)
|
19
|
+
unit_group_presenters(product_group).sample.rate
|
20
|
+
end
|
21
|
+
|
22
|
+
def unit_group_total(product_group)
|
23
|
+
money(
|
24
|
+
unit_group_presenters(product_group).
|
25
|
+
map(&:unit_group).
|
26
|
+
map(&:total).
|
27
|
+
reduce(:+)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def unit_group_qty(product_group)
|
32
|
+
trunc_hours(
|
33
|
+
unit_group_presenters(product_group).
|
34
|
+
map(&:unit_group).
|
35
|
+
map(&:qty).
|
36
|
+
reduce(:+)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def unit_group_presenters(product_group=nil)
|
41
|
+
if product_group
|
42
|
+
@unit_group_presenters.select {|ugp| ugp.product_group == product_group}
|
43
|
+
else
|
44
|
+
@unit_group_presenters
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Shushu
|
2
|
+
class ReportPresenter < BasePresenter
|
3
|
+
|
4
|
+
def initialize(report, line_item_builder=LineItemBuilder)
|
5
|
+
@report = report
|
6
|
+
@units = @report.billable_units
|
7
|
+
@builder = line_item_builder
|
8
|
+
end
|
9
|
+
|
10
|
+
def total
|
11
|
+
money(@report.total)
|
12
|
+
end
|
13
|
+
|
14
|
+
def line_item_presenters
|
15
|
+
@line_item_presenters ||= begin
|
16
|
+
@builder.build(@units).map do |li|
|
17
|
+
LineItemPresenter.new(li)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Shushu
|
2
|
+
class UnitGroupPresenter < BasePresenter
|
3
|
+
|
4
|
+
attr_reader :unit_group
|
5
|
+
|
6
|
+
def initialize(unit_group)
|
7
|
+
@unit_group = unit_group
|
8
|
+
end
|
9
|
+
|
10
|
+
def id
|
11
|
+
[:hid, :name].map {|m| unit_group.send(m)}.join("_").gsub("-", "_")
|
12
|
+
end
|
13
|
+
|
14
|
+
def unit_presenters
|
15
|
+
@unit_presenters ||= @unit_group.units.map {|u| UnitPresenter.new(u)}
|
16
|
+
end
|
17
|
+
|
18
|
+
def daily_report_url
|
19
|
+
#TODO remove chart stub
|
20
|
+
#payments_uri("/billable_units/daily")
|
21
|
+
"/billable_units/daily"
|
22
|
+
end
|
23
|
+
|
24
|
+
def compacted_reports
|
25
|
+
#TODO remove chart stub
|
26
|
+
#BillableUnitReporter.compacted(@unit_group.units)
|
27
|
+
[]
|
28
|
+
end
|
29
|
+
|
30
|
+
def total
|
31
|
+
money(@unit_group.total)
|
32
|
+
end
|
33
|
+
|
34
|
+
def qty
|
35
|
+
trunc_hours(@unit_group.qty)
|
36
|
+
end
|
37
|
+
|
38
|
+
def rate
|
39
|
+
@unit_group.rate
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
@unit_group.name
|
44
|
+
end
|
45
|
+
|
46
|
+
def description
|
47
|
+
@unit_group.description
|
48
|
+
end
|
49
|
+
|
50
|
+
def product_name
|
51
|
+
@unit_group.product_name
|
52
|
+
end
|
53
|
+
|
54
|
+
def product_group
|
55
|
+
@unit_group.product_group
|
56
|
+
end
|
57
|
+
|
58
|
+
def unit_of_measure
|
59
|
+
@unit_group.rate_period
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Shushu
|
2
|
+
class UnitPresenter < BasePresenter
|
3
|
+
|
4
|
+
def initialize(unit)
|
5
|
+
@unit = unit
|
6
|
+
end
|
7
|
+
|
8
|
+
def total
|
9
|
+
money(@unit.total)
|
10
|
+
end
|
11
|
+
|
12
|
+
def rate
|
13
|
+
[money(@unit.rate), @unit.rate_period].join("/")
|
14
|
+
end
|
15
|
+
|
16
|
+
def qty
|
17
|
+
trunc_hours(@unit.qty)
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
@unit.from
|
22
|
+
end
|
23
|
+
|
24
|
+
def end
|
25
|
+
@unit.to
|
26
|
+
end
|
27
|
+
|
28
|
+
def description
|
29
|
+
@unit.product_name
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Shushu
|
2
|
+
module LineItemBuilder
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Give build() a collection of units and it will return a
|
6
|
+
# collection of line_items. Each line_item will have a collection
|
7
|
+
# of unit_groups wich will hold a collection of units
|
8
|
+
|
9
|
+
def build(units)
|
10
|
+
units.group_by(&:hid).map do |hid, units_by_hid|
|
11
|
+
units_by_hid.group_by(&:product_name).map do |product_name, units_by_name|
|
12
|
+
UnitGroup.new(units_by_name)
|
13
|
+
end
|
14
|
+
end.map {|unit_groups| LineItem.new(unit_groups)}.flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/shushu.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "yajl"
|
2
|
+
require "rest-client"
|
3
|
+
require "helpers/http_helpers"
|
4
|
+
|
5
|
+
require "active_support/inflector"
|
6
|
+
Inflector = ActiveSupport::Inflector.send(:extend, ActiveSupport::Inflector)
|
7
|
+
|
8
|
+
module Shushu
|
9
|
+
extend self
|
10
|
+
extend HttpHelpers
|
11
|
+
|
12
|
+
ShushuException = Class.new(Exception)
|
13
|
+
AuthenticationError = Class.new(ShushuException)
|
14
|
+
AuthorizationError = Class.new(ShushuException)
|
15
|
+
ServiceDownError = Class.new(ShushuException)
|
16
|
+
UnexpectedError = Class.new(ShushuException)
|
17
|
+
SymanticError = Class.new(ShushuException)
|
18
|
+
|
19
|
+
def url
|
20
|
+
Client.url
|
21
|
+
end
|
22
|
+
|
23
|
+
def url=(url)
|
24
|
+
Client.url = url
|
25
|
+
end
|
26
|
+
|
27
|
+
class Client
|
28
|
+
def self.url=(url)
|
29
|
+
@@url = url
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.url
|
33
|
+
@@url || ENV["SHUSHU_URL"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(url)
|
37
|
+
self.class.url = url
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](api)
|
41
|
+
Shushu.const_get(Inflector.camelize(api))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
require "models/account"
|
48
|
+
require "models/billable_unit"
|
49
|
+
require "models/report"
|
50
|
+
require "models/unit_group"
|
51
|
+
require "models/line_item"
|
52
|
+
require "models/b_event"
|
53
|
+
require "models/res_own"
|
54
|
+
require "models/r_code"
|
55
|
+
require "models/rev_rep"
|
56
|
+
require "models/heart_beat"
|
57
|
+
|
58
|
+
require "services/line_item_builder"
|
59
|
+
|
60
|
+
require "presenters/base_presenter"
|
61
|
+
require "presenters/unit_group_presenter"
|
62
|
+
require "presenters/unit_presenter"
|
63
|
+
require "presenters/line_item_presenter"
|
64
|
+
require "presenters/report_presenter"
|
data/readme.md
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
# Shushu Client
|
2
|
+
|
3
|
+
## Purpose
|
4
|
+
|
5
|
+
This gem wraps the APIs defined [here](https://github.com/heroku/shushu/tree/master/doc).
|
6
|
+
The shushu client also provides a set of objects that help with the presentation
|
7
|
+
of an invoice.
|
8
|
+
|
9
|
+
## Setup
|
10
|
+
|
11
|
+
```bash
|
12
|
+
$ gem install shushu
|
13
|
+
```
|
14
|
+
|
15
|
+
```bash
|
16
|
+
# Optional. When set, shushu will ignore bad responses from Shushu's API.
|
17
|
+
# Default: false
|
18
|
+
$SHUSHU_CLIENT_UNSAFE=true
|
19
|
+
|
20
|
+
# Required
|
21
|
+
# Default: nil
|
22
|
+
$SHUSHU_URL=https://shushu-stg.heroku.com
|
23
|
+
```
|
24
|
+
|
25
|
+
**Ruby configuration will take precedence over environment variables.**
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Shushu.url = "https://core:secret@shushu-stg.heroku.com"
|
29
|
+
```
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### PaymentMethods
|
34
|
+
|
35
|
+
This API deals primarily with credit cards. PaymentMethods can be created
|
36
|
+
indipendintly of Accounts. You will need a payment_method to generate an
|
37
|
+
invoice.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
#TODO Build API
|
41
|
+
```
|
42
|
+
|
43
|
+
### Accounts
|
44
|
+
|
45
|
+
This API deals with accounts which is a primitive for grouping resources. You
|
46
|
+
will need an account to generate a usage report.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Shushu::Account.create
|
50
|
+
#=> {:id => "001"}
|
51
|
+
```
|
52
|
+
|
53
|
+
### AccountOwnerships
|
54
|
+
|
55
|
+
Use this API when you want to setup associations between Vault accounts and
|
56
|
+
Vault payment_methods.
|
57
|
+
|
58
|
+
For complete details on the semantics of this API, read the [AccountOwnerships
|
59
|
+
API docs.](https://github.com/heroku/shushu/blob/master/doc/account_ownership_api.md)
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
# To associate an account with a payment_method
|
63
|
+
Shushu::AcctOwn.act(
|
64
|
+
:account_id => vault_account_id,
|
65
|
+
:payment_method_id => payment_method_id,
|
66
|
+
:entity_id => entity_id,
|
67
|
+
:time => time
|
68
|
+
)
|
69
|
+
#=> {"payment_method_id"=>"123", "account_id"=>"1", "entity_id"=>"entity123"}
|
70
|
+
|
71
|
+
# Now we need to change the payment_method on an account
|
72
|
+
Shushu::AcctOwn.xfr(
|
73
|
+
:prev_payment_method_id => prev_payment_method_id,
|
74
|
+
:payment_method_id => new_payment_method_id,
|
75
|
+
:account_id => account_id,
|
76
|
+
:prev_entity_id => prev_entity_id,
|
77
|
+
:entity_id => entity_id,
|
78
|
+
:time => time
|
79
|
+
)
|
80
|
+
#=> {"payment_method_id"=>"456", "account_id"=>"1", "entity_id"=>"event124"}
|
81
|
+
```
|
82
|
+
|
83
|
+
### RateCodes
|
84
|
+
|
85
|
+
Before you can send BillableEvents to Shushu, you will need to provision a
|
86
|
+
RateCode.
|
87
|
+
|
88
|
+
* rate - int. units should be pennies.
|
89
|
+
* product_group - string.
|
90
|
+
* product_name - string. optional. you can also pass the product name in an event.
|
91
|
+
|
92
|
+
[Shushu API Docs](https://github.com/heroku/shushu/blob/master/doc/rate_code_api.md)
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
Shushu::RCode.create(
|
96
|
+
:rate => 5,
|
97
|
+
:product_group => "addon",
|
98
|
+
:product_name => "postgres"
|
99
|
+
)
|
100
|
+
#=> {:slug => "AO01", :rate => 5, :product_group => "addon", :product_name => "postgres"}
|
101
|
+
```
|
102
|
+
|
103
|
+
### ResourceOwnerships
|
104
|
+
|
105
|
+
Use this API when dealing with resources and Vault accounts. For instance:
|
106
|
+
Heroku apps and Teams.
|
107
|
+
|
108
|
+
For complete details on the semantics of this API, read the [ResourceOwnerships
|
109
|
+
API docs.](https://github.com/heroku/shushu/blob/master/doc/resource_ownership_api.md)
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# When a new app is created, activate a new resource_ownership record.
|
113
|
+
Shushu::ResOwn.act(
|
114
|
+
:hid => hid,
|
115
|
+
:entity_id => entity_id,
|
116
|
+
:account_id => vault_account_id,
|
117
|
+
:time => time
|
118
|
+
)
|
119
|
+
#=> {"hid"=>"123", "account_id"=>"1", "entity_id"=>"event123"}
|
120
|
+
|
121
|
+
|
122
|
+
# When an app is transfered to another vault account, transfer the resource_ownership record.
|
123
|
+
Shushu::ResOwn.xfr(
|
124
|
+
:hid => hid,
|
125
|
+
:prev_entity_id => prev_entity_id,
|
126
|
+
:prev_vault_account_id => prev_vault_account_id,
|
127
|
+
:entity_id => entity_id,
|
128
|
+
:account_id => new_vault_account_id,
|
129
|
+
:time => time
|
130
|
+
)
|
131
|
+
#=> {"hid"=>"123", "account_id"=>"1", "entity_id"=>"event123"}
|
132
|
+
|
133
|
+
# When an app is destroyed, deactivate the resource_ownership record.
|
134
|
+
Shushu::ResOwn.deact(
|
135
|
+
:hid => hid,
|
136
|
+
:entity_id => entity_id,
|
137
|
+
:account_id => vault_account_id,
|
138
|
+
:time => time
|
139
|
+
)
|
140
|
+
#=> {"hid"=>"123", "account_id"=>"1", "entity_id"=>"event123"}
|
141
|
+
```
|
142
|
+
|
143
|
+
### BillableEvents
|
144
|
+
|
145
|
+
Use this API when you want to start billing for a resource. You can start
|
146
|
+
emitting events prior to setting up relationships between accounts and
|
147
|
+
payment_methods. (Although, usage reports and invoices will not be available
|
148
|
+
until account_ownerships and resource_ownerships have been established.)
|
149
|
+
|
150
|
+
For complete details on the semantics of this API, read the [BillableEvents
|
151
|
+
API docs.](https://github.com/heroku/shushu/blob/master/doc/events_api.md)
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
# Open an event when you would like to start billing.
|
155
|
+
Shushu::BEvent.open(
|
156
|
+
:entity_id => entity_id,
|
157
|
+
:hid => hid,
|
158
|
+
:time => time,
|
159
|
+
:rate_code => rate_code,
|
160
|
+
:product_name => product_name,
|
161
|
+
:qty => qty
|
162
|
+
)
|
163
|
+
|
164
|
+
# Don't forget to close it.
|
165
|
+
Shushu::BEvent.close(
|
166
|
+
:entity_id => entity_id,
|
167
|
+
:time => time
|
168
|
+
)
|
169
|
+
```
|
170
|
+
|
171
|
+
### UsageReports
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
report = Shushu::UsageReport.new(account_id, from, to)
|
175
|
+
report.billable_units
|
176
|
+
```
|
177
|
+
|
178
|
+
|
179
|
+
### Invoices
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
invoice = Shushu::UsageReport.new(account_id, from, to)
|
183
|
+
invoice.billable_units
|
184
|
+
```
|
185
|
+
|
186
|
+
|
187
|
+
### Report Generation
|
188
|
+
|
189
|
+
Invoices and UsageReports can be used for report generation. Basically, the
|
190
|
+
report generation code expects a collection of BillableUnits. BillableUnits are
|
191
|
+
returned from both the Invoice API and the UsageReport API. However, the details
|
192
|
+
of the billable_units may be different with respect to the type of the report.
|
193
|
+
|
194
|
+
#### Presenters
|
195
|
+
|
196
|
+
Clients of this library will want to generate some sort of view for the reports, the
|
197
|
+
presenter objects were created to aid with that effort. You should only need to
|
198
|
+
use the presenters while building views. Each view wraps a simple model object.
|
199
|
+
All of the models and presenters are derived from the billable_unit which is
|
200
|
+
retreived from the remote API.
|
201
|
+
|
202
|
+
Report --> LineItem --> UnitGroup --> Billable Unit
|
203
|
+
|
204
|
+
|
205
|
+
#### ReportPresenter
|
206
|
+
|
207
|
+
The report presenter is how you will kick off the process of generating a
|
208
|
+
report. You hand it a report object, either a UsageReport or and Invoice, and
|
209
|
+
using the reports billable_units, it will build the line_items and a set of
|
210
|
+
line_item_presenters for the line_items.
|
211
|
+
|
212
|
+
#### LineItemBuilder
|
213
|
+
|
214
|
+
The ReportPresenter will create a set of line_items based upon the
|
215
|
+
billable_units in the report. The default is to group things by HID. Thus there
|
216
|
+
will be a line_item for each distinct HID in the set of billable_units. The
|
217
|
+
builder will also give a collection of unit_groups to the line_item. By default,
|
218
|
+
the unit_groups will be partitioned by the product_group. (i.e. dyno, addon)
|
219
|
+
|
220
|
+
You can customize the LineItemBuilder by creating a new class that responds to
|
221
|
+
build and passing it to the ReportPresenter.
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
ReportPresenter.new(report, CustomLineItemBuilder)
|
225
|
+
```
|
226
|
+
|
227
|
+
#### LineItemPresenter
|
228
|
+
|
229
|
+
The LineItemPresenter is responsible for handling the total and names of the
|
230
|
+
line_items. It also manages the set of unit_groups. Since the default
|
231
|
+
LineItemBuilder partitioned unit_groups based upon product_group, you can ask
|
232
|
+
the LineItemPresenter for infomation about subsets of unit_groups. For instance:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
line_item_presenter.unit_group_presenters("dyno")
|
236
|
+
line_item_presenter.unit_group_total("dyno")
|
237
|
+
line_item_presenter.unit_group_qty("dyno")
|
238
|
+
```
|
239
|
+
|
240
|
+
|
241
|
+
#### UnitGroupPresenter
|
242
|
+
|
243
|
+
UnitGroups are collections of billable_units that are partitioned by
|
244
|
+
product_name. So if there exists a set of billable_units that all have
|
245
|
+
product_group = "dyno" and product_name= "worker", then this presenter
|
246
|
+
will give you information about that group.
|
247
|
+
|
248
|
+
|
249
|
+
#### UnitPresenter
|
250
|
+
|
251
|
+
Finally, the UnitPresenter wraps a billable_unit and exposes methods to show
|
252
|
+
totals, quantities and other meta-data.
|
data/test/fixtures.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Fixtures
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def billable_units
|
5
|
+
[
|
6
|
+
{
|
7
|
+
:hid => 123,
|
8
|
+
:from => Time.mktime(2000,1),
|
9
|
+
:to => Time.mktime(2000,2),
|
10
|
+
:qty => 744,
|
11
|
+
:rate => 5,
|
12
|
+
:rate_period => "hour",
|
13
|
+
:product_group => "dyno",
|
14
|
+
:product_name => "web"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
:hid => 123,
|
18
|
+
:from => Time.mktime(2000,1),
|
19
|
+
:to => Time.mktime(2000,2),
|
20
|
+
:qty => 744,
|
21
|
+
:rate => 5,
|
22
|
+
:rate_period => "hour",
|
23
|
+
:product_group => "dyno",
|
24
|
+
:product_name => "worker"
|
25
|
+
}
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class BEventTest < ShushuTest
|
4
|
+
def test_open
|
5
|
+
our_params = {
|
6
|
+
:hid => "app123",
|
7
|
+
:entity_id => 1,
|
8
|
+
:rate_code => "RT01",
|
9
|
+
:time => Time.utc(2012,1).to_s,
|
10
|
+
:product_name => "web",
|
11
|
+
:qty => 1
|
12
|
+
}
|
13
|
+
api_resp_body = Shushu::HttpHelpers.enc_json(our_params)
|
14
|
+
Shushu.url = "http://provider:password@shushu.heroku.com"
|
15
|
+
FakeWeb.register_uri(:put, (Shushu.url + "/resources/app123/billable_events/1"), :body => api_resp_body)
|
16
|
+
event = Shushu::BEvent.open(our_params)
|
17
|
+
assert_equal(1, event["entity_id"])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class LineItemTest < ShushuTest
|
4
|
+
def test_total
|
5
|
+
unit1 = Shushu::BillableUnit.new("rate" => 10, "qty" => 10)
|
6
|
+
unit_group1 = Shushu::UnitGroup.new([unit1])
|
7
|
+
line_item = Shushu::LineItem.new([unit_group1])
|
8
|
+
assert_equal(100, line_item.total)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class RCodeTest < ShushuTest
|
4
|
+
def test_open
|
5
|
+
our_params = {
|
6
|
+
:rate => 5,
|
7
|
+
:product_group => "addon",
|
8
|
+
:product_name => "postgres",
|
9
|
+
:description => "A database service."
|
10
|
+
}
|
11
|
+
api_resp_body = Shushu::HttpHelpers.enc_json(our_params.merge(:slug => "AO01"))
|
12
|
+
Shushu.url = "http://provider:password@shushu.heroku.com"
|
13
|
+
FakeWeb.register_uri(:post, (Shushu.url + "/rate_codes"), :body => api_resp_body)
|
14
|
+
rate_code = Shushu::RCode.create(our_params)
|
15
|
+
assert_equal("AO01", rate_code["slug"])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class RevRepTest < ShushuTest
|
4
|
+
def test_for_month
|
5
|
+
api_resp_body = Shushu::HttpHelpers.enc_json({:ok => true})
|
6
|
+
Shushu.url = "http://provider:password@shushu.heroku.com"
|
7
|
+
url = Shushu.url + "/rev_rep?from%3D2011-01-01+00%3A00%3A00+UTC&to%3D2011-02-01+00%3A00%3A00+UTC"
|
8
|
+
FakeWeb.register_uri(:get, url, :body => api_resp_body)
|
9
|
+
Shushu::RevRep.get(Time.utc(2011,1), Time.utc(2011,2))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class UnitGroupTest < ShushuTest
|
4
|
+
def test_total
|
5
|
+
unit1 = Shushu::BillableUnit.new("rate" => 10, "qty" => 10)
|
6
|
+
unit_group1 = Shushu::UnitGroup.new([unit1])
|
7
|
+
assert_equal(100, unit_group1.total)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_check_rates
|
11
|
+
unit1 = Shushu::BillableUnit.new("rate" => 10, "qty" => 10)
|
12
|
+
unit2 = Shushu::BillableUnit.new("rate" => 9, "qty" => 10)
|
13
|
+
unit_group1 = Shushu::UnitGroup.new([unit1])
|
14
|
+
unit_group2 = Shushu::UnitGroup.new([unit2])
|
15
|
+
assert_raises(Shushu::UnitGroup::InvalidUnitGroup) {Shushu::UnitGroup.new([unit1, unit2])}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class LineItemPresenterTest < ShushuTest
|
4
|
+
def test_unit_group_total
|
5
|
+
unit1 = Shushu::BillableUnit.new("rate" => 1000, "qty" => 10, "product_group" => "dyno")
|
6
|
+
unit_group1 = Shushu::UnitGroup.new([unit1])
|
7
|
+
line_item = Shushu::LineItem.new([unit_group1])
|
8
|
+
lip = Shushu::LineItemPresenter.new(line_item)
|
9
|
+
assert_equal("100.00", lip.unit_group_total("dyno"))
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_unit_group_qty
|
13
|
+
unit1 = Shushu::BillableUnit.new("rate" => 1000, "qty" => 10, "product_group" => "dyno")
|
14
|
+
unit2 = Shushu::BillableUnit.new("rate" => 1000, "qty" => 1, "product_group" => "dyno")
|
15
|
+
unit_group1 = Shushu::UnitGroup.new([unit1, unit2])
|
16
|
+
line_item = Shushu::LineItem.new([unit_group1])
|
17
|
+
lip = Shushu::LineItemPresenter.new(line_item)
|
18
|
+
assert_equal("11.000", lip.unit_group_qty("dyno"))
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_unit_group_presenters
|
22
|
+
unit1 = Shushu::BillableUnit.new("product_group" => "dyno")
|
23
|
+
unit2 = Shushu::BillableUnit.new("product_group" => "dyno")
|
24
|
+
unit3 = Shushu::BillableUnit.new("product_group" => "addon")
|
25
|
+
unit_group1 = Shushu::UnitGroup.new([unit1, unit2])
|
26
|
+
unit_group2 = Shushu::UnitGroup.new([unit3])
|
27
|
+
line_item = Shushu::LineItem.new([unit_group1, unit_group2])
|
28
|
+
lip = Shushu::LineItemPresenter.new(line_item)
|
29
|
+
|
30
|
+
assert_equal(2, lip.unit_group_presenters.length)
|
31
|
+
assert_equal(1, lip.unit_group_presenters("dyno").map(&:product_group).uniq.length)
|
32
|
+
assert_equal("addon", lip.unit_group_presenters("addon").map(&:product_group).uniq.first)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class ReportPresenterTest < ShushuTest
|
4
|
+
|
5
|
+
def report
|
6
|
+
Shushu::Report.new.tap do |r|
|
7
|
+
r.billable_units = Fixtures.billable_units.map {|h| Shushu::BillableUnit.new(h)}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_init
|
12
|
+
presenter = Shushu::ReportPresenter.new(report)
|
13
|
+
assert(!presenter.nil?)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_returns_line_item_presenters
|
17
|
+
presenter = Shushu::ReportPresenter.new(report)
|
18
|
+
lip = presenter.line_item_presenters
|
19
|
+
assert_equal(Shushu::LineItemPresenter, lip.first.class)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class LineItemBuilderTest < ShushuTest
|
4
|
+
|
5
|
+
def units
|
6
|
+
Fixtures.billable_units.map {|h| Shushu::BillableUnit.new(h)}
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_build
|
10
|
+
line_items = Shushu::LineItemBuilder.build(units)
|
11
|
+
assert(!line_items.nil?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_build_creates_line_item_by_hid
|
15
|
+
line_items = Shushu::LineItemBuilder.build(units)
|
16
|
+
assert_equal(1, line_items.length)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_build_creates_one_unit_group_for_line_item
|
20
|
+
line_item = Shushu::LineItemBuilder.build(units).pop
|
21
|
+
assert_equal(1, line_item.unit_groups.length)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_build_puts_both_units_on_unit_group
|
25
|
+
line_item = Shushu::LineItemBuilder.build(units).pop
|
26
|
+
unit_group = line_item.unit_groups.pop
|
27
|
+
assert_equal(2, unit_group.units.length)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift("lib")
|
2
|
+
$:.unshift("test")
|
3
|
+
|
4
|
+
ENV["RACK_ENV"] = "test"
|
5
|
+
|
6
|
+
require "rubygems"
|
7
|
+
require "bundler"
|
8
|
+
Bundler.require(:default, :test)
|
9
|
+
|
10
|
+
require "minitest/autorun"
|
11
|
+
require "shushu"
|
12
|
+
require "fakeweb"
|
13
|
+
require "fixtures"
|
14
|
+
|
15
|
+
puts "Blocking network connectivity"
|
16
|
+
FakeWeb.allow_net_connect = false
|
17
|
+
|
18
|
+
class ShushuTest < MiniTest::Unit::TestCase
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shushu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Smith
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-29 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rest-client
|
16
|
+
requirement: &26931360 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.6.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *26931360
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yajl-ruby
|
27
|
+
requirement: &26930880 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *26930880
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: active_support
|
38
|
+
requirement: &26930500 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *26930500
|
47
|
+
description: A ruby wrapper around Shushu's HTTP API.
|
48
|
+
email: ryan@heroku.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- readme.md
|
54
|
+
- lib/presenters/base_presenter.rb
|
55
|
+
- lib/presenters/unit_presenter.rb
|
56
|
+
- lib/presenters/unit_group_presenter.rb
|
57
|
+
- lib/presenters/report_presenter.rb
|
58
|
+
- lib/presenters/line_item_presenter.rb
|
59
|
+
- lib/shushu.rb
|
60
|
+
- lib/models/account.rb
|
61
|
+
- lib/models/acct_own.rb
|
62
|
+
- lib/models/report.rb
|
63
|
+
- lib/models/unit_group.rb
|
64
|
+
- lib/models/rev_rep.rb
|
65
|
+
- lib/models/line_item.rb
|
66
|
+
- lib/models/heart_beat.rb
|
67
|
+
- lib/models/r_code.rb
|
68
|
+
- lib/models/res_own.rb
|
69
|
+
- lib/models/b_event.rb
|
70
|
+
- lib/models/billable_unit.rb
|
71
|
+
- lib/helpers/http_helpers.rb
|
72
|
+
- lib/services/line_item_builder.rb
|
73
|
+
- test/presenters/line_item_presenter_test.rb
|
74
|
+
- test/presenters/report_presenter_test.rb
|
75
|
+
- test/fixtures.rb
|
76
|
+
- test/models/r_code_test.rb
|
77
|
+
- test/models/billable_unit_test.rb
|
78
|
+
- test/models/b_event_test.rb
|
79
|
+
- test/models/unit_group_test.rb
|
80
|
+
- test/models/rev_rep_test.rb
|
81
|
+
- test/models/line_item_test.rb
|
82
|
+
- test/services/line_item_builder_test.rb
|
83
|
+
- test/test_helper.rb
|
84
|
+
homepage: http://github.com/heroku/shushu
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.10
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Shushu client lib
|
108
|
+
test_files:
|
109
|
+
- test/presenters/line_item_presenter_test.rb
|
110
|
+
- test/presenters/report_presenter_test.rb
|
111
|
+
- test/models/r_code_test.rb
|
112
|
+
- test/models/billable_unit_test.rb
|
113
|
+
- test/models/b_event_test.rb
|
114
|
+
- test/models/unit_group_test.rb
|
115
|
+
- test/models/rev_rep_test.rb
|
116
|
+
- test/models/line_item_test.rb
|
117
|
+
- test/services/line_item_builder_test.rb
|