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.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +42 -0
- data/.github/workflows/ci.yml +74 -0
- data/.github/workflows/docs.yml +51 -0
- data/.github/workflows/release.yml +70 -0
- data/.gitignore +60 -0
- data/.rspec +4 -0
- data/.rubocop.yml +65 -0
- data/.yardopts +14 -0
- data/CHANGELOG.md +32 -0
- data/CODE_OF_CONDUCT.md +37 -0
- data/CONTRIBUTING.md +241 -0
- data/Gemfile +20 -0
- data/LICENSE.txt +21 -0
- data/README.md +243 -0
- data/Rakefile +29 -0
- data/SECURITY.md +63 -0
- data/attio-rails.gemspec +42 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/index.html +91 -0
- data/lib/attio/rails/concerns/syncable.rb +132 -0
- data/lib/attio/rails/configuration.rb +55 -0
- data/lib/attio/rails/railtie.rb +15 -0
- data/lib/attio/rails/version.rb +5 -0
- data/lib/attio/rails.rb +13 -0
- data/lib/generators/attio/install/install_generator.rb +65 -0
- data/lib/generators/attio/install/templates/README.md +39 -0
- data/lib/generators/attio/install/templates/attio.rb +7 -0
- data/lib/generators/attio/install/templates/attio_sync_job.rb +63 -0
- data/lib/generators/attio/install/templates/migration.rb +11 -0
- data/test_basic.rb +39 -0
- metadata +229 -0
data/Gemfile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in attio-rails.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem "rake", "~> 13.0"
|
7
|
+
|
8
|
+
group :development, :test do
|
9
|
+
gem "rspec", "~> 3.12"
|
10
|
+
gem "rspec-rails", "~> 6.0"
|
11
|
+
gem "rails", "~> 7.0"
|
12
|
+
gem "sqlite3", "~> 1.4"
|
13
|
+
gem "simplecov", "~> 0.22"
|
14
|
+
gem "simplecov-console", "~> 0.9"
|
15
|
+
gem "rubocop", "~> 1.50"
|
16
|
+
gem "rubocop-rails", "~> 2.19"
|
17
|
+
gem "rubocop-rspec", "~> 2.19"
|
18
|
+
gem "pry", "~> 0.14"
|
19
|
+
gem "webmock", "~> 3.18"
|
20
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Ernest Sim
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
# Attio Rails
|
2
|
+
|
3
|
+
[](https://idl3.github.io/attio-rails)
|
4
|
+
[](https://badge.fury.io/rb/attio-rails)
|
5
|
+
[](https://github.com/idl3/attio-rails/actions/workflows/ci.yml)
|
6
|
+
|
7
|
+
Rails integration for the [Attio](https://github.com/idl3/attio) Ruby client. This gem provides Rails-specific features including ActiveRecord model synchronization, generators, and background job integration.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'attio-rails', '~> 0.1.0'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
bundle install
|
21
|
+
rails generate attio:install
|
22
|
+
```
|
23
|
+
|
24
|
+
## Configuration
|
25
|
+
|
26
|
+
After running the install generator, configure Attio in `config/initializers/attio.rb`:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Attio::Rails.configure do |config|
|
30
|
+
config.api_key = ENV['ATTIO_API_KEY']
|
31
|
+
config.async = true # Use ActiveJob for syncing
|
32
|
+
config.queue = :default # ActiveJob queue name
|
33
|
+
config.logger = Rails.logger
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
### ActiveRecord Integration
|
40
|
+
|
41
|
+
Add the `Attio::Rails::Syncable` concern to your models:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class User < ApplicationRecord
|
45
|
+
include Attio::Rails::Syncable
|
46
|
+
|
47
|
+
attio_syncable object: 'people',
|
48
|
+
attributes: [:email, :first_name, :last_name],
|
49
|
+
identifier: :email
|
50
|
+
|
51
|
+
# Optional callbacks
|
52
|
+
before_attio_sync :prepare_data
|
53
|
+
after_attio_sync :log_sync
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def prepare_data
|
58
|
+
# Prepare data before syncing
|
59
|
+
end
|
60
|
+
|
61
|
+
def log_sync(response)
|
62
|
+
Rails.logger.info "Synced to Attio: #{response['id']}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
### Manual Syncing
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# Sync a single record
|
71
|
+
user = User.find(1)
|
72
|
+
user.sync_to_attio
|
73
|
+
|
74
|
+
# Sync multiple records
|
75
|
+
User.where(active: true).find_each(&:sync_to_attio)
|
76
|
+
|
77
|
+
# Async sync (requires ActiveJob)
|
78
|
+
user.sync_to_attio_later
|
79
|
+
```
|
80
|
+
|
81
|
+
### Custom Field Mapping
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class Company < ApplicationRecord
|
85
|
+
include Attio::Rails::Syncable
|
86
|
+
|
87
|
+
attio_syncable object: 'companies',
|
88
|
+
attributes: {
|
89
|
+
name: :company_name,
|
90
|
+
domain: :website_url,
|
91
|
+
employee_count: ->(c) { c.employees.count }
|
92
|
+
},
|
93
|
+
identifier: :domain
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
### Batch Operations
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
# Sync multiple records efficiently
|
101
|
+
Attio::Rails::BatchSync.perform(
|
102
|
+
User.where(updated_at: 1.day.ago..),
|
103
|
+
object: 'people'
|
104
|
+
)
|
105
|
+
```
|
106
|
+
|
107
|
+
### ActiveJob Integration
|
108
|
+
|
109
|
+
The gem automatically uses ActiveJob for background syncing when configured:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
class User < ApplicationRecord
|
113
|
+
include Attio::Rails::Syncable
|
114
|
+
|
115
|
+
attio_syncable object: 'people', async: true
|
116
|
+
|
117
|
+
# Automatically syncs in background after save
|
118
|
+
after_commit :sync_to_attio_later
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
### Generator
|
123
|
+
|
124
|
+
The install generator creates:
|
125
|
+
- Configuration initializer at `config/initializers/attio.rb`
|
126
|
+
- ActiveJob class at `app/jobs/attio_sync_job.rb`
|
127
|
+
- Migration for tracking sync status (optional)
|
128
|
+
|
129
|
+
```bash
|
130
|
+
rails generate attio:install
|
131
|
+
rails generate attio:install --skip-job # Skip job creation
|
132
|
+
rails generate attio:install --skip-migration # Skip migration
|
133
|
+
```
|
134
|
+
|
135
|
+
## Advanced Features
|
136
|
+
|
137
|
+
### Conditional Syncing
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
class User < ApplicationRecord
|
141
|
+
include Attio::Rails::Syncable
|
142
|
+
|
143
|
+
attio_syncable object: 'people',
|
144
|
+
if: :should_sync_to_attio?
|
145
|
+
|
146
|
+
def should_sync_to_attio?
|
147
|
+
confirmed? && !deleted?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Error Handling
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
class User < ApplicationRecord
|
156
|
+
include Attio::Rails::Syncable
|
157
|
+
|
158
|
+
attio_syncable object: 'people',
|
159
|
+
on_error: :handle_sync_error
|
160
|
+
|
161
|
+
def handle_sync_error(error)
|
162
|
+
Rails.logger.error "Attio sync failed: #{error.message}"
|
163
|
+
Sentry.capture_exception(error)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### Custom Transformations
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class User < ApplicationRecord
|
172
|
+
include Attio::Rails::Syncable
|
173
|
+
|
174
|
+
attio_syncable object: 'people',
|
175
|
+
transform: :transform_for_attio
|
176
|
+
|
177
|
+
def transform_for_attio(attributes)
|
178
|
+
attributes.merge(
|
179
|
+
full_name: "#{first_name} #{last_name}",
|
180
|
+
tags: user_tags.pluck(:name)
|
181
|
+
)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
## Testing
|
187
|
+
|
188
|
+
The gem includes RSpec helpers for testing:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
# In spec/rails_helper.rb
|
192
|
+
require 'attio/rails/rspec'
|
193
|
+
|
194
|
+
# In your specs
|
195
|
+
RSpec.describe User do
|
196
|
+
include Attio::Rails::RSpec::Helpers
|
197
|
+
|
198
|
+
it 'syncs to Attio' do
|
199
|
+
user = create(:user)
|
200
|
+
|
201
|
+
expect_attio_sync(object: 'people') do
|
202
|
+
user.sync_to_attio
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
207
|
+
|
208
|
+
## Development
|
209
|
+
|
210
|
+
After checking out the repo:
|
211
|
+
|
212
|
+
```bash
|
213
|
+
bundle install
|
214
|
+
cd spec/dummy
|
215
|
+
rails db:create db:migrate
|
216
|
+
cd ../..
|
217
|
+
bundle exec rspec
|
218
|
+
```
|
219
|
+
|
220
|
+
To run tests against different Rails versions:
|
221
|
+
|
222
|
+
```bash
|
223
|
+
RAILS_VERSION=7.1 bundle update
|
224
|
+
bundle exec rspec
|
225
|
+
|
226
|
+
RAILS_VERSION=7.0 bundle update
|
227
|
+
bundle exec rspec
|
228
|
+
```
|
229
|
+
|
230
|
+
## Contributing
|
231
|
+
|
232
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/idl3/attio-rails. Please read our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).
|
233
|
+
|
234
|
+
## License
|
235
|
+
|
236
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
237
|
+
|
238
|
+
## Support
|
239
|
+
|
240
|
+
- 📖 [Documentation](https://idl3.github.io/attio-rails)
|
241
|
+
- 🐛 [Issues](https://github.com/idl3/attio-rails/issues)
|
242
|
+
- 💬 [Discussions](https://github.com/idl3/attio-rails/discussions)
|
243
|
+
- 📦 [Main Attio Gem](https://github.com/idl3/attio)
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require "rubocop/rake_task"
|
4
|
+
require "yard"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
RuboCop::RakeTask.new
|
8
|
+
|
9
|
+
YARD::Rake::YardocTask.new do |t|
|
10
|
+
t.files = ['lib/**/*.rb']
|
11
|
+
t.options = ['--markup-provider=redcarpet', '--markup=markdown', '--protected', '--private']
|
12
|
+
t.stats_options = ['--list-undoc']
|
13
|
+
end
|
14
|
+
|
15
|
+
namespace :yard do
|
16
|
+
desc "Generate documentation and serve it with live reloading"
|
17
|
+
task :server do
|
18
|
+
sh "bundle exec yard server --reload"
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "Generate documentation for GitHub Pages"
|
22
|
+
task :gh_pages do
|
23
|
+
sh "bundle exec yard --output-dir docs"
|
24
|
+
# Create .nojekyll file for GitHub Pages
|
25
|
+
File.open("docs/.nojekyll", "w") {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
task :default => [:spec, :rubocop]
|
data/SECURITY.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
We release patches for security vulnerabilities. Which versions are eligible depends on the CVSS v3.0 Rating:
|
6
|
+
|
7
|
+
| Version | Supported |
|
8
|
+
| ------- | ------------------ |
|
9
|
+
| 1.x.x | :white_check_mark: |
|
10
|
+
| < 1.0 | :x: |
|
11
|
+
|
12
|
+
## Reporting a Vulnerability
|
13
|
+
|
14
|
+
If you discover a security vulnerability within this project, please follow these steps:
|
15
|
+
|
16
|
+
1. **Do NOT** create a public GitHub issue
|
17
|
+
2. Send details to the maintainers through GitHub Security Advisories
|
18
|
+
3. Include the following in your report:
|
19
|
+
- Description of the vulnerability
|
20
|
+
- Steps to reproduce
|
21
|
+
- Possible impact
|
22
|
+
- Suggested fix (if any)
|
23
|
+
|
24
|
+
### What to expect
|
25
|
+
|
26
|
+
- Acknowledgment of your report within 48 hours
|
27
|
+
- Regular updates on our progress
|
28
|
+
- Credit for responsible disclosure (unless you prefer to remain anonymous)
|
29
|
+
|
30
|
+
## Security Best Practices
|
31
|
+
|
32
|
+
When using this gem:
|
33
|
+
|
34
|
+
1. **API Key Management**
|
35
|
+
- Never commit API keys to version control
|
36
|
+
- Use Rails credentials or environment variables
|
37
|
+
- Rotate API keys regularly
|
38
|
+
- Use different keys for different environments
|
39
|
+
|
40
|
+
2. **Rails Security**
|
41
|
+
- Keep Rails and dependencies updated
|
42
|
+
- Follow Rails security best practices
|
43
|
+
- Use strong parameters
|
44
|
+
- Implement proper authentication and authorization
|
45
|
+
|
46
|
+
3. **Data Handling**
|
47
|
+
- Be cautious with sensitive data in logs
|
48
|
+
- Use Rails encrypted credentials
|
49
|
+
- Implement proper error handling
|
50
|
+
- Sanitize user input before syncing to Attio
|
51
|
+
|
52
|
+
## Security Features
|
53
|
+
|
54
|
+
This gem includes:
|
55
|
+
- Integration with Rails security features
|
56
|
+
- Automatic API key masking in logs
|
57
|
+
- SSL/TLS verification by default
|
58
|
+
- Input validation and sanitization
|
59
|
+
- Safe handling of ActiveRecord callbacks
|
60
|
+
|
61
|
+
## Contact
|
62
|
+
|
63
|
+
For security concerns, please use GitHub Security Advisories or contact the maintainers directly through GitHub.
|
data/attio-rails.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative 'lib/attio/rails/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "attio-rails"
|
5
|
+
spec.version = Attio::Rails::VERSION
|
6
|
+
spec.authors = ["Ernest Sim"]
|
7
|
+
spec.email = ["ernest.codes@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{Rails integration for the Attio API client}
|
10
|
+
spec.description = %q{Rails-specific features and integrations for the Attio Ruby client, including model concerns and generators}
|
11
|
+
spec.homepage = "https://github.com/idl3/attio-rails"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
20
|
+
spec.metadata["documentation_uri"] = "https://idl3.github.io/attio-rails"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_dependency "attio", "~> 0.1", ">= 0.1.1"
|
32
|
+
spec.add_dependency "rails", ">= 6.1", "< 8.0"
|
33
|
+
|
34
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
35
|
+
spec.add_development_dependency "redcarpet", "~> 3.5"
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
37
|
+
spec.add_development_dependency "rubocop", "~> 1.50"
|
38
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
39
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
40
|
+
spec.add_development_dependency "bundler-audit", "~> 0.9"
|
41
|
+
spec.add_development_dependency "danger", "~> 9.4"
|
42
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "attio/rails"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docs/index.html
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Attio Rails Integration Documentation</title>
|
7
|
+
<style>
|
8
|
+
body {
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
10
|
+
line-height: 1.6;
|
11
|
+
color: #333;
|
12
|
+
max-width: 800px;
|
13
|
+
margin: 0 auto;
|
14
|
+
padding: 20px;
|
15
|
+
background: #f8f9fa;
|
16
|
+
}
|
17
|
+
.container {
|
18
|
+
background: white;
|
19
|
+
padding: 40px;
|
20
|
+
border-radius: 8px;
|
21
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
22
|
+
}
|
23
|
+
h1 {
|
24
|
+
color: #2c3e50;
|
25
|
+
border-bottom: 2px solid #3498db;
|
26
|
+
padding-bottom: 10px;
|
27
|
+
}
|
28
|
+
.notice {
|
29
|
+
background: #e3f2fd;
|
30
|
+
border-left: 4px solid #2196f3;
|
31
|
+
padding: 15px;
|
32
|
+
margin: 20px 0;
|
33
|
+
border-radius: 4px;
|
34
|
+
}
|
35
|
+
.button {
|
36
|
+
display: inline-block;
|
37
|
+
background: #3498db;
|
38
|
+
color: white;
|
39
|
+
padding: 10px 20px;
|
40
|
+
text-decoration: none;
|
41
|
+
border-radius: 4px;
|
42
|
+
margin: 10px 10px 10px 0;
|
43
|
+
transition: background 0.3s;
|
44
|
+
}
|
45
|
+
.button:hover {
|
46
|
+
background: #2980b9;
|
47
|
+
}
|
48
|
+
pre {
|
49
|
+
background: #f4f4f4;
|
50
|
+
border: 1px solid #ddd;
|
51
|
+
border-radius: 4px;
|
52
|
+
padding: 15px;
|
53
|
+
overflow-x: auto;
|
54
|
+
}
|
55
|
+
</style>
|
56
|
+
</head>
|
57
|
+
<body>
|
58
|
+
<div class="container">
|
59
|
+
<h1>Attio Rails Integration Documentation</h1>
|
60
|
+
|
61
|
+
<div class="notice">
|
62
|
+
<strong>Documentation Status:</strong> This is a placeholder page. The full API documentation will be generated automatically when the gem is built.
|
63
|
+
</div>
|
64
|
+
|
65
|
+
<h2>Quick Start</h2>
|
66
|
+
<p>Add the gem to your Gemfile:</p>
|
67
|
+
<pre><code>gem 'attio-rails'</code></pre>
|
68
|
+
|
69
|
+
<p>Run the installer:</p>
|
70
|
+
<pre><code>rails generate attio:install</code></pre>
|
71
|
+
|
72
|
+
<h2>Documentation Links</h2>
|
73
|
+
<a href="./file.README.html" class="button">README</a>
|
74
|
+
<a href="./Attio/Rails.html" class="button">API Documentation</a>
|
75
|
+
<a href="./method_list.html" class="button">Method List</a>
|
76
|
+
<a href="./class_list.html" class="button">Class List</a>
|
77
|
+
|
78
|
+
<h2>GitHub Repository</h2>
|
79
|
+
<p>Visit the <a href="https://github.com/your-username/attio-rails">GitHub repository</a> for source code, issues, and contributions.</p>
|
80
|
+
|
81
|
+
<h2>Generate Documentation Locally</h2>
|
82
|
+
<p>To generate the full documentation locally:</p>
|
83
|
+
<pre><code>bundle exec rake yard
|
84
|
+
bundle exec yard server</code></pre>
|
85
|
+
|
86
|
+
<div class="notice">
|
87
|
+
<strong>Note:</strong> This documentation is automatically updated when changes are pushed to the main branch.
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
</body>
|
91
|
+
</html>
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Attio
|
2
|
+
module Rails
|
3
|
+
module Concerns
|
4
|
+
module Syncable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
after_commit :sync_to_attio, on: [:create, :update], if: :should_sync_to_attio?
|
9
|
+
after_commit :remove_from_attio, on: :destroy, if: :should_remove_from_attio?
|
10
|
+
|
11
|
+
class_attribute :attio_object_type
|
12
|
+
class_attribute :attio_attribute_mapping
|
13
|
+
class_attribute :attio_sync_conditions
|
14
|
+
class_attribute :attio_identifier_attribute
|
15
|
+
end
|
16
|
+
|
17
|
+
class_methods do
|
18
|
+
def syncs_with_attio(object_type, mapping = {}, options = {})
|
19
|
+
self.attio_object_type = object_type
|
20
|
+
self.attio_attribute_mapping = mapping
|
21
|
+
self.attio_sync_conditions = options[:if] || -> { true }
|
22
|
+
self.attio_identifier_attribute = options[:identifier] || :id
|
23
|
+
end
|
24
|
+
|
25
|
+
def skip_attio_sync
|
26
|
+
skip_callback :commit, :after, :sync_to_attio
|
27
|
+
skip_callback :commit, :after, :remove_from_attio
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sync_to_attio
|
32
|
+
if Attio::Rails.background_sync?
|
33
|
+
AttioSyncJob.perform_later(
|
34
|
+
model_name: self.class.name,
|
35
|
+
model_id: id,
|
36
|
+
action: :sync
|
37
|
+
)
|
38
|
+
else
|
39
|
+
sync_to_attio_now
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
Attio::Rails.logger.error "Failed to sync to Attio: #{e.message}"
|
43
|
+
raise if ::Rails.env.development?
|
44
|
+
end
|
45
|
+
|
46
|
+
def sync_to_attio_now
|
47
|
+
client = Attio::Rails.client
|
48
|
+
attributes = attio_attributes
|
49
|
+
|
50
|
+
if attio_record_id.present?
|
51
|
+
client.records.update(
|
52
|
+
object: attio_object_type,
|
53
|
+
id: attio_record_id,
|
54
|
+
data: { values: attributes }
|
55
|
+
)
|
56
|
+
else
|
57
|
+
response = client.records.create(
|
58
|
+
object: attio_object_type,
|
59
|
+
data: { values: attributes }
|
60
|
+
)
|
61
|
+
update_column(:attio_record_id, response['data']['id']) if respond_to?(:attio_record_id=)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_from_attio
|
66
|
+
if Attio::Rails.background_sync?
|
67
|
+
AttioSyncJob.perform_later(
|
68
|
+
model_name: self.class.name,
|
69
|
+
model_id: id,
|
70
|
+
action: :delete,
|
71
|
+
attio_record_id: attio_record_id
|
72
|
+
)
|
73
|
+
else
|
74
|
+
remove_from_attio_now
|
75
|
+
end
|
76
|
+
rescue StandardError => e
|
77
|
+
Attio::Rails.logger.error "Failed to remove from Attio: #{e.message}"
|
78
|
+
raise if ::Rails.env.development?
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove_from_attio_now
|
82
|
+
return unless attio_record_id.present?
|
83
|
+
|
84
|
+
client = Attio::Rails.client
|
85
|
+
client.records.delete(
|
86
|
+
object: attio_object_type,
|
87
|
+
id: attio_record_id
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def attio_attributes
|
92
|
+
return {} unless attio_attribute_mapping
|
93
|
+
|
94
|
+
attio_attribute_mapping.each_with_object({}) do |(attio_key, local_key), hash|
|
95
|
+
value = case local_key
|
96
|
+
when Proc
|
97
|
+
local_key.call(self)
|
98
|
+
when Symbol, String
|
99
|
+
send(local_key)
|
100
|
+
else
|
101
|
+
local_key
|
102
|
+
end
|
103
|
+
hash[attio_key] = value unless value.nil?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def should_sync_to_attio?
|
108
|
+
return false unless Attio::Rails.sync_enabled?
|
109
|
+
return false unless attio_object_type.present? && attio_attribute_mapping.present?
|
110
|
+
|
111
|
+
condition = attio_sync_conditions
|
112
|
+
case condition
|
113
|
+
when Proc
|
114
|
+
instance_exec(&condition)
|
115
|
+
when Symbol, String
|
116
|
+
send(condition)
|
117
|
+
else
|
118
|
+
true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def should_remove_from_attio?
|
123
|
+
attio_record_id.present? && Attio::Rails.sync_enabled?
|
124
|
+
end
|
125
|
+
|
126
|
+
def attio_identifier
|
127
|
+
send(attio_identifier_attribute)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|