mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
f81c6bc040
Obviously #key is a too common name to be included in the AMo interface, #to_key fits better and also relates nicely to #to_param. Thx wycats, koz and josevalim for the suggestion. AR's #to_key implementation now takes customized primary keys into account and there's a testcase for that too. The #to_param AMo lint makes no assumptions on how the method behaves in the presence of composite primary keys. It leaves the decision wether to provide a default, or to raise and thus signal to the user that implementing this method will need his special attention, up to the implementers. All AMo cares about is that #to_param is implemented and returns nil in case of a new_record?. The default CompliantObject used in lint_test provides a naive default implementation that just joins all key attributes with '-'. The #to_key default implementation in lint_test's CompliantObject now returns [id] instead of [1]. This was previously causing the (wrong) tests I added for AR's #to_key implementation to pass. The #to_key tests added with this patch should be better. The CI failure was caused by my lack of knowledge about the test:isolated task. The tests for the record_identifier code in action_controller are using fake non AR models and I forgot to stub the #to_key method over there. This issue didn't come up when running the test task, only test:isolated revealed it. This patch fixes that. All tests pass isolated or not, well, apart from one previously unpended test in action_controller that is unrelated to my patch.
110 lines
4.6 KiB
Ruby
110 lines
4.6 KiB
Ruby
require 'active_support/core_ext/module'
|
|
|
|
module ActionController
|
|
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
|
|
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
|
|
# the view actions to a higher logical level. Example:
|
|
#
|
|
# # routes
|
|
# map.resources :posts
|
|
#
|
|
# # view
|
|
# <% div_for(post) do %> <div id="post_45" class="post">
|
|
# <%= post.body %> What a wonderful world!
|
|
# <% end %> </div>
|
|
#
|
|
# # controller
|
|
# def destroy
|
|
# post = Post.find(params[:id])
|
|
# post.destroy
|
|
#
|
|
# respond_to do |format|
|
|
# format.html { redirect_to(post) } # Calls polymorphic_url(post) which in turn calls post_url(post)
|
|
# format.js do
|
|
# # Calls: new Effect.fade('post_45');
|
|
# render(:update) { |page| page[post].visual_effect(:fade) }
|
|
# end
|
|
# end
|
|
# end
|
|
#
|
|
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
|
|
# that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
|
|
# convention and allows you to write less code if you follow it.
|
|
module RecordIdentifier
|
|
extend self
|
|
|
|
JOIN = '_'.freeze
|
|
NEW = 'new'.freeze
|
|
|
|
# The DOM class convention is to use the singular form of an object or class. Examples:
|
|
#
|
|
# dom_class(post) # => "post"
|
|
# dom_class(Person) # => "person"
|
|
#
|
|
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_class:
|
|
#
|
|
# dom_class(post, :edit) # => "edit_post"
|
|
# dom_class(Person, :edit) # => "edit_person"
|
|
def dom_class(record_or_class, prefix = nil)
|
|
singular = singular_class_name(record_or_class)
|
|
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
|
|
end
|
|
|
|
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
|
|
# If no id is found, prefix with "new_" instead. Examples:
|
|
#
|
|
# dom_id(Post.find(45)) # => "post_45"
|
|
# dom_id(Post.new) # => "new_post"
|
|
#
|
|
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
|
|
#
|
|
# dom_id(Post.find(45), :edit) # => "edit_post_45"
|
|
def dom_id(record, prefix = nil)
|
|
if record_id = record_key_for_dom_id(record)
|
|
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
|
|
else
|
|
dom_class(record, prefix || NEW)
|
|
end
|
|
end
|
|
|
|
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
|
|
# This can be overwritten to customize the default generated string representation if desired.
|
|
# If you need to read back a key from a dom_id in order to query for the underlying database record,
|
|
# you should write a helper like 'person_record_from_dom_id' that will extract the key either based
|
|
# on the default implementation (which just joins all key attributes with '-') or on your own
|
|
# overwritten version of the method. By default, this implementation passes the key string through a
|
|
# method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to
|
|
# make sure yourself that your dom ids are valid, in case you overwrite this method.
|
|
def record_key_for_dom_id(record)
|
|
return record.id unless record.respond_to?(:to_model)
|
|
key = record.to_model.to_key
|
|
key ? sanitize_dom_id(key.join('_')) : key
|
|
end
|
|
|
|
# Replaces characters that are invalid in HTML DOM ids with valid ones.
|
|
def sanitize_dom_id(candidate_id)
|
|
candidate_id # TODO implement conversion to valid DOM id values
|
|
end
|
|
|
|
# Returns the plural class name of a record or class. Examples:
|
|
#
|
|
# plural_class_name(post) # => "posts"
|
|
# plural_class_name(Highrise::Person) # => "highrise_people"
|
|
def plural_class_name(record_or_class)
|
|
model_name_from_record_or_class(record_or_class).plural
|
|
end
|
|
|
|
# Returns the singular class name of a record or class. Examples:
|
|
#
|
|
# singular_class_name(post) # => "post"
|
|
# singular_class_name(Highrise::Person) # => "highrise_person"
|
|
def singular_class_name(record_or_class)
|
|
model_name_from_record_or_class(record_or_class).singular
|
|
end
|
|
|
|
private
|
|
def model_name_from_record_or_class(record_or_class)
|
|
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
|
|
end
|
|
end
|
|
end
|