encoded_id-rails 1.0.0.rc1 → 1.0.0.rc6

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -18
  3. data/LICENSE.txt +1 -1
  4. data/README.md +76 -479
  5. data/context/encoded_id-rails.md +433 -0
  6. data/context/encoded_id.md +283 -0
  7. data/lib/encoded_id/rails/active_record_finders.rb +52 -0
  8. data/lib/encoded_id/rails/annotated_id.rb +8 -0
  9. data/lib/encoded_id/rails/annotated_id_parser.rb +8 -1
  10. data/lib/encoded_id/rails/coder.rb +20 -2
  11. data/lib/encoded_id/rails/configuration.rb +44 -4
  12. data/lib/encoded_id/rails/encoder_methods.rb +9 -1
  13. data/lib/encoded_id/rails/finder_methods.rb +10 -0
  14. data/lib/encoded_id/rails/model.rb +25 -2
  15. data/lib/encoded_id/rails/path_param.rb +7 -0
  16. data/lib/encoded_id/rails/persists.rb +52 -8
  17. data/lib/encoded_id/rails/query_methods.rb +20 -4
  18. data/lib/encoded_id/rails/railtie.rb +13 -0
  19. data/lib/encoded_id/rails/salt.rb +7 -0
  20. data/lib/encoded_id/rails/slugged_id.rb +8 -0
  21. data/lib/encoded_id/rails/slugged_id_parser.rb +8 -1
  22. data/lib/encoded_id/rails/slugged_path_param.rb +7 -0
  23. data/lib/encoded_id/rails.rb +9 -6
  24. data/lib/generators/encoded_id/rails/templates/encoded_id.rb +22 -2
  25. metadata +13 -23
  26. data/.devcontainer/Dockerfile +0 -17
  27. data/.devcontainer/compose.yml +0 -10
  28. data/.devcontainer/devcontainer.json +0 -12
  29. data/.standard.yml +0 -3
  30. data/Appraisals +0 -9
  31. data/Gemfile +0 -24
  32. data/Rakefile +0 -20
  33. data/Steepfile +0 -4
  34. data/gemfiles/.bundle/config +0 -2
  35. data/gemfiles/rails_7.2.gemfile +0 -19
  36. data/gemfiles/rails_8.0.gemfile +0 -19
  37. data/lib/encoded_id/rails/version.rb +0 -7
  38. data/rbs_collection.yaml +0 -24
  39. data/sig/encoded_id/rails.rbs +0 -141
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module EncodedId
6
+ module Rails
7
+ # This module overrides standard ActiveRecord finder methods to automatically decode encoded IDs.
8
+ # Important: This module should NOT be used with models that use string-based primary keys (e.g., UUIDs)
9
+ # as it will cause conflicts between string IDs and encoded IDs.
10
+ module ActiveRecordFinders
11
+ extend ActiveSupport::Concern
12
+
13
+ # @rbs!
14
+ # include ::ActiveRecord::FinderMethods
15
+ # extend ::ActiveRecord::QueryMethods
16
+
17
+ included do
18
+ if columns_hash["id"]&.type == :string
19
+ ::Rails.logger.warn("EncodedId::Rails::ActiveRecordFinders has been included in #{name}, but this model uses string-based IDs. This may cause conflicts with encoded ID handling.")
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ # @rbs (*untyped args) -> untyped
25
+ def find(*args)
26
+ return super unless args.size == 1 && args.first.is_a?(String)
27
+
28
+ decoded_ids = decode_encoded_id(args.first)
29
+
30
+ if decoded_ids.blank?
31
+ raise ::ActiveRecord::RecordNotFound
32
+ elsif decoded_ids.size == 1
33
+ super(decoded_ids.first)
34
+ else
35
+ super(decoded_ids)
36
+ end
37
+ end
38
+
39
+ # Override find_by_id to handle encoded IDs
40
+ # @rbs (untyped id) -> untyped
41
+ def find_by_id(id)
42
+ if id.is_a?(String)
43
+ decoded_ids = decode_encoded_id(id)
44
+ return nil if decoded_ids.blank?
45
+ return super(decoded_ids.first)
46
+ end
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,16 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  require "cgi"
4
6
 
