attio-ruby 0.1.3 → 0.1.5

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: 24bed31b6dd0aa8d5a0c9be52298f636255c368c47dcbf41091609712c3a56f7
4
- data.tar.gz: 64e25c9ec1329ecbebd5352374df18650ccf18d7d135d28b3eca8a8c6bc6554b
3
+ metadata.gz: 55a5174c89fcc718a7152c4ab1027c5dd82766d6c35620e7dc3c8acc58b3f983
4
+ data.tar.gz: 205f0e84a49ba3e7f22d3a9d99a42586d6469977c84aea3163e16e9f80b7cd33
5
5
  SHA512:
6
- metadata.gz: fba01e91fb72e0ee903bab6e4d2f94a622ed2f6ec67f7f44ea4bc2bdc38f02e548500f4cd964cca4cfb6facc2679f6d059fc65872e907fc05a17fdd1a81134e6
7
- data.tar.gz: 348490a48d8347932536ff5b419f112444b88d9fefcb87b47a9f4074f6dab0bb5cdc23ecac1a323b6fb48d759e7310f36475c2a469de3841a3eeae73731ba835
6
+ metadata.gz: ce8cb37b9adb959502703ff0e0bf04b96f757457494e9563f69dce4b6b104e534eaff0fc4badbf176fd4e5aa181dac867617261cf94a3e9cd5c66a7c64aa2052
7
+ data.tar.gz: 7318b994c5ba70fa0f57af7d2555711d6262c0a0fdec8aeef972ba77b79b7d4717a0c2ccaea6743bef1ee588f6412e40536a8ef4b7806e34dc26d5cb94c6b524
data/.rubocop.yml CHANGED
@@ -84,7 +84,7 @@ Style/Documentation:
84
84
 
85
85
  # RSpec Configuration
86
86
  RSpec/ExampleLength:
87
- Max: 30
87
+ Max: 45 # API mocking tests require comprehensive setup
88
88
  Exclude:
89
89
  - 'spec/integration/**/*'
90
90
 
data/CHANGELOG.md CHANGED
@@ -5,6 +5,62 @@ 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.5] - 2025-08-11
9
+
10
+ ### Fixed
11
+ - **Code Quality**: Comprehensive RuboCop compliance fixes
12
+ - Replaced all instance variables with `let` helpers in specs
13
+ - Fixed nested describe/context blocks exceeding depth limits
14
+ - Changed `before(:all)` to `before` to avoid state leakage
15
+ - Used proper RSpec matchers (`all` instead of iteration)
16
+ - Fixed context wording to follow RSpec conventions
17
+ - Used `described_class` instead of explicit class names
18
+ - Refactored long test examples for better readability
19
+ - Fixed indentation and formatting issues throughout
20
+
21
+ ## [0.1.4] - 2025-08-08
22
+
23
+ ### Added
24
+ - **TimeFilterable Concern**: New reusable module for time-based filtering across resources
25
+ - Methods like `created_after`, `created_before`, `updated_after`, `updated_before`
26
+ - Support for `created_between` and `updated_between` with date ranges
27
+ - Automatic date/time parsing and formatting
28
+ - **TimePeriod Utility**: Comprehensive time period handling
29
+ - Support for standard periods (today, yesterday, this_week, last_week, this_month, etc.)
30
+ - Quarter calculations (this_quarter, last_quarter, etc.)
31
+ - Custom date range support
32
+ - Flexible period parsing from strings
33
+ - **CurrencyFormatter Utility**: Professional currency formatting
34
+ - Support for 40+ international currencies
35
+ - Proper symbol placement and formatting per currency
36
+ - Thousand separators and decimal handling
37
+ - **Enhanced Deal Resource**: Major improvements to Deal functionality
38
+ - Time-based filtering methods: `recently_created`, `recently_updated`, `created_this_month`, etc.
39
+ - Monetary value methods: `amount`, `currency`, `formatted_amount`, `raw_value`
40
+ - Advanced querying: `high_value`, `low_value`, `with_value`, `without_value`
41
+ - Assignment filters: `assigned_to`, `unassigned`
42
+ - Metrics calculation: `metrics_for_period` with optimized API calls
43
+ - Pipeline velocity: `average_days_to_close`, `conversion_rate`
44
+ - Stage helpers: `stage_display_name` for human-readable stage names
45
+
46
+ ### Fixed
47
+ - Fixed `ListObject#first` method to accept optional argument like Ruby's `Array#first`
48
+ - Updated currency formatting test expectations to properly include cents
49
+ - Corrected integration test for workspace member retrieval
50
+
51
+ ### Changed
52
+ - Improved Deal stage handling to align with actual Attio API behavior
53
+ - Optimized `metrics_for_period` to use targeted API calls instead of loading all deals
54
+ - Simplified monetary value extraction to use consistent `currency_value` format
55
+
56
+ ### Testing
57
+ - Added comprehensive test coverage for all new features
58
+ - 336 new tests for TimeFilterable concern
59
+ - 415 tests for TimePeriod utility
60
+ - 162 tests for CurrencyFormatter
61
+ - Extensive integration tests for Deal improvements
62
+ - Test coverage remains high at ~90%
63
+
8
64
  ## [0.1.3] - 2025-08-07
