Skip to content

Commit a717583

Browse files
committed
19% reduction is string object allocations per request
Benchmark is here: https://github.com/tenderlove/ko1-test-app/blob/d4acd6aba86e3be3a16368296277bc973db8d1a7/perf.rake#L96-L109 I ran it like this: KO1TEST_PATH=/users/sign_in RAILS_ENV=production rake -f perf.rake allocated_objects It runs against a basic devise login page.
1 parent 54dde36 commit a717583

File tree

12 files changed

+68
-44
lines changed

12 files changed

+68
-44
lines changed

actionpack/lib/abstract_controller/helpers.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def helper_method(*meths)
6767
meths.each do |meth|
6868
_helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
6969
def #{meth}(*args, &blk) # def current_user(*args, &blk)
70-
controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
70+
controller.send(%(#{meth}).freeze, *args, &blk) # controller.send(:current_user, *args, &blk)
7171
end # end
7272
ruby_eval
7373
end

actionview/lib/action_view/helpers/active_model_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def object_has_errors?
4242
end
4343

4444
def tag_generate_errors?(options)
45-
options['type'] != 'hidden'
45+
options['type'.freeze] != 'hidden'.freeze
4646
end
4747
end
4848
end

actionview/lib/action_view/helpers/form_tag_helper.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,9 @@ def radio_button_tag(name, value, checked = false, options = {})
427427
def submit_tag(value = "Save changes", options = {})
428428
options = options.stringify_keys
429429

430-
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options)
430+
tag :input, { "type".freeze => "submit".freeze,
431+
"name".freeze => "commit".freeze,
432+
"value".freeze => value }.update(options)
431433
end
432434

433435
# Creates a button element that defines a <tt>submit</tt> button,

actionview/lib/action_view/helpers/tag_helper.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ module TagHelper
6565
# tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
6666
# # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
6767
def tag(name, options = nil, open = false, escape = true)
68-
"<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
68+
"<#{name}#{tag_options(options, escape) if options}#{open ? ">".freeze : " />".freeze}".html_safe
6969
end
7070

7171
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
@@ -141,7 +141,7 @@ def tag_options(options, escape = true)
141141
return if options.blank?
142142
attrs = []
143143
options.each_pair do |key, value|
144-
if key.to_s == 'data' && value.is_a?(Hash)
144+
if key.to_s == 'data'.freeze && value.is_a?(Hash)
145145
value.each_pair do |k, v|
146146
attrs << data_tag_option(k, v, escape)
147147
end
@@ -151,7 +151,7 @@ def tag_options(options, escape = true)
151151
attrs << tag_option(key, value, escape)
152152
end
153153
end
154-
" #{attrs.sort! * ' '}".html_safe unless attrs.empty?
154+
" #{attrs.sort! * ' '.freeze}".html_safe unless attrs.empty?
155155
end
156156

157157
def data_tag_option(key, value, escape)

actionview/lib/action_view/helpers/tags/base.rb

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,33 +58,39 @@ def retrieve_autoindex(pre_match)
5858
end
5959
end
6060

61+
NAME = 'name'.freeze
62+
ID = 'id'.freeze
63+
INDEX = 'index'.freeze
64+
MULTIPLE = 'multiple'.freeze
65+
NAMESPACE = 'namespace'.freeze
66+
6167
def add_default_name_and_id_for_value(tag_value, options)
6268
if tag_value.nil?
6369
add_default_name_and_id(options)
6470
else
65-
specified_id = options["id"]
71+
specified_id = options[ID]
6672
add_default_name_and_id(options)
6773

68-
if specified_id.blank? && options["id"].present?
69-
options["id"] += "_#{sanitized_value(tag_value)}"
74+
if specified_id.blank? && options[ID].present?
75+
options[ID] += "_#{sanitized_value(tag_value)}"
7076
end
7177
end
7278
end
7379

7480
def add_default_name_and_id(options)
75-
if options.has_key?("index")
76-
options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"], options["multiple"]) }
77-
options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
78-
options.delete("index")
81+
if options.has_key?(INDEX)
82+
options[NAME] ||= options.fetch(NAME){ tag_name_with_index(options[INDEX], options["multiple"]) }
83+
options[ID] = options.fetch(ID){ tag_id_with_index(options[INDEX]) }
84+
options.delete(INDEX)
7985
elsif defined?(@auto_index)
80-
options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index, options["multiple"]) }
81-
options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
86+
options[NAME] ||= options.fetch(NAME){ tag_name_with_index(@auto_index, options["multiple"]) }
87+
options[ID] = options.fetch(ID){ tag_id_with_index(@auto_index) }
8288
else
83-
options["name"] ||= options.fetch("name"){ tag_name(options["multiple"]) }
84-
options["id"] = options.fetch("id"){ tag_id }
89+
options[NAME] ||= options.fetch(NAME){ tag_name(options[MULTIPLE]) }
90+
options[ID] = options.fetch(ID){ tag_id }
8591
end
8692

87-
options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
93+
options[ID] = [options.delete(NAMESPACE), options[ID]].compact.join("_".freeze).presence
8894
end
8995

9096
def tag_name(multiple = false)
@@ -104,7 +110,7 @@ def tag_id_with_index(index)
104110
end
105111

106112
def sanitized_object_name
107-
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
113+
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_".freeze).sub(/_$/, "".freeze)
108114
end
109115

110116
def sanitized_method_name

actionview/lib/action_view/helpers/tags/check_box.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,17 @@ def checked?(value)
5656
end
5757