5
7
  module EncodedId
6
8
  module Rails
7
9
  class AnnotatedId
10
+ # @rbs @annotation: String
11
+ # @rbs @id_part: String
12
+ # @rbs @separator: String
13
+
14
+ # @rbs (annotation: String, id_part: String, ?separator: String) -> void
8
15
  def initialize(annotation:, id_part:, separator: "_")
9
16
  @annotation = annotation
10
17
  @id_part = id_part
11
18
  @separator = separator
12
19
  end
13
20
 
21
+ # @rbs return: String
14
22
  def annotated_id
15
23
  unless @id_part.present? && @annotation.present?
16
24
  raise ::StandardError, "The model does not provide a valid ID and/or annotation"
@@ -1,8 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  class AnnotatedIdParser
8
+ # @rbs @annotation: String?
9
+ # @rbs @id: String
10
+
11
+ # @rbs (String annotated_id, ?separator: String) -> void
6
12
  def initialize(annotated_id, separator: "_")
7
13
  if separator && annotated_id.include?(separator)
8
14
  parts = annotated_id.split(separator)
@@ -13,7 +19,8 @@ module EncodedId
13
19
  end
14
20
  end
15
21
 
16
- attr_reader :annotation, :id
22
+ attr_reader :annotation #: String?
23
+ attr_reader :id #: String
17
24
  end
18
25
  end
19
26
  end
@@ -1,20 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  class Coder
6
- def initialize(salt:, id_length:, character_group_size:, separator:, alphabet:)
8
+ # @rbs @salt: String
9
+ # @rbs @id_length: Integer
10
+ # @rbs @character_group_size: Integer
11
+ # @rbs @separator: String
12
+ # @rbs @alphabet: ::EncodedId::Alphabet
13
+ # @rbs @encoder: (Symbol | ::EncodedId::Encoders::Base)
14
+ # @rbs @blocklist: ::EncodedId::Blocklist?
15
+
16
+ # @rbs (salt: String, id_length: Integer, character_group_size: Integer, separator: String, alphabet: ::EncodedId::Alphabet, ?encoder: Symbol?, ?blocklist: ::EncodedId::Blocklist?) -> void
17
+ def initialize(salt:, id_length:, character_group_size:, separator:, alphabet:, encoder: nil, blocklist: nil)
7
18
  @salt = salt
8
19
  @id_length = id_length
9
20
  @character_group_size = character_group_size
10
21
  @separator = separator
11
22
  @alphabet = alphabet
23
+ @encoder = encoder || EncodedId::Rails.configuration.encoder
24
+ @blocklist = blocklist || EncodedId::Rails.configuration.blocklist
12
25
  end
13
26
 
27
+ # @rbs (Integer | Array[Integer] id) -> String
14
28
  def encode(id)
15
29
  coder.encode(id)
16
30
  end
17
31
 
32
+ # @rbs (String encoded_id) -> Array[Integer]?
18
33
  def decode(encoded_id)
19
34
  coder.decode(encoded_id)
20
35
  rescue EncodedId::EncodedIdFormatError, EncodedId::InvalidInputError
@@ -23,13 +38,16 @@ module EncodedId
23
38
 
24
39
  private
25
40
 
41
+ # @rbs return: ::EncodedId::ReversibleId
26
42
  def coder
27
43
  ::EncodedId::ReversibleId.new(
28
44
  salt: @salt,
29
45
  length: @id_length,
30
46
  split_at: @character_group_size,
31
47
  split_with: @separator,
32
- alphabet: @alphabet
48
+ alphabet: @alphabet,
49
+ encoder: @encoder,
50
+ blocklist: @blocklist
33
51
  )
34
52
  end
35
53
  end
@@ -1,14 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  # Configuration class for initializer
6
8
  class Configuration
