external_id 0.1.0 → 0.1.2

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: c7f37fe1f61ae7cc606c98a516bcbf92158799383751ecfa602b3b29c32a7da2
4
- data.tar.gz: 71a51fe88cebf3fe95516cd87c4a173d111925cdf1da6fcf3c5a8caddebc299e
3
+ metadata.gz: 1b9df2ebbd5ffd553c97841c5d7b4b66f5a4c5ad03e6c6514ce81d05f8fcc476
4
+ data.tar.gz: f591813e0d6ac93edb75491131543fd52ea0ccf10e71e95bc46ddec6cfe5fcc6
5
5
  SHA512:
6
- metadata.gz: 911f2c68eff82fc94075e67f295f6c9a49324013ea9636c845705bc4d9bf8c07282d432d7cf691ca37c579b4d256a20397a7a0ef3c69abcbb1999a28d4f5ba80
7
- data.tar.gz: 24c2e9299bb4658cda2dc0a0c0d0a805036181cf0f89096074f3fb60b831251e47be98b1e74aa97cd1e8ca250e678fecb171912d691baf5a20892d8dd8100883
6
+ metadata.gz: 39983d6348cecc46842550d55a688b85dc32adc865c2e946b0d0eb4a2a1f71fa3db8cdaf637963be9e1dcd805ea167aa493bce0184c000440b3ff97e19ea812e
7
+ data.tar.gz: a043ce4201d839ad871040314eab48517426a8931cbc2d046faddef1dbe85be57d6987f2aeb6ef96d0b412d42a3ab8fdcfe58b9c7571f27eaca9b40082ce3731
data/CHANGELOG.md CHANGED
@@ -1,11 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.1] - 2025-12-02
4
+ - add installer for external_id factory
5
+ - fix: copy migration to correct path
6
+
3
7
  ## [0.1.0] - 2025-12-02
4
8
 
5
9
  - Initial release
6
10
  - Polymorphic external ID associations for Rails models
7
11
  - `ExternalId` model for database-backed storage
8
- - `ExternalIdValue` value object for clean data handling
12
+ - `ExternalId::Value` value object for clean data handling
9
13
  - `WithExternalId` concern for easy model integration
10
14
  - Configurable providers with enum support
11
15
  - UUID support for distributed systems
data/README.md CHANGED
@@ -63,6 +63,11 @@ ExternalId.configure do |config|
63
63
 
64
64
  # Optional: Use UUID for primary keys (default: true)
65
65
  # config.use_uuid = true
66
+
67
+ # Optional: Enable auditing with the audited gem (if loaded)
68
+ # Default: true
69
+ # Set to false if you don't want ExternalId records to be audited
70
+ # config.enable_auditing = true
66
71
  end
67
72
  ```
68
73
 
@@ -88,8 +93,8 @@ customer = Customer.find(123)
88
93
  # Option 1: Using keyword arguments
89
94
  customer.add_external_id(provider: 'raynet', id: 'R-12345')
90
95
 
91
- # Option 2: Using an ExternalIdValue object
92
- external_id = ExternalId::ExternalIdValue.new(provider: 'salesforce', id: 'SF-67890')
96
+ # Option 2: Using an ExternalId::Value object
97
+ external_id = ExternalId::Value.new(provider: 'salesforce', id: 'SF-67890')
93
98
  customer.add_external_id(external_id)
94
99
  ```
95
100
 
@@ -111,7 +116,7 @@ Customer.find_by_external_id('unknown', '123') # => ArgumentError
111
116
  ```ruby
112
117
  customer = Customer.find(123)
113
118
 
114
- # Returns an ExternalIdValue object
119
+ # Returns an ExternalId::Value object
115
120
  external_id = customer.external_id
116
121
 
117
122
  if external_id.present?
@@ -132,7 +137,7 @@ customer_without_eid.external_id.blank? # => true
132
137
  customer = Customer.find(123)
133
138
 
134
139
  # Access the external_id record directly
135
- customer.eid # => ExternalId::ExternalId instance or nil
140
+ customer.eid # => ExternalId::Record instance or nil
136
141
 
137
142
  # The association is dependent: :destroy
138
143
  # Deleting the customer will also delete the external_id
@@ -157,13 +162,13 @@ add_index :external_ids, [:provider, :resource_type, :resource_id],
157
162
 
158
163
  This ensures that each resource can only have one external ID per provider.
159
164
 
160
- ## ExternalIdValue
165
+ ## ExternalId::Value
161
166
 
162
- The `ExternalIdValue` class is a value object that provides a clean interface for working with external IDs:
167
+ The `ExternalId::Value` class is a value object that provides a clean interface for working with external IDs:
163
168
 
164
169
  ```ruby