5858
def hidden_field_for_checkbox(options)
59-
@unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
59+
@unchecked_value ? input_tag(options, @unchecked_value) : "".freeze.html_safe
60+
end
61+
62+
def input_tag(options, unchecked_value)
63+
name = "input".freeze
64+
new_options = options.slice("name".freeze,
65+
"disabled".freeze,
66+
"form".freeze).merge!(
67+
"type".freeze => "hidden".freeze,
68+
"value".freeze => unchecked_value)
69+
tag(name, new_options)
6070
end
6171
end
6272
end

actionview/lib/action_view/helpers/tags/label.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,25 @@ def initialize(object_name, method_name, template_object, content_or_options = n
1616
super(object_name, method_name, template_object, options)
1717
end
1818

19+
FOR = 'for'.freeze
20+
ID = 'id'.freeze
21+
1922
def render(&block)
2023
options = @options.stringify_keys
21-
tag_value = options.delete("value")
24+
tag_value = options.delete("value".freeze)
2225
name_and_id = options.dup
2326

24-
if name_and_id["for"]
25-
name_and_id["id"] = name_and_id["for"]
27+
if name_and_id[FOR]
28+
name_and_id[ID] = name_and_id[FOR]
2629
else
27-
name_and_id.delete("id")
30+
name_and_id.delete(ID)
2831
end
2932

3033
add_default_name_and_id_for_value(tag_value, name_and_id)
31-
options.delete("index")
32-
options.delete("namespace")
33-
options.delete("multiple")
34-
options["for"] = name_and_id["id"] unless options.key?("for")
34+
options.delete("index".freeze)
35+
options.delete("namespace".freeze)
36+
options.delete("multiple".freeze)
37+
options[FOR] = name_and_id[ID] unless options.key?(FOR)
3538

3639
if block_given?
3740
content = @template_object.capture(&block)
@@ -42,11 +45,11 @@ def render(&block)
4245

4346
if object.respond_to?(:to_model)
4447
key = object.class.model_name.i18n_key
45-
i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
48+
i18n_default = ["#{key}.#{method_and_value}".to_sym, "".freeze]
4649
end
4750

48-
i18n_default ||= ""
49-
I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
51+
i18n_default ||= "".freeze
52+
I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label".freeze).presence
5053
else
5154
@content.to_s
5255
end
@@ -58,7 +61,7 @@ def render(&block)
5861
content ||= @method_name.humanize
5962
end
6063

61-
label_tag(name_and_id["id"], content, options)
64+
label_tag(name_and_id[ID], content, options)
6265
end
6366
end
6467
end

actionview/lib/action_view/helpers/tags/text_field.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@ module ActionView
22
module Helpers
33
module Tags # :nodoc:
44
class TextField < Base # :nodoc:
5+
SIZE = "size".freeze
6+
VALUE = "value".freeze
7+
58
def render
69
options = @options.stringify_keys
7-
options["size"] = options["maxlength"] unless options.key?("size")
8-
options["type"] ||= field_type
9-
options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
10-
options["value"] &&= ERB::Util.html_escape(options["value"])
10+
options[SIZE] = options["maxlength".freeze] unless options.key?(SIZE)
11+
options["type".freeze] ||= field_type
12+
options[VALUE] = options.fetch(VALUE) { value_before_type_cast(object) } unless field_type == "file".freeze
13+
options[VALUE] &&= ERB::Util.html_escape(options[VALUE])
1114
add_default_name_and_id(options)
12-
tag("input", options)
15+
tag("input".freeze, options)
1316
end
1417

1518
class << self
1619
def field_type
17-
@field_type ||= self.name.split("::").last.sub("Field", "").downcase
20+
@field_type ||= self.name.split("::".freeze).last.sub("Field".freeze, "".freeze).downcase
1821
end
1922
end
2023

actionview/lib/action_view/lookup_context.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def detail_args_for(options)
158158
# name instead of the prefix.
159159
def normalize_name(name, prefixes) #:nodoc:
160160
prefixes = prefixes.presence
161-
parts = name.to_s.split('/')
161+
parts = name.to_s.split('/'.freeze)
162162
parts.shift if parts.first.empty?
163163
name = parts.pop
164164

actionview/lib/action_view/template/handlers/erb.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def add_text(src, text)
1818
src << "@output_buffer.safe_append='"
1919
src << "\n" * @newline_pending if @newline_pending > 0
2020
src << escape_text(text)
21-
src << "';"
21+
src << "'.freeze;"
2222

2323
@newline_pending = 0
2424
end
@@ -67,7 +67,7 @@ def add_postamble(src)
6767

6868
def flush_newline_if_pending(src)
6969
if @newline_pending > 0
70-
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
70+
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
7171
@newline_pending = 0
7272
end
7373
end

activemodel/lib/active_model/translation.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ def lookup_ancestors
4242
# Specify +options+ with additional translating options.
4343
def human_attribute_name(attribute, options = {})
4444
options = { count: 1 }.merge!(options)
45-
parts = attribute.to_s.split(".")
45+
parts = attribute.to_s.split(".".freeze)
4646
attribute = parts.pop
47-
namespace = parts.join("/") unless parts.empty?
47+
namespace = parts.join("/".freeze) unless parts.empty?
4848
attributes_scope = "#{self.i18n_scope}.attributes"
4949

5050
if namespace

activesupport/lib/active_support/subscriber.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def finish(name, id, payload)
8787
event.end = finished
8888
event.payload.merge!(payload)
8989

90-
method = name.split('.').first
90+
method = name.split('.'.freeze, 2).first
9191
send(method, event)
9292
end
9393

0 commit comments

Comments
 (0)