3

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:

enter image description here

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:

enter image description here

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)?

Brad West
  • 943
  • 7
  • 19

2 Answers2

6

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?

pablobm
  • 2,026
  • 2
  • 20
  • 30
  • I'm getting "uninitialized constant Administrate::Field::LimitedHasManyField" on `courses: Field::LimitedHasManyField`. Perhaps I've added the `LimitedHasManyField` class in the wrong place? I added it in app/fields/limited_has_many_field.rb. – Brad West Apr 17 '21 at 20:30
  • Ah, I found it. Someone else had [the same problem](https://github.com/thoughtbot/administrate/issues/159#issuecomment-155200795) a few years back. It should be `courses: LimitedHasManyField`. The thing is, this doesn't solve the problem. I've still got a *complete* entry on the Courses Index page. – Brad West Apr 17 '21 at 21:10
  • Apologies, I only monkeypatched enough to fix the table headers ``, but not the table records ``. I have modified the code to be generic enough to work for both. – pablobm Apr 18 '21 at 10:27
  • With these changes I'm getting "can't modify frozen Array: [:complete, :title, :lessons, :users]" in the `collection.attribute_names` method. – Brad West Apr 18 '21 at 14:31
  • The `fields.dup.delete(:complete)` change fixes the error, but I'm still getting the *complete* entry on the Courses Index page. – Brad West Apr 18 '21 at 14:55
  • Oh, darn. I did it the other way around. I hid it in `HasMany`, but you want it hidden in the index page. In that case, delist it from `CourseDashboard::COLLECTION_ATTRIBUTES`, and change `attribute_names` to do `[:complete] + super`. Let me know if it works, and I'll fix the answer properly. – pablobm Apr 18 '21 at 15:00
  • Yep, that's got it. Thank you so much. I really appreciate you sticking with me on this. – Brad West Apr 18 '21 at 15:19
  • No worries. Fixed now. Thank you for the prompt feedback. – pablobm Apr 18 '21 at 15:36
  • old answer but, was helpful to me today (associations to show fewer columns in the parent's show than their own index). Appreciate it – Garrett Davis Jun 04 '21 at 17:44
-1

Check out the documentation in https://administrate-demo.herokuapp.com/customizing_dashboards

You need to add the attribute in SHOW_PAGE_ATTRIBUTES. Not in COLLECTION_ATTRIBUTES.

Juan Artau
  • 357
  • 2
  • 13
  • Perhaps I explained my issue poorly, but this is not what I'm trying to accomplish. If I add `complete` to `SHOW_PAGE_ATTRIBUTES` it will show on the Course show page. I don't want it there. I want `complete` to show *only* on the course information of the User show page. I've updated my question. – Brad West Apr 17 '21 at 12:52
  • As I understand you need to declare `complete` in ` ATTRIBUTE_TYPES` of your `course_dashboard.rb` not in `COLLECTION_ATTRIBUTES`. Then in your `user_dashboard.rb` declare the user association in ` ATTRIBUTE_TYPES` and call it in `SHOW_PAGE_ATTRIBUTES` `COLLECTION_ATTRIBUTES` are the attributes that appear in the index template. – Juan Artau Apr 17 '21 at 14:17
  • I have made the `complete` *declaration* in the `ATTRIBUTE_TYPES` array. I don't understand what you mean by "declare the user association in user_dashbaord.rb." Can you elaborate on that? – Brad West Apr 17 '21 at 16:08