Hi,
There is a requirement that we are exploring and it is to hide the ID of a record in the nested forms for this record and to use something that we call “public_uid”. The reason is security and privacy. It kind of seems difficult to just say to rails “hey, I would like you to use this column as a public id instead of that column”
Is there a reason for this? How would you feel if there is an option to fields_for with id_column_name: “my_public_uid_that_the_world_could_know_about_and_is_not_sequential”. Or probably somewhere on active_record.
This is already in the API. If you set a to_param method on your model, you can choose whatever key you like to identify a record. You have to overload the self.find method as well (although I think there is another method you can use to set this up on that side, too). There is also FriendlyID, which is probably a larger solution than you need.
class Foo < ApplicationRecord
def to_param
public_uid
end
def self.find(id)
find_by! public_uid: id
end
end
Thanks Walter. You are right that the to_param helps. It generally does this with URLs. FriendlyID also does this with URLs. When i am overriding the to_param method of the user like
class User
def to_param
public_uid
end
end
This will change the url of the user, but if the user is included in a form with fields_for then the generated html will contain the id of the user along with it’s value which in this case is 2 - check out the hidden field for questionnair[child_attributes][id]. Child is the relationship on Questionnaire with a class_name: “User”.
Also looking at FormHelper.rb code it depends strictly on the method being called :id
def hidden_field(method, options = {})
@emitted_hidden_id = true if method == :id
@template.hidden_field(@object_name, method, objectify_options(options))
end
...
def fields_for_nested_model(name, object, fields_options, block)
object = convert_to_model(object)
emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) {
options.fetch(:include_id, true)
}
@template.fields_for(name, object, fields_options) do |f|
output = @template.capture(f, &block)
output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id?
output
end
end
It does not call :to_param and I can see no way to inject and tell fields_for to use another column as the id. So basically even if we hide the ids from the url we can not hide them from the forms. Am I missing something?
That ID will not be in the URL, which is where you were hoping to make the change, right? FriendlyID has a nice fallback in its find method where if it doesn’t get a record with the slug, it tries the primary key (numerical or UUID or whatever was in the base model).
But I would not get too hung up around the literal name id in any code that references an ActiveRecord object. If you use self.primary_key = :wibble in a model, and then refer to that object from another in a association, you don’t have to mention the name of the real primary key in that code. AR takes care of the translation for you.
Not exactly. I am not hoping to remove the id from the url, this is easily done with to_param. I am hoping to remove the id from the form generated with f.fields_for (which does not take into account the to_param method and does not call it. It calls the :id method of the record)
But I would not get too hung up around the literal name id in any code that references an ActiveRecord object. If you use self.primary_key = :wibble in a model, and then refer to that object from another in a association, you don’t have to mention the name of the real primary key in that code. AR takes care of the translation for you.