attio-ruby 0.1.1 → 0.1.3
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 +41 -8
- data/attio-ruby.gemspec +61 -0
- data/lib/attio/resources/deal.rb +79 -9
- data/lib/attio/resources/workspace_member.rb +4 -4
- data/lib/attio/util/configuration.rb +12 -2
- data/lib/attio/version.rb +1 -1
- data/lib/attio-ruby.rb +5 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24bed31b6dd0aa8d5a0c9be52298f636255c368c47dcbf41091609712c3a56f7
|
4
|
+
data.tar.gz: 64e25c9ec1329ecbebd5352374df18650ccf18d7d135d28b3eca8a8c6bc6554b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fba01e91fb72e0ee903bab6e4d2f94a622ed2f6ec67f7f44ea4bc2bdc38f02e548500f4cd964cca4cfb6facc2679f6d059fc65872e907fc05a17fdd1a81134e6
|
7
|
+
data.tar.gz: 348490a48d8347932536ff5b419f112444b88d9fefcb87b47a9f4074f6dab0bb5cdc23ecac1a323b6fb48d759e7310f36475c2a469de3841a3eeae73731ba835
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [0.1.3] - 2025-08-07
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
- Added `lib/attio-ruby.rb` to fix Rails auto-require issue. The gem can now be auto-required by Rails/Bundler without needing `require: 'attio'` in the Gemfile.
|
12
|
+
|
13
|
+
## [0.1.2] - 2025-08-07
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- Deal status configuration system for customizing won/lost/open statuses
|
17
|
+
- `Deal.in_stage` method to query deals by multiple stage names
|
18
|
+
- Convenience class methods: `Deal.won`, `Deal.lost`, `Deal.open_deals`
|
19
|
+
- Instance methods for deals: `current_status`, `status_changed_at`, `won_at`, `closed_at`
|
20
|
+
- Improved `won?`, `lost?`, and `open?` methods that use configuration
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- WorkspaceMember.active and WorkspaceMember.admins methods argument passing issue
|
24
|
+
- Configuration arrays are now properly duplicated to avoid frozen array issues
|
25
|
+
|
8
26
|
## [0.1.1] - 2025-08-07
|
9
27
|
|
10
28
|
### Fixed
|
data/README.md
CHANGED
@@ -390,10 +390,12 @@ deal = Attio::Deal.create(
|
|
390
390
|
)
|
391
391
|
|
392
392
|
# Access methods
|
393
|
-
deal.name
|
394
|
-
deal.value
|
395
|
-
deal.stage
|
396
|
-
deal.status
|
393
|
+
deal.name # Returns deal name
|
394
|
+
deal.value # Returns currency object with currency_value
|
395
|
+
deal.stage # Returns status object with nested title
|
396
|
+
deal.status # Alias for stage
|
397
|
+
deal.current_status # Returns the current status title as a string
|
398
|
+
deal.status_changed_at # Returns when the status was last changed
|
397
399
|
|
398
400
|
# Update methods
|
399
401
|
deal.update_stage("Won 🎉")
|
@@ -404,10 +406,20 @@ big_deals = Attio::Deal.find_by_value_range(min: 100000)
|
|
404
406
|
mid_deals = Attio::Deal.find_by_value_range(min: 50000, max: 100000)
|
405
407
|
won_deals = Attio::Deal.find_by(stage: "Won 🎉")
|
406
408
|
|
407
|
-
#
|
408
|
-
|
409
|
-
|
410
|
-
|
409
|
+
# Query by status using convenience methods
|
410
|
+
won_deals = Attio::Deal.won # All deals with "Won 🎉" status
|
411
|
+
lost_deals = Attio::Deal.lost # All deals with "Lost" status
|
412
|
+
open_deals = Attio::Deal.open_deals # All deals with "Lead" or "In Progress"
|
413
|
+
|
414
|
+
# Query by custom stages
|
415
|
+
custom_deals = Attio::Deal.in_stage(stage_names: ["Won 🎉", "Contract Signed"])
|
416
|
+
|
417
|
+
# Check deal status (uses configuration)
|
418
|
+
deal.open? # true if status is "Lead" or "In Progress"
|
419
|
+
deal.won? # true if status is "Won 🎉"
|
420
|
+
deal.lost? # true if status is "Lost"
|
421
|
+
deal.won_at # timestamp when deal was won (or nil)
|
422
|
+
deal.closed_at # timestamp when deal was closed (won or lost)
|
411
423
|
|
412
424
|
# Associate with companies and people
|
413
425
|
deal = Attio::Deal.create(
|
@@ -420,6 +432,27 @@ deal = Attio::Deal.create(
|
|
420
432
|
)
|
421
433
|
```
|
422
434
|
|
435
|
+
##### Customizing Deal Statuses
|
436
|
+
|
437
|
+
The gem uses Attio's default deal statuses ("Lead", "In Progress", "Won 🎉", "Lost") but you can customize these for your workspace:
|
438
|
+
|
439
|
+
```ruby
|
440
|
+
# In config/initializers/attio.rb
|
441
|
+
Attio.configure do |config|
|
442
|
+
config.api_key = ENV["ATTIO_API_KEY"]
|
443
|
+
|
444
|
+
# Customize which statuses are considered won, lost, or open
|
445
|
+
config.won_statuses = ["Won 🎉", "Contract Signed", "Customer"]
|
446
|
+
config.lost_statuses = ["Lost", "Disqualified", "No Budget"]
|
447
|
+
config.open_statuses = ["Lead", "Qualified Lead", "Prospect"]
|
448
|
+
config.in_progress_statuses = ["In Progress", "Negotiation", "Proposal Sent"]
|
449
|
+
end
|
450
|
+
|
451
|
+
# Now the convenience methods use your custom statuses
|
452
|
+
won_deals = Attio::Deal.won # Finds deals with any of your won_statuses
|
453
|
+
deal.won? # Returns true if deal status is in your won_statuses
|
454
|
+
```
|
455
|
+
|
423
456
|
#### TypedRecord Methods
|
424
457
|
|
425
458
|
All typed records (Person, Company, and custom objects) support:
|
data/attio-ruby.gemspec
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/attio/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "attio-ruby"
|
7
|
+
spec.version = Attio::VERSION
|
8
|
+
spec.authors = ["Robert Beene"]
|
9
|
+
spec.email = ["robert@ismly.com"]
|
10
|
+
|
11
|
+
spec.summary = "Ruby client library for the Attio API"
|
12
|
+
spec.description = "A comprehensive Ruby client library for the Attio CRM API with OAuth support, type safety, and extensive test coverage"
|
13
|
+
spec.homepage = "https://github.com/rbeene/attio_ruby"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.4.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/rbeene/attio_ruby"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/rbeene/attio_ruby/blob/main/CHANGELOG.md"
|
21
|
+
spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/attio-ruby"
|
22
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/rbeene/attio_ruby/issues"
|
23
|
+
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) ||
|
28
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
# Runtime dependencies
|
36
|
+
spec.add_dependency "faraday", "~> 2.0"
|
37
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
38
|
+
spec.add_dependency "ostruct", "~> 0.6"
|
39
|
+
|
40
|
+
# Development dependencies
|
41
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
42
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
43
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
44
|
+
spec.add_development_dependency "webmock", "~> 3.18"
|
45
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
46
|
+
spec.add_development_dependency "simplecov-cobertura", "~> 2.1"
|
47
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
48
|
+
spec.add_development_dependency "redcarpet", "~> 3.6"
|
49
|
+
spec.add_development_dependency "rubocop", "~> 1.50"
|
50
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.20"
|
51
|
+
spec.add_development_dependency "rubocop-performance", "~> 1.17"
|
52
|
+
spec.add_development_dependency "standard", "~> 1.28"
|
53
|
+
spec.add_development_dependency "benchmark-ips", "~> 2.12"
|
54
|
+
spec.add_development_dependency "pry", "~> 0.14"
|
55
|
+
spec.add_development_dependency "pry-byebug", "~> 3.10"
|
56
|
+
spec.add_development_dependency "dotenv", "~> 2.8"
|
57
|
+
spec.add_development_dependency "timecop", "~> 0.9"
|
58
|
+
spec.add_development_dependency "bundle-audit", "~> 0.1"
|
59
|
+
spec.add_development_dependency "brakeman", "~> 6.0"
|
60
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
61
|
+
end
|
data/lib/attio/resources/deal.rb
CHANGED
@@ -80,6 +80,42 @@ module Attio
|
|
80
80
|
super(values: values, **opts)
|
81
81
|
end
|
82
82
|
|
83
|
+
# Find deals by stage names
|
84
|
+
# @param stage_names [Array<String>] Array of stage names to filter by
|
85
|
+
# @return [Attio::ListObject] List of matching deals
|
86
|
+
def in_stage(stage_names:, **opts)
|
87
|
+
# If only one stage, use simple equality
|
88
|
+
if stage_names.length == 1
|
89
|
+
filter = { stage: stage_names.first }
|
90
|
+
else
|
91
|
+
# Multiple stages need $or operator
|
92
|
+
filter = {
|
93
|
+
"$or": stage_names.map { |stage| { stage: stage } }
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
list(**opts.merge(params: {filter: filter}))
|
98
|
+
end
|
99
|
+
|
100
|
+
# Find won deals using configured statuses
|
101
|
+
# @return [Attio::ListObject] List of won deals
|
102
|
+
def won(**opts)
|
103
|
+
in_stage(stage_names: Attio.configuration.won_statuses, **opts)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Find lost deals using configured statuses
|
107
|
+
# @return [Attio::ListObject] List of lost deals
|
108
|
+
def lost(**opts)
|
109
|
+
in_stage(stage_names: Attio.configuration.lost_statuses, **opts)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Find open deals (Lead + In Progress) using configured statuses
|
113
|
+
# @return [Attio::ListObject] List of open deals
|
114
|
+
def open_deals(**opts)
|
115
|
+
all_open_statuses = Attio.configuration.open_statuses + Attio.configuration.in_progress_statuses
|
116
|
+
in_stage(stage_names: all_open_statuses, **opts)
|
117
|
+
end
|
118
|
+
|
83
119
|
# Find deals within a value range
|
84
120
|
# @param min [Numeric] Minimum value (optional)
|
85
121
|
# @param max [Numeric] Maximum value (optional)
|
@@ -246,31 +282,65 @@ module Attio
|
|
246
282
|
# (value * probability / 100.0).round(2)
|
247
283
|
# end
|
248
284
|
|
285
|
+
# Get the current status title
|
286
|
+
# @return [String, nil] The current status title
|
287
|
+
def current_status
|
288
|
+
return nil unless stage
|
289
|
+
|
290
|
+
if stage.is_a?(Hash)
|
291
|
+
stage.dig("status", "title")
|
292
|
+
else
|
293
|
+
stage
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Get the timestamp when the status changed
|
298
|
+
# @return [Time, nil] The timestamp when status changed
|
299
|
+
def status_changed_at
|
300
|
+
return nil unless stage
|
301
|
+
|
302
|
+
if stage.is_a?(Hash) && stage["active_from"]
|
303
|
+
Time.parse(stage["active_from"])
|
304
|
+
else
|
305
|
+
nil
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
249
309
|
# Check if the deal is open
|
250
310
|
# @return [Boolean] True if the deal is open
|
251
311
|
def open?
|
252
|
-
return false unless
|
312
|
+
return false unless current_status
|
253
313
|
|
254
|
-
|
255
|
-
|
314
|
+
all_open_statuses = Attio.configuration.open_statuses + Attio.configuration.in_progress_statuses
|
315
|
+
all_open_statuses.include?(current_status)
|
256
316
|
end
|
257
317
|
|
258
318
|
# Check if the deal is won
|
259
319
|
# @return [Boolean] True if the deal is won
|
260
320
|
def won?
|
261
|
-
return false unless
|
321
|
+
return false unless current_status
|
262
322
|
|
263
|
-
|
264
|
-
stage_title && stage_title.downcase.include?("won")
|
323
|
+
Attio.configuration.won_statuses.include?(current_status)
|
265
324
|
end
|
266
325
|
|
267
326
|
# Check if the deal is lost
|
268
327
|
# @return [Boolean] True if the deal is lost
|
269
328
|
def lost?
|
270
|
-
return false unless
|
329
|
+
return false unless current_status
|
271
330
|
|
272
|
-
|
273
|
-
|
331
|
+
Attio.configuration.lost_statuses.include?(current_status)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Get the timestamp when the deal was won
|
335
|
+
# @return [Time, nil] The timestamp when deal was won, or nil if not won
|
336
|
+
def won_at
|
337
|
+
won? ? status_changed_at : nil
|
338
|
+
end
|
339
|
+
|
340
|
+
# Get the timestamp when the deal was closed (won or lost)
|
341
|
+
# @return [Time, nil] The timestamp when deal was closed, or nil if still open
|
342
|
+
def closed_at
|
343
|
+
(won? || lost?) ? status_changed_at : nil
|
274
344
|
end
|
275
345
|
|
276
346
|
# # Check if the deal is overdue
|
@@ -123,13 +123,13 @@ module Attio
|
|
123
123
|
end
|
124
124
|
|
125
125
|
# List active members only
|
126
|
-
def active(**)
|
127
|
-
list(**).select(&:active?)
|
126
|
+
def active(**opts)
|
127
|
+
list(**opts).select(&:active?)
|
128
128
|
end
|
129
129
|
|
130
130
|
# List admin members only
|
131
|
-
def admins(**)
|
132
|
-
list(**).select(&:admin?)
|
131
|
+
def admins(**opts)
|
132
|
+
list(**opts).select(&:admin?)
|
133
133
|
end
|
134
134
|
|
135
135
|
# This resource doesn't support creation, updates, or deletion
|
@@ -22,6 +22,10 @@ module Attio
|
|
22
22
|
ca_bundle_path
|
23
23
|
verify_ssl_certs
|
24
24
|
use_faraday
|
25
|
+
won_statuses
|
26
|
+
lost_statuses
|
27
|
+
open_statuses
|
28
|
+
in_progress_statuses
|
25
29
|
].freeze
|
26
30
|
|
27
31
|
# All available configuration settings
|
@@ -38,7 +42,11 @@ module Attio
|
|
38
42
|
debug: false,
|
39
43
|
ca_bundle_path: nil,
|
40
44
|
verify_ssl_certs: true,
|
41
|
-
use_faraday: true
|
45
|
+
use_faraday: true,
|
46
|
+
won_statuses: ["Won 🎉"].freeze,
|
47
|
+
lost_statuses: ["Lost"].freeze,
|
48
|
+
open_statuses: ["Lead"].freeze,
|
49
|
+
in_progress_statuses: ["In Progress"].freeze
|
42
50
|
}.freeze
|
43
51
|
|
44
52
|
attr_reader(*ALL_SETTINGS)
|
@@ -157,7 +165,9 @@ module Attio
|
|
157
165
|
|
158
166
|
def reset_without_lock!
|
159
167
|
DEFAULT_SETTINGS.each do |key, value|
|
160
|
-
|
168
|
+
# For arrays, create a new copy to avoid frozen arrays
|
169
|
+
actual_value = value.is_a?(Array) ? value.dup : value
|
170
|
+
instance_variable_set("@#{key}", actual_value)
|
161
171
|
end
|
162
172
|
@api_key = nil
|
163
173
|
end
|
data/lib/attio/version.rb
CHANGED
data/lib/attio-ruby.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attio-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Beene
|
@@ -335,6 +335,7 @@ files:
|
|
335
335
|
- LICENSE
|
336
336
|
- README.md
|
337
337
|
- Rakefile
|
338
|
+
- attio-ruby.gemspec
|
338
339
|
- docs/CODECOV_SETUP.md
|
339
340
|
- examples/app_specific_typed_record.md
|
340
341
|
- examples/basic_usage.rb
|
@@ -343,6 +344,7 @@ files:
|
|
343
344
|
- examples/oauth_flow_README.md
|
344
345
|
- examples/typed_records_example.rb
|
345
346
|
- examples/webhook_server.rb
|
347
|
+
- lib/attio-ruby.rb
|
346
348
|
- lib/attio.rb
|
347
349
|
- lib/attio/api_resource.rb
|
348
350
|
- lib/attio/builders/name_builder.rb
|