3

I have a common pattern or repeated code that I'd like to DRY up in my ActiveAdmin views. I'm using arbre components to render as much of my views as I can and I'd like to keep it that way if possible (i.e. I don't really want to convert to straight up HTML in the normal fashion -- I'm trying to understand the arbre way here). Here's the code I'd like to DRY up:

clients.in_groups_of(3).each do |clients_group|
  columns do
    clients_group.compact.each do |client|
      column do
        panel client.name do
          # ...
        end
      end
    end
  end
end

After reading through the documentation in the arbre gem, I started to try to create my own, custom arbre component. But I was quickly forced to realize that I have no idea how to satisfy arbre. I couldn't figure out how to pass my local variables into the block. For example:

# config/initializers/active_admin.rb

module ActiveAdmin
  module Views
    class ClientsBreakdown < ActiveAdmin::Component
      builder_method :clients_breakdown

      def build(clients, attributes = {})
        group_size = attributes.delete(:in_groups_of) { 3 }

        clients.in_groups_of(group_size).each do |clients_group|
          columns do
            clients_group.compact.each do |client|
              column do
                panel client.name do
                  super(attributes) # Doesn't seem to matter where this `super` call
                                    # is, but I do want to be able to pass `client`
                                    # into the `clients_breakdown` block here
                  # yield(client)   # -- I've also tried adding this.
                end
              end
            end
          end
        end
      end
    end
  end
end

Then, calling this in my ActiveAdmin User view might look like:

clients_breakdown(Client.all, in_groups_of: 2) do |client|
  ul do
    li client.name
  end
end

Running the above code results in this error:

UPDATE 2 The exception has changed to this after moving my custom component code into the ActiveAdmin::Views module.

New Exception

My key issue seems to be that I can't just call yield(client) where I currently have super(attributes). But that's an arbre thing so I don't know what to do there to pass the client into the calling block. Is this the right track or is there another way to DRY this up?

UPDATE 1

I've realized that the call to super can happen anywhere in the build method and really has nothing to do with what is output. So even if I move the super(attributes) call up... I still can't figure out what to put inside of the panel block so that I can render the rest of my arbre components in there from the call to clients_breakdown.

pdobb
  • 17,688
  • 5
  • 59
  • 74
  • 1
    Wouldn't you need to yield the client, and have it as the block param in your user view's `clients_breakdown` loop? I have examples of doing this but not on this machine; what you're trying is definitely possible, I just don't remember the exact mechanics. Obviously with no `client` in your view, though, trying to reference it won't work. – Dave Newton Jun 24 '14 at 14:56
  • Thanks @DaveNewton. I updated my code to reflect including `client` in my receiving block's arguments list. It helps but then chokes. I still don't see how to pass the `client` through the component, though (via that `super` call)? – pdobb Jun 24 '14 at 14:59
  • I'd probably just try a `yield client` after the call to `super` but honestly I just don't remember :/ Sorry. – Dave Newton Jun 24 '14 at 15:06
  • 1
    Did you define your ClientsBreakdown class in ActiveAdmin::Views module? And have you tried ActiveAdmin::Component instead of Arbre::Component? – nistvan Jun 27 '14 at 06:52
  • Thanks, @nistvan, that feels like progress. I've updated my question to use both of your suggestions and the exception has changed in nature. My `client` variable now contains an `ActiveAdmin::Views::ClientsBreakdown` object (see updated image of my exception above and text "UPDATE 2"). – pdobb Jun 27 '14 at 12:39

1 Answers1

7

Here is one potential solution.

A few things to note are super(attributes) should not be called unless the ClientBreakdown Arbre component is outputting its own HTML. Arbre components are generally used to build up HTML from scratch, not necessarily to compose components.

module ActiveAdmin
  module Views
    class ClientsBreakdown < ActiveAdmin::Component
      builder_method :clients_breakdown

      def build(clients, attributes = {})
        group_size = attributes.delete(:in_groups_of) { 3 }

        clients.in_groups_of(group_size).each do |clients_group|
          columns do
            clients_group.compact.each do |client|
              column do
                panel client.name do
                  yield client
                end
              end
            end
          end
        end
      end
    end
  end
end

Another approach would be to define helper methods to provide the same functionality in a module to be included in ActiveAdmin::Views::Pages::Base. This is where ActiveAdmin defines its helper methods to build the various views, like attributes_table.

module ClientsBreakdown
  def clients_breakdown(clients, attributes = {})
    group_size = attributes.delete(:in_groups_of) { 3 }

    clients.in_groups_of(group_size).each do |clients_group|
      columns do
        clients_group.compact.each do |client|
          column do
            panel client.name do
              yield client
            end
          end
        end
      end
    end
  end
end

# config/initializers/active_admin.rb
ActiveAdmin::Views::Pages::Base.include ClientsBreakdown
Charles Maresh
  • 3,323
  • 22
  • 29
  • This is a great answer, thanks very much! The explanation about what Components are for helped a lot. Both solutions worked, but I've switched my approach to use a helper now, instead (as you suggested), for the alternative approach. I wasn't really sure where to put the `ClientsBreakdown` module, though, so I just put my `clients_breakdown` method directly into `ActiveAdmin::Views::Pages::Base` within the initializer file. I realize it's outside the scope of this SO question/answer, but do you have a suggestion on where better to put the module? – pdobb Jun 27 '14 at 14:53
  • Any sub directory of `/app` could work, because Rails autoloading will find and load the file. Maybe `/app/helpers` or make a new diretory `/app/active_admin_helpers`. – Charles Maresh Jun 27 '14 at 15:57