9
65
 
10
66
  ### Fixed
data/examples/deals.rb CHANGED
@@ -69,7 +69,7 @@ puts "Is lost? #{deal.lost?}"
69
69
  puts "\n=== Deal Pipeline Analysis ==="
70
70
 
71
71
  # List all deals and analyze by stage
72
- all_deals = Attio::Deal.list(params: { limit: 50 })
72
+ all_deals = Attio::Deal.list(params: {limit: 50})
73
73
  stage_counts = Hash.new(0)
74
74
 
75
75
  all_deals.each do |d|
@@ -109,4 +109,4 @@ puts "\n=== Cleanup ==="
109
109
  puts "Deleted deal: #{d.name}"
110
110
  end
111
111
 
112
- puts "\nDone!"
112
+ puts "\nDone!"
@@ -409,10 +409,11 @@ module Attio
409
409
  alias_method :size, :length
410
410
  alias_method :count, :length
411
411
 
412
- # Get the first item in the current page
413
- # @return [APIResource, nil] The first resource or nil if empty
414
- def first
415
- @data.first
412
+ # Get the first item(s) in the current page
413
+ # @param [Integer] n Number of items to return (optional)
414
+ # @return [APIResource, Array<APIResource>, nil] The first resource(s) or nil if empty
415
+ def first(n = nil)
416
+ n.nil? ? @data.first : @data.first(n)
416
417
  end
417
418
 
