jbr 3.8.0 → 3.10.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.
- checksums.yaml +4 -4
- data/README.md +23 -8
- data/lib/jbr/jobs.rb +4 -25
- data/lib/jbr/listable.rb +96 -0
- data/lib/jbr/mock/jobs.rb +5 -13
- data/lib/jbr/mock/visits.rb +5 -13
- data/lib/jbr/resource.rb +4 -24
- data/lib/jbr/version.rb +1 -1
- data/lib/jbr/visits.rb +5 -24
- data/lib/jbr.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a315b6ba52c573f0d7fa1a0adec8145578550bd67a136071e7b8a02ea328ea1
|
|
4
|
+
data.tar.gz: 8c338c2f5a13ce4e98c99c2b5c11377274c71908a2b93fc6c114d8fb109ae540
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e82a0f7dac54170d4f791a752f7497d0b783dce98f4057c606c3ee1f5fe73c85ef32032eb297e2406b95d82bf531d6ba1cf2f4db8a9de5be1fbef38ac1c1c69b
|
|
7
|
+
data.tar.gz: 82a90ec3316204bb8ccf87c4d4011e249cd7b5d4484697fc30477f553bd4146baa9402bff10f4c6c2f893ce93bc7d06509831ecaf4a7d8a16a60dfad5b0ae67d
|
data/README.md
CHANGED
|
@@ -111,15 +111,19 @@ job.scheduled_at # => 2026-05-14 23:02:52
|
|
|
111
111
|
job.completed_at # => 2026-05-18 11:36:13
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Or walk the account's jobs, oldest first.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
Or walk the account's jobs, oldest first. Either half of the schedule takes how much of it
|
|
115
|
+
you meant — a duration, measured from the same now the half is split at — and a walk that
|
|
116
|
+
stops at a boundary reads only the pages up to it. Jobber is asked for a page at a time, and
|
|
117
|
+
only once the page before it runs out, so `first` costs one request where `to_a` costs as many
|
|
118
|
+
as the account has pages. A walk is priced by what its pages carry, and a long one can be
|
|
119
|
+
refused for it: see [Rate limits](#rate-limits).
|
|
118
120
|
|
|
119
121
|
```ruby
|
|
120
122
|
jobs = oauth.jobs # => an Enumerable of every job, nothing fetched yet
|
|
121
|
-
oauth.jobs.past # =>
|
|
122
|
-
oauth.jobs.upcoming # =>
|
|
123
|
+
oauth.jobs.past # => the ones dated before now, nothing fetched yet
|
|
124
|
+
oauth.jobs.upcoming # => the ones dated from now on, nothing fetched yet
|
|
125
|
+
oauth.jobs.past(1.year) # => only as far back as a year, which is fewer pages to read
|
|
126
|
+
oauth.jobs.past.ids # => %w[Z2lkOi8vS ...], every page of them, and nothing else about them
|
|
123
127
|
|
|
124
128
|
job = jobs.first
|
|
125
129
|
job.name # => 'Furnace tune-up', or the job's ID where nobody titled it. Never nil or empty
|
|
@@ -175,8 +179,10 @@ Walk the account's visits, oldest first, the same way as its jobs:
|
|
|
175
179
|
|
|
176
180
|
```ruby
|
|
177
181
|
visits = oauth.visits # => an Enumerable of every visit, nothing fetched yet
|
|
178
|
-
oauth.visits.upcoming # =>
|
|
179
|
-
oauth.visits.past # =>
|
|
182
|
+
oauth.visits.upcoming # => the ones dated from now on, nothing fetched yet
|
|
183
|
+
oauth.visits.past # => the ones dated before now, nothing fetched yet
|
|
184
|
+
oauth.visits.upcoming(3.months) # => only as far ahead as three months
|
|
185
|
+
oauth.visits.upcoming.ids # => %w[Z2lkOi8vS ...], every page of them, and nothing else
|
|
180
186
|
|
|
181
187
|
visit = visits.first
|
|
182
188
|
visit.id # => 'Z2lkOi8vS'
|
|
@@ -247,6 +253,15 @@ holding a transaction open.
|
|
|
247
253
|
Every connection the gem asks for is bounded, to keep a query on the affordable side of that:
|
|
248
254
|
twenty lines to a job, and twenty jobs or visits to a page.
|
|
249
255
|
|
|
256
|
+
`ids` is the cheap way to walk an account. It asks for the ID and nothing else, which prices
|
|
257
|
+
a row at a fraction of a record and buys a hundred of them to a page — so five times the
|
|
258
|
+
account is read for a fraction of the budget. Reach for it where each record is then read on
|
|
259
|
+
its own with `find`, one background job at a time:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
oauth.jobs.past.ids.each { |id| ImportJob.perform_later id }
|
|
263
|
+
```
|
|
264
|
+
|
|
250
265
|
### Events
|
|
251
266
|
|
|
252
267
|
Parse the payload of a Jobber event webhook:
|
data/lib/jbr/jobs.rb
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
module Jbr
|
|
2
2
|
# The jobs on a Jobber account, oldest first, walked a page at a time.
|
|
3
3
|
class Jobs < Resource
|
|
4
|
-
include
|
|
4
|
+
include Includable, Listable
|
|
5
5
|
|
|
6
6
|
# What a job answers with wherever one is read, before anything it was asked to bring
|
|
7
7
|
# back with it.
|
|
8
8
|
FIELDS = 'id title instructions jobStatus total createdAt startAt completedAt ' \
|
|
9
9
|
'quote { id amounts { total } }'
|
|
10
10
|
|
|
11
|
-
# Every job on the account, past and future alike. Nothing is read until the walk starts,
|
|
12
|
-
# and a page is read only once the one before it runs out.
|
|
13
|
-
def each(&) = walk.each(&)
|
|
14
|
-
|
|
15
|
-
# @return [Enumerator<Job>] the jobs scheduled from now on.
|
|
16
|
-
def upcoming = walk from_now
|
|
17
|
-
|
|
18
|
-
# @return [Enumerator<Job>] the jobs that started before now.
|
|
19
|
-
def past = walk until_now
|
|
20
|
-
|
|
21
11
|
# Shadows Enumerable#find on purpose, the way Active Record does: a job is reached by the
|
|
22
12
|
# ID Jobber files it under, not by asking every job on the account whether it is the one.
|
|
23
13
|
# @param id [String] the Jobber ID of the job.
|
|
@@ -29,20 +19,7 @@ module Jbr
|
|
|
29
19
|
|
|
30
20
|
private
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
# what an includes brings back is charged for on top of every row of it — so a page of jobs
|
|
34
|
-
# carrying their lines, their property and its client priced past what a bucket holds. Half
|
|
35
|
-
# the page costs half the query and loses nothing, since a walk simply reads more pages.
|
|
36
|
-
def page
|
|
37
|
-
<<~GRAPHQL
|
|
38
|
-
query($after: String, $filter: JobFilterAttributes) {
|
|
39
|
-
jobs(first: 20, after: $after, filter: $filter) {
|
|
40
|
-
nodes { #{FIELDS} #{selections} }
|
|
41
|
-
pageInfo { hasNextPage endCursor }
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
GRAPHQL
|
|
45
|
-
end
|
|
22
|
+
def page = paged "#{FIELDS} #{selections}", PAGE
|
|
46
23
|
|
|
47
24
|
def one
|
|
48
25
|
<<~GRAPHQL
|
|
@@ -54,6 +31,8 @@ module Jbr
|
|
|
54
31
|
|
|
55
32
|
def field = 'jobs'
|
|
56
33
|
|
|
34
|
+
def filtered = 'JobFilterAttributes'
|
|
35
|
+
|
|
57
36
|
def item(node) = Job.new node: node
|
|
58
37
|
end
|
|
59
38
|
end
|
data/lib/jbr/listable.rb
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# What a list of records answers, and how much of an account it costs to ask. Jobber prices
|
|
3
|
+
# a query by the page it asks for and by what every row of that page carries, so a list is
|
|
4
|
+
# read either as records or as the IDs alone, each with a page sized to what it carries.
|
|
5
|
+
module Listable
|
|
6
|
+
include Enumerable
|
|
7
|
+
|
|
8
|
+
# Records a page, not forty and not a hundred: what an includes brings back is charged for
|
|
9
|
+
# on top of every row of it, so a page of jobs carrying their lines, their property and its
|
|
10
|
+
# client priced past what a bucket holds. Half the page costs half the query and loses
|
|
11
|
+
# nothing, since a walk simply reads more pages.
|
|
12
|
+
PAGE = 20
|
|
13
|
+
|
|
14
|
+
# IDs a page, which the same budget affords five times over where a row carries one field
|
|
15
|
+
# and no nesting at all.
|
|
16
|
+
IDS_PAGE = 100
|
|
17
|
+
|
|
18
|
+
# Every record the list is narrowed to, oldest first. Nothing is read until the walk
|
|
19
|
+
# starts, and a page is read only once the one before it runs out.
|
|
20
|
+
def each(&) = walk(page).each(&)
|
|
21
|
+
|
|
22
|
+
# @param within [ActiveSupport::Duration, Numeric, nil] how far ahead to look, or nil for
|
|
23
|
+
# as far ahead as the account is scheduled.
|
|
24
|
+
# @return [Listable] the same list, narrowed to what is scheduled from now on.
|
|
25
|
+
def upcoming(within = nil)
|
|
26
|
+
now = Time.now
|
|
27
|
+
narrowed after: now, before: (now + within if within)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param within [ActiveSupport::Duration, Numeric, nil] how far back to look, or nil for
|
|
31
|
+
# as far back as the account goes.
|
|
32
|
+
# @return [Listable] the same list, narrowed to what started before now.
|
|
33
|
+
def past(within = nil)
|
|
34
|
+
now = Time.now
|
|
35
|
+
narrowed before: now, after: (now - within if within)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The ID Jobber files each record under, and nothing else about it: the cheapest question
|
|
39
|
+
# an account can be walked with, and the one to ask where every record is then read on its
|
|
40
|
+
# own through {#find}.
|
|
41
|
+
# @return [Array<String>] every ID in the list, every page of them read.
|
|
42
|
+
def ids = walk(ids_page).map(&:id)
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# The two halves of a schedule, split at the moment they are asked for rather than per
|
|
47
|
+
# page: read page by page the boundary would slide, and something could cross it unseen.
|
|
48
|
+
# The same moment is the near end of a window, so the far end is measured from it too.
|
|
49
|
+
def narrowed(after: nil, before: nil)
|
|
50
|
+
bounds = { after: after&.iso8601, before: before&.iso8601 }.compact
|
|
51
|
+
self.class.new oauth: @oauth, includes: @includes, filter: { startAt: bounds }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Whether a moment falls in the stretch of the schedule the list was narrowed to, for
|
|
55
|
+
# anything answering one without asking Jobber. A record with no moment at all is in an
|
|
56
|
+
# open-ended upcoming list, since nothing has started it, and in no window, since a window
|
|
57
|
+
# is a stretch it would have to have fallen in.
|
|
58
|
+
def scheduled?(at)
|
|
59
|
+
return true unless @filter
|
|
60
|
+
|
|
61
|
+
after, before = @filter[:startAt].values_at :after, :before
|
|
62
|
+
return before.nil? unless at
|
|
63
|
+
|
|
64
|
+
(after.nil? || at >= Time.iso8601(after)) && (before.nil? || at <= Time.iso8601(before))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Every record a paged query answers, one at a time, a page read only once the one before
|
|
68
|
+
# it runs out. The filter is data: narrowed to nothing, the query narrows nothing.
|
|
69
|
+
def walk(statement)
|
|
70
|
+
Enumerator.new do |yielder|
|
|
71
|
+
after = nil
|
|
72
|
+
loop do
|
|
73
|
+
answered = @oauth.query statement, variables: { after: after, filter: @filter }.compact
|
|
74
|
+
current = answered.fetch field, {}
|
|
75
|
+
current.fetch('nodes', []).each { |node| yielder << item(node) }
|
|
76
|
+
break unless current.dig 'pageInfo', 'hasNextPage'
|
|
77
|
+
|
|
78
|
+
after = current.dig 'pageInfo', 'endCursor'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def ids_page = paged 'id', IDS_PAGE
|
|
84
|
+
|
|
85
|
+
def paged(fields, size)
|
|
86
|
+
<<~GRAPHQL
|
|
87
|
+
query($after: String, $filter: #{filtered}) {
|
|
88
|
+
#{field}(first: #{size}, after: $after, filter: $filter) {
|
|
89
|
+
nodes { #{fields} }
|
|
90
|
+
pageInfo { hasNextPage endCursor }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
GRAPHQL
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/jbr/mock/jobs.rb
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
module Jbr
|
|
2
|
-
# The jobs an app under test asked {Jbr.mock} to answer with.
|
|
2
|
+
# The jobs an app under test asked {Jbr.mock} to answer with. Only the walk is mocked:
|
|
3
|
+
# narrowing a list, and reading it as records or as IDs, is the same code a real one runs.
|
|
3
4
|
class Mock::Jobs < Jobs
|
|
4
5
|
# The job filed under that ID where the app listed one, and otherwise the single job it
|
|
5
6
|
# named — which is every app that mocks a lookup without mocking a list.
|
|
6
7
|
# @return [Mock::Job] the job asked for.
|
|
7
8
|
def find(id) = Mock::Job.new node: listed(id) || Jbr.mock.job
|
|
8
9
|
|
|
9
|
-
# @return [Enumerator<Mock::Job>] every job the app named.
|
|
10
|
-
def each(&) = mocked(Jbr.mock.jobs).each(&)
|
|
11
|
-
|
|
12
|
-
# @return [Enumerator<Mock::Job>] those it dated from now on, and any it left undated.
|
|
13
|
-
def upcoming = mocked Jbr.mock.jobs.reject { |job| started? job }
|
|
14
|
-
|
|
15
|
-
# @return [Enumerator<Mock::Job>] those it dated before now.
|
|
16
|
-
def past = mocked Jbr.mock.jobs.select { |job| started? job }
|
|
17
|
-
|
|
18
10
|
private
|
|
19
11
|
|
|
20
|
-
def
|
|
21
|
-
Enumerator.new { |yielder|
|
|
12
|
+
def walk(_statement)
|
|
13
|
+
Enumerator.new { |yielder| selected.each { |job| yielder << Mock::Job.new(node: job) } }
|
|
22
14
|
end
|
|
23
15
|
|
|
24
|
-
def
|
|
16
|
+
def selected = Jbr.mock.jobs.select { |job| scheduled? job[:scheduled_at] }
|
|
25
17
|
|
|
26
18
|
# Only a real list is looked through: an app that mocked the list as something raising
|
|
27
19
|
# was mocking the walk failing, and a lookup is a question of its own.
|
data/lib/jbr/mock/visits.rb
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
module Jbr
|
|
2
|
-
# The visits an app under test asked {Jbr.mock} to answer with.
|
|
2
|
+
# The visits an app under test asked {Jbr.mock} to answer with. Only the walk is mocked:
|
|
3
|
+
# narrowing a list, and reading it as records or as IDs, is the same code a real one runs.
|
|
3
4
|
class Mock::Visits < Visits
|
|
4
|
-
# @return [Enumerator<Mock::Visit>] every visit the app named.
|
|
5
|
-
def each(&) = mocked(Jbr.mock.visits).each(&)
|
|
6
|
-
|
|
7
|
-
# @return [Enumerator<Mock::Visit>] those it dated from now on, and any it left undated.
|
|
8
|
-
def upcoming = mocked Jbr.mock.visits.reject { |visit| started? visit }
|
|
9
|
-
|
|
10
|
-
# @return [Enumerator<Mock::Visit>] those it dated before now.
|
|
11
|
-
def past = mocked Jbr.mock.visits.select { |visit| started? visit }
|
|
12
|
-
|
|
13
5
|
# @return [Mock::Visit] the visit the app listed under that ID.
|
|
14
6
|
def find(id) = Mock::Visit.new node: listed(id)
|
|
15
7
|
|
|
16
8
|
private
|
|
17
9
|
|
|
18
|
-
def
|
|
19
|
-
Enumerator.new { |yielder|
|
|
10
|
+
def walk(_statement)
|
|
11
|
+
Enumerator.new { |yielder| selected.each { |it| yielder << Mock::Visit.new(node: it) } }
|
|
20
12
|
end
|
|
21
13
|
|
|
22
|
-
def
|
|
14
|
+
def selected = Jbr.mock.visits.select { |visit| scheduled? visit[:starts_at] }
|
|
23
15
|
|
|
24
16
|
def listed(id) = Jbr.mock.visits.to_a.find { |visit| visit[:id] == id }.to_h
|
|
25
17
|
end
|
data/lib/jbr/resource.rb
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
module Jbr
|
|
2
2
|
# What every Jobber resource shares: the node Jobber answered with, the credentials it was
|
|
3
|
-
# read through, and
|
|
3
|
+
# read through, and what a list of them was asked and narrowed to.
|
|
4
4
|
class Resource
|
|
5
5
|
# @param oauth [OAuth] the credentials to reach Jobber with, where reaching it is needed.
|
|
6
6
|
# @param node [Hash] the record as Jobber answered it.
|
|
7
7
|
# @param includes [Hash] what a list was asked to bring back beside its records.
|
|
8
|
-
|
|
8
|
+
# @param filter [Hash] what a list was narrowed to, in the shape Jobber filters by.
|
|
9
|
+
def initialize(oauth: nil, node: {}, includes: {}, filter: nil)
|
|
9
10
|
@oauth = oauth
|
|
10
11
|
@node = node
|
|
11
12
|
@id = node['id']
|
|
12
13
|
@includes = includes
|
|
14
|
+
@filter = filter
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
# @return [String, nil] the Jobber ID, from the node it came in or once one was created.
|
|
@@ -20,27 +22,5 @@ module Jbr
|
|
|
20
22
|
# @return [Time, nil] what Jobber answered under a key, as a time. An empty answer is no
|
|
21
23
|
# answer: Time.iso8601 raises on one, where nothing at all it simply has none of.
|
|
22
24
|
def time(key) = (Time.iso8601 @node[key] if @node[key].present?)
|
|
23
|
-
|
|
24
|
-
# Every item a paged query answers, one at a time, a page read only once the one before
|
|
25
|
-
# it runs out. The filter is data: handed none, the query narrows nothing.
|
|
26
|
-
def walk(filter = nil)
|
|
27
|
-
Enumerator.new do |yielder|
|
|
28
|
-
after = nil
|
|
29
|
-
loop do
|
|
30
|
-
answered = @oauth.query(page, variables: { after: after, filter: filter }.compact)
|
|
31
|
-
current = answered.fetch field, {}
|
|
32
|
-
current.fetch('nodes', []).each { |node| yielder << item(node) }
|
|
33
|
-
break unless current.dig 'pageInfo', 'hasNextPage'
|
|
34
|
-
|
|
35
|
-
after = current.dig 'pageInfo', 'endCursor'
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# The two halves of a schedule, split at the moment they are asked for rather than per
|
|
41
|
-
# page: read page by page the boundary would slide, and something could cross it unseen.
|
|
42
|
-
def from_now = { startAt: { after: Time.now.iso8601 } }
|
|
43
|
-
|
|
44
|
-
def until_now = { startAt: { before: Time.now.iso8601 } }
|
|
45
25
|
end
|
|
46
26
|
end
|
data/lib/jbr/version.rb
CHANGED
data/lib/jbr/visits.rb
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
module Jbr
|
|
2
2
|
# The visits on a Jobber account, oldest first, walked a page at a time.
|
|
3
3
|
class Visits < Resource
|
|
4
|
-
include
|
|
4
|
+
include Includable, Listable
|
|
5
5
|
|
|
6
6
|
# What a visit answers with, before anything it was asked to bring back with it.
|
|
7
7
|
FIELDS = 'id title startAt endAt allDay clientConfirmed job { id }'
|
|
8
8
|
|
|
9
|
-
# Every visit on the account, past and future alike. Nothing is read until the walk
|
|
10
|
-
# starts, and a page is read only once the one before it runs out.
|
|
11
|
-
def each(&) = walk.each(&)
|
|
12
|
-
|
|
13
|
-
# @return [Enumerator<Visit>] the visits scheduled from now on.
|
|
14
|
-
def upcoming = walk from_now
|
|
15
|
-
|
|
16
|
-
# @return [Enumerator<Visit>] the visits that started before now.
|
|
17
|
-
def past = walk until_now
|
|
18
|
-
|
|
19
9
|
# Shadows Enumerable#find on purpose, the way jobs do: a visit is reached by the ID Jobber
|
|
20
10
|
# files it under, not by asking every visit on the account whether it is the one.
|
|
21
11
|
# @param id [String] the Jobber ID of the visit.
|
|
@@ -27,6 +17,8 @@ module Jbr
|
|
|
27
17
|
|
|
28
18
|
private
|
|
29
19
|
|
|
20
|
+
def page = paged "#{FIELDS} #{selections}", PAGE
|
|
21
|
+
|
|
30
22
|
def one
|
|
31
23
|
<<~GRAPHQL
|
|
32
24
|
query($id: EncodedId!) {
|
|
@@ -35,21 +27,10 @@ module Jbr
|
|
|
35
27
|
GRAPHQL
|
|
36
28
|
end
|
|
37
29
|
|
|
38
|
-
# Twenty a page, the same as jobs: Jobber prices a query by its page size, and what an
|
|
39
|
-
# includes brings back is charged for on top of every row of it.
|
|
40
|
-
def page
|
|
41
|
-
<<~GRAPHQL
|
|
42
|
-
query($after: String, $filter: VisitFilterAttributes) {
|
|
43
|
-
visits(first: 20, after: $after, filter: $filter) {
|
|
44
|
-
nodes { #{FIELDS} #{selections} }
|
|
45
|
-
pageInfo { hasNextPage endCursor }
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
GRAPHQL
|
|
49
|
-
end
|
|
50
|
-
|
|
51
30
|
def field = 'visits'
|
|
52
31
|
|
|
32
|
+
def filtered = 'VisitFilterAttributes'
|
|
33
|
+
|
|
53
34
|
def item(node) = Visit.new node: node
|
|
54
35
|
end
|
|
55
36
|
end
|
data/lib/jbr.rb
CHANGED
|
@@ -36,6 +36,7 @@ require 'jbr/account'
|
|
|
36
36
|
require 'jbr/property'
|
|
37
37
|
require 'jbr/properted'
|
|
38
38
|
require 'jbr/includable'
|
|
39
|
+
require 'jbr/listable'
|
|
39
40
|
# LineItem before Itemized, and both before Job: the lines a job is made of are asked for
|
|
40
41
|
# by a constant the include reads as it loads.
|
|
41
42
|
require 'jbr/line_item'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jbr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
@@ -120,6 +120,7 @@ files:
|
|
|
120
120
|
- lib/jbr/job.rb
|
|
121
121
|
- lib/jbr/jobs.rb
|
|
122
122
|
- lib/jbr/line_item.rb
|
|
123
|
+
- lib/jbr/listable.rb
|
|
123
124
|
- lib/jbr/mock.rb
|
|
124
125
|
- lib/jbr/mock/account.rb
|
|
125
126
|
- lib/jbr/mock/client.rb
|