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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84a54ed9baf9d0656cb14f49fc0209bad919cce78efe67de8b5b02ba56eac3bc
4
- data.tar.gz: e3813aeea763037d5764d580b1074c18e82f6fa71eb1d1f8a9ab45adb0642b2c
3
+ metadata.gz: 24bed31b6dd0aa8d5a0c9be52298f636255c368c47dcbf41091609712c3a56f7
4
+ data.tar.gz: 64e25c9ec1329ecbebd5352374df18650ccf18d7d135d28b3eca8a8c6bc6554b
5
5
  SHA512:
6
- metadata.gz: e0eba5da5d484ff911e6c37893b043504cccb6e87e188ca0a60edd867579936ff49ac3b47715e60ee8d91a15dccbb3eaa3bb5e8accbd1883e12a803b1563a100
7
- data.tar.gz: c95586cbc9a9e5e6d78ed417fec243409f359edc98a221aaf8316d96163a8de2f2eff0a4439fb2850571cf547f979d5f031f02f55ec0a4c538b107b485ddfeb8
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 # 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
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
- # Check deal status
408
- deal.open? # true if not won or lost
409
- deal.won? # true if stage includes "won"
410
- deal.lost? # true if stage is "lost"
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:
@@ -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
@@ -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 stage
312
+ return false unless current_status
253
313
 
254
- stage_title = stage.is_a?(Hash) ? stage.dig("status", "title") : stage
255
- stage_title && !["won 🎉", "lost"].include?(stage_title.downcase)
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 stage
321
+ return false unless current_status
262
322
 
263
- stage_title = stage.is_a?(Hash) ? stage.dig("status", "title") : stage
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 stage
329
+ return false unless current_status
271
330
 
272
- stage_title = stage.is_a?(Hash) ? stage.dig("status", "title") : stage
273
- stage_title && stage_title.downcase == "lost"
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
- instance_variable_set("@#{key}", value)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Attio
4
4
  # Current version of the Attio Ruby gem
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.3"
6
6
  end
data/lib/attio-ruby.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file exists to match the gem name for auto-requiring
4
+ # It simply requires the main attio module
5
+ require_relative "attio"
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.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