jbr 3.0.0 → 3.1.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/CHANGELOG.md +18 -0
- data/README.md +6 -3
- data/lib/graphql/client.rb +2 -2
- data/lib/jbr/client.rb +4 -6
- data/lib/jbr/job.rb +1 -5
- data/lib/jbr/named.rb +9 -0
- data/lib/jbr/property.rb +2 -2
- data/lib/jbr/resource.rb +3 -2
- data/lib/jbr/version.rb +1 -1
- data/lib/jbr/visit.rb +1 -1
- data/lib/jbr.rb +7 -0
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97dff097405bb45eb1ac6048e9ce131d6905f19221e1e910ccff1f533ede76ed
|
|
4
|
+
data.tar.gz: 2cf46317599aa7146b73fa734441a0c995749692cea4565bcc6646b80fff478c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6fc99f2dbaa979073811af9382023b10acde8182e89ded303876876f92e54bcbce9f2ee732917003571823aa2b3127b8f960ad912f88ab53935e8041022b3f4a
|
|
7
|
+
data.tar.gz: 656ca93656a1702f226d9f8aa4ed12e7e0d31f01a1ac4cda426096ec7eea48e30e5b2977ccbc113a5dfa21fdb6084009b400caae836f23335a4bd28a4626511d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [3.1.0] - 2026-08-13
|
|
2
|
+
|
|
3
|
+
- [Fix] An answer Jobber left empty now reads as no answer rather than as an empty string.
|
|
4
|
+
`client.name` and `job.name` fall through a blank first name or a blank title instead of
|
|
5
|
+
handing one back, so a caller that validates presence is not handed `""` to store. A job
|
|
6
|
+
with no title still answers the ID it is filed under; a client with neither a first name
|
|
7
|
+
nor a company name answers nil
|
|
8
|
+
- [New] A visit answers `name` as a job does -- its title, or the ID Jobber files it under
|
|
9
|
+
where nobody titled it. Both read it from `Jbr::Named`, so a record Jobber lets go
|
|
10
|
+
untitled is named the same way wherever it appears
|
|
11
|
+
- [Fix] A blank address field does not come back from `property.address`, and is not sent
|
|
12
|
+
when a property is opened. Street, city, state and ZIP are absent rather than empty
|
|
13
|
+
- [Fix] A blank timestamp reads as no time. `Time.iso8601` raises on an empty string, so a
|
|
14
|
+
visit or job Jobber dated with one used to take the whole walk down with it
|
|
15
|
+
- [Change] Depend on Active Support, for `blank?`, `present?`, `presence` and
|
|
16
|
+
`compact_blank`. Two of its files are required, not the whole library: telling an empty
|
|
17
|
+
answer from a missing one was being done by hand, and being done inconsistently
|
|
18
|
+
|
|
1
19
|
## [3.0.0] - 2026-08-13
|
|
2
20
|
|
|
3
21
|
- [Breaking change] `oauth.visits` and `oauth.jobs` answer the whole collection rather than
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Jobber API Ruby client
|
|
2
2
|
|
|
3
|
-
A client for the Jobber GraphQL API. It needs
|
|
3
|
+
A client for the Jobber GraphQL API. It needs the standard library and two files of Active
|
|
4
|
+
Support, to tell a field Jobber answered empty from one it never answered at all.
|
|
4
5
|
|
|
5
6
|
## Available methods
|
|
6
7
|
|
|
@@ -83,7 +84,7 @@ oauth.jobs.past # => an Enumerator of the ones dated before now
|
|
|
83
84
|
oauth.jobs.upcoming # => an Enumerator of the ones dated from now on
|
|
84
85
|
|
|
85
86
|
job = jobs.first
|
|
86
|
-
job.name # => 'Furnace tune-up', or the job's ID where nobody titled it. Never nil
|
|
87
|
+
job.name # => 'Furnace tune-up', or the job's ID where nobody titled it. Never nil or empty
|
|
87
88
|
job.title # => 'Furnace tune-up'
|
|
88
89
|
job.instructions # => 'Ring the doorbell twice'
|
|
89
90
|
job.status # => 'requires_invoicing'
|
|
@@ -116,6 +117,7 @@ oauth.visits.past # => an Enumerator of the ones dated before now
|
|
|
116
117
|
|
|
117
118
|
visit = visits.first
|
|
118
119
|
visit.id # => 'Z2lkOi8vS'
|
|
120
|
+
visit.name # => 'Furnace tune-up', or the visit's ID where nobody titled it. Never nil or empty
|
|
119
121
|
visit.title # => 'Furnace tune-up'
|
|
120
122
|
visit.job_id # => 'Z2lkOi8vS'
|
|
121
123
|
visit.starts_at # => 2026-08-09 14:00:00
|
|
@@ -132,7 +134,8 @@ asked for. Chain `includes` the way Active Record does, on visits or on jobs:
|
|
|
132
134
|
```ruby
|
|
133
135
|
visit = oauth.visits.includes(:client, property: :client).upcoming.first
|
|
134
136
|
|
|
135
|
-
visit.client.name # => 'Jane', or the business's name where the client is a business
|
|
137
|
+
visit.client.name # => 'Jane', or the business's name where the client is a business.
|
|
138
|
+
# Never an empty string: a blank first name falls through to the company
|
|
136
139
|
visit.client.first_name # => 'Jane'
|
|
137
140
|
visit.client.last_name # => 'Doe'
|
|
138
141
|
visit.client.company_name # => nil
|
data/lib/graphql/client.rb
CHANGED
|
@@ -22,8 +22,8 @@ module GraphQL
|
|
|
22
22
|
raise Unauthorized, response.body if response.code == '401'
|
|
23
23
|
raise Error, response.body unless response.is_a? Net::HTTPSuccess
|
|
24
24
|
body = JSON.parse(response.body)
|
|
25
|
-
errors = body['errors']
|
|
26
|
-
raise Error, errors.map { |error| error['message'] }.join('; ')
|
|
25
|
+
errors = body['errors']
|
|
26
|
+
raise Error, errors.map { |error| error['message'] }.join('; ') if errors.present?
|
|
27
27
|
body.fetch('data')
|
|
28
28
|
end
|
|
29
29
|
|
data/lib/jbr/client.rb
CHANGED
|
@@ -32,8 +32,8 @@ module Jbr
|
|
|
32
32
|
def company_name = @node['companyName']
|
|
33
33
|
|
|
34
34
|
# Jobber files nobody without one name or the other, so there is always one to call them.
|
|
35
|
-
# @return [String] the person's first name, or the business's name.
|
|
36
|
-
def name = first_name || company_name
|
|
35
|
+
# @return [String, nil] the person's first name, or the business's name. Never empty.
|
|
36
|
+
def name = first_name.presence || company_name.presence
|
|
37
37
|
|
|
38
38
|
# @return [String, nil] the address to write to.
|
|
39
39
|
def email = @node['email']
|
|
@@ -89,12 +89,10 @@ module Jbr
|
|
|
89
89
|
address, email = @create_params[:address], @create_params[:email]
|
|
90
90
|
{ firstName: @create_params[:first_name],
|
|
91
91
|
lastName: @create_params[:last_name],
|
|
92
|
-
properties: ([ { address: Property.address_from(address) } ] if present?
|
|
92
|
+
properties: ([ { address: Property.address_from(address) } ] if address.present?),
|
|
93
93
|
phones: [ { number: @create_params[:phone], primary: true } ],
|
|
94
|
-
emails: ([ { address: email, primary: true } ] if present?
|
|
94
|
+
emails: ([ { address: email, primary: true } ] if email.present?),
|
|
95
95
|
}.compact
|
|
96
96
|
end
|
|
97
|
-
|
|
98
|
-
def present?(value) = !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
|
|
99
97
|
end
|
|
100
98
|
end
|
data/lib/jbr/job.rb
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
module Jbr
|
|
2
2
|
# Work a Jobber user accepted and scheduled.
|
|
3
3
|
class Job < Resource
|
|
4
|
-
include Cliental, Properted
|
|
4
|
+
include Cliental, Named, Properted
|
|
5
5
|
|
|
6
6
|
# @return [String, nil] what the job is called, where whoever opened it named it.
|
|
7
7
|
def title = @node['title']
|
|
8
8
|
|
|
9
|
-
# A job goes untitled often enough, and something has to stand in for it on a list.
|
|
10
|
-
# @return [String] the title, or the ID Jobber files the job under.
|
|
11
|
-
def name = title || id
|
|
12
|
-
|
|
13
9
|
# @return [String, nil] what the work is, in the words whoever opened the job wrote.
|
|
14
10
|
def instructions = @node['instructions']
|
|
15
11
|
|
data/lib/jbr/named.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# Extends a record Jobber lets go untitled: a visit, a job, anything somebody may have
|
|
3
|
+
# opened without ever typing a name for it.
|
|
4
|
+
module Named
|
|
5
|
+
# Untitled happens often enough, and something has to stand in for it on a list.
|
|
6
|
+
# @return [String] the title, or the ID Jobber files the record under. Never empty.
|
|
7
|
+
def name = title.presence || id
|
|
8
|
+
end
|
|
9
|
+
end
|
data/lib/jbr/property.rb
CHANGED
|
@@ -44,7 +44,7 @@ module Jbr
|
|
|
44
44
|
# @param fields [Hash] any of :street, :city, :state and :zip.
|
|
45
45
|
# @return [Hash] the address, without the fields the caller left out.
|
|
46
46
|
def self.address_from(fields = {})
|
|
47
|
-
FIELDS.to_h { |jobber, ours| [ jobber, fields[ours] ] }.
|
|
47
|
+
FIELDS.to_h { |jobber, ours| [ jobber, fields[ours] ] }.compact_blank
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# The fields a caller reads, from the address as Jobber holds it.
|
|
@@ -55,7 +55,7 @@ module Jbr
|
|
|
55
55
|
address ||= {}
|
|
56
56
|
coordinates = address['coordinates'] || {}
|
|
57
57
|
FIELDS.to_h { |jobber, ours| [ ours, address[jobber.to_s] ] }.
|
|
58
|
-
merge(COORDINATES.to_h { |ours| [ ours, coordinates[ours.to_s] ] }).
|
|
58
|
+
merge(COORDINATES.to_h { |ours| [ ours, coordinates[ours.to_s] ] }).compact_blank
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Reach the property at an address, adding one when none of the client's matches.
|
data/lib/jbr/resource.rb
CHANGED
|
@@ -17,8 +17,9 @@ module Jbr
|
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
-
# @return [Time, nil] what Jobber answered under a key, as a time.
|
|
21
|
-
|
|
20
|
+
# @return [Time, nil] what Jobber answered under a key, as a time. An empty answer is no
|
|
21
|
+
# answer: Time.iso8601 raises on one, where nothing at all it simply has none of.
|
|
22
|
+
def time(key) = (Time.iso8601 @node[key] if @node[key].present?)
|
|
22
23
|
|
|
23
24
|
# Every item a paged query answers, one at a time, a page read only once the one before
|
|
24
25
|
# it runs out. The filter is data: handed none, the query narrows nothing.
|
data/lib/jbr/version.rb
CHANGED
data/lib/jbr/visit.rb
CHANGED
data/lib/jbr.rb
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
require 'net/http'
|
|
3
3
|
|
|
4
|
+
# Only the two Active Support files whose methods are used, rather than the whole of it:
|
|
5
|
+
# Jobber answers a field it holds nothing for with an empty string as readily as with null,
|
|
6
|
+
# and a caller who validates presence needs those to arrive as the same nothing.
|
|
7
|
+
require 'active_support/core_ext/object/blank'
|
|
8
|
+
require 'active_support/core_ext/enumerable'
|
|
9
|
+
|
|
4
10
|
require 'graphql/error'
|
|
5
11
|
require 'graphql/unauthorized'
|
|
6
12
|
require 'graphql/client'
|
|
@@ -13,6 +19,7 @@ require 'jbr/error'
|
|
|
13
19
|
# Jobber for about a client is built as they load.
|
|
14
20
|
require 'jbr/phone'
|
|
15
21
|
require 'jbr/cliental'
|
|
22
|
+
require 'jbr/named'
|
|
16
23
|
require 'jbr/resource'
|
|
17
24
|
require 'jbr/request'
|
|
18
25
|
require 'jbr/oauth'
|
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.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
@@ -9,6 +9,20 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: minitest
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -117,6 +131,7 @@ files:
|
|
|
117
131
|
- lib/jbr/mock/visit.rb
|
|
118
132
|
- lib/jbr/mock/visits.rb
|
|
119
133
|
- lib/jbr/mocking.rb
|
|
134
|
+
- lib/jbr/named.rb
|
|
120
135
|
- lib/jbr/oauth.rb
|
|
121
136
|
- lib/jbr/phone.rb
|
|
122
137
|
- lib/jbr/properted.rb
|