Skip to content

Commit d70707d

Browse files
authored
Merge pull request rails#49020 from Shopify/allow-redefining-to-param-delimiter
Allow redefining `to_param` delimiter using `param_delimiter`
2 parents 59542e8 + 564a3d6 commit d70707d

File tree

6 files changed

+65
-3
lines changed

6 files changed

+65
-3
lines changed

activemodel/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
2+
3+
*Nikita Vasilevsky*
4+
15
* `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
26

37
*Nikita Vasilevsky*

activemodel/lib/active_model/conversion.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ module ActiveModel
2424
module Conversion
2525
extend ActiveSupport::Concern
2626

27+
included do
28+
##
29+
# :singleton-method:
30+
#
31+
# Accepts a string that will be used as a delimiter of object's key values in the `to_param` method.
32+
class_attribute :param_delimiter, instance_reader: false, default: "-"
33+
end
34+
2735
# If your object is already designed to implement all of the \Active \Model
2836
# you can use the default <tt>:to_model</tt> implementation, which simply
2937
# returns +self+.
@@ -80,7 +88,7 @@ def to_key
8088
# person = Person.new(1)
8189
# person.to_param # => "1"
8290
def to_param
83-
(persisted? && key = to_key) ? key.join("-") : nil
91+
(persisted? && key = to_key) ? key.join(self.class.param_delimiter) : nil
8492
end
8593

8694
# Returns a +string+ identifying the path associated with the object.

activemodel/test/cases/conversion_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,25 @@ def persisted?
5353
test "to_partial_path handles namespaced models" do
5454
assert_equal "helicopter/comanches/comanche", Helicopter::Comanche.new.to_partial_path
5555
end
56+
57+
test "#to_param_delimiter allows redefining the delimiter used in #to_param" do
58+
old_delimiter = Contact.param_delimiter
59+
Contact.param_delimiter = "_"
60+
assert_equal("abc_xyz", Contact.new(id: ["abc", "xyz"]).to_param)
61+
ensure
62+
Contact.param_delimiter = old_delimiter
63+
end
64+
65+
test "#to_param_delimiter is defined per class" do
66+
old_contact_delimiter = Contact.param_delimiter
67+
custom_contract = Class.new(Contact)
68+
69+
Contact.param_delimiter = "_"
70+
custom_contract.param_delimiter = ";"
71+
72+
assert_equal("abc_xyz", Contact.new(id: ["abc", "xyz"]).to_param)
73+
assert_equal("abc;xyz", custom_contract.new(id: ["abc", "xyz"]).to_param)
74+
ensure
75+
Contact.param_delimiter = old_contact_delimiter
76+
end
5677
end

activerecord/lib/active_record/base.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,8 @@ class Base
331331
include Suppressor
332332
include Normalization
333333
include Marshalling::Methods
334+
335+
self.param_delimiter = "_"
334336
end
335337

336338
ActiveSupport.run_load_hooks(:active_record, Base)

activerecord/lib/active_record/integration.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ module Integration
5555
# user = User.find_by(name: 'Phusion')
5656
# user_path(user) # => "/users/Phusion"
5757
def to_param
58-
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
59-
id && id.to_s # Be sure to stringify the id for routes
58+
return unless id
59+
Array(id).join(self.class.param_delimiter)
6060
end
6161

6262
# Returns a stable cache key that can be used to identify this record.

activerecord/test/cases/integration_test.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require "models/computer"
77
require "models/owner"
88
require "models/pet"
9+
require "models/cpk"
910

1011
class IntegrationTest < ActiveRecord::TestCase
1112
fixtures :companies, :developers, :owners, :pets
@@ -97,6 +98,32 @@ def test_to_param_with_no_arguments
9798
assert_equal "Firm", Firm.to_param
9899
end
99100

101+
def test_to_param_for_a_composite_primary_key_model
102+
assert_equal "1_123", Cpk::Order.new(id: [1, 123]).to_param
103+
end
104+
105+
def test_param_delimiter_changes_delimiter_used_in_to_param
106+
old_delimiter = Cpk::Order.param_delimiter
107+
Cpk::Order.param_delimiter = ","
108+
assert_equal("1,123", Cpk::Order.new(id: [1, 123]).to_param)
109+
ensure
110+
Cpk::Order.param_delimiter = old_delimiter
111+
end
112+
113+
def test_param_delimiter_is_defined_per_class
114+
old_order_delimiter = Cpk::Order.param_delimiter
115+
old_book_delimiter = Cpk::Book.param_delimiter
116+
117+
Cpk::Order.param_delimiter = ","
118+
Cpk::Book.param_delimiter = ";"
119+
120+
assert_equal("1,123", Cpk::Order.new(id: [1, 123]).to_param)
121+
assert_equal("1;123", Cpk::Book.new(id: [1, 123]).to_param)
122+
ensure
123+
Cpk::Order.param_delimiter = old_order_delimiter
124+
Cpk::Order.param_delimiter = old_book_delimiter
125+
end
126+
100127
def test_cache_key_for_existing_record_is_not_timezone_dependent
101128
utc_key = Developer.first.cache_key
102129

0 commit comments

Comments
 (0)