165
170
  # Create a value object
166
- eid = ExternalId::ExternalIdValue.new(provider: 'raynet', id: '12345')
171
+ eid = ExternalId::Value.new(provider: 'raynet', id: '12345')
167
172
 
168
173
  # Check presence
169
174
  eid.present? # => true
@@ -179,14 +184,14 @@ eid.to_hash # => { provider: 'raynet', id: '12345' }
179
184
  eid.to_a # => ['raynet', '12345']
180
185
 
181
186
  # Create from array
182
- ExternalId::ExternalIdValue.from_array(['raynet', '12345'])
187
+ ExternalId::Value.from_array(['raynet', '12345'])
183
188
 
184
189
  # Create blank value
185
- ExternalId::ExternalIdValue.blank # => blank instance
190
+ ExternalId::Value.blank # => blank instance
186
191
 
187
192
  # Comparison
188
- eid1 = ExternalId::ExternalIdValue.new(provider: 'raynet', id: '123')
189
- eid2 = ExternalId::ExternalIdValue.new(provider: 'raynet', id: '123')
193
+ eid1 = ExternalId::Value.new(provider: 'raynet', id: '123')
194
+ eid2 = ExternalId::Value.new(provider: 'raynet', id: '123')
190
195
  eid1 == eid2 # => true
191
196
  ```
192
197
 
@@ -203,13 +208,13 @@ class ApplicationRecord < ActiveRecord::Base
203
208
  end
204
209
 
205
210
  # Add custom scopes
206
- class ExternalId::ExternalId
211
+ class ExternalId::Record
207
212
  scope :raynet, -> { where(provider: 'raynet') }
208
213
  scope :customers, -> { where(resource_type: 'Customer') }
209
214
  end
210
215
 
211
216
  # Use them
212
- ExternalId::ExternalId.raynet.customers
217
+ ExternalId::Record.raynet.customers
213
218
  ```
214
219
 
215
220
  ### Handling multiple providers
@@ -228,6 +233,47 @@ customer.add_external_id(provider: 'raynet', id: 'R-99999') # => ActiveRecord::
228
233
  # Note: Currently limited to one external_id per resource
