async-firefly 1.0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/lib/async/firefly/about.rb +79 -0
  3. data/lib/async/firefly/accounts.rb +129 -0
  4. data/lib/async/firefly/attachments.rb +53 -0
  5. data/lib/async/firefly/autocomplete.rb +435 -0
  6. data/lib/async/firefly/available_budgets.rb +59 -0
  7. data/lib/async/firefly/bills.rb +107 -0
  8. data/lib/async/firefly/budgets.rb +165 -0
  9. data/lib/async/firefly/categories.rb +63 -0
  10. data/lib/async/firefly/charts.rb +73 -0
  11. data/lib/async/firefly/client.rb +89 -0
  12. data/lib/async/firefly/configurations.rb +62 -0
  13. data/lib/async/firefly/currencies.rb +71 -0
  14. data/lib/async/firefly/data.rb +37 -0
  15. data/lib/async/firefly/error.rb +57 -0
  16. data/lib/async/firefly/escape.rb +9 -0
  17. data/lib/async/firefly/exchange_rates.rb +77 -0
  18. data/lib/async/firefly/insight.rb +185 -0
  19. data/lib/async/firefly/links.rb +69 -0
  20. data/lib/async/firefly/nested.rb +75 -0
  21. data/lib/async/firefly/object_groups.rb +33 -0
  22. data/lib/async/firefly/page.rb +139 -0
  23. data/lib/async/firefly/piggy_banks.rb +153 -0
  24. data/lib/async/firefly/preferences.rb +58 -0
  25. data/lib/async/firefly/recurrences.rb +169 -0
  26. data/lib/async/firefly/redacted.rb +32 -0
  27. data/lib/async/firefly/representation.rb +99 -0
  28. data/lib/async/firefly/rule_groups.rb +55 -0
  29. data/lib/async/firefly/rules.rb +105 -0
  30. data/lib/async/firefly/search.rb +40 -0
  31. data/lib/async/firefly/submit.rb +139 -0
  32. data/lib/async/firefly/summary.rb +45 -0
  33. data/lib/async/firefly/sums.rb +17 -0
  34. data/lib/async/firefly/tags.rb +57 -0
  35. data/lib/async/firefly/transactions.rb +231 -0
  36. data/lib/async/firefly/user_groups.rb +49 -0
  37. data/lib/async/firefly/users.rb +35 -0
  38. data/lib/async/firefly/version.rb +5 -0
  39. data/lib/async/firefly/webhooks.rb +101 -0
  40. data/lib/async/firefly/wrapper.rb +65 -0
  41. data/lib/async/firefly.rb +2 -0
  42. data/license.md +21 -0
  43. data/readme.md +498 -0
  44. metadata +110 -0