7
- attr_accessor :salt, :character_group_size, :alphabet, :id_length
8
- attr_accessor :slug_value_method_name, :annotation_method_name
9
- attr_accessor :model_to_param_returns_encoded_id
10
- attr_reader :group_separator, :slugged_id_separator, :annotated_id_separator
9
+ # @rbs @salt: String
10
+ # @rbs @character_group_size: Integer
11
+ # @rbs @alphabet: ::EncodedId::Alphabet
12
+ # @rbs @id_length: Integer
13
+ # @rbs @slug_value_method_name: Symbol
14
+ # @rbs @annotation_method_name: Symbol
15
+ # @rbs @model_to_param_returns_encoded_id: bool
16
+ # @rbs @blocklist: ::EncodedId::Blocklist
17
+ # @rbs @group_separator: String
18
+ # @rbs @slugged_id_separator: String
19
+ # @rbs @annotated_id_separator: String
20
+ # @rbs @encoder: Symbol
21
+
22
+ attr_accessor :salt #: String
23
+ attr_accessor :character_group_size #: Integer
24
+ attr_accessor :alphabet #: ::EncodedId::Alphabet
25
+ attr_accessor :id_length #: Integer
26
+ attr_accessor :slug_value_method_name #: Symbol
27
+ attr_accessor :annotation_method_name #: Symbol
28
+ attr_accessor :model_to_param_returns_encoded_id #: bool
29
+ attr_accessor :blocklist #: ::EncodedId::Blocklist
30
+ attr_reader :group_separator #: String
31
+ attr_reader :slugged_id_separator #: String
32
+ attr_reader :annotated_id_separator #: String
33
+ attr_reader :encoder #: Symbol
11
34
 
35
+ # @rbs () -> void
12
36
  def initialize
13
37
  @character_group_size = 4
14
38
  @group_separator = "-"
@@ -19,10 +43,21 @@ module EncodedId
19
43
  @annotation_method_name = :annotation_for_encoded_id
20
44
  @annotated_id_separator = "_"
21
45
  @model_to_param_returns_encoded_id = false
46
+ @encoder = :hashids
47
+ @blocklist = ::EncodedId::Blocklist.empty
48
+ end
49
+
50
+ # @rbs (Symbol value) -> Symbol
51
+ def encoder=(value)
52
+ unless ::EncodedId::ReversibleId::VALID_ENCODERS.include?(value)
53
+ raise ArgumentError, "Encoder must be one of: #{::EncodedId::ReversibleId::VALID_ENCODERS.join(", ")}"
54
+ end
55
+ @encoder = value
22
56
  end
23
57
 
24
58
  # Perform validation vs alphabet on these assignments
25
59
 
60
+ # @rbs (String value) -> String
26
61
  def group_separator=(value)
27
62
  unless valid_separator?(value, alphabet)
28
63
  raise ArgumentError, "Group separator characters must not be part of the alphabet"
@@ -30,6 +65,7 @@ module EncodedId
30
65
  @group_separator = value
31
66
  end
32
67
 
68
+ # @rbs (String value) -> String
33
69
  def slugged_id_separator=(value)
34
70
  if value.blank? || value == group_separator || !valid_separator?(value, alphabet)
35
71
  raise ArgumentError, "Slugged ID separator characters must not be part of the alphabet or the same as the group separator"
@@ -37,6 +73,7 @@ module EncodedId
37
73
  @slugged_id_separator = value
38
74
  end
39
75
 
76
+ # @rbs (String value) -> String
40
77
  def annotated_id_separator=(value)
41
78
  if value.blank? || value == group_separator || !valid_separator?(value, alphabet)
42
79
  raise ArgumentError, "Annotated ID separator characters must not be part of the alphabet or the same as the group separator"
@@ -44,6 +81,9 @@ module EncodedId
44
81
  @annotated_id_separator = value
45
82
  end
46
83
 
84
+ private
85
+
86
+ # @rbs (String separator, ::EncodedId::Alphabet characters) -> bool
47
87
  def valid_separator?(separator, characters)
48
88
  separator.chars.none? { |v| characters.include?(v) }
49
89
  end
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  module EncoderMethods
8
+ # @rbs (Array[Integer] | Integer ids, ?Hash[Symbol, untyped] options) -> String
6
9
  def encode_encoded_id(ids, options = {})
7
10
  raise StandardError, "You must pass an ID or array of IDs" if ids.blank?
8
11
  encoded_id_coder(options).encode(ids)
9
12
  end
10
13
 
