I'm creating a Learning Management System. I've got Users enrolled in Courses. I'd like to show when a User has completed a course on the Users Show page. Like this:
If I add the complete
Attribute in app/dashboards/course_dashboard.rb
under COLLECTION_ATTRIBUTES
, it shows in the Course Index page also, which I don't want:
How can I add this complete
attribute to the user's course information on the User Show page (as in the first image) but not add it to the Course Index page (as in the second image)?
Unfortunately, Administrate doesn't support this out of the box. However it's possible with a little bit of Ruby trickery. This is unofficial and the exact implementation may change with new versions of Administrate. It should work with Administrate 0.15, and probably other versions.
The key is this template in Administrate's code: https://github.com/thoughtbot/administrate/blob/c16b8d1ee3a5ef1d622f9470738e89d73dbb8f1b/app/views/administrate/application/_collection.html.erb
There are two lines that are important here. The first one lists the table headers <th>
, and it is this one:
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
The second one lists the data columns <td>
for each record, and it looks like this:
<% collection_presenter.attributes_for(resource).each do |attribute| %>
The link is to the template that Administrate uses to render collections. This can be an index page, or a list of records in a HasMany field. In each of the lines above, it iterates through the collection attributes defined for the dashboard, as returned by collection_presenter.attribute_types
and collection_presenter.attributes_for(...)
, depending on the case.
In order to achieve your desired effect, you need those lists to be different when rendering an index page or when rendering the HasMany list. Currently there isn't an option for HasMany fields to dictate that this list has to be any different in their case.
Fortunately we can hack something together here.
First, remove :complete
from CourseDashboard::COLLECTION_ATTRIBUTES
. You don't want it listed in the index page, so it shouldn't appear there. Do not remove it from CourseDashboard::ATTRIBUTE_TYPES
, as we still need to define it so that we can use it elsewhere.
Second, create a new field. I'm going to call it CustomHasMany
, but it could be anything:
$ ./bin/rails g administrate:field custom_has_many
create app/fields/custom_has_many_field.rb
create app/views/fields/custom_has_many_field/_show.html.erb
create app/views/fields/custom_has_many_field/_index.html.erb
create app/views/fields/custom_has_many_field/_form.html.erb
We'll use this field for your courses
attribute in UserDashboard
:
require "administrate/base_dashboard"
class UserDashboard < Administrate::BaseDashboard
ATTRIBUTE_TYPES = {
# ...
courses: CustomHasManyField
# ...
}
This is not going to work initially, as the field is new. The first thing it needs is to copy the behaviour of the existing HasMany
field. We can do this with class inheritance:
class CustomHasManyField < Administrate::Field::HasMany
end
This won't quite mimic the HasMany
field because it's using the basic templates provided by the generator. Let's tell it to use the has_many
templates instead:
class CustomHasManyField < Administrate::Field::HasMany
def to_partial_path
"/fields/has_many/#{page}"
end
end
OK, so now it should be the same as a HasMany
field. So far this has been using public interfaces and "official" Administrate stuff. I have to admit that to_partial_path
is not well documented, but I think it's stable enough.
So now we have to tell it to add complete
to the list of fields... This is where the hack comes in play.
If you read the source code of Administrate, you'll find that collection_presenter
above is provided by the upper-level template. In turn this is defined as field.associated_collection(order)
, where field
is the field object, which in our case is an instance of CustomHasManyField
.
So if we can hack CustomHasManyField#associated_collection
to return a collection whose attribute_types
and attributes_for
include :complete
... we should be ok?
Looking at the code for collections, we can see that both lists are in turn based on the result of another method attribute_names
, which is the source of truth as to which attributes should be rendered: https://github.com/thoughtbot/administrate/blob/c16b8d1ee3a5ef1d622f9470738e89d73dbb8f1b/lib/administrate/page/collection.rb If we modify this attribute_names
method, the rest should follow suit.
Monkeypatching to the rescue:
class CustomHasManyField < Administrate::Field::HasMany
def associated_collection(*)
collection = super
def collection.attribute_names
[:complete] + super
end
collection
end
def to_partial_path
"/fields/has_many/#{page}"
end
end
That looks like it works in my computer. Does it work for you?
As for doing this in a more official manner... Do you feel like creating a PR for the project?