actionview 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +274 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +34 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +205 -0
  7. data/lib/action_view/buffers.rb +49 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +93 -0
  10. data/lib/action_view/digestor.rb +116 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/helpers.rb +64 -0
  13. data/lib/action_view/helpers/active_model_helper.rb +49 -0
  14. data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
  15. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  16. data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
  17. data/lib/action_view/helpers/cache_helper.rb +200 -0
  18. data/lib/action_view/helpers/capture_helper.rb +216 -0
  19. data/lib/action_view/helpers/controller_helper.rb +25 -0
  20. data/lib/action_view/helpers/csrf_helper.rb +30 -0
  21. data/lib/action_view/helpers/date_helper.rb +1075 -0
  22. data/lib/action_view/helpers/debug_helper.rb +39 -0
  23. data/lib/action_view/helpers/form_helper.rb +1876 -0
  24. data/lib/action_view/helpers/form_options_helper.rb +843 -0
  25. data/lib/action_view/helpers/form_tag_helper.rb +746 -0
  26. data/lib/action_view/helpers/javascript_helper.rb +75 -0
  27. data/lib/action_view/helpers/number_helper.rb +425 -0
  28. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  29. data/lib/action_view/helpers/record_tag_helper.rb +108 -0
  30. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  31. data/lib/action_view/helpers/sanitize_helper.rb +256 -0
  32. data/lib/action_view/helpers/tag_helper.rb +176 -0
  33. data/lib/action_view/helpers/tags.rb +41 -0
  34. data/lib/action_view/helpers/tags/base.rb +148 -0
  35. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  36. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  37. data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
  38. data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  41. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  42. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  43. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  44. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  45. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  46. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  47. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  48. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  50. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  51. data/lib/action_view/helpers/tags/label.rb +65 -0
  52. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  53. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  54. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  56. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  58. data/lib/action_view/helpers/tags/select.rb +41 -0
  59. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  61. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  62. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  63. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  65. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  66. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  67. data/lib/action_view/helpers/text_helper.rb +447 -0
  68. data/lib/action_view/helpers/translation_helper.rb +111 -0
  69. data/lib/action_view/helpers/url_helper.rb +625 -0
  70. data/lib/action_view/layouts.rb +426 -0
  71. data/lib/action_view/locale/en.yml +56 -0
  72. data/lib/action_view/log_subscriber.rb +44 -0
  73. data/lib/action_view/lookup_context.rb +249 -0
  74. data/lib/action_view/model_naming.rb +12 -0
  75. data/lib/action_view/path_set.rb +77 -0
  76. data/lib/action_view/railtie.rb +49 -0
  77. data/lib/action_view/record_identifier.rb +84 -0
  78. data/lib/action_view/renderer/abstract_renderer.rb +47 -0
  79. data/lib/action_view/renderer/partial_renderer.rb +492 -0
  80. data/lib/action_view/renderer/renderer.rb +50 -0
  81. data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
  82. data/lib/action_view/renderer/template_renderer.rb +96 -0
  83. data/lib/action_view/rendering.rb +145 -0
  84. data/lib/action_view/routing_url_for.rb +109 -0
  85. data/lib/action_view/tasks/dependencies.rake +17 -0
  86. data/lib/action_view/template.rb +340 -0
  87. data/lib/action_view/template/error.rb +141 -0
  88. data/lib/action_view/template/handlers.rb +53 -0
  89. data/lib/action_view/template/handlers/builder.rb +26 -0
  90. data/lib/action_view/template/handlers/erb.rb +145 -0
  91. data/lib/action_view/template/handlers/raw.rb +11 -0
  92. data/lib/action_view/template/resolver.rb +329 -0
  93. data/lib/action_view/template/text.rb +34 -0
  94. data/lib/action_view/template/types.rb +57 -0
  95. data/lib/action_view/test_case.rb +272 -0
  96. data/lib/action_view/testing/resolvers.rb +50 -0
  97. data/lib/action_view/vendor/html-scanner.rb +20 -0
  98. data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
  99. data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
  100. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
  101. data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
  102. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
  103. data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
  104. data/lib/action_view/version.rb +11 -0
  105. data/lib/action_view/view_paths.rb +96 -0
  106. metadata +218 -0
