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 +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +56 -0
- data/examples/deals.rb +2 -2
- data/lib/attio/api_resource.rb +5 -4
- data/lib/attio/concerns/time_filterable.rb +153 -0
- data/lib/attio/internal/record.rb +1 -1
- data/lib/attio/resources/company.rb +3 -4
- data/lib/attio/resources/deal.rb +288 -57
- data/lib/attio/resources/meta.rb +5 -7
- data/lib/attio/resources/object.rb +2 -2
- data/lib/attio/resources/person.rb +2 -3
- data/lib/attio/resources/typed_record.rb +5 -5
- data/lib/attio/resources/workspace_member.rb +1 -1
- data/lib/attio/util/currency_formatter.rb +143 -0
- data/lib/attio/util/time_period.rb +219 -0
- data/lib/attio/version.rb +1 -1
- data/lib/attio-ruby.rb +1 -1
- metadata +4 -2
- data/attio-ruby.gemspec +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55a5174c89fcc718a7152c4ab1027c5dd82766d6c35620e7dc3c8acc58b3f983
|
4
|
+
data.tar.gz: 205f0e84a49ba3e7f22d3a9d99a42586d6469977c84aea3163e16e9f80b7cd33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce8cb37b9adb959502703ff0e0bf04b96f757457494e9563f69dce4b6b104e534eaff0fc4badbf176fd4e5aa181dac867617261cf94a3e9cd5c66a7c64aa2052
|
7
|
+
data.tar.gz: 7318b994c5ba70fa0f57af7d2555711d6262c0a0fdec8aeef972ba77b79b7d4717a0c2ccaea6743bef1ee588f6412e40536a8ef4b7806e34dc26d5cb94c6b524
|
data/.rubocop.yml
CHANGED
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: {
|
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!"
|
data/lib/attio/api_resource.rb
CHANGED
@@ -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
|
-
# @
|
414
|
-
|
415
|
-
|
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
|
-
|
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
|
{
|