418
419
  # Get the last item in the current page
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../util/time_period"
4
+
5
+ module Attio
6
+ module Concerns
7
+ # Provides time-based filtering methods for any model
8
+ # Include this module to add time filtering capabilities
9
+ module TimeFilterable
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ end
13
+
14
+ module ClassMethods
15
+ # Filter records by a time period for a specific date field
16
+ # @param period [Util::TimePeriod] The time period to filter by
17
+ # @param date_field [Symbol] The field to check (default: :created_at)
18
+ # @return [Array] Records within the period
19
+ def in_period(period, date_field: :created_at, **opts)
20
+ all(**opts).select do |record|
21
+ # Try accessor method first, then bracket notation
22
+ date_value = if record.respond_to?(date_field)
23
+ record.send(date_field)
24
+ else
25
+ record[date_field]
26
+ end
27
+
28
+ if date_value
29
+ parsed_date = date_value.is_a?(String) ? Time.parse(date_value) : date_value
30
+ period.includes?(parsed_date)
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
36
+
37
+ # Get records created in the last N days
38
+ # @param days [Integer] Number of days to look back
39
+ # @return [Array] Recently created records
40
+ def recently_created(days = 7, **opts)
41
+ in_period(Util::TimePeriod.last_days(days), date_field: :created_at, **opts)
42
+ end
43
+
44
+ # Get records updated in the last N days
45
+ # @param days [Integer] Number of days to look back
46
+ # @return [Array] Recently updated records
47
+ def recently_updated(days = 7, **opts)
48
+ in_period(Util::TimePeriod.last_days(days), date_field: :updated_at, **opts)
49
+ end
50
+
51
+ # Get records created this year
52
+ # @return [Array] Records created in current year
53
+ def created_this_year(**opts)
54
+ in_period(Util::TimePeriod.current_year, date_field: :created_at, **opts)
55
+ end
56
+
57
+ # Get records created this month
58
+ # @return [Array] Records created in current month
59
+ def created_this_month(**opts)
60
+ in_period(Util::TimePeriod.current_month, date_field: :created_at, **opts)
61
+ end
62
+
63
+ # Get records created year to date
64
+ # @return [Array] Records created YTD
65
+ def created_year_to_date(**opts)
66
+ in_period(Util::TimePeriod.year_to_date, date_field: :created_at, **opts)
67
+ end
68
+
69
+ # Get records created in a specific month
70
+ # @param year [Integer] The year
71
+ # @param month [Integer] The month (1-12)
72
+ # @return [Array] Records created in that month
73
+ def created_in_month(year, month, **opts)
74
+ in_period(Util::TimePeriod.month(year, month), date_field: :created_at, **opts)
75
+ end
76
+
77
+ # Get records created in a specific quarter
78
+ # @param year [Integer] The year
79
+ # @param quarter [Integer] The quarter (1-4)
80
+ # @return [Array] Records created in that quarter
81
+ def created_in_quarter(year, quarter, **opts)
82
+ in_period(Util::TimePeriod.quarter(year, quarter), date_field: :created_at, **opts)
83
+ end
84
+
85
+ # Get records created in a specific year
86
+ # @param year [Integer] The year
87
+ # @return [Array] Records created in that year
88
+ def created_in_year(year, **opts)
89
+ in_period(Util::TimePeriod.year(year), date_field: :created_at, **opts)
90
+ end
91
+
92
+ # Get activity metrics for a period
93
+ # @param period [Util::TimePeriod] The time period
94
+ # @return [Hash] Metrics about records in the period
95
+ def activity_metrics(period, **opts)
96
+ created = in_period(period, date_field: :created_at, **opts)
97
+ updated = in_period(period, date_field: :updated_at, **opts)
98
+
99
+ {
100
+ period: period.label,
101
+ created_count: created.size,
102
+ updated_count: updated.size,
103
+ total_activity: (created + updated).uniq.size
104
+ }
105
+ end
106
+ end
107
+
108
+ # Instance methods for time-based checks
109
+
110
+ # Check if this record was created in a specific period
111
+ # @param period [Util::TimePeriod] The time period
112
+ # @return [Boolean] True if created in the period
113
+ def created_in?(period)
114
+ return false unless respond_to?(:created_at) && created_at
115
+ date = created_at.is_a?(String) ? Time.parse(created_at) : created_at
116
+ period.includes?(date)
117
+ end
118
+
119
+ # Check if this record was updated in a specific period
120
+ # @param period [Util::TimePeriod] The time period
121
+ # @return [Boolean] True if updated in the period
122
+ def updated_in?(period)
123
+ return false unless respond_to?(:updated_at) && updated_at
124
+ date = updated_at.is_a?(String) ? Time.parse(updated_at) : updated_at
125
+ period.includes?(date)
126
+ end
127
+
128
+ # Get the age of the record in days
129
+ # @return [Integer] Days since creation
130
+ def age_in_days
131
+ return nil unless respond_to?(:created_at) && created_at
132
+ created = created_at.is_a?(String) ? Time.parse(created_at) : created_at
133
+ ((Time.now - created) / (24 * 60 * 60)).round
134
+ end
135
+
136
+ # Check if record is new (created recently)
137
+ # @param days [Integer] Number of days to consider "new"
138
+ # @return [Boolean] True if created within specified days
139
+ def new?(days = 7)
140
+ age = age_in_days
141
+ age && age <= days
142
+ end
143
+
144
+ # Check if record is old
145
+ # @param days [Integer] Number of days to consider "old"
146
+ # @return [Boolean] True if created more than specified days ago
147
+ def old?(days = 365)
148
+ age = age_in_days
149
+ age && age > days
150
+ end
151
+ end
152
+ end
153
+ end
@@ -341,7 +341,7 @@ module Attio
341
341
  if value_data.key?(:value) || value_data.key?("value")
342
342
  value_data[:value] || value_data["value"]
343
343
  elsif value_data.key?(:target_object) || value_data.key?("target_object") ||
344
- value_data.key?(:referenced_actor_type) || value_data.key?("referenced_actor_type")
344
+ value_data.key?(:referenced_actor_type) || value_data.key?("referenced_actor_type")
345
345
  # Reference value - return the full reference object
346
346
  value_data
347
347
  elsif value_data.key?(:currency_value) || value_data.key?("currency_value")
@@ -176,7 +176,6 @@ module Attio
176
176
  super(values: values, **opts)
177
177
  end
178
178
 
179
-
180
179
  # Find companies by employee count range
181
180
  # @param min [Integer] Minimum employee count
182
181
  # @param max [Integer] Maximum employee count (optional)
@@ -196,9 +195,9 @@ module Attio
196
195
 
197
196
  list(**opts.merge(params: {filter: filter}))
198
197
  end
199
-
198
+
200
199
  private
201
-
200
+
202
201
  # Build filter for domain field
203
202
  def filter_by_domain(value)
204
203
  # Strip protocol if present
@@ -211,7 +210,7 @@ module Attio
211
210
  }
212
211
  }
213
212
  end
214
-
213
+
215
214
  # Build filter for name field
216
215
  def filter_by_name(value)
217
216
  {