@@ -0,0 +1,44 @@
1
+ require 'active_support/log_subscriber'
2
+
3
+ module ActionView
4
+ # = Action View Log Subscriber
5
+ #
6
+ # Provides functionality so that Rails can output logs from Action View.
7
+ class LogSubscriber < ActiveSupport::LogSubscriber
8
+ VIEWS_PATTERN = /^app\/views\//
9
+
10
+ def initialize
11
+ @root = nil
12
+ super
13
+ end
14
+
15
+ def render_template(event)
16
+ return unless logger.info?
17
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}"
18
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
19
+ message << " (#{event.duration.round(1)}ms)"
20
+ info(message)
21
+ end
22
+ alias :render_partial :render_template
23
+ alias :render_collection :render_template
24
+
25
+ def logger
26
+ ActionView::Base.logger
27
+ end
28
+
29
+ protected
30
+
31
+ EMPTY = ''
32
+ def from_rails_root(string)
33
+ string = string.sub(rails_root, EMPTY)
34
+ string.sub!(VIEWS_PATTERN, EMPTY)
35
+ string
36
+ end
37
+
38
+ def rails_root
39
+ @root ||= "#{Rails.root}/"
40
+ end
41
+ end
42
+ end
43
+
44
+ ActionView::LogSubscriber.attach_to :action_view
@@ -0,0 +1,249 @@
1
+ require 'thread_safe'
2
+ require 'active_support/core_ext/module/remove_method'
3
+ require 'active_support/core_ext/module/attribute_accessors'
4
+
5
+ module ActionView
6
+ # = Action View Lookup Context
7
+ #
8
+ # LookupContext is the object responsible to hold all information required to lookup
9
+ # templates, i.e. view paths and details. The LookupContext is also responsible to
10
+ # generate a key, given to view paths, used in the resolver cache lookup. Since
11
+ # this key is generated just once during the request, it speeds up all cache accesses.
12
+ class LookupContext #:nodoc:
13
+ attr_accessor :prefixes, :rendered_format
14
+
15
+ mattr_accessor :fallbacks
16
+ @@fallbacks = FallbackFileSystemResolver.instances
17
+
18
+ mattr_accessor :registered_details
19
+ self.registered_details = []
20
+
21
+ def self.register_detail(name, options = {}, &block)
22
+ self.registered_details << name
23
+ initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
24
+
25
+ Accessors.send :define_method, :"default_#{name}", &block
26
+ Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
27
+ def #{name}
28
+ @details.fetch(:#{name}, [])
29
+ end
30
+
31
+ def #{name}=(value)
32
+ value = value.present? ? Array(value) : default_#{name}
33
+ _set_detail(:#{name}, value) if value != @details[:#{name}]
34
+ end
35
+
36
+ remove_possible_method :initialize_details
37
+ def initialize_details(details)
38
+ #{initialize.join("\n")}
39
+ end
40
+ METHOD
41
+ end
42
+
43
+ # Holds accessors for the registered details.
44
+ module Accessors #:nodoc:
45
+ end
46
+
47
+ register_detail(:locale) do
48
+ locales = [I18n.locale]
49
+ locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
50
+ locales << I18n.default_locale
51
+ locales.uniq!
52
+ locales
53
+ end
54
+ register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] }
55
+ register_detail(:variants) { [] }
56
+ register_detail(:handlers){ Template::Handlers.extensions }
57
+
58
+ class DetailsKey #:nodoc:
59
+ alias :eql? :equal?
60
+ alias :object_hash :hash
61
+
62
+ attr_reader :hash
63
+ @details_keys = ThreadSafe::Cache.new
64
+
65
+ def self.get(details)
66
+ if details[:formats]
67
+ details = details.dup
68
+ syms = Set.new Mime::SET.symbols
69
+ details[:formats] = details[:formats].select { |v|
70
+ syms.include? v
71
+ }
72
+ end
73
+ @details_keys[details] ||= new
74
+ end
75
+
76
+ def self.clear
77
+ @details_keys.clear
78
+ end
79
+
80
+ def initialize
81
+ @hash = object_hash
82
+ end
83
+ end
84
+
85
+ # Add caching behavior on top of Details.
86
+ module DetailsCache
87
+ attr_accessor :cache
88
+
89
+ # Calculate the details key. Remove the handlers from calculation to improve performance
90
+ # since the user cannot modify it explicitly.
91
+ def details_key #:nodoc:
92
+ @details_key ||= DetailsKey.get(@details) if @cache
93
+ end
94
+
95
+ # Temporary skip passing the details_key forward.
96
+ def disable_cache
97
+ old_value, @cache = @cache, false
98
+ yield
99
+ ensure
100
+ @cache = old_value
101
+ end
102
+
103
+ protected
104
+
105
+ def _set_detail(key, value)
106
+ @details = @details.dup if @details_key
107
+ @details_key = nil
108
+ @details[key] = value
109
+ end
110
+ end
111
+
112
+ # Helpers related to template lookup using the lookup context information.
113
+ module ViewPaths
114
+ attr_reader :view_paths, :html_fallback_for_js
115
+
116
+ # Whenever setting view paths, makes a copy so we can manipulate then in
117
+ # instance objects as we wish.
118
+ def view_paths=(paths)
119
+ @view_paths = ActionView::PathSet.new(Array(paths))
120
+ end
121
+
122
+ def find(name, prefixes = [], partial = false, keys = [], options = {})
123
+ @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
124
+ end
125
+ alias :find_template :find
126
+
127
+ def find_all(name, prefixes = [], partial = false, keys = [], options = {})
128
+ @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
129
+ end
130
+
131
+ def exists?(name, prefixes = [], partial = false, keys = [], options = {})
132
+ @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options))
133
+ end
134
+ alias :template_exists? :exists?
135
+
136
+ # Add fallbacks to the view paths. Useful in cases you are rendering a :file.
137
+ def with_fallbacks
138
+ added_resolvers = 0
139
+ self.class.fallbacks.each do |resolver|
140
+ next if view_paths.include?(resolver)
141
+ view_paths.push(resolver)
142
+ added_resolvers += 1
143
+ end
144
+ yield
145
+ ensure
146
+ added_resolvers.times { view_paths.pop }
147
+ end
148
+
149
+ protected
150
+
151
+ def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc:
152
+ name, prefixes = normalize_name(name, prefixes)
153
+ details, details_key = detail_args_for(details_options)
154
+ [name, prefixes, partial || false, details, details_key, keys]
155
+ end
156
+
157
+ # Compute details hash and key according to user options (e.g. passed from #render).
158
+ def detail_args_for(options)
159
+ return @details, details_key if options.empty? # most common path.
160
+ user_details = @details.merge(options)
161
+ [user_details, DetailsKey.get(user_details)]
162
+ end
163
+
164
+ # Support legacy foo.erb names even though we now ignore .erb
165
+ # as well as incorrectly putting part of the path in the template
166
+ # name instead of the prefix.
167
+ def normalize_name(name, prefixes) #:nodoc:
168
+ prefixes = prefixes.presence
169
+ parts = name.to_s.split('/')
170
+ parts.shift if parts.first.empty?
171
+ name = parts.pop
172
+
173
+ return name, prefixes || [""] if parts.empty?
174
+
175
+ parts = parts.join('/')
176
+ prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
177
+
178
+ return name, prefixes
179
+ end
180
+ end
181
+
182
+ include Accessors
183
+ include DetailsCache
184
+ include ViewPaths
185
+
186
+ def initialize(view_paths, details = {}, prefixes = [])
187
+ @details, @details_key = {}, nil
188
+ @skip_default_locale = false
189
+ @cache = true
190
+ @prefixes = prefixes
191
+ @rendered_format = nil
192
+
193
+ self.view_paths = view_paths
194
+ initialize_details(details)
195
+ end
196
+
197
+ # Override formats= to expand ["*/*"] values and automatically
198
+ # add :html as fallback to :js.
199
+ def formats=(values)
200
+ if values
201
+ values.concat(default_formats) if values.delete "*/*"
202
+ if values == [:js]
203
+ values << :html
204
+ @html_fallback_for_js = true
205
+ end
206
+ end
207
+ super(values)
208
+ end
209
+
210
+ # Do not use the default locale on template lookup.
211
+ def skip_default_locale!
212
+ @skip_default_locale = true
213
+ self.locale = nil
214
+ end
215
+
216
+ # Override locale to return a symbol instead of array.
217
+ def locale
218
+ @details[:locale].first
219
+ end
220
+
221
+ # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
222
+ # to original_config, it means that it's has a copy of the original I18n configuration and it's
223
+ # acting as proxy, which we need to skip.
224
+ def locale=(value)
225
+ if value
226
+ config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config
227
+ config.locale = value
228
+ end
229
+
230
+ super(@skip_default_locale ? I18n.locale : default_locale)
231
+ end
232
+
233
+ # A method which only uses the first format in the formats array for layout lookup.
234
+ def with_layout_format
235
+ if formats.size == 1
236
+ yield
237
+ else
238
+ old_formats = formats
239
+ _set_detail(:formats, formats[0,1])
240
+
241
+ begin
242
+ yield
243
+ ensure
244
+ _set_detail(:formats, old_formats)
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,12 @@
1
+ module ActionView
2
+ module ModelNaming
3
+ # Converts the given object to an ActiveModel compliant one.
4
+ def convert_to_model(object)
5
+ object.respond_to?(:to_model) ? object.to_model : object
6
+ end
7
+
8
+ def model_name_from_record_or_class(record_or_class)
9
+ (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,77 @@
1
+ module ActionView #:nodoc:
2
+ # = Action View PathSet
3
+ #
4
+ # This class is used to store and access paths in Action View. A number of
5
+ # operations are defined so that you can search among the paths in this
6
+ # set and also perform operations on other +PathSet+ objects.
7
+ #
8
+ # A +LookupContext+ will use a +PathSet+ to store the paths in its context.
9
+ class PathSet #:nodoc:
10
+ include Enumerable
11
+
12
+ attr_reader :paths
13
+
14
+ delegate :[], :include?, :pop, :size, :each, to: :paths
15
+
16
+ def initialize(paths = [])
17
+ @paths = typecast paths
18
+ end
19
+
20
+ def initialize_copy(other)
21
+ @paths = other.paths.dup
22
+ self
23
+ end
24
+
25
+ def to_ary
26
+ paths.dup
27
+ end
28
+
29
+ def compact
30
+ PathSet.new paths.compact
31
+ end
32
+
33
+ def +(array)
34
+ PathSet.new(paths + array)
35
+ end
36
+
37
+ %w(<< concat push insert unshift).each do |method|
38
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
39
+ def #{method}(*args)
40
+ paths.#{method}(*typecast(args))
41
+ end
42
+ METHOD
43
+ end
44
+
45
+ def find(*args)
46
+ find_all(*args).first || raise(MissingTemplate.new(self, *args))
47
+ end
48
+
49
+ def find_all(path, prefixes = [], *args)
50
+ prefixes = [prefixes] if String === prefixes
51
+ prefixes.each do |prefix|
52
+ paths.each do |resolver|
53
+ templates = resolver.find_all(path, prefix, *args)
54
+ return templates unless templates.empty?
55
+ end
56
+ end
57
+ []
58
+ end
59
+
60
+ def exists?(path, prefixes, *args)
61
+ find_all(path, prefixes, *args).any?
62
+ end
63
+
64
+ private
65
+
66
+ def typecast(paths)
67
+ paths.map do |path|
68
+ case path
69
+ when Pathname, String
70
+ OptimizedFileSystemResolver.new path.to_s
71
+ else
72
+ path
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,49 @@
1
+ require "action_view"
2
+ require "rails"
3
+
4
+ module ActionView
5
+ # = Action View Railtie
6
+ class Railtie < Rails::Railtie # :nodoc:
7
+ config.action_view = ActiveSupport::OrderedOptions.new
8
+ config.action_view.embed_authenticity_token_in_remote_forms = false
9
+
10
+ config.eager_load_namespaces << ActionView
11
+
12
+ initializer "action_view.embed_authenticity_token_in_remote_forms" do |app|
13
+ ActiveSupport.on_load(:action_view) do
14
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
15
+ app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
16
+ end
17
+ end
18
+
19
+ initializer "action_view.logger" do
20
+ ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
21
+ end
22
+
23
+ initializer "action_view.set_configs" do |app|
24
+ ActiveSupport.on_load(:action_view) do
25
+ app.config.action_view.each do |k,v|
26
+ send "#{k}=", v
27
+ end
28
+ end
29
+ end
30
+
31
+ initializer "action_view.caching" do |app|
32
+ ActiveSupport.on_load(:action_view) do
33
+ if app.config.action_view.cache_template_loading.nil?
34
+ ActionView::Resolver.caching = app.config.cache_classes
35
+ end
36
+ end
37
+ end
38
+
39
+ initializer "action_view.setup_action_pack" do |app|
40
+ ActiveSupport.on_load(:action_controller) do
41
+ ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor)
42
+ end
43
+ end
44
+
45
+ rake_tasks do
46
+ load "action_view/tasks/dependencies.rake"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,84 @@
1
+ require 'active_support/core_ext/module'
2
+ require 'action_view/model_naming'
3
+
4
+ module ActionView
5
+ # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
6
+ # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
7
+ # a higher logical level.
8
+ #
9
+ # # routes
10
+ # resources :posts
11
+ #
12
+ # # view
13
+ # <%= div_for(post) do %> <div id="post_45" class="post">
14
+ # <%= post.body %> What a wonderful world!
15
+ # <% end %> </div>
16
+ #
17
+ # # controller
18
+ # def update
19
+ # post = Post.find(params[:id])
20
+ # post.update(params[:post])
21
+ #
22
+ # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
23
+ # end
24
+ #
25
+ # As the example above shows, you can stop caring to a large extent what the actual id of the post is.
26
+ # You just know that one is being assigned and that the subsequent calls in redirect_to expect that
27
+ # same naming convention and allows you to write less code if you follow it.
28
+ module RecordIdentifier
29
+ extend self
30
+ extend ModelNaming
31
+
32
+ include ModelNaming
33
+
34
+ JOIN = '_'.freeze
35
+ NEW = 'new'.freeze
36
+
37
+ # The DOM class convention is to use the singular form of an object or class.
38
+ #
39
+ # dom_class(post) # => "post"
40
+ # dom_class(Person) # => "person"
41
+ #
42
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
43
+ #
44
+ # dom_class(post, :edit) # => "edit_post"
45
+ # dom_class(Person, :edit) # => "edit_person"
46
+ def dom_class(record_or_class, prefix = nil)
47
+ singular = model_name_from_record_or_class(record_or_class).param_key
48
+ prefix ? "#{prefix}#{JOIN}#{singular}" : singular
49
+ end
50
+
51
+ # The DOM id convention is to use the singular form of an object or class with the id following an underscore.
52
+ # If no id is found, prefix with "new_" instead.
53
+ #
54
+ # dom_id(Post.find(45)) # => "post_45"
55
+ # dom_id(Post.new) # => "new_post"
56
+ #
57
+ # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
58
+ #
59
+ # dom_id(Post.find(45), :edit) # => "edit_post_45"
60
+ # dom_id(Post.new, :custom) # => "custom_post"
61
+ def dom_id(record, prefix = nil)
62
+ if record_id = record_key_for_dom_id(record)
63
+ "#{dom_class(record, prefix)}#{JOIN}#{record_id}"
64
+ else
65
+ dom_class(record, prefix || NEW)
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
72
+ # This can be overwritten to customize the default generated string representation if desired.
73
+ # If you need to read back a key from a dom_id in order to query for the underlying database record,
74
+ # you should write a helper like 'person_record_from_dom_id' that will extract the key either based
75
+ # on the default implementation (which just joins all key attributes with '_') or on your own
76
+ # overwritten version of the method. By default, this implementation passes the key string through a
77
+ # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
78
+ # make sure yourself that your dom ids are valid, in case you overwrite this method.
79
+ def record_key_for_dom_id(record)
80
+ key = convert_to_model(record).to_key
81
+ key ? key.join('_') : key
82
+ end
83
+ end
84
+ end