14
+ # @rbs (String slugged_encoded_id, ?Hash[Symbol, untyped] options) -> Array[Integer]?
11
15
  def decode_encoded_id(slugged_encoded_id, options = {})
12
16
  return if slugged_encoded_id.blank?
13
17
  raise StandardError, "You must pass a string encoded ID" unless slugged_encoded_id.is_a?(String)
@@ -18,11 +22,13 @@ module EncodedId
18
22
  end
19
23
 
20
24
  # This can be overridden in the model to provide a custom salt
25
+ # @rbs return: String
21
26
  def encoded_id_salt
22
27
  # @type self: Class
23
28
  EncodedId::Rails::Salt.new(self, EncodedId::Rails.configuration.salt).generate!
24
29
  end
25
30
 
31
+ # @rbs (?Hash[Symbol, untyped] options) -> EncodedId::Rails::Coder
26
32
  def encoded_id_coder(options = {})
27
33
  config = EncodedId::Rails.configuration
28
34
  EncodedId::Rails::Coder.new(
@@ -30,7 +36,9 @@ module EncodedId
30
36
  id_length: options[:id_length] || config.id_length,
31
37
  character_group_size: options.key?(:character_group_size) ? options[:character_group_size] : config.character_group_size,
32
38
  alphabet: options[:alphabet] || config.alphabet,
33
- separator: options.key?(:separator) ? options[:separator] : config.group_separator
39
+ separator: options.key?(:separator) ? options[:separator] : config.group_separator,
40
+ encoder: options[:encoder] || config.encoder,
41
+ blocklist: options[:blocklist] || config.blocklist
34
42
  )
35
43
  end
36
44
  end
@@ -1,9 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  module FinderMethods
8
+ # @rbs!
9
+ # include ::EncodedId::Rails::EncoderMethods
10
+ # include ActiveRecordFinders
11
+
6
12
  # Find by encoded ID and optionally ensure record ID is the same as constraint (can be slugged)
13
+ # @rbs (String encoded_id, ?with_id: Symbol?) -> untyped
7
14
  def find_by_encoded_id(encoded_id, with_id: nil)
8
15
  decoded_id = decode_encoded_id(encoded_id)
9
16
  return if decoded_id.nil? || decoded_id.blank?
@@ -13,6 +20,7 @@ module EncodedId
13
20
  record
14
21
  end
15
22
 
23
+ # @rbs (String encoded_id, ?with_id: Symbol?) -> untyped
16
24
  def find_by_encoded_id!(encoded_id, with_id: nil)
17
25
  decoded_id = decode_encoded_id(encoded_id)
18
26
  raise ActiveRecord::RecordNotFound if decoded_id.nil? || decoded_id.blank?
@@ -23,12 +31,14 @@ module EncodedId
23
31
  record
24
32
  end
25
33
 
34
+ # @rbs (String encoded_id) -> Array[untyped]?
26
35
  def find_all_by_encoded_id(encoded_id)
27
36
  decoded_ids = decode_encoded_id(encoded_id)
28
37
  return if decoded_ids.blank?
29
38
  where(id: decoded_ids).to_a
30
39
  end
31
40
 
41
+ # @rbs (String encoded_id) -> Array[untyped]
32
42
  def find_all_by_encoded_id!(encoded_id)
33
43
  decoded_ids = decode_encoded_id(encoded_id)
34
44
  raise ActiveRecord::RecordNotFound if decoded_ids.nil? || decoded_ids.blank?
@@ -1,11 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  require "active_record"
4
6
  require "encoded_id"
5
7
 
6
8
  module EncodedId
7
9
  module Rails
8
10
  module Model
11
+ # @rbs!
12
+ # extend ::EncodedId::Rails::EncoderMethods
13
+ # extend ::EncodedId::Rails::FinderMethods
14
+ # extend ::EncodedId::Rails::QueryMethods
15
+ #
16
+ #
17
+ # # Model attributes that must exist, plus related AR methods
18
+ # def id: () -> Integer?
19
+
20
+ # @rbs @encoded_id_hash: String?
21
+ # @rbs @encoded_id: String?
22
+ # @rbs @slugged_encoded_id: String?
23
+ # @rbs @encoded_id_memoized_with_id: untyped
24
+
25
+ # @rbs (untyped base) -> void
9
26
  def self.included(base)