229
234
  ```
230
235
 
236
+ ### Auditing with the audited gem
237
+
238
+ If you have the `audited` gem loaded in your application, the `ExternalId` model will automatically track all changes to external ID records. This includes tracking changes to:
239
+ - `provider` attribute
240
+ - `external_id` attribute
241
+ - `resource` polymorphic association
242
+
243
+ Auditing is enabled by default when the audited gem is present. You can disable it in your initializer:
244
+
245
+ ```ruby
246
+ ExternalId.configure do |config|
247
+ config.enable_auditing = false
248
+ end
249
+ ```
250
+
251
+ To access the audit history of an external ID record:
252
+
253
+ ```ruby
254
+ external_id_record = ExternalId::Record.find(some_id)
255
+
256
+ # Get all audits for this record
257
+ external_id_record.audits
258
+
259
+ # Get the last audit
260
+ external_id_record.audits.last
261
+
262
+ # Access audit details
263
+ audit = external_id_record.audits.last
264
+ audit.action # => "create", "update", or "destroy"
265
+ audit.audited_changes # => Hash of changed attributes
266
+ audit.created_at # => When the change occurred
267
+ audit.user # => The user who made the change (if set)
268
+ ```
269
+
270
+ Note: Make sure you've run the audited gem's generator to create the `audits` table:
271
+
272
+ ```bash
273
+ rails generate audited:install
274
+ rails db:migrate
275
+ ```
276
+
231
277
  ## Development
232
278
 
233
279
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -5,7 +5,7 @@ module ExternalId
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- has_one :eid, class_name: 'ExternalId::ExternalId', as: :resource, dependent: :destroy
8
+ has_one :eid, class_name: 'ExternalId::Record', as: :resource, dependent: :destroy
9
9
  end
10
10
 
11
11
  class_methods do
@@ -16,22 +16,22 @@ module ExternalId
16
16
  "Provider must be one of #{providers.keys}, was '#{provider}'"
17
17
  end
18
18
 
19
- ::ExternalId::ExternalId.find_by(provider: provider, external_id: external_id, resource_type: name)&.resource
19
+ ::ExternalId::Record.find_by(provider: provider, external_id: external_id, resource_type: name)&.resource
20
20
  end
21
21
  end
22
22
 
23
23
  def add_external_id(external_id_object = nil, provider: nil, id: nil)
24
24
  if external_id_object.blank? && provider.blank? && id.blank?
25
- raise 'Either ExternalIdValue or provider and id are required'
25
+ raise 'Either ExternalId::Value or provider and id are required'
26
26
  end
27
27
 
28
- eid = external_id_object.is_a?(::ExternalId::ExternalIdValue) ? external_id_object : ::ExternalId::ExternalIdValue.new(provider: provider, id: id)
28
+ eid = external_id_object.is_a?(::ExternalId::Value) ? external_id_object : ::ExternalId::Value.new(provider: provider, id: id)
29
29
 
30
- ::ExternalId::ExternalId.find_or_create_by!(provider: eid.provider, external_id: eid.id, resource: self)
30
+ ::ExternalId::Record.find_or_create_by!(provider: eid.provider, external_id: eid.id, resource: self)
31
31
  end
32
32
 
33
33
  def external_id
34
- ::ExternalId::ExternalIdValue.from_model(eid)
34
+ ::ExternalId::Value.from_model(eid)
35
35
  end
36
36
  end
37
37
  end
@@ -2,12 +2,13 @@
2
2
 
3
3
  module ExternalId
4
4
  class Configuration
5
- attr_accessor :providers, :base_class, :use_uuid
5
+ attr_accessor :providers, :base_class, :use_uuid, :enable_auditing
6
6
 
7
7
  def initialize
8
8
  @providers = {}
9
9
  @base_class = 'ActiveRecord::Base'
10
10
  @use_uuid = true
11
+ @enable_auditing = true
11
12
  end
12
13
 
13
14
  def providers=(value)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExternalId
4
- class ExternalId < ActiveRecord::Base
4
+ class Record < ActiveRecord::Base
5
5
  self.table_name = 'external_ids'
6
6
 
7
7
  belongs_to :resource, polymorphic: true
@@ -36,10 +36,10 @@ module ExternalId
36
36
  # Call setup when the class is loaded
37
37
  def self.inherited(subclass)
38
38
  super
39
- subclass.setup_enum! if subclass == ExternalId::ExternalId
39
+ subclass.setup_enum! if subclass == ExternalId::Record
40
40
  end
41
41
  end
42
42
  end
43
43
 
44
44
  # Setup enum after configuration
45
- ExternalId::ExternalId.setup_enum! if defined?(Rails) && Rails.application
45
+ ExternalId::Record.setup_enum! if defined?(Rails) && Rails.application
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExternalId
4
- # ExternalId value object
5
- class ExternalIdValue
4
+ # ID Value value object
5
+ class Value
6
6
  include ActiveModel::Model
7
7
  include ActiveModel::Attributes
8
8
 
@@ -48,7 +48,7 @@ module ExternalId
48
48
  end
49
49
 
50
50
  def ==(other)
51
- return false unless other.is_a?(ExternalIdValue)
51
+ return false unless other.is_a?(::ExternalId::Value)
52
52
 
53
53
  provider == other.provider && id == other.id
54
54
  end
@@ -6,6 +6,12 @@ module ExternalId
6
6
  ActiveSupport.on_load(:active_record) do
7
7
  # Make the concern available to all ActiveRecord models
8
8
  # Users will still need to explicitly include it
9
+
10
+ # Enable auditing if the audited gem is loaded and configured
11
+ # This runs after initializers so user configuration is available
12
+ if defined?(Audited) && ::ExternalId.configuration.enable_auditing
13
+ ::ExternalId::Record.audited
14
+ end
9
15
  end
10
16
  end
11
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExternalId
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/lib/external_id.rb CHANGED
@@ -5,8 +5,8 @@ require 'active_support'
5
5
 
6
6
  require_relative 'external_id/version'
7
7
  require_relative 'external_id/configuration'
8
- require_relative 'external_id/models/external_id_value'
9
- require_relative 'external_id/models/external_id'
8
+ require_relative 'external_id/models/value'
9
+ require_relative 'external_id/models/record'
10
10
  require_relative 'external_id/concerns/with_external_id'
11
11
 
12
12
  module ExternalId
@@ -11,24 +11,28 @@ Next steps:
11
11
  rails db:migrate
12
12
 
13
13
  3. Include the concern in your models:
14
-
14
+
15
15
  class Customer < ApplicationRecord
16
16
  include ExternalId::WithExternalId
17
17
  end
18
18
 
19
19
  4. Start using external IDs in your application:
20
-
20
+
21
21
  # Add an external ID to a record
22
22
  customer.add_external_id(provider: 'raynet', id: '12345')
23
-
23
+
24
24
  # Find a record by external ID
25
25
  customer = Customer.find_by_external_id('raynet', '12345')
26
-
26
+
27
27
  # Get the external ID value object
28
28
  external_id = customer.external_id
29
29
  external_id.provider # => 'raynet'
30
30
  external_id.id # => '12345'
31
31
 
32
+ 5. If FactoryBot was detected, a factory file was created in your
33
+ test factories directory. Customize the :external_id factory
34
+ to link to your models.
35
+
32
36
  For more information, see: https://github.com/yourusername/external-id
33
37
 
34
38
  ===============================================================================
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :external_id, class: 'ExternalId::Record' do
5
+ provider { 'default_provider' }
6
+ sequence(:external_id) { |n| "ext-#{n}" }
7
+ # association :resource, factory: :your_model
8
+ end
9
+ end
@@ -20,4 +20,9 @@ ExternalId.configure do |config|
20
20
  # Use UUID for primary keys (recommended for distributed systems)
21
21
  # Default: true
22
22
  # config.use_uuid = true
23
+
24
+ # Enable auditing with the audited gem (if loaded)
25
+ # Default: true
26
+ # Set to false if you don't want ExternalId records to be audited
27
+ # config.enable_auditing = true
23
28
  end
@@ -2,10 +2,10 @@
2
2
 
3
3
  class CreateExternalIds < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
4
  def change
5
- create_table :external_ids<%= ', id: :uuid' if ExternalId.configuration.use_uuid %> do |t|
5
+ create_table :external_ids<%= ', id: :uuid' if @use_uuid %> do |t|
6
6
  t.string :provider, null: false, index: true
7
7
  t.string :external_id, null: false
8
- t.references :resource, polymorphic: true, null: false, index: true<%= ', type: :uuid' if ExternalId.configuration.use_uuid %>
8
+ t.references :resource, polymorphic: true, null: false, index: true<%= ', type: :uuid' if @use_uuid %>
9
9
 
10
10
  t.timestamps
11
11
  end
@@ -16,8 +16,21 @@ module ExternalId
16
16
  template 'initializer.rb', 'config/initializers/external_id.rb'
17
17
  end
18
18
 
19
- def create_migration
20
- migration_template 'migration.rb.tt', File.join(db_migrate_path, 'create_external_ids.rb')
19
+ def copy_migration
20
+ @use_uuid = ExternalId.configuration.use_uuid rescue false
21
+ migration_template 'migration.rb.tt', 'db/migrate/create_external_ids.rb'
22
+ end
23
+
24
+ def copy_factory
25
+ return unless factory_bot_available?
26
+
27
+ test_dir = detect_test_directory
28
+ return unless test_dir
29
+
30
+ factories_dir = File.join(test_dir, 'factories')
31
+ empty_directory factories_dir unless File.directory?(factories_dir)
32
+
33
+ template 'external_ids_factory.rb', File.join(factories_dir, 'external_ids.rb')
21
34
  end
22
35
 
23
36
  def show_readme
@@ -26,13 +39,30 @@ module ExternalId
26
39
 
27
40
  private
28
41
 
29
- def db_migrate_path
30
- if defined?(Rails.application) && Rails.application.config.paths['db/migrate']
31
- Rails.application.config.paths['db/migrate'].to_a.first
32
- else
33
- 'db/migrate'
42
+ def factory_bot_available?
43
+ return @factory_bot_available if defined?(@factory_bot_available)
44
+
45
+ @factory_bot_available = begin
46
+ # Check Gemfile for factory_bot
47
+ gemfile_path = File.join(destination_root, 'Gemfile')
48
+ if File.exist?(gemfile_path)
49
+ gemfile_content = File.read(gemfile_path)
50
+ gemfile_content.match?(/factory_bot|factory_girl/)
51
+ else
52
+ false
53
+ end
34
54
  end
35
55
  end
56
+
57
+ def detect_test_directory
58
+ # Check common test directory names in order of preference
59
+ %w[spec test rspec].each do |dir|
60
+ path = File.join(destination_root, dir)
61
+ return dir if File.directory?(path)
62
+ end
63
+
64
+ nil
65
+ end
36
66
  end
37
67
  end
38
68
  end
data/sig/external_id.rbs CHANGED
@@ -45,16 +45,16 @@ module ExternalId
45
45
  end
46
46
 
47
47
  # Value object for external IDs
48
- class ExternalIdValue
48
+ class Value
49
49
  include ActiveModel::Model
50
50
  include ActiveModel::Attributes
51
51
 
52
52
  attr_accessor provider: String?
53
53
  attr_accessor id: String?
54
54
 
55
- def self.blank: () -> ExternalIdValue
56
- def self.from_model: (ExternalId?) -> ExternalIdValue
57
- def self.from_array: (Array[String?]?) -> ExternalIdValue
55
+ def self.blank: () -> Value
56
+ def self.from_model: (ExternalId?) -> Value
57
+ def self.from_array: (Array[String?]?) -> Value
58
58
 
59
59
  def initialize: (?provider: String? | Symbol?, ?id: String?) -> void
60
60
  def present?: () -> bool
@@ -98,8 +98,8 @@ module ExternalId
98
98
  # Instance methods available to including classes
99
99
  def eid: () -> ExternalId?
100
100
  def eid=: (ExternalId?) -> ExternalId?
101
- def add_external_id: (?ExternalIdValue?, ?provider: String? | Symbol?, ?id: String?) -> ExternalId
102
- def external_id: () -> ExternalIdValue
101
+ def add_external_id: (?Value?, ?provider: String? | Symbol?, ?id: String?) -> ExternalId
102
+ def external_id: () -> Value
103
103
 
104
104
  # Class methods added to including classes
105
105
  module ClassMethods
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: external_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomáš Landovský
@@ -143,11 +143,12 @@ files:
143
143
  - lib/external_id.rb
144
144
  - lib/external_id/concerns/with_external_id.rb
145
145
  - lib/external_id/configuration.rb
146
- - lib/external_id/models/external_id.rb
147
- - lib/external_id/models/external_id_value.rb
146
+ - lib/external_id/models/record.rb
147
+ - lib/external_id/models/value.rb
148
148
  - lib/external_id/railtie.rb
149
149
  - lib/external_id/version.rb
150
150
  - lib/generators/external_id/install/templates/README
151
+ - lib/generators/external_id/install/templates/external_ids_factory.rb
151
152
  - lib/generators/external_id/install/templates/initializer.rb
152
153
  - lib/generators/external_id/install/templates/migration.rb.tt
153
154
  - lib/generators/external_id/install_generator.rb