@@ -0,0 +1,37 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ module Data
6
+ def bulk_update_transactions(query)
7
+ self.discard(self.with(path: "v1/data/bulk/transactions", parameters: {query: query}), nil, method: :post)
8
+ end
9
+
10
+ def destroy!(objects, unused: nil)
11
+ self.discard(self.with(path: "v1/data/destroy", parameters: {objects: objects, unused: unused}.compact))
12
+ end
13
+
14
+ def purge! = self.discard(self.with(path: "v1/data/purge"))
15
+
16
+ def export_accounts(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/accounts", parameters: parameters))
17
+
18
+ def export_bills(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/bills", parameters: parameters))
19
+
20
+ def export_subscriptions(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/subscriptions", parameters: parameters))
21
+
22
+ def export_budgets(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/budgets", parameters: parameters))
23
+
24
+ def export_categories(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/categories", parameters: parameters))
25
+
26
+ def export_piggy_banks(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/piggy-banks", parameters: parameters))
27
+
28
+ def export_recurring(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/recurring", parameters: parameters))
29
+
30
+ def export_rules(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/rules", parameters: parameters))
31
+
32
+ def export_tags(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/tags", parameters: parameters))
33
+
34
+ def export_transactions(**parameters) = self.fetch(Download, self.with(path: "v1/data/export/transactions", parameters: parameters))
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,57 @@
1
+ require "async/rest/error"
2
+
3
+ module Async
4
+ module Firefly
5
+ class Error < StandardError
6
+ end
7
+
8
+ class Failure < Async::REST::ResponseError
9
+ def self.for(response)
10
+ self.new(response, self.problem_for(self.payload_of(response)))
11
+ end
12
+
13
+ def self.payload_of(response)
14
+ response.read
15
+ rescue StandardError => error
16
+ error.message
17
+ end
18
+
19
+ def self.problem_for(payload)
20
+ case payload
21
+ in Hash => problem
22
+ problem
23
+ else
24
+ {message: payload.to_s}
25
+ end
26
+ end
27
+
28
+ def self.summary_of(problem)
29
+ problem[:message] || problem[:exception] || problem.inspect
30
+ end
31
+
32
+ def initialize(response, problem = {})
33
+ super(response)
34
+
35
+ @problem = problem
36
+ end
37
+
38
+ attr :problem
39
+
40
+ def message = self.status.to_s + ": " + self.class.summary_of(@problem)
41
+
42
+ def to_s = self.message
43
+
44
+ def status = @response.status
45
+
46
+ def retry_after = @response.headers["retry-after"]
47
+
48
+ def detail = @problem[:message]
49
+
50
+ def exception_class = @problem[:exception]
51
+
52
+ def errors = @problem.fetch(:errors, {})
53
+
54
+ def invalid? = !self.errors.empty?
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,9 @@
1
+ require "protocol/url/encoding"
2
+
3
+ module Async
4
+ module Firefly
5
+ def self.escape(value)
6
+ ::Protocol::URL::Encoding.escape(value.to_s)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,77 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class CurrencyExchangeRate < Representation
6
+ def created_at = self.attributes[:created_at]
7
+
8
+ def updated_at = self.attributes[:updated_at]
9
+
10
+ def from_currency_id = self.attributes[:from_currency_id]
11
+
12
+ def from_currency_name = self.attributes[:from_currency_name]
13
+
14
+ def from_currency_code = self.attributes[:from_currency_code]
15
+
16
+ def from_currency_symbol = self.attributes[:from_currency_symbol]
17
+
18
+ def from_currency_decimal_places = self.attributes[:from_currency_decimal_places]
19
+
20
+ def to_currency_id = self.attributes[:to_currency_id]
21
+
22
+ def to_currency_name = self.attributes[:to_currency_name]
23
+
24
+ def to_currency_code = self.attributes[:to_currency_code]
25
+
26
+ def to_currency_symbol = self.attributes[:to_currency_symbol]
27
+
28
+ def to_currency_decimal_places = self.attributes[:to_currency_decimal_places]
29
+
30
+ def rate = self.attributes[:rate]
31
+
32
+ def date = self.attributes[:date]
33
+
34
+ def update(payload) = self.submit(CurrencyExchangeRate, @resource, payload, method: :put)
35
+
36
+ def delete = self.discard(@resource)
37
+ end
38
+
39
+ class CurrencyExchangeRatePage < Page
40
+ MEMBER = CurrencyExchangeRate
41
+ end
42
+
43
+ class ExchangeRatePairPage < Page
44
+ MEMBER = CurrencyExchangeRate
45
+
46
+ def member_path(record) = "../../../exchange-rates/" + self.identifier(record, :id)
47
+ end
48
+
49
+ class ExchangeRateDatedPage < Page
50
+ MEMBER = CurrencyExchangeRate
51
+
52
+ def member_path(record) = "../../../../exchange-rates/" + self.identifier(record, :id)
53
+ end
54
+
55
+ module ExchangeRates
56
+ def exchange_rates(**parameters) = Collection.for(self, "v1/exchange-rates", CurrencyExchangeRatePage, **parameters)
57
+
58
+ def exchange_rate(id) = CurrencyExchangeRate.new(self.with(path: "v1/exchange-rates/" + Firefly.escape(id)))
59
+
60
+ def create_exchange_rate(payload) = self.submit(CurrencyExchangeRate, self.with(path: "v1/exchange-rates"), payload, identify: true)
61
+
62
+ def exchange_rates_between(from, to, **parameters) = Collection.for(self, "v1/exchange-rates/" + Firefly.escape(from) + "/" + Firefly.escape(to), ExchangeRatePairPage, **parameters)
63
+
64
+ def delete_exchange_rates_between(from, to) = self.discard(self.with(path: "v1/exchange-rates/" + Firefly.escape(from) + "/" + Firefly.escape(to)))
65
+
66
+ def exchange_rates_on(from, to, date, **parameters) = Collection.for(self, "v1/exchange-rates/" + Firefly.escape(from) + "/" + Firefly.escape(to) + "/" + Firefly.escape(date), ExchangeRateDatedPage, **parameters)
67
+
68
+ def update_exchange_rate_on(from, to, date, payload) = self.submit(CurrencyExchangeRate, self.with(path: "v1/exchange-rates/" + Firefly.escape(from) + "/" + Firefly.escape(to) + "/" + Firefly.escape(date)), payload, method: :put)
69
+
70
+ def delete_exchange_rate_on(from, to, date) = self.discard(self.with(path: "v1/exchange-rates/" + Firefly.escape(from) + "/" + Firefly.escape(to) + "/" + Firefly.escape(date)))
71
+
72
+ def create_exchange_rates_by_date(date, payload) = self.submit(CurrencyExchangeRatePage, self.with(path: "v1/exchange-rates/by-date/" + Firefly.escape(date)), payload, identify: nil)
73
+
74
+ def create_exchange_rates_by_pair(from, to, payload) = self.submit(CurrencyExchangeRatePage, self.with(path: "v1/exchange-rates/by-currencies/" + Firefly.escape(from) + "/" + Firefly.escape(to)), payload, identify: nil)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,185 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class InsightGroupEntry < Representation
6
+ def id = self.value[:id]
7
+
8
+ def name = self.value[:name]
9
+
10
+ def difference = self.value[:difference]
11
+
12
+ def difference_float = self.value[:difference_float]
13
+
14
+ def currency_id = self.value[:currency_id]
15
+
16
+ def currency_code = self.value[:currency_code]
17
+ end
18
+
19
+ class InsightTotalEntry < Representation
20
+ def difference = self.value[:difference]
21
+
22
+ def difference_float = self.value[:difference_float]
23
+
24
+ def currency_id = self.value[:currency_id]
25
+
26
+ def currency_code = self.value[:currency_code]
27
+ end
28
+
29
+ class InsightTransferEntry < Representation
30
+ def id = self.value[:id]
31
+
32
+ def name = self.value[:name]
33
+
34
+ def difference = self.value[:difference]
35
+
36
+ def difference_float = self.value[:difference_float]
37
+
38
+ def in = self.value[:in]
39
+
40
+ def in_float = self.value[:in_float]
41
+
42
+ def out = self.value[:out]
43
+
44
+ def out_float = self.value[:out_float]
45
+
46
+ def currency_id = self.value[:currency_id]
47
+
48
+ def currency_code = self.value[:currency_code]
49
+ end
50
+
51
+ class InsightGroup < Representation
52
+ include Enumerable
53
+
54
+ def each(&block)
55
+ if block
56
+ self.deconstruct.each{|record| block.call(InsightGroupEntry.new(@resource, value: record, metadata: self.metadata))}
57
+ else
58
+ self.to_enum(:each)
59
+ end
60
+ end
61
+ end
62
+
63
+ class InsightTotal < Representation
64
+ include Enumerable
65
+
66
+ def each(&block)
67
+ if block
68
+ self.deconstruct.each{|record| block.call(InsightTotalEntry.new(@resource, value: record, metadata: self.metadata))}
69
+ else
70
+ self.to_enum(:each)
71
+ end
72
+ end
73
+ end
74
+
75
+ class InsightTransfer < Representation
76
+ include Enumerable
77
+
78
+ def each(&block)
79
+ if block
80
+ self.deconstruct.each{|record| block.call(InsightTransferEntry.new(@resource, value: record, metadata: self.metadata))}
81
+ else
82
+ self.to_enum(:each)
83
+ end
84
+ end
85
+ end
86
+
87
+ module Insight
88
+ def expense_by_expense_account(start:, end:, accounts: nil)
89
+ InsightGroup.new(self.with(path: "v1/insight/expense/expense", parameters: {start:, end:, accounts:}.compact))
90
+ end
91
+
92
+ def expense_by_asset_account(start:, end:, accounts: nil)
93
+ InsightGroup.new(self.with(path: "v1/insight/expense/asset", parameters: {start:, end:, accounts:}.compact))
94
+ end
95
+
96
+ def income_by_revenue_account(start:, end:, accounts: nil)
97
+ InsightGroup.new(self.with(path: "v1/insight/income/revenue", parameters: {start:, end:, accounts:}.compact))
98
+ end
99
+
100
+ def income_by_asset_account(start:, end:, accounts: nil)
101
+ InsightGroup.new(self.with(path: "v1/insight/income/asset", parameters: {start:, end:, accounts:}.compact))
102
+ end
103
+
104
+ def transfer_by_asset_account(start:, end:, accounts: nil)
105
+ InsightTransfer.new(self.with(path: "v1/insight/transfer/asset", parameters: {start:, end:, accounts:}.compact))
106
+ end
107
+
108
+ def expense_by_bill(start:, end:, bills: nil, accounts: nil)
109
+ InsightGroup.new(self.with(path: "v1/insight/expense/bill", parameters: {start:, end:, bills:, accounts:}.compact))
110
+ end
111
+
112
+ def expense_without_bill(start:, end:, accounts: nil)
113
+ InsightTotal.new(self.with(path: "v1/insight/expense/no-bill", parameters: {start:, end:, accounts:}.compact))
114
+ end
115
+
116
+ def expense_by_budget(start:, end:, budgets: nil, accounts: nil)
117
+ InsightGroup.new(self.with(path: "v1/insight/expense/budget", parameters: {start:, end:, budgets:, accounts:}.compact))
118
+ end
119
+
120
+ def expense_without_budget(start:, end:, accounts: nil)
121
+ InsightTotal.new(self.with(path: "v1/insight/expense/no-budget", parameters: {start:, end:, accounts:}.compact))
122
+ end
123
+
124
+ def expense_by_category(start:, end:, categories: nil, accounts: nil)
125
+ InsightGroup.new(self.with(path: "v1/insight/expense/category", parameters: {start:, end:, categories:, accounts:}.compact))
126
+ end
127
+
128
+ def expense_without_category(start:, end:, accounts: nil)
129
+ InsightTotal.new(self.with(path: "v1/insight/expense/no-category", parameters: {start:, end:, accounts:}.compact))
130
+ end
131
+
132
+ def income_by_category(start:, end:, categories: nil, accounts: nil)
133
+ InsightGroup.new(self.with(path: "v1/insight/income/category", parameters: {start:, end:, categories:, accounts:}.compact))
134
+ end
135
+
136
+ def income_without_category(start:, end:, accounts: nil)
137
+ InsightTotal.new(self.with(path: "v1/insight/income/no-category", parameters: {start:, end:, accounts:}.compact))
138
+ end
139
+
140
+ def transfer_by_category(start:, end:, categories: nil, accounts: nil)
141
+ InsightGroup.new(self.with(path: "v1/insight/transfer/category", parameters: {start:, end:, categories:, accounts:}.compact))
142
+ end
143
+
144
+ def transfer_without_category(start:, end:, accounts: nil)
145
+ InsightTotal.new(self.with(path: "v1/insight/transfer/no-category", parameters: {start:, end:, accounts:}.compact))
146
+ end
147
+
148
+ def expense_by_tag(start:, end:, tags: nil, accounts: nil)
149
+ InsightGroup.new(self.with(path: "v1/insight/expense/tag", parameters: {start:, end:, tags:, accounts:}.compact))
150
+ end
151
+
152
+ def expense_without_tag(start:, end:, accounts: nil)
153
+ InsightTotal.new(self.with(path: "v1/insight/expense/no-tag", parameters: {start:, end:, accounts:}.compact))
154
+ end
155
+
156
+ def income_by_tag(start:, end:, tags: nil, accounts: nil)
157
+ InsightGroup.new(self.with(path: "v1/insight/income/tag", parameters: {start:, end:, tags:, accounts:}.compact))
158
+ end
159
+
160
+ def income_without_tag(start:, end:, accounts: nil)
161
+ InsightTotal.new(self.with(path: "v1/insight/income/no-tag", parameters: {start:, end:, accounts:}.compact))
162
+ end
163
+
164
+ def transfer_by_tag(start:, end:, tags: nil, accounts: nil)
165
+ InsightGroup.new(self.with(path: "v1/insight/transfer/tag", parameters: {start:, end:, tags:, accounts:}.compact))
166
+ end
167
+
168
+ def transfer_without_tag(start:, end:, accounts: nil)
169
+ InsightTotal.new(self.with(path: "v1/insight/transfer/no-tag", parameters: {start:, end:, accounts:}.compact))
170
+ end
171
+
172
+ def expense_total(start:, end:, accounts: nil)
173
+ InsightTotal.new(self.with(path: "v1/insight/expense/total", parameters: {start:, end:, accounts:}.compact))
174
+ end
175
+
176
+ def income_total(start:, end:, accounts: nil)
177
+ InsightTotal.new(self.with(path: "v1/insight/income/total", parameters: {start:, end:, accounts:}.compact))
178
+ end
179
+
180
+ def transfer_total(start:, end:, accounts: nil)
181
+ InsightTotal.new(self.with(path: "v1/insight/transfer/total", parameters: {start:, end:, accounts:}.compact))
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,69 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class LinkType < Representation
6
+ def name = self.attributes[:name]
7
+
8
+ def inward = self.attributes[:inward]
9
+
10
+ def outward = self.attributes[:outward]
11
+
12
+ def editable = self.attributes[:editable]
13
+
14
+ def transactions(**parameters) = Collection.for(@resource, "transactions", LinkTypeTransactionPage, **parameters)
15
+
16
+ def update(payload) = self.submit(LinkType, @resource, payload, method: :put)
17
+
18
+ def delete = self.discard(@resource)
19
+ end
20
+
21
+ class TransactionLink < Representation
22
+ def created_at = self.attributes[:created_at]
23
+
24
+ def updated_at = self.attributes[:updated_at]
25
+
26
+ def link_type_id = self.attributes[:link_type_id]
27
+
28
+ def link_type_name = self.attributes[:link_type_name]
29
+
30
+ def inward_id = self.attributes[:inward_id]
31
+
32
+ def outward_id = self.attributes[:outward_id]
33
+
34
+ def notes = self.attributes[:notes]
35
+
36
+ def update(payload) = self.submit(TransactionLink, @resource, payload, method: :put)
37
+
38
+ def delete = self.discard(@resource)
39
+ end
40
+
41
+ class LinkTypePage < Page
42
+ MEMBER = LinkType
43
+ end
44
+
45
+ class TransactionLinkPage < Page
46
+ MEMBER = TransactionLink
47
+ end
48
+
49
+ class LinkTypeTransactionPage < Page
50
+ def member_class = Transaction
51
+
52
+ def member_path(record) = "../../../transactions/" + self.identifier(record, :id)
53
+ end
54
+
55
+ module Links
56
+ def link_types(**parameters) = Collection.for(self, "v1/link-types", LinkTypePage, **parameters)
57
+
58
+ def link_type(id) = LinkType.new(self.with(path: "v1/link-types/" + Firefly.escape(id)))
59
+
60
+ def create_link_type(payload) = self.submit(LinkType, self.with(path: "v1/link-types"), payload, identify: true)
61
+
62
+ def transaction_links(**parameters) = Collection.for(self, "v1/transaction-links", TransactionLinkPage, **parameters)
63
+
64
+ def transaction_link(id) = TransactionLink.new(self.with(path: "v1/transaction-links/" + Firefly.escape(id)))
65
+
66
+ def create_transaction_link(payload) = self.submit(TransactionLink, self.with(path: "v1/transaction-links"), payload, identify: true)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,75 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class NestedPage < Page
6
+ def collection = raise NotImplementedError
7
+
8
+ def member_path(record) = "../../../" + self.collection + "/" + self.identifier(record, :id)
9
+ end
10
+
11
+ class NestedAccountPage < NestedPage
12
+ def member_class = Account
13
+
14
+ def collection = "accounts"
15
+ end
16
+
17
+ class NestedTransactionPage < NestedPage
18
+ def member_class = Transaction
19
+
20
+ def collection = "transactions"
21
+ end
22
+
23
+ class NestedAttachmentPage < NestedPage
24
+ def member_class = Attachment
25
+
26
+ def collection = "attachments"
27
+ end
28
+
29
+ class NestedPiggyBankPage < NestedPage
30
+ def member_class = PiggyBank
31
+
32
+ def collection = "piggy-banks"
33
+ end
34
+
35
+ class NestedBillPage < NestedPage
36
+ def member_class = Bill
37
+
38
+ def collection = "bills"
39
+ end
40
+
41
+ class NestedRulePage < NestedPage
42
+ def member_class = Rule
43
+
44
+ def collection = "rules"
45
+ end
46
+
47
+ class NestedRecurrencePage < NestedPage
48
+ def member_class = Recurrence
49
+
50
+ def collection = "recurrences"
51
+ end
52
+
53
+ class NestedAvailableBudgetPage < NestedPage
54
+ def member_class = AvailableBudget
55
+
56
+ def collection = "available-budgets"
57
+ end
58
+
59
+ class NestedExchangeRatePage < NestedPage
60
+ def member_class = CurrencyExchangeRate
61
+
62
+ def collection = "exchange-rates"
63
+ end
64
+
65
+ class NestedBudgetLimitPage < NestedPage
66
+ def member_class = BudgetLimit
67
+
68
+ def collection = "budget-limits"
69
+
70
+ def member_path(record)
71
+ "../../../budgets/" + self.identifier(record, :attributes, :budget_id) + "/limits/" + self.identifier(record, :id)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ require_relative "page"
2
+
3
+ module Async
4
+ module Firefly
5
+ class ObjectGroup < Representation
6
+ def created_at = self.attributes[:created_at]
7
+
8
+ def updated_at = self.attributes[:updated_at]
9
+
10
+ def title = self.attributes[:title]
11
+
12
+ def order = self.attributes[:order]
13
+
14
+ def piggy_banks(**parameters) = Collection.for(@resource, "piggy-banks", NestedPiggyBankPage, **parameters)
15
+
16
+ def bills(**parameters) = Collection.for(@resource, "bills", NestedBillPage, **parameters)
17
+
18
+ def update(payload) = self.submit(ObjectGroup, @resource, payload, method: :put)
19
+
20
+ def delete = self.discard(@resource)
21
+ end
22
+
23
+ class ObjectGroupPage < Page
24
+ MEMBER = ObjectGroup
25
+ end
26
+
27
+ module ObjectGroups
28
+ def object_groups(**parameters) = Collection.for(self, "v1/object-groups", ObjectGroupPage, **parameters)
29
+
30
+ def object_group(id) = ObjectGroup.new(self.with(path: "v1/object-groups/" + Firefly.escape(id)))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,139 @@
1
+ require_relative "error"
2
+ require_relative "representation"
3
+
4
+ module Async
5
+ module Firefly
6
+ class Page < Representation
7
+ include Enumerable
8
+
9
+ MEMBER = Representation
10
+
11
+ def records
12
+ case self.value
13
+ in {data: Array => records}
14
+ records
15
+ else
16
+ []
17
+ end
18
+ end
19
+
20
+ def pagination
21
+ case self.value
22
+ in {meta: {pagination: Hash => pagination}}
23
+ pagination
24
+ else
25
+ {}
26
+ end
27
+ end
28
+
29
+ def total = self.pagination[:total].to_i
30
+
31
+ def per_page = self.pagination[:per_page].to_i
32
+
33
+ def current_page = self.pagination[:current_page].to_i
34
+
35
+ def total_pages = self.pagination[:total_pages].to_i
36
+
37
+ def last? = self.current_page >= [self.total_pages, 1].max
38
+
39
+ def empty? = self.records.empty?
40
+
41
+ def each(&block)
42
+ if block
43
+ self.records.each do |record|
44
+ block.call(self.member_for(record))
45
+ end
46
+ else
47
+ self.to_enum(:each)
48
+ end
49
+ end
50
+
51
+ def member_class = self.class::MEMBER
52
+
53
+ def member_path(record) = self.identifier(record, :id)
54
+
55
+ def identifier(record, *keys)
56
+ value = record.dig(*keys)
57
+
58
+ if value.nil? || value.to_s.empty?
59
+ raise Error, self.class.to_s + " cannot address a member: the response carried no " + keys.join(".")
60
+ end
61
+
62
+ Firefly.escape(value)
63
+ end
64
+
65
+ def member_resource(record)
66
+ @resource.with(path: self.member_path(record), parameters: nil, merge: false)
67
+ end
68
+
69
+ def member_for(record)
70
+ self.member_class.new(self.member_resource(record), value: {data: record}, metadata: self.metadata)
71
+ end
72
+ end
73
+
74
+ class Collection
75
+ include Enumerable
76
+
77
+ DEFAULT_LIMIT = 50
78
+
79
+ def self.for(parent, path, page_class, **parameters)
80
+ self.new(parent.with(path: path, parameters: parameters), page_class)
81
+ end
82
+
83
+ def initialize(resource, page_class)
84
+ @resource = resource
85
+ @page_class = page_class
86
+ end
87
+
88
+ attr :resource
89
+
90
+ attr :page_class
91
+
92
+ def each_page(limit: DEFAULT_LIMIT, &block)
93
+ if block
94
+ self.walk(limit, &block)
95
+ else
96
+ self.to_enum(:each_page, limit: limit)
97
+ end
98
+ end
99
+
100
+ def each(**options, &block)
101
+ if block
102
+ self.each_page(**options) do |page|
103
+ page.each(&block)
104
+ end
105
+ else
106
+ self.to_enum(:each, **options)
107
+ end
108
+ end
109
+
110
+ def page(number, limit: DEFAULT_LIMIT)
111
+ @page_class.new(@resource.with(parameters: {page: number, limit: limit}))
112
+ end
113
+
114
+ def total
115
+ self.page(1, limit: 1).total
116
+ end
117
+
118
+ def walk(limit, &block)
119
+ number = 1
120
+
121
+ while true
122
+ page = self.page(number, limit: limit)
123
+
124
+ if page.empty?
125
+ break
126
+ end
127
+
128
+ block.call(page)
129
+
130
+ if page.last?
131
+ break
132
+ end
133
+
134
+ number += 1
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end