10
27
  base.extend(EncoderMethods)
11
28
  base.extend(FinderMethods)
@@ -17,8 +34,9 @@ module EncodedId
17
34
  end
18
35
  end
19
36
 
20
- attr_accessor :encoded_id_memoized_with_id
37
+ attr_accessor :encoded_id_memoized_with_id #: untyped
21
38
 
39
+ # @rbs () -> void
22
40
  def clear_encoded_id_cache!
23
41
  [:@encoded_id_hash, :@encoded_id, :@slugged_encoded_id].each do |var|
24
42
  remove_instance_variable(var) if instance_variable_defined?(var)
@@ -26,10 +44,12 @@ module EncodedId
26
44
  self.encoded_id_memoized_with_id = nil
27
45
  end
28
46
 
47
+ # @rbs () -> void
29
48
  def check_and_clear_memoization
30
49
  clear_encoded_id_cache! if encoded_id_memoized_with_id && encoded_id_memoized_with_id != id
31
50
  end
32
51
 
52
+ # @rbs () -> String?
33
53
  def encoded_id_hash
34
54
  return unless id
35
55
  check_and_clear_memoization
@@ -39,6 +59,7 @@ module EncodedId
39
59
  @encoded_id_hash = self.class.encode_encoded_id(id)
40
60
  end
41
61
 
62
+ # @rbs () -> String?
42
63
  def encoded_id
43
64
  return unless id
44
65
  check_and_clear_memoization
@@ -53,6 +74,7 @@ module EncodedId
53
74
  @encoded_id = EncodedId::Rails::AnnotatedId.new(id_part: encoded, annotation: send(annotated_by.to_s), separator: separator).annotated_id
54
75
  end
55
76
 
77
+ # @rbs () -> String?
56
78
  def slugged_encoded_id
57
79
  return unless id
58
80
  check_and_clear_memoization
@@ -67,6 +89,7 @@ module EncodedId
67
89
  @slugged_encoded_id = EncodedId::Rails::SluggedId.new(id_part: encoded, slug_part: send(with.to_s), separator: separator).slugged_id
68
90
  end
69
91
 
92
+ # @rbs (?untyped? options) -> untyped
70
93
  def reload(options = nil)
71
94
  result = super
72
95
  clear_encoded_id_cache!
@@ -80,7 +103,7 @@ module EncodedId
80
103
  name.underscore
81
104
  end
82
105
 
83
- # By default trying to generate a slug without defining how will raise.
106
+ # By default, trying to generate a slug without defining how will raise.
84
107
  # You either override this method per model, pass an alternate method name to
85
108
  # #slugged_encoded_id or setup an alias to another model method in your ApplicationRecord class
86
109
  def name_for_encoded_id_slug
@@ -1,11 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  require "active_record"
4
6
  require "encoded_id"
5
7
 
6
8
  module EncodedId
7
9
  module Rails
8
10
  module PathParam
11
+ # Method provided by model
12
+ # @rbs!
13
+ # def encoded_id: () -> String?
14
+
15
+ # @rbs () -> String
9
16
  def to_param
10
17
  encoded_id || raise(StandardError, "Cannot create path param for #{self.class.name} without an encoded id")
11
18
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  module Persists
8
+ # @rbs (untyped base) -> void
6
9
  def self.included(base)
7
10
  base.extend ClassMethods
8
11
 
@@ -19,13 +22,38 @@ module EncodedId
19
22
  end
20
23
 
21
24
  module ClassMethods
25
+ # Encoder methods come from ::EncodedId::Rails::Model but thats not working with this pattern of defining class
26
+ # methods.
27
+ # @rbs!
28
+ # include ::EncodedId::Rails::EncoderMethods
29
+
30
+ # @rbs (Integer id) -> String
22
31
  def encode_normalized_encoded_id(id)
23
32
  encode_encoded_id(id, character_group_size: nil)
24
33
  end
25
34
  end
26
35
 
