attio-rails 0.1.1 → 0.2.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 +4 -4
- data/.github/workflows/{release.yml → build-and-publish.yml} +3 -13
- data/.github/workflows/ci.yml +1 -1
- data/.github/workflows/docs.yml +2 -2
- data/.rubocop.yml +381 -35
- data/.rubocop_todo.yml +2 -0
- data/CHANGELOG.md +40 -1
- data/CONCEPTS.md +448 -0
- data/Gemfile +14 -8
- data/Rakefile +8 -6
- data/attio-rails.gemspec +7 -18
- data/lib/attio/rails/concerns/syncable.rb +27 -16
- data/lib/attio/rails/configuration.rb +10 -3
- data/lib/attio/rails/railtie.rb +7 -3
- data/lib/attio/rails/rspec/helpers.rb +209 -0
- data/lib/attio/rails/rspec/matchers.rb +145 -0
- data/lib/attio/rails/rspec.rb +9 -0
- data/lib/attio/rails/version.rb +3 -1
- data/lib/attio/rails.rb +3 -0
- data/lib/generators/attio/install/install_generator.rb +25 -23
- data/lib/generators/attio/install/templates/attio.rb +5 -3
- data/lib/generators/attio/install/templates/attio_sync_job.rb +11 -11
- data/lib/generators/attio/install/templates/{migration.rb → migration.rb.erb} +1 -1
- data/test_basic.rb +15 -14
- metadata +15 -175
data/lib/attio/rails/railtie.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Attio
|
2
4
|
module Rails
|
3
5
|
class Railtie < ::Rails::Railtie
|
4
6
|
initializer "attio.configure_rails_initialization" do
|
5
7
|
ActiveSupport.on_load(:active_record) do
|
6
|
-
require
|
8
|
+
require "attio/rails/concerns/syncable"
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
12
|
+
# :nocov:
|
10
13
|
generators do
|
11
|
-
require
|
14
|
+
require "generators/attio/install/install_generator"
|
12
15
|
end
|
16
|
+
# :nocov:
|
13
17
|
end
|
14
18
|
end
|
15
|
-
end
|
19
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
module Rails
|
5
|
+
module RSpec
|
6
|
+
# RSpec helper methods for testing Attio Rails integration
|
7
|
+
#
|
8
|
+
# @example Including in your specs
|
9
|
+
# RSpec.configure do |config|
|
10
|
+
# config.include Attio::Rails::RSpec::Helpers
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# @example Basic usage
|
14
|
+
# it 'syncs to Attio' do
|
15
|
+
# stub_attio_client
|
16
|
+
#
|
17
|
+
# user = User.create!(email: 'test@example.com')
|
18
|
+
# expect(user.attio_record_id).to eq('attio-test-id')
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @example Testing with expectations
|
22
|
+
# it 'sends correct attributes' do
|
23
|
+
# expect_attio_sync(
|
24
|
+
# object: 'people',
|
25
|
+
# attributes: { email: 'test@example.com' }
|
26
|
+
# ) do
|
27
|
+
# User.create!(email: 'test@example.com')
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
module Helpers
|
31
|
+
# Stub the Attio client and records API
|
32
|
+
#
|
33
|
+
# @return [Hash{Symbol => RSpec::Mocks::Double}] Hash with :client and :records doubles
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# stubs = stub_attio_client
|
37
|
+
# allow(stubs[:records]).to receive(:create).and_return(response)
|
38
|
+
def stub_attio_client
|
39
|
+
client = instance_double(Attio::Client)
|
40
|
+
records = instance_double(Attio::Resources::Records)
|
41
|
+
|
42
|
+
allow(Attio::Rails).to receive(:client).and_return(client)
|
43
|
+
allow(client).to receive(:records).and_return(records)
|
44
|
+
|
45
|
+
{ client: client, records: records }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Stub Attio create operations
|
49
|
+
#
|
50
|
+
# @param response [Hash] Custom response to return (default: { "data" => { "id" => "attio-test-id" } })
|
51
|
+
# @return [Hash{Symbol => RSpec::Mocks::Double}] Hash with :client and :records doubles
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# stub_attio_create
|
55
|
+
# User.create!(email: 'test@example.com')
|
56
|
+
#
|
57
|
+
# @example With custom response
|
58
|
+
# stub_attio_create('data' => { 'id' => 'custom-id' })
|
59
|
+
def stub_attio_create(response = { "data" => { "id" => "attio-test-id" } })
|
60
|
+
stubs = stub_attio_client
|
61
|
+
allow(stubs[:records]).to receive(:create).and_return(response)
|
62
|
+
stubs
|
63
|
+
end
|
64
|
+
|
65
|
+
# Stub Attio update operations
|
66
|
+
#
|
67
|
+
# @param response [Hash] Custom response to return
|
68
|
+
# @return [Hash{Symbol => RSpec::Mocks::Double}] Hash with :client and :records doubles
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# stub_attio_update
|
72
|
+
# user.update!(name: 'New Name')
|
73
|
+
def stub_attio_update(response = { "data" => { "id" => "attio-test-id" } })
|
74
|
+
stubs = stub_attio_client
|
75
|
+
allow(stubs[:records]).to receive(:update).and_return(response)
|
76
|
+
stubs
|
77
|
+
end
|
78
|
+
|
79
|
+
# Stub Attio delete operations
|
80
|
+
#
|
81
|
+
# @param response [Hash] Custom response to return
|
82
|
+
# @return [Hash{Symbol => RSpec::Mocks::Double}] Hash with :client and :records doubles
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# stub_attio_delete
|
86
|
+
# user.destroy!
|
87
|
+
def stub_attio_delete(response = { "data" => { "deleted" => true } })
|
88
|
+
stubs = stub_attio_client
|
89
|
+
allow(stubs[:records]).to receive(:delete).and_return(response)
|
90
|
+
stubs
|
91
|
+
end
|
92
|
+
|
93
|
+
# Expect a sync to Attio with specific parameters
|
94
|
+
#
|
95
|
+
# @param object [String] Expected Attio object type
|
96
|
+
# @param attributes [Hash, nil] Expected attributes (nil to match any)
|
97
|
+
# @yield Block to execute that should trigger the sync
|
98
|
+
# @return [Hash{Symbol => RSpec::Mocks::Double}] Hash with :client and :records doubles
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# expect_attio_sync(object: 'people', attributes: { email: 'test@example.com' }) do
|
102
|
+
# User.create!(email: 'test@example.com')
|
103
|
+
# end
|
104
|
+
def expect_attio_sync(object:, attributes: nil)
|
105
|
+
stubs = stub_attio_client
|
106
|
+
|
107
|
+
if attributes
|
108
|
+
expect(stubs[:records]).to receive(:create).with(
|
109
|
+
object: object,
|
110
|
+
data: { values: attributes }
|
111
|
+
).and_return({ "data" => { "id" => "attio-test-id" } })
|
112
|
+
else
|
113
|
+
expect(stubs[:records]).to receive(:create).with(
|
114
|
+
hash_including(object: object)
|
115
|
+
).and_return({ "data" => { "id" => "attio-test-id" } })
|
116
|
+
end
|
117
|
+
|
118
|
+
yield if block_given?
|
119
|
+
|
120
|
+
stubs
|
121
|
+
end
|
122
|
+
|
123
|
+
# Expect no sync to Attio
|
124
|
+
#
|
125
|
+
# @yield Block to execute that should not trigger any sync
|
126
|
+
# @return [Hash{Symbol => RSpec::Mocks::Double}] Hash with :client and :records doubles
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# expect_no_attio_sync do
|
130
|
+
# with_attio_sync_disabled do
|
131
|
+
# User.create!(email: 'test@example.com')
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
def expect_no_attio_sync
|
135
|
+
stubs = stub_attio_client
|
136
|
+
|
137
|
+
expect(stubs[:records]).not_to receive(:create)
|
138
|
+
expect(stubs[:records]).not_to receive(:update)
|
139
|
+
|
140
|
+
yield if block_given?
|
141
|
+
|
142
|
+
stubs
|
143
|
+
end
|
144
|
+
|
145
|
+
# Temporarily disable Attio syncing
|
146
|
+
#
|
147
|
+
# @yield Block to execute with syncing disabled
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# with_attio_sync_disabled do
|
151
|
+
# User.create!(email: 'test@example.com') # Won't sync
|
152
|
+
# end
|
153
|
+
def with_attio_sync_disabled
|
154
|
+
original_value = Attio::Rails.configuration.sync_enabled
|
155
|
+
Attio::Rails.configure { |c| c.sync_enabled = false }
|
156
|
+
|
157
|
+
yield
|
158
|
+
ensure
|
159
|
+
Attio::Rails.configure { |c| c.sync_enabled = original_value }
|
160
|
+
end
|
161
|
+
|
162
|
+
# Temporarily enable background sync
|
163
|
+
#
|
164
|
+
# @yield Block to execute with background sync enabled
|
165
|
+
#
|
166
|
+
# @example
|
167
|
+
# with_attio_background_sync do
|
168
|
+
# User.create!(email: 'test@example.com') # Will sync in background
|
169
|
+
# end
|
170
|
+
# expect(attio_sync_jobs.size).to eq(1)
|
171
|
+
def with_attio_background_sync
|
172
|
+
original_value = Attio::Rails.configuration.background_sync
|
173
|
+
Attio::Rails.configure { |c| c.background_sync = true }
|
174
|
+
|
175
|
+
yield
|
176
|
+
ensure
|
177
|
+
Attio::Rails.configure { |c| c.background_sync = original_value }
|
178
|
+
end
|
179
|
+
|
180
|
+
# Get all enqueued AttioSyncJob jobs
|
181
|
+
#
|
182
|
+
# @return [Array<Hash>] Array of enqueued job hashes
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# User.create!(email: 'test@example.com')
|
186
|
+
# expect(attio_sync_jobs.size).to eq(1)
|
187
|
+
# expect(attio_sync_jobs.first[:args]).to include('model_name' => 'User')
|
188
|
+
def attio_sync_jobs
|
189
|
+
ActiveJob::Base.queue_adapter.enqueued_jobs.select do |job|
|
190
|
+
job[:job] == AttioSyncJob
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Clear all enqueued AttioSyncJob jobs
|
195
|
+
#
|
196
|
+
# @return [Array<Hash>] The deleted jobs
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# clear_attio_sync_jobs
|
200
|
+
# expect(attio_sync_jobs).to be_empty
|
201
|
+
def clear_attio_sync_jobs
|
202
|
+
ActiveJob::Base.queue_adapter.enqueued_jobs.delete_if do |job|
|
203
|
+
job[:job] == AttioSyncJob
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Attio
|
4
|
+
module Rails
|
5
|
+
module RSpec
|
6
|
+
module Matchers
|
7
|
+
::RSpec::Matchers.define :sync_to_attio do |expected|
|
8
|
+
match do |actual|
|
9
|
+
return false unless actual.class.respond_to?(:attio_object_type)
|
10
|
+
|
11
|
+
if expected
|
12
|
+
actual.class.attio_object_type == expected[:object] || expected[:object_type]
|
13
|
+
else
|
14
|
+
actual.class.attio_object_type.present?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
failure_message do |actual|
|
19
|
+
if actual.class.respond_to?(:attio_object_type)
|
20
|
+
expected_obj = expected[:object] || expected[:object_type]
|
21
|
+
actual_obj = actual.class.attio_object_type
|
22
|
+
"expected #{actual.class} to sync to Attio object '#{expected_obj}' but syncs to '#{actual_obj}'"
|
23
|
+
else
|
24
|
+
"expected #{actual.class} to include Attio::Rails::Concerns::Syncable"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
failure_message_when_negated do |actual|
|
29
|
+
"expected #{actual.class} not to sync to Attio"
|
30
|
+
end
|
31
|
+
|
32
|
+
description do
|
33
|
+
"sync to Attio"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
::RSpec::Matchers.define :have_attio_attribute do |attio_attr|
|
38
|
+
match do |actual|
|
39
|
+
return false unless actual.class.respond_to?(:attio_attribute_mapping)
|
40
|
+
|
41
|
+
mapping = actual.class.attio_attribute_mapping
|
42
|
+
if @mapped_to
|
43
|
+
mapping[attio_attr] == @mapped_to
|
44
|
+
else
|
45
|
+
mapping.key?(attio_attr)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
chain :mapped_to do |local_attr|
|
50
|
+
@mapped_to = local_attr
|
51
|
+
end
|
52
|
+
|
53
|
+
failure_message do |actual|
|
54
|
+
if actual.class.respond_to?(:attio_attribute_mapping)
|
55
|
+
mapping = actual.class.attio_attribute_mapping
|
56
|
+
if @mapped_to
|
57
|
+
actual_mapping = mapping[attio_attr]
|
58
|
+
"expected #{actual.class} to map Attio attribute '#{attio_attr}' to '#{@mapped_to}' " \
|
59
|
+
"but it maps to '#{actual_mapping}'"
|
60
|
+
else
|
61
|
+
available_attrs = mapping.keys.join(", ")
|
62
|
+
"expected #{actual.class} to have Attio attribute '#{attio_attr}' but has #{available_attrs}"
|
63
|
+
end
|
64
|
+
else
|
65
|
+
"expected #{actual.class} to include Attio::Rails::Concerns::Syncable"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
description do
|
70
|
+
if @mapped_to
|
71
|
+
"have Attio attribute '#{attio_attr}' mapped to '#{@mapped_to}'"
|
72
|
+
else
|
73
|
+
"have Attio attribute '#{attio_attr}'"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
::RSpec::Matchers.define :enqueue_attio_sync_job do # rubocop:disable Metrics/BlockLength
|
79
|
+
supports_block_expectations
|
80
|
+
|
81
|
+
match do |block|
|
82
|
+
initial_jobs = attio_sync_jobs.dup
|
83
|
+
block.call
|
84
|
+
new_jobs = attio_sync_jobs - initial_jobs
|
85
|
+
@actual_count = new_jobs.size
|
86
|
+
|
87
|
+
if @expected_count
|
88
|
+
@actual_count == @expected_count
|
89
|
+
elsif @expected_action
|
90
|
+
new_jobs.any? { |job| job[:args].first["action"]["value"] == @expected_action.to_s }
|
91
|
+
else
|
92
|
+
@actual_count > 0
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
chain :with_action do |action|
|
97
|
+
@expected_action = action
|
98
|
+
end
|
99
|
+
|
100
|
+
chain :exactly do |count|
|
101
|
+
@expected_count = count
|
102
|
+
end
|
103
|
+
|
104
|
+
failure_message do
|
105
|
+
build_failure_message
|
106
|
+
end
|
107
|
+
|
108
|
+
failure_message_when_negated do
|
109
|
+
"expected not to enqueue AttioSyncJob but #{@actual_count} were enqueued"
|
110
|
+
end
|
111
|
+
|
112
|
+
description do
|
113
|
+
build_description
|
114
|
+
end
|
115
|
+
|
116
|
+
private def build_failure_message
|
117
|
+
if @expected_count
|
118
|
+
"expected to enqueue #{@expected_count} AttioSyncJob(s) but enqueued #{@actual_count}"
|
119
|
+
elsif @expected_action
|
120
|
+
"expected to enqueue AttioSyncJob with action '#{@expected_action}'"
|
121
|
+
else
|
122
|
+
"expected to enqueue AttioSyncJob but none were enqueued"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private def build_description
|
127
|
+
if @expected_count
|
128
|
+
"enqueue #{@expected_count} AttioSyncJob(s)"
|
129
|
+
elsif @expected_action
|
130
|
+
"enqueue AttioSyncJob with action '#{@expected_action}'"
|
131
|
+
else
|
132
|
+
"enqueue AttioSyncJob"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private def attio_sync_jobs
|
137
|
+
ActiveJob::Base.queue_adapter.enqueued_jobs.select do |job|
|
138
|
+
job[:job] == AttioSyncJob
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "attio/rails/rspec/helpers"
|
4
|
+
require "attio/rails/rspec/matchers"
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.include Attio::Rails::RSpec::Helpers, type: :model
|
8
|
+
config.include Attio::Rails::RSpec::Matchers, type: :model
|
9
|
+
end
|
data/lib/attio/rails/version.rb
CHANGED
data/lib/attio/rails.rb
CHANGED
@@ -1,29 +1,31 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/base"
|
2
4
|
|
3
5
|
module Attio
|
4
6
|
module Generators
|
5
7
|
class InstallGenerator < Rails::Generators::Base
|
6
|
-
source_root File.expand_path(
|
7
|
-
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
8
10
|
class_option :skip_job, type: :boolean, default: false, desc: "Skip creating the sync job"
|
9
11
|
class_option :skip_migration, type: :boolean, default: false, desc: "Skip creating the migration"
|
10
12
|
|
11
13
|
def check_requirements
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
return if defined?(ActiveJob)
|
15
|
+
|
16
|
+
say "Warning: ActiveJob is not available. Skipping job creation.", :yellow
|
17
|
+
@skip_job = true
|
16
18
|
end
|
17
19
|
|
18
20
|
def create_initializer
|
19
|
-
template
|
21
|
+
template "attio.rb", "config/initializers/attio.rb"
|
20
22
|
end
|
21
23
|
|
22
24
|
def create_migration
|
23
25
|
return if options[:skip_migration]
|
24
|
-
|
26
|
+
|
25
27
|
if defined?(ActiveRecord)
|
26
|
-
migration_template
|
28
|
+
migration_template "migration.rb.erb", "db/migrate/add_attio_record_id_to_tables.rb"
|
27
29
|
else
|
28
30
|
say "ActiveRecord not detected. Skipping migration.", :yellow
|
29
31
|
end
|
@@ -31,35 +33,35 @@ module Attio
|
|
31
33
|
|
32
34
|
def create_sync_job
|
33
35
|
return if options[:skip_job] || @skip_job
|
34
|
-
|
35
|
-
template
|
36
|
+
|
37
|
+
template "attio_sync_job.rb", "app/jobs/attio_sync_job.rb"
|
36
38
|
end
|
37
39
|
|
38
40
|
def add_to_gemfile
|
41
|
+
return if gemfile_contains?("attio-rails")
|
42
|
+
|
39
43
|
gem_group :production do
|
40
|
-
gem
|
41
|
-
end
|
44
|
+
gem "attio-rails"
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
48
|
def display_readme
|
45
|
-
readme
|
49
|
+
readme "README.md"
|
46
50
|
end
|
47
51
|
|
48
|
-
private
|
49
|
-
|
50
|
-
|
51
|
-
File.read('Gemfile').include?(gem_name)
|
52
|
-
rescue
|
52
|
+
private def gemfile_contains?(gem_name)
|
53
|
+
File.read("Gemfile").include?(gem_name)
|
54
|
+
rescue StandardError
|
53
55
|
false
|
54
56
|
end
|
55
57
|
|
56
|
-
def rails_version
|
58
|
+
private def rails_version
|
57
59
|
Rails::VERSION::STRING
|
58
60
|
end
|
59
61
|
|
60
|
-
def migration_version
|
62
|
+
private def migration_version
|
61
63
|
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
65
|
-
end
|
67
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
Attio::Rails.configure do |config|
|
2
4
|
# Set your Attio API key
|
3
|
-
config.api_key = ENV
|
4
|
-
|
5
|
+
config.api_key = ENV.fetch("ATTIO_API_KEY", nil)
|
6
|
+
|
5
7
|
# Optional: Set a default workspace ID
|
6
8
|
# config.default_workspace_id = 'your-workspace-id'
|
7
|
-
end
|
9
|
+
end
|
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class AttioSyncJob < ApplicationJob
|
2
4
|
queue_as :low
|
3
|
-
|
5
|
+
|
4
6
|
retry_on Attio::RateLimitError, wait: 1.minute, attempts: 3
|
5
7
|
retry_on Attio::ServerError, wait: :exponentially_longer, attempts: 5
|
6
8
|
discard_on ActiveJob::DeserializationError
|
7
9
|
|
8
10
|
def perform(model_name:, model_id:, action:, attio_record_id: nil)
|
9
11
|
return unless Attio::Rails.sync_enabled?
|
10
|
-
|
12
|
+
|
11
13
|
model_class = model_name.constantize
|
12
|
-
|
14
|
+
|
13
15
|
case action
|
14
16
|
when :sync
|
15
17
|
sync_record(model_class, model_id)
|
@@ -30,19 +32,17 @@ class AttioSyncJob < ApplicationJob
|
|
30
32
|
raise e
|
31
33
|
end
|
32
34
|
|
33
|
-
private
|
34
|
-
|
35
|
-
def sync_record(model_class, model_id)
|
35
|
+
private def sync_record(model_class, model_id)
|
36
36
|
model = model_class.find_by(id: model_id)
|
37
37
|
return unless model
|
38
|
-
|
38
|
+
|
39
39
|
# Check if model should still be synced
|
40
40
|
return unless model.should_sync_to_attio?
|
41
|
-
|
41
|
+
|
42
42
|
model.sync_to_attio_now
|
43
43
|
end
|
44
44
|
|
45
|
-
def delete_record(model_class, model_id, attio_record_id)
|
45
|
+
private def delete_record(model_class, model_id, attio_record_id)
|
46
46
|
return unless attio_record_id.present?
|
47
47
|
|
48
48
|
# Model might already be deleted, so we work with the class
|
@@ -54,10 +54,10 @@ class AttioSyncJob < ApplicationJob
|
|
54
54
|
object: object_type,
|
55
55
|
id: attio_record_id
|
56
56
|
)
|
57
|
-
|
57
|
+
|
58
58
|
Attio::Rails.logger.info "Deleted Attio record #{attio_record_id} for #{model_class.name}##{model_id}"
|
59
59
|
rescue Attio::NotFoundError
|
60
60
|
# Record already deleted in Attio, nothing to do
|
61
61
|
Attio::Rails.logger.info "Attio record #{attio_record_id} already deleted"
|
62
62
|
end
|
63
|
-
end
|
63
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class AddAttioRecordIdToTables < ActiveRecord::Migration<%= migration_version %>
|
1
|
+
class AddAttioRecordIdToTables < ActiveRecord::Migration<%= migration_version %> # rubocop:disable Rails/Migration
|
2
2
|
def change
|
3
3
|
# Add attio_record_id to each table that needs to sync with Attio
|
4
4
|
# Example:
|
data/test_basic.rb
CHANGED
@@ -1,26 +1,27 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
# Test without loading external dependencies
|
4
|
-
$LOAD_PATH.unshift(File.expand_path(
|
5
|
-
$LOAD_PATH.unshift(File.expand_path(
|
5
|
+
$LOAD_PATH.unshift(File.expand_path("lib", __dir__))
|
6
|
+
$LOAD_PATH.unshift(File.expand_path("../attio/lib", __dir__))
|
6
7
|
|
7
8
|
puts "Testing Attio Rails gem structure..."
|
8
9
|
|
9
10
|
# Test version file
|
10
|
-
require
|
11
|
+
require "attio/rails/version"
|
11
12
|
puts "✓ Version loaded: #{Attio::Rails::VERSION}"
|
12
13
|
|
13
14
|
# Test that all files exist
|
14
15
|
files_to_check = [
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
"lib/attio/rails.rb",
|
17
|
+
"lib/attio/rails/configuration.rb",
|
18
|
+
"lib/attio/rails/concerns/syncable.rb",
|
19
|
+
"lib/attio/rails/railtie.rb",
|
20
|
+
"lib/generators/attio/install/install_generator.rb",
|
21
|
+
"lib/generators/attio/install/templates/attio.rb",
|
22
|
+
"lib/generators/attio/install/templates/attio_sync_job.rb",
|
23
|
+
"lib/generators/attio/install/templates/migration.rb",
|
24
|
+
"lib/generators/attio/install/templates/README.md",
|
24
25
|
]
|
25
26
|
|
26
27
|
files_to_check.each do |file|
|
@@ -32,8 +33,8 @@ files_to_check.each do |file|
|
|
32
33
|
end
|
33
34
|
|
34
35
|
# Test spec files exist
|
35
|
-
spec_files = Dir.glob(
|
36
|
+
spec_files = Dir.glob("spec/**/*_spec.rb")
|
36
37
|
puts "\nFound #{spec_files.length} spec files:"
|
37
38
|
spec_files.each { |f| puts " - #{f}" }
|
38
39
|
|
39
|
-
puts "\nBasic structure test completed!"
|
40
|
+
puts "\nBasic structure test completed!"
|