How to DRY up arbre code into a reusable component?

2.2k views Asked by At

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.

1

There are 1 answers

2
Charles Maresh On BEST ANSWER

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