attio-rails 0.1.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.
@@ -0,0 +1,55 @@
1
+ module Attio
2
+ module Rails
3
+ class Configuration
4
+ attr_accessor :api_key, :default_workspace_id, :logger, :sync_enabled, :background_sync
5
+
6
+ def initialize
7
+ @api_key = ENV['ATTIO_API_KEY']
8
+ @default_workspace_id = nil
9
+ @logger = defined?(::Rails) ? ::Rails.logger : Logger.new(STDOUT)
10
+ @sync_enabled = true
11
+ @background_sync = true
12
+ end
13
+
14
+ def valid?
15
+ !api_key.nil? && !api_key.empty?
16
+ end
17
+ end
18
+
19
+ class << self
20
+ attr_writer :configuration
21
+
22
+ def configuration
23
+ @configuration ||= Configuration.new
24
+ end
25
+
26
+ def configure
27
+ yield(configuration)
28
+ reset_client!
29
+ end
30
+
31
+ def client
32
+ raise ConfigurationError, "Attio API key not configured" unless configuration.valid?
33
+ @client ||= ::Attio.client(api_key: configuration.api_key)
34
+ end
35
+
36
+ def reset_client!
37
+ @client = nil
38
+ end
39
+
40
+ def sync_enabled?
41
+ configuration.sync_enabled
42
+ end
43
+
44
+ def background_sync?
45
+ configuration.background_sync
46
+ end
47
+
48
+ def logger
49
+ configuration.logger
50
+ end
51
+ end
52
+
53
+ class ConfigurationError < StandardError; end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ module Attio
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ initializer "attio.configure_rails_initialization" do
5
+ ActiveSupport.on_load(:active_record) do
6
+ require 'attio/rails/concerns/syncable'
7
+ end
8
+ end
9
+
10
+ generators do
11
+ require 'generators/attio/install/install_generator'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Attio
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ require "attio"
2
+ require "rails"
3
+ require "active_support"
4
+
5
+ require "attio/rails/version"
6
+ require "attio/rails/configuration"
7
+ require "attio/rails/railtie"
8
+
9
+ module Attio
10
+ module Rails
11
+ class Error < StandardError; end
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Attio
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ class_option :skip_job, type: :boolean, default: false, desc: "Skip creating the sync job"
9
+ class_option :skip_migration, type: :boolean, default: false, desc: "Skip creating the migration"
10
+
11
+ def check_requirements
12
+ unless defined?(ActiveJob)
13
+ say "Warning: ActiveJob is not available. Skipping job creation.", :yellow
14
+ @skip_job = true
15
+ end
16
+ end
17
+
18
+ def create_initializer
19
+ template 'attio.rb', 'config/initializers/attio.rb'
20
+ end
21
+
22
+ def create_migration
23
+ return if options[:skip_migration]
24
+
25
+ if defined?(ActiveRecord)
26
+ migration_template 'migration.rb', 'db/migrate/add_attio_record_id_to_tables.rb'
27
+ else
28
+ say "ActiveRecord not detected. Skipping migration.", :yellow
29
+ end
30
+ end
31
+
32
+ def create_sync_job
33
+ return if options[:skip_job] || @skip_job
34
+
35
+ template 'attio_sync_job.rb', 'app/jobs/attio_sync_job.rb'
36
+ end
37
+
38
+ def add_to_gemfile
39
+ gem_group :production do
40
+ gem 'attio-rails'
41
+ end unless gemfile_contains?('attio-rails')
42
+ end
43
+
44
+ def display_readme
45
+ readme 'README.md'
46
+ end
47
+
48
+ private
49
+
50
+ def gemfile_contains?(gem_name)
51
+ File.read('Gemfile').include?(gem_name)
52
+ rescue
53
+ false
54
+ end
55
+
56
+ def rails_version
57
+ Rails::VERSION::STRING
58
+ end
59
+
60
+ def migration_version
61
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,39 @@
1
+ # Attio Rails Setup
2
+
3
+ The Attio Rails gem has been installed and configured!
4
+
5
+ ## Next Steps
6
+
7
+ 1. Set your Attio API key in your environment variables:
8
+ ```
9
+ ATTIO_API_KEY=your_api_key_here
10
+ ```
11
+
12
+ 2. Add the Syncable concern to models you want to sync with Attio:
13
+ ```ruby
14
+ class User < ApplicationRecord
15
+ include Attio::Rails::Concerns::Syncable
16
+
17
+ syncs_with_attio 'users', {
18
+ email: :email,
19
+ name: :full_name,
20
+ company: -> (user) { user.company.name }
21
+ }
22
+ end
23
+ ```
24
+
25
+ 3. Run migrations to add the attio_record_id column to your models:
26
+ ```
27
+ rails db:migrate
28
+ ```
29
+
30
+ 4. Your models will now automatically sync to Attio on create, update, and destroy!
31
+
32
+ ## Manual Sync
33
+
34
+ You can also manually trigger syncs:
35
+ ```ruby
36
+ user.sync_to_attio
37
+ ```
38
+
39
+ For more information, visit: https://github.com/your-username/attio-ruby
@@ -0,0 +1,7 @@
1
+ Attio::Rails.configure do |config|
2
+ # Set your Attio API key
3
+ config.api_key = ENV['ATTIO_API_KEY']
4
+
5
+ # Optional: Set a default workspace ID
6
+ # config.default_workspace_id = 'your-workspace-id'
7
+ end
@@ -0,0 +1,63 @@
1
+ class AttioSyncJob < ApplicationJob
2
+ queue_as :low
3
+
4
+ retry_on Attio::RateLimitError, wait: 1.minute, attempts: 3
5
+ retry_on Attio::ServerError, wait: :exponentially_longer, attempts: 5
6
+ discard_on ActiveJob::DeserializationError
7
+
8
+ def perform(model_name:, model_id:, action:, attio_record_id: nil)
9
+ return unless Attio::Rails.sync_enabled?
10
+
11
+ model_class = model_name.constantize
12
+
13
+ case action
14
+ when :sync
15
+ sync_record(model_class, model_id)
16
+ when :delete
17
+ delete_record(model_class, model_id, attio_record_id)
18
+ else
19
+ Attio::Rails.logger.error "Unknown Attio sync action: #{action}"
20
+ end
21
+ rescue Attio::AuthenticationError => e
22
+ Attio::Rails.logger.error "Attio authentication failed: #{e.message}"
23
+ raise # Re-raise to trigger job failure notifications
24
+ rescue Attio::ValidationError => e
25
+ Attio::Rails.logger.error "Attio validation error for #{model_name}##{model_id}: #{e.message}"
26
+ # Don't retry validation errors
27
+ rescue StandardError => e
28
+ Attio::Rails.logger.error "Attio sync failed for #{model_name}##{model_id}: #{e.message}"
29
+ Attio::Rails.logger.error e.backtrace.join("\n") if Rails.env.development?
30
+ raise e
31
+ end
32
+
33
+ private
34
+
35
+ def sync_record(model_class, model_id)
36
+ model = model_class.find_by(id: model_id)
37
+ return unless model
38
+
39
+ # Check if model should still be synced
40
+ return unless model.should_sync_to_attio?
41
+
42
+ model.sync_to_attio_now
43
+ end
44
+
45
+ def delete_record(model_class, model_id, attio_record_id)
46
+ return unless attio_record_id.present?
47
+
48
+ # Model might already be deleted, so we work with the class
49
+ object_type = model_class.attio_object_type
50
+ return unless object_type.present?
51
+
52
+ client = Attio::Rails.client
53
+ client.records.delete(
54
+ object: object_type,
55
+ id: attio_record_id
56
+ )
57
+
58
+ Attio::Rails.logger.info "Deleted Attio record #{attio_record_id} for #{model_class.name}##{model_id}"
59
+ rescue Attio::NotFoundError
60
+ # Record already deleted in Attio, nothing to do
61
+ Attio::Rails.logger.info "Attio record #{attio_record_id} already deleted"
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ class AddAttioRecordIdToTables < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ # Add attio_record_id to each table that needs to sync with Attio
4
+ # Example:
5
+ # add_column :users, :attio_record_id, :string
6
+ # add_index :users, :attio_record_id
7
+ #
8
+ # add_column :companies, :attio_record_id, :string
9
+ # add_index :companies, :attio_record_id
10
+ end
11
+ end
data/test_basic.rb ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Test without loading external dependencies
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
5
+ $LOAD_PATH.unshift(File.expand_path('../../attio/lib', __FILE__))
6
+
7
+ puts "Testing Attio Rails gem structure..."
8
+
9
+ # Test version file
10
+ require 'attio/rails/version'
11
+ puts "✓ Version loaded: #{Attio::Rails::VERSION}"
12
+
13
+ # Test that all files exist
14
+ files_to_check = [
15
+ 'lib/attio/rails.rb',
16
+ 'lib/attio/rails/configuration.rb',
17
+ 'lib/attio/rails/concerns/syncable.rb',
18
+ 'lib/attio/rails/railtie.rb',
19
+ 'lib/generators/attio/install/install_generator.rb',
20
+ 'lib/generators/attio/install/templates/attio.rb',
21
+ 'lib/generators/attio/install/templates/attio_sync_job.rb',
22
+ 'lib/generators/attio/install/templates/migration.rb',
23
+ 'lib/generators/attio/install/templates/README.md'
24
+ ]
25
+
26
+ files_to_check.each do |file|
27
+ if File.exist?(file)
28
+ puts "✓ #{file} exists"
29
+ else
30
+ puts "✗ #{file} is missing!"
31
+ end
32
+ end
33
+
34
+ # Test spec files exist
35
+ spec_files = Dir.glob('spec/**/*_spec.rb')
36
+ puts "\nFound #{spec_files.length} spec files:"
37
+ spec_files.each { |f| puts " - #{f}" }
38
+
39
+ puts "\nBasic structure test completed!"
metadata ADDED
@@ -0,0 +1,229 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attio-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ernest Sim
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: attio
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '0.1'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.1.1
32
+ - !ruby/object:Gem::Dependency
33
+ name: rails
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '6.1'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '8.0'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '6.1'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '8.0'
52
+ - !ruby/object:Gem::Dependency
53
+ name: yard
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: '0.9'
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '0.9'
66
+ - !ruby/object:Gem::Dependency
67
+ name: redcarpet
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '3.5'
73
+ type: :development
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '3.5'
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '3.12'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '3.12'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rubocop
96
+ requirement: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '1.50'
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '1.50'
108
+ - !ruby/object:Gem::Dependency
109
+ name: simplecov
110
+ requirement: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '0.22'
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '0.22'
122
+ - !ruby/object:Gem::Dependency
123
+ name: rake
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '13.0'
129
+ type: :development
130
+ prerelease: false
131
+ version_requirements: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '13.0'
136
+ - !ruby/object:Gem::Dependency
137
+ name: bundler-audit
138
+ requirement: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '0.9'
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: '0.9'
150
+ - !ruby/object:Gem::Dependency
151
+ name: danger
152
+ requirement: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: '9.4'
157
+ type: :development
158
+ prerelease: false
159
+ version_requirements: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - "~>"
162
+ - !ruby/object:Gem::Version
163
+ version: '9.4'
164
+ description: Rails-specific features and integrations for the Attio Ruby client, including
165
+ model concerns and generators
166
+ email:
167
+ - ernest.codes@gmail.com
168
+ executables: []
169
+ extensions: []
170
+ extra_rdoc_files: []
171
+ files:
172
+ - ".github/dependabot.yml"
173
+ - ".github/workflows/ci.yml"
174
+ - ".github/workflows/docs.yml"
175
+ - ".github/workflows/release.yml"
176
+ - ".gitignore"
177
+ - ".rspec"
178
+ - ".rubocop.yml"
179
+ - ".yardopts"
180
+ - CHANGELOG.md
181
+ - CODE_OF_CONDUCT.md
182
+ - CONTRIBUTING.md
183
+ - Gemfile
184
+ - LICENSE.txt
185
+ - README.md
186
+ - Rakefile
187
+ - SECURITY.md
188
+ - attio-rails.gemspec
189
+ - bin/console
190
+ - bin/setup
191
+ - docs/index.html
192
+ - lib/attio/rails.rb
193
+ - lib/attio/rails/concerns/syncable.rb
194
+ - lib/attio/rails/configuration.rb
195
+ - lib/attio/rails/railtie.rb
196
+ - lib/attio/rails/version.rb
197
+ - lib/generators/attio/install/install_generator.rb
198
+ - lib/generators/attio/install/templates/README.md
199
+ - lib/generators/attio/install/templates/attio.rb
200
+ - lib/generators/attio/install/templates/attio_sync_job.rb
201
+ - lib/generators/attio/install/templates/migration.rb
202
+ - test_basic.rb
203
+ homepage: https://github.com/idl3/attio-rails
204
+ licenses:
205
+ - MIT
206
+ metadata:
207
+ allowed_push_host: https://rubygems.org
208
+ homepage_uri: https://github.com/idl3/attio-rails
209
+ source_code_uri: https://github.com/idl3/attio-rails
210
+ changelog_uri: https://github.com/idl3/attio-rails/blob/main/CHANGELOG.md
211
+ documentation_uri: https://idl3.github.io/attio-rails
212
+ rdoc_options: []
213
+ require_paths:
214
+ - lib
215
+ required_ruby_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: 3.0.0
220
+ required_rubygems_version: !ruby/object:Gem::Requirement
221
+ requirements:
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ version: '0'
225
+ requirements: []
226
+ rubygems_version: 3.6.9
227
+ specification_version: 4
228
+ summary: Rails integration for the Attio API client
229
+ test_files: []