36
+ # Method provided by model
37
+ # @rbs!
38
+ # include ::ActiveRecord::Persistence
39
+ #
40
+ # include ::EncodedId::Rails::Model
41
+ #
42
+ # extend ClassMethods
43
+ #
44
+ # # Model attributes that must exist, plus related AR methods
45
+ # def id: () -> Integer?
46
+ # def id_changed?: () -> bool
47
+ # def prefixed_encoded_id: () -> String?
48
+ # def prefixed_encoded_id=: (String?) -> String?
49
+ # def normalized_encoded_id: () -> String?
50
+ # def normalized_encoded_id=: (String?) -> String?
51
+ # def clear_prefixed_encoded_id_change: () -> void
52
+ # def clear_normalized_encoded_id_change: () -> void
53
+
27
54
  # On duplication we need to reset the encoded ID to nil as this new record will have a new ID.
28
55
  # We need to also prevent these changes from marking the record as dirty.
56
+ # @rbs () -> untyped
29
57
  def dup
30
58
  copy = super
31
59
  copy.prefixed_encoded_id = nil
@@ -35,39 +63,55 @@ module EncodedId
35
63
  copy
36
64
  end
37
65
 
38
- def set_normalized_encoded_id!
66
+ # @rbs () -> Integer
67
+ def resolved_id
39
68
  validate_id_for_encoded_id!
40
69
 
41
- update_columns(normalized_encoded_id: self.class.encode_normalized_encoded_id(id), prefixed_encoded_id: encoded_id)
70
+ id #: Integer
42
71
  end
43
72
 
44
- def update_normalized_encoded_id!
45
- validate_id_for_encoded_id!
73
+ # @rbs () -> void
74
+ def set_normalized_encoded_id!
75
+ update_columns(normalized_encoded_id: self.class.encode_normalized_encoded_id(resolved_id), prefixed_encoded_id: encoded_id)
76
+ end
46
77
 
47
- self.normalized_encoded_id = self.class.encode_normalized_encoded_id(id)
78
+ # @rbs () -> void
79
+ def update_normalized_encoded_id!
80
+ self.normalized_encoded_id = self.class.encode_normalized_encoded_id(resolved_id)
48
81
  self.prefixed_encoded_id = encoded_id
49
82
  end
50
83
 
84
+ # @rbs () -> void
51
85
  def check_encoded_id_persisted!
52
- if normalized_encoded_id != self.class.encode_normalized_encoded_id(id)
53
- raise StandardError, "The persisted encoded ID #{normalized_encoded_id} for #{self.class.name} is not the same as currently computing #{self.class.encode_normalized_encoded_id(id)}"
86
+ validate_id_for_encoded_id!
87
+
88
+ encoded_from_current_id = self.class.encode_normalized_encoded_id(resolved_id)
89
+
90
+ if normalized_encoded_id != encoded_from_current_id
91
+ raise StandardError, "The persisted encoded ID #{normalized_encoded_id} for #{self.class.name} is not the same as currently computing #{encoded_from_current_id}"
54
92
  end
55
93
 
56
- raise StandardError, "The persisted prefixed encoded ID (for #{self.class.name} with id: #{id}, normalized_encoded_id: #{normalized_encoded_id}) is not correct: it is #{prefixed_encoded_id} instead of #{encoded_id}" if prefixed_encoded_id != encoded_id
94
+ return if prefixed_encoded_id == encoded_id
95
+
96
+ raise StandardError, "The persisted prefixed encoded ID (for #{self.class.name} with id: #{id}, normalized_encoded_id: #{normalized_encoded_id}) is not correct: it is #{prefixed_encoded_id} instead of #{encoded_id}"
57
97
  end
58
98
 
99
+ # @rbs () -> bool
59
100
  def should_update_normalized_encoded_id?
60
101
  id_changed? || (normalized_encoded_id.blank? && persisted?)
61
102
  end
62
103
 
104
+ # @rbs () -> void
63
105
  def validate_id_for_encoded_id!
64
106
  raise StandardError, "You cannot set the normalized ID of a record which is not persisted" if id.blank?
65
107
  end
66
108
 
109
+ # @rbs () -> void
67
110
  def prevent_update_of_normalized_encoded_id!
