formable 0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +158 -0
- data/lib/formable/client.rb +31 -0
- data/lib/formable/error.rb +14 -0
- data/lib/formable/http.rb +93 -0
- data/lib/formable/params.rb +45 -0
- data/lib/formable/resources/redline_requests.rb +50 -0
- data/lib/formable/resources/signature_requests.rb +65 -0
- data/lib/formable/resources/templates.rb +66 -0
- data/lib/formable/version.rb +5 -0
- data/lib/formable.rb +18 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 661116dc7b31f9ec87081b7cd8a217c9af6990a9d2b36155aabb791c1de681fc
|
|
4
|
+
data.tar.gz: a8a7a898ad4e18b48d2b0662614780e5cc5d6fd59ff1f1bc30ad274148ee04fd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 78617740a61a52f1df0e1423915e24d1d434a9008a364485418637f9abf35720725da61d8ecbfa57ce3e3f6e227ad1eeb6f1c3c86811d9a5be6724670d703f2f
|
|
7
|
+
data.tar.gz: fe15e224a3f82d7b3b05185320a27112887467848fc7fa2699c4d73de1fafb5b3831f05262db90a3f87e3ecd51bd17df1bf9c218a9cf4c43b03b35e2ca1dcd82
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Formable
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# formable-ruby
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the [Formable API](https://api.formabledocs.com) (v1). Covers templates, signature requests, redlining, and billing.
|
|
4
|
+
|
|
5
|
+
- Faraday HTTP client (injectable)
|
|
6
|
+
- Keyword arguments and snake_case method names
|
|
7
|
+
- Ruby 3.1+
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# Gemfile
|
|
13
|
+
gem "formable"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bundle add formable
|
|
18
|
+
# or
|
|
19
|
+
gem install formable
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "formable"
|
|
26
|
+
|
|
27
|
+
formable = Formable.new(api_key: ENV.fetch("FORMABLE_API_KEY"))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Templates
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
result = formable.templates.create(
|
|
34
|
+
file: "nda.docx",
|
|
35
|
+
signer_roles: [
|
|
36
|
+
{ name: "Client", order: 0 },
|
|
37
|
+
{ name: "Witness", order: 1 }
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
template_id = result["templateId"]
|
|
42
|
+
|
|
43
|
+
# Mint a fresh edit URL later (expires after 1 day)
|
|
44
|
+
edit = formable.templates.create_edit_url(template_id)
|
|
45
|
+
puts edit["editUrl"], edit["expiresAt"]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`file` accepts a path, binary string, or IO. Pass `filename:` when `file` is not a path.
|
|
49
|
+
|
|
50
|
+
### Signature requests
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
# Formable emails each signer a signing link
|
|
54
|
+
request = formable.signature_requests.create(
|
|
55
|
+
template_id: template_id,
|
|
56
|
+
signers: [
|
|
57
|
+
{ email: "jane@example.com", name: "Jane Doe", role: "Client" },
|
|
58
|
+
{ email: "bob@example.com", name: "Bob Smith", role: "Witness" }
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Embedded flow: mint signing URLs to embed in an iframe yourself
|
|
63
|
+
embedded = formable.signature_requests.create_embedded(
|
|
64
|
+
template_id: template_id,
|
|
65
|
+
signers: [{ email: "jane@example.com", name: "Jane Doe", role: "Client" }],
|
|
66
|
+
test_mode: true
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
signer = embedded["signers"].first
|
|
70
|
+
signing = formable.signature_requests.create_signing_url(
|
|
71
|
+
signer["recipientSignatureId"]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Track progress
|
|
75
|
+
current = formable.signature_requests.get(embedded["signatureRequestId"])
|
|
76
|
+
all_requests = formable.signature_requests.list(
|
|
77
|
+
updated_since: Time.utc(2026, 1, 1)
|
|
78
|
+
)
|
|
79
|
+
events = formable.signature_requests.get_events(embedded["signatureRequestId"])
|
|
80
|
+
|
|
81
|
+
# Download the signed document once completed
|
|
82
|
+
envelope = formable.signature_requests.get_signed_envelope(
|
|
83
|
+
embedded["signatureRequestId"]
|
|
84
|
+
)
|
|
85
|
+
puts envelope["signedEnvelopePresignedUrl"]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Redline requests
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
created = formable.redline_requests.create(
|
|
92
|
+
template_id: template_id,
|
|
93
|
+
members: [
|
|
94
|
+
{ email: "us@example.com", display_name: "John Doe", role: "DisclosingParty" },
|
|
95
|
+
{ email: "them@example.com", display_name: "Jane Smith", role: "ReceivingParty" }
|
|
96
|
+
],
|
|
97
|
+
metadata: { subject: "Mutual NDA" }
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
redline_request_id = created["redlineRequestId"]
|
|
101
|
+
|
|
102
|
+
# Mint a redline URL for a member (embed in an iframe)
|
|
103
|
+
url = formable.redline_requests.create_url(redline_request_id, "them@example.com")
|
|
104
|
+
|
|
105
|
+
# Manage members and track progress
|
|
106
|
+
formable.redline_requests.update_members(
|
|
107
|
+
redline_request_id,
|
|
108
|
+
[{ email: "counsel@example.com", display_name: "Counsel", role: "ReceivingCounsel" }]
|
|
109
|
+
)
|
|
110
|
+
redline = formable.redline_requests.get(redline_request_id)
|
|
111
|
+
events = formable.redline_requests.get_events(redline_request_id)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Billing and health
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
billing = formable.billing
|
|
118
|
+
puts billing["numberOfRedliningSessions"]
|
|
119
|
+
|
|
120
|
+
health = formable.health
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Error handling
|
|
124
|
+
|
|
125
|
+
All non-2xx responses raise a `Formable::Error` with the server's error message, HTTP status, and parsed response body.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
begin
|
|
129
|
+
formable.signature_requests.get("missing-id")
|
|
130
|
+
rescue Formable::Error => error
|
|
131
|
+
warn "#{error.status} #{error.message}"
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Configuration
|
|
136
|
+
|
|
137
|
+
| Option | Description | Default |
|
|
138
|
+
| ------------ | --------------------------------------------------------- | --------------------------------- |
|
|
139
|
+
| `api_key` | Your Formable API key (sent as a bearer token). Required. | - |
|
|
140
|
+
| `base_url` | Override the API base URL. | `https://api.formabledocs.com/v1` |
|
|
141
|
+
| `timeout` | Per-request timeout in seconds. | `60` |
|
|
142
|
+
| `connection` | Custom `Faraday::Connection`. | Built-in client with 60s timeout |
|
|
143
|
+
|
|
144
|
+
Request hashes accept snake_case keys (`template_id`, `display_name`, `field_id`). Responses use the API's camelCase field names (`templateId`, `displayName`).
|
|
145
|
+
|
|
146
|
+
## Development
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bundle install
|
|
150
|
+
bundle exec rake test
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Publishing
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
gem build formable.gemspec
|
|
157
|
+
gem push formable-*.gem
|
|
158
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Formable
|
|
4
|
+
class Client
|
|
5
|
+
attr_reader :templates, :signature_requests, :redline_requests
|
|
6
|
+
|
|
7
|
+
def initialize(api_key:, base_url: nil, timeout: 60, connection: nil)
|
|
8
|
+
@http = Http.new(
|
|
9
|
+
api_key: api_key,
|
|
10
|
+
base_url: base_url,
|
|
11
|
+
timeout: timeout,
|
|
12
|
+
connection: connection
|
|
13
|
+
)
|
|
14
|
+
@templates = Templates.new(@http)
|
|
15
|
+
@signature_requests = SignatureRequests.new(@http)
|
|
16
|
+
@redline_requests = RedlineRequests.new(@http)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def billing
|
|
20
|
+
@http.get("/billing")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def health
|
|
24
|
+
@http.get("/health")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def close
|
|
28
|
+
@http.close
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Formable
|
|
4
|
+
# Raised for any non-2xx response from the Formable API.
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
attr_reader :status, :body
|
|
7
|
+
|
|
8
|
+
def initialize(message, status:, body: nil)
|
|
9
|
+
super(message)
|
|
10
|
+
@status = status
|
|
11
|
+
@body = body
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/multipart"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Formable
|
|
8
|
+
class Http
|
|
9
|
+
def initialize(api_key:, base_url: nil, timeout: 60, connection: nil)
|
|
10
|
+
raise ArgumentError, "Formable API key is required" if api_key.nil? || api_key.strip.empty?
|
|
11
|
+
|
|
12
|
+
@api_key = api_key
|
|
13
|
+
@base_url = (base_url || DEFAULT_BASE_URL).sub(%r{/+\z}, "")
|
|
14
|
+
@connection = connection || build_connection(timeout)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get(path, query = {})
|
|
18
|
+
request(:get, path, query: query)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post(path, body = nil)
|
|
22
|
+
request(:post, path, json: body)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def put(path, body = nil)
|
|
26
|
+
request(:put, path, json: body)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def post_form(path, fields)
|
|
30
|
+
request(:post, path, form: fields)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def close
|
|
34
|
+
@connection.close if @connection.respond_to?(:close)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def build_connection(timeout)
|
|
40
|
+
Faraday.new do |conn|
|
|
41
|
+
conn.request :multipart
|
|
42
|
+
conn.options.timeout = timeout
|
|
43
|
+
conn.options.open_timeout = timeout
|
|
44
|
+
conn.adapter Faraday.default_adapter
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def request(method, path, query: nil, json: nil, form: nil)
|
|
49
|
+
response = @connection.run_request(method, url(path), nil, headers) do |req|
|
|
50
|
+
req.params.update(Params.compact(query)) if query
|
|
51
|
+
if form
|
|
52
|
+
req.body = form
|
|
53
|
+
elsif !json.nil?
|
|
54
|
+
req.headers["Content-Type"] = "application/json"
|
|
55
|
+
req.body = JSON.generate(json)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
handle(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def url(path)
|
|
62
|
+
@base_url + path
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def headers
|
|
66
|
+
{
|
|
67
|
+
"Authorization" => "Bearer #{@api_key}",
|
|
68
|
+
"Accept" => "application/json",
|
|
69
|
+
"User-Agent" => "formable-ruby/#{VERSION}"
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def handle(response)
|
|
74
|
+
body = parse_body(response.body)
|
|
75
|
+
return body if response.success?
|
|
76
|
+
|
|
77
|
+
message = body.is_a?(Hash) ? body["error"] : nil
|
|
78
|
+
raise Error.new(
|
|
79
|
+
message || "Request failed with status #{response.status}",
|
|
80
|
+
status: response.status,
|
|
81
|
+
body: body
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_body(text)
|
|
86
|
+
return if text.nil? || text.empty?
|
|
87
|
+
|
|
88
|
+
JSON.parse(text)
|
|
89
|
+
rescue JSON::ParserError
|
|
90
|
+
text
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Formable
|
|
6
|
+
module Params
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def encode_path(value)
|
|
10
|
+
URI.encode_uri_component(value)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def iso8601(value)
|
|
14
|
+
return if value.nil?
|
|
15
|
+
return value.iso8601 if value.respond_to?(:iso8601)
|
|
16
|
+
|
|
17
|
+
value.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compact(hash)
|
|
21
|
+
hash.reject { |_, value| value.nil? }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def camelize_keys(value)
|
|
25
|
+
case value
|
|
26
|
+
when Array
|
|
27
|
+
value.map { |item| camelize_keys(item) }
|
|
28
|
+
when Hash
|
|
29
|
+
value.each_with_object({}) do |(key, item), result|
|
|
30
|
+
result[camelize_key(key)] = camelize_keys(item)
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def json_body(hash)
|
|
38
|
+
compact(camelize_keys(hash))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def camelize_key(key)
|
|
42
|
+
key.to_s.gsub(/_([a-z\d])/) { ::Regexp.last_match(1).upcase }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Formable
|
|
4
|
+
class RedlineRequests
|
|
5
|
+
def initialize(http)
|
|
6
|
+
@http = http
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(template_id:, members:, test_mode: nil, metadata: nil)
|
|
10
|
+
@http.post("/redline-requests", Params.json_body(
|
|
11
|
+
template_id: template_id,
|
|
12
|
+
members: members,
|
|
13
|
+
test_mode: test_mode,
|
|
14
|
+
metadata: metadata
|
|
15
|
+
))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def list(updated_since: nil)
|
|
19
|
+
@http.get("/redline-requests", "updatedSince" => Params.iso8601(updated_since))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get(redline_request_id)
|
|
23
|
+
@http.get(path(redline_request_id))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update_members(redline_request_id, members)
|
|
27
|
+
@http.put(
|
|
28
|
+
path(redline_request_id, "/members"),
|
|
29
|
+
Params.json_body(members: members)
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_url(redline_request_id, member_email)
|
|
34
|
+
@http.post(
|
|
35
|
+
path(redline_request_id, "/url"),
|
|
36
|
+
Params.json_body(member_email: member_email)
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_events(redline_request_id)
|
|
41
|
+
@http.get(path(redline_request_id, "/events"))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def path(redline_request_id, suffix = "")
|
|
47
|
+
"/redline-requests/#{Params.encode_path(redline_request_id)}#{suffix}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Formable
|
|
4
|
+
class SignatureRequests
|
|
5
|
+
def initialize(http)
|
|
6
|
+
@http = http
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(template_id:, signers:, sender: nil, test_mode: nil, fields: nil)
|
|
10
|
+
@http.post("/signature-requests", create_body(
|
|
11
|
+
template_id: template_id,
|
|
12
|
+
signers: signers,
|
|
13
|
+
sender: sender,
|
|
14
|
+
test_mode: test_mode,
|
|
15
|
+
fields: fields
|
|
16
|
+
))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_embedded(template_id:, signers:, sender: nil, test_mode: nil, fields: nil)
|
|
20
|
+
@http.post("/signature-requests/embedded", create_body(
|
|
21
|
+
template_id: template_id,
|
|
22
|
+
signers: signers,
|
|
23
|
+
sender: sender,
|
|
24
|
+
test_mode: test_mode,
|
|
25
|
+
fields: fields
|
|
26
|
+
))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def list(updated_since: nil)
|
|
30
|
+
@http.get("/signature-requests", "updatedSince" => Params.iso8601(updated_since))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get(signature_request_id)
|
|
34
|
+
@http.get(path(signature_request_id))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_events(signature_request_id)
|
|
38
|
+
@http.get(path(signature_request_id, "/events"))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_signed_envelope(signature_request_id)
|
|
42
|
+
@http.get(path(signature_request_id, "/signed-envelope"))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_signing_url(recipient_signature_id)
|
|
46
|
+
@http.post("/recipient-signatures/#{Params.encode_path(recipient_signature_id)}/url")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def create_body(template_id:, signers:, sender:, test_mode:, fields:)
|
|
52
|
+
Params.json_body(
|
|
53
|
+
template_id: template_id,
|
|
54
|
+
signers: signers,
|
|
55
|
+
sender: sender,
|
|
56
|
+
test_mode: test_mode,
|
|
57
|
+
fields: fields
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def path(signature_request_id, suffix = "")
|
|
62
|
+
"/signature-requests/#{Params.encode_path(signature_request_id)}#{suffix}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "stringio"
|
|
6
|
+
|
|
7
|
+
require "faraday/multipart"
|
|
8
|
+
|
|
9
|
+
module Formable
|
|
10
|
+
class Templates
|
|
11
|
+
def initialize(http)
|
|
12
|
+
@http = http
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create(file:, filename: nil, signer_roles: nil)
|
|
16
|
+
io_or_path, resolved_filename = normalize_file(file, filename)
|
|
17
|
+
fields = {
|
|
18
|
+
"file" => Faraday::Multipart::FilePart.new(
|
|
19
|
+
io_or_path,
|
|
20
|
+
"application/octet-stream",
|
|
21
|
+
resolved_filename
|
|
22
|
+
),
|
|
23
|
+
"filename" => resolved_filename
|
|
24
|
+
}
|
|
25
|
+
unless signer_roles.nil?
|
|
26
|
+
fields["signer_roles"] = JSON.generate(signer_roles)
|
|
27
|
+
end
|
|
28
|
+
@http.post_form("/templates", fields)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_edit_url(template_id)
|
|
32
|
+
@http.post("/templates/#{Params.encode_path(template_id)}/edit-url")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def normalize_file(file, filename)
|
|
38
|
+
case file
|
|
39
|
+
when Pathname
|
|
40
|
+
raise ArgumentError, "Unable to read file: #{file}" unless file.file?
|
|
41
|
+
|
|
42
|
+
[file.to_s, filename || file.basename.to_s]
|
|
43
|
+
when String
|
|
44
|
+
if File.file?(file)
|
|
45
|
+
[file, filename || File.basename(file)]
|
|
46
|
+
else
|
|
47
|
+
require_filename!(filename, "file is not a path")
|
|
48
|
+
[StringIO.new(file), filename]
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
unless file.respond_to?(:read)
|
|
52
|
+
raise ArgumentError, "file must be a path, IO, or binary string"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
require_filename!(filename, "file is an IO")
|
|
56
|
+
[file, filename]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def require_filename!(filename, reason)
|
|
61
|
+
return unless filename.nil? || filename.empty?
|
|
62
|
+
|
|
63
|
+
raise ArgumentError, "filename is required when #{reason}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/formable.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "formable/version"
|
|
4
|
+
require_relative "formable/error"
|
|
5
|
+
require_relative "formable/params"
|
|
6
|
+
require_relative "formable/http"
|
|
7
|
+
require_relative "formable/resources/templates"
|
|
8
|
+
require_relative "formable/resources/signature_requests"
|
|
9
|
+
require_relative "formable/resources/redline_requests"
|
|
10
|
+
require_relative "formable/client"
|
|
11
|
+
|
|
12
|
+
module Formable
|
|
13
|
+
DEFAULT_BASE_URL = "https://api.formabledocs.com/v1"
|
|
14
|
+
|
|
15
|
+
def self.new(**kwargs)
|
|
16
|
+
Client.new(**kwargs)
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: formable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Formable
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.9'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.9'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-multipart
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
description: Official Ruby SDK for the Formable API. Covers templates, signature requests,
|
|
41
|
+
redlining, and billing.
|
|
42
|
+
email:
|
|
43
|
+
- matt@formabledocs.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- LICENSE
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/formable.rb
|
|
51
|
+
- lib/formable/client.rb
|
|
52
|
+
- lib/formable/error.rb
|
|
53
|
+
- lib/formable/http.rb
|
|
54
|
+
- lib/formable/params.rb
|
|
55
|
+
- lib/formable/resources/redline_requests.rb
|
|
56
|
+
- lib/formable/resources/signature_requests.rb
|
|
57
|
+
- lib/formable/resources/templates.rb
|
|
58
|
+
- lib/formable/version.rb
|
|
59
|
+
homepage: https://www.formabledocs.com
|
|
60
|
+
licenses:
|
|
61
|
+
- MIT
|
|
62
|
+
metadata:
|
|
63
|
+
homepage_uri: https://www.formabledocs.com
|
|
64
|
+
source_code_uri: https://github.com/FormableDocs/formable-ruby
|
|
65
|
+
bug_tracker_uri: https://github.com/FormableDocs/formable-ruby/issues
|
|
66
|
+
rdoc_options: []
|
|
67
|
+
require_paths:
|
|
68
|
+
- lib
|
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '3.1'
|
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
requirements: []
|
|
80
|
+
rubygems_version: 4.0.16
|
|
81
|
+
specification_version: 4
|
|
82
|
+
summary: Official Ruby SDK for the Formable API
|
|
83
|
+
test_files: []
|