68
111
  raise ActiveRecord::ReadonlyAttributeError, "You cannot update the normalized encoded ID '#{normalized_encoded_id}' of a record #{self.class.name} #{id}, if you need to refresh it use set_normalized_encoded_id!"
69
112
  end
70
113
 
114
+ # @rbs () -> void
71
115
  def prevent_update_of_prefixed_encoded_id!
72
116
  raise ActiveRecord::ReadonlyAttributeError, "You cannot update the prefixed encoded ID '#{prefixed_encoded_id}' of a record #{self.class.name} #{id}, if you need to refresh it use set_normalized_encoded_id!"
73
117
  end
@@ -1,12 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  module QueryMethods
6
- def where_encoded_id(slugged_encoded_id)
7
- decoded_id = decode_encoded_id(slugged_encoded_id)
8
- raise ActiveRecord::RecordNotFound if decoded_id.nil?
9
- where(id: decoded_id)
8
+ # Methods provided by other mixins/ActiveRecord
9
+ # @rbs!
10
+ # def decode_encoded_id: (String) -> Array[Integer]?
11
+ # def where: (**untyped) -> untyped
12
+
13
+ # @rbs (*String slugged_encoded_ids) -> untyped
14
+ def where_encoded_id(*slugged_encoded_ids)
15
+ slugged_encoded_ids = slugged_encoded_ids.flatten
16
+
17
+ raise ::ActiveRecord::RecordNotFound if slugged_encoded_ids.empty?
18
+
19
+ decoded_ids = slugged_encoded_ids.flat_map do |slugged_encoded_id|
20
+ decode_encoded_id(slugged_encoded_id).tap do |decoded_id|
21
+ raise ::ActiveRecord::RecordNotFound if decoded_id.nil?
22
+ end
23
+ end
24
+
25
+ where(id: decoded_ids)
10
26
  end
11
27
  end
12
28
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module EncodedId
6
+ module Rails
7
+ # Railtie for Rails integration
8
+ class Railtie < ::Rails::Railtie
9
+ # initializer "encoded_id.rails.initialize" do
10
+ # end
11
+ end
12
+ end
13
+ end
@@ -1,13 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  class Salt
8
+ # @rbs @klass: Class
9
+ # @rbs @salt: String
10
+
11
+ # @rbs (Class klass, String salt) -> void
6
12
  def initialize(klass, salt)
7
13
  @klass = klass
8
14
  @salt = salt
9
15
  end
10
16
 
17
+ # @rbs return: String
11
18
  def generate!
12
19
  unless @klass.respond_to?(:name) && @klass.name.present?
13
20
  raise ::StandardError, "The class must have a `name` to ensure encode id uniqueness. " \
@@ -1,16 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  require "cgi"
4
6
 
5
7
  module EncodedId
6
8
  module Rails
7
9
  class SluggedId
10
+ # @rbs @slug_part: String
11
+ # @rbs @id_part: String
12
+ # @rbs @separator: String
13
+
14
+ # @rbs (slug_part: String, id_part: String, ?separator: String) -> void
8
15
  def initialize(slug_part:, id_part:, separator: "--")
9
16
  @slug_part = slug_part
10
17
  @id_part = id_part
11
18
  @separator = separator
12
19
  end
13
20
 
21
+ # @rbs return: String
14
22
  def slugged_id
15
23
  unless @id_part.present? && @slug_part.present?
16
24
  raise ::StandardError, "The model does not return a valid ID and/or slug"
@@ -1,8 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rbs_inline: enabled
4
+
3
5
  module EncodedId
4
6
  module Rails
5
7
  class SluggedIdParser
8
+ # @rbs @slug: String?
9
+ # @rbs @id: String
10
+
11
+ # @rbs (String slugged_id, ?separator: String) -> void
6
12
  def initialize(slugged_id, separator: "--")
7
13
  if separator && slugged_id.include?(separator)
8
14
  parts = slugged_id.split(separator)
@@ -13,7 +19,8 @@ module EncodedId
13
19
  end
14
20
  end
15
21
 
16
- attr_reader :slug, :id
22
+ attr_reader :slug #: String?
23
+ attr_reader :id #: String
17
24
  end
18
25
  end
19
26
  end