Skip to content

Render Info

Andreas Dausenau edited this page Jul 7, 2023 · 29 revisions

The render_info method is generated by the ezscaff generator to provide information how to render attributes of the models in the backend views. The method returns a hash having the models attributes keys and the render information as values.

Maybe the following example helps to understand what the render_info is used for:

module ArticlesHelper
  def render_info_article
    {
      title: {
        label: Article.human_attribute_name(:title)
      },
      title_image: {
        label: Article.human_attribute_name(:title_image),
        type: :image
      },
      content: {
        label: Article.human_attribute_name(:content),
        hide: [:index, :search]
      },
      author: {
        label: Article.human_attribute_name(:author),
        label_method: :username
      },
      category: {
        label: Article.human_attribute_name(:category),
        type: :enum
      },
      tags: {
        label: Article.human_attribute_name(:tags),
        label_method: :name
      },
      date: {
        label: Article.human_attribute_name(:date),
        format: '%Y-%m-%d'
      },
      published: {
        label: Article.human_attribute_name(:published)
      }
    }
  end
end

The render_info is also used to apply some additional settings to the models attributes, eg. for nested forms, and also makes it possible to render additional content on the management pages (eg. show, new, ...) that is not related to some attribute. Hence you can add custom content here without the need to eject the partials from ez-on-rails.

Note that per default also the permitted parametrs are recognized via the render_info method. Of course you are able to change this behavior. But in case you did not change this, do not remove attributes from the render_info method.

The following sections will provide information about how to use the render_info and what is possible to do with it.

Render Info options

Labels

The label option is used to identify the label shown in the models views. It is used in every partial, eg. it defines the title in the index table or the label of the attribute in the form.

Additionally a labels style can be changed via the label_class option. You can pass any css classes here.

def render_info_article
  {
    title: {
      label: 'Title',
      label_class: 'title-label-style'
    },
    ...
  }
end

Alternatively you can pass a proc to the label to completely customize the rendering. The return value of the block is the shown content in the label. If nil will be returned, the label will be hidden.

def render_info_article
  {
    title: {
      label: proc { tag.span 'Title', class: 'title-label-style' }
    },
    content: {
      label: proc { nil }
    },
    ...
  }
end

By the way, if you generated the resource via the ezscaff generator, I18n localization files were also generated, hence you can use the translations of the attributes label like in the first example of this section.

Hide attributes

Attributes can be hidden in any view. Just pass an array with the views names where you dont want the attribute to be shown.

def render_info_article
  content: {
    label: Article.human_attribute_name(:content),
    hide: [:index, :search],
  }
  ...
end

You can pass any partial name that usually matches the default scaffold actions names (new, edit, show, index). Additionaly you can pass :search, :search_form and :model_form, where search hides the attribute from the search result page, search_form hides the attribute from the search form on the index page and model_form hides the attribute from new and edit.

You also can hide the attribute from custom renderers. Since this is an advanced feature, refer to the [custom renderer](# Custom renderer) section.

Change date and time formats

If you have date, time or datetime attributes you can change the format those values are shown. Just pass a format option. Those option must hold a format string. You can refer to the strftime documentation to see how to build the format string.

def render_info_article
  {
    date: {
      label: Article.human_attribute_name(:date),
      format: '%F %H:%M'
    },
    ...
  }
end

Disable sort option

In the index action and search results the user is able to sort the results by clicking the results table header. This can be omitted by passing the no_sort option.

def render_info_article
  {
    tags: {
      label: Article.human_attribute_name(:tags),
      no_sort: true
    },
    ...
  }
end

Form help and hint texts

It is possible to add some hints to the form, like the information how long a password needs to be to be accepted.

The help option accepts a string or a proc. The proc gets the form object from the rails form_with or form_for helper.

def render_info_article
  {
    title: {
      label: Article.human_attribute_name(:title),
      help: 'Please do not use clickbaits here. This would be just frustrating.'
    },
    tags: {
      label: Article.human_attribute_name(:tags),
      help: proc { |form| "This will add associations of #{form.object.class.to_s} records." }
    }
  }
end

Default Values

You can provide default values the form field should be filled with if no input was provided yet.

You can either pass the value directly or some proc. If you pass a proc, the block gets the form object from the rails form_with or form_for helper as first argument.

def render_info_article
  {
    title: {
      label: Article.human_attribute_name(:title),
      default_value: 'New Article'
    },
    date: {
      label: Article.human_attribute_name(:date),
      default_value: proc { |form| form.object.created_at || Time.zone.now },
    },
    ...
  }
end

Passing html options

You can pass html options that usualy can be used by the rails form helpers by setting the html_options option.

  def render_info_article
    {
      title: {
        label: Article.human_attribute_name(:title),
        html_options: {
          class: 'title-style-class'
        }
      }
    }
  end

Associations

ez-on-rails forms and partials are able to render associations on a human readable way. This is possible for every type of association, from has_one over belongs_to to has_many and has_many through.

def render_info_article
  {
    author: {
      label: Article.human_attribute_name(:author),
      label_method: :username,
      hide_link_to: true,
    },
    tags: {
      label: Article.human_attribute_name(:tags),
      label_method: :name,
      association_show_label: { |obj| "#{obj.name}" },
      separator: ',',
      url_for: proc { |obj| "#{url_for(obj)}/details" },
      render_as: :check_boxes,
      data: Tag.all.to_a
    },
    ...
  }
end

The most needed basic option here is the label_nethod. This defines the attribute name of the associated model that is used to show the current relation. For instance this could be the username, hence you don't have to select an associated user using its id.

association_show_label is optional and makes it possible to overwrite the label of the relation object(s) in show and index actions. If this is a block, the result of the block will be used as label. If this is a string, the string will be used directly.

separator is used to define how has_many relations are seperated in the views in the index und show actions. This works also for active storage attachments.

max_count is used to define the maximum number of records shown in the list and show actions without a details tag. If this limit is reached, a summary and details tag will be rendered to expand the view to show all records of the relation. If this is not passed, the default value 3 will be considered. To show all records without limitation, pass :all here. This works also for active storage attachments.

hide_link_to can be used to prevent the association from being rendered with a hyperlink to its show page. Per default all associations are rendered with those hyperlinks.

url_for overwrites the hyperlink targets to the records show pages. This can either be a proc or a string. The proc takes the object itself as paremeter. This enables you to build restful urls. If url_for is not provided, the target will be the default rest show action.

_render_as_ gives the ability to change the render behavior for edit views. This can be :check_boxes, :radio_buttons, :select oder :combobox. If no value is provided the field will be shown as :combobox, which is a select2 field being a combination of search field and dropdown.

data gives the ability to define the collection of objects that should be selectable. This can be an array of records or an active record collection.

Polymorphic associations

Polymorphic associations can not be handled like default associations, because ez-on-rails needs the records classes to build the fields automatically. But ez-on-rails provides a field type polymorphic_association that enables us to use them.

...
def render_info_payment
  {
    target: {
      type: :polymorphic_association
      label: Payment.human_attribute_name(:target),
      data: {
        organizations: Organization.all,
        customers: Customer.all
      }
    },
    ...
  }
end
...

You can use the most attributes like in the association field type. Hence you can use label_nethod, association_show_label, separator, max_count, hide_link_to and url_for. Have a look at the previous section for this attributes.

But there are some differences. First of all, you can not use the _render_as_ option here.

The data attributes differs from the default association, because it is mandatory and is now expected to be a key value pair. The keys must be the pluralized, underscored names of the used polymorphic record classes. The values are the collections of the objects the user should be able to select in edit and create views.

Note that this type only works for belongs_to relations for now.

Also Note that this can already have some special cases that must be handled using some tweaks. If you get any error or have a problem, have a look at this article with some helpful tweaks and tipps.

Active storage attachments

Active storage attachments should be rendered as upload field as default. Regardless if you use has_one_attached or has_many_attached, both should work out of the box.

But you are able to pass some additional information to restrict the uploaded content. Therefore you can pass a data hash, having the values max_size, accept and max_files.

max_size defines the maximum allowed size of one file (in bytes). This defaults to 10Mb.

accept defines the type of of allowed files. Eg. 'image/*'

max_files defines the maximum number of files for a has_many_attached relation. This defaults to 5.

Additionally you can pass a seperator option, that defines how the attachments for has_many_attched relations should be seperated in the showing views.

def render_info_article 
  {
    title_image: {
      label: Article.human_attribute_name(:title_image),
      data: { 
        max_size: 1000000,
        accept: 'image/*'
      } 
    },
    attachments: {
      label: Article.human_attribute_name(:attachments),
      separator: ',',
      data: { 
        max_size: 1000000,
        max_files: 5,
        accept: 'application/pdf'
      } 
    }
}
end

NOTE: The components to upload active storage files do not use the default active storage routes. This normally should not have any impact to your system. See Active Storage Endpoints documentation for details.

Special feature: Images as type

If you want your image attachment to be clickable and only show a thumbnail that is clickable and opens a full view modal, you just can pass the type: :image or type: :images option.

def render_info_model_article
  {
    title_image: {
      label: Article.human_attribute_name(:title_image),
      type: :image
    },
    image_attachments: {
      label: Article.human_attribute_name(:image_attachments),
      type: :images
    }
  }
end

Customize search

Search labels

Like the default labels, you can define labels for the search form on the index page of the resource. ez-on-rails distinguishes this because in some cases you may change the search operator from contains to somer other search method, hence the label may be misleading.

You are also able to use strings or procs here.

def render_info_article
  {
    title: {
      search_label: 'Title'
    },
    content: {
      search_label: proc { tag.span 'Content', class: 'content-label-style' }
    },
    author: {
      search_label: Article.human_attribute_name(:author)
    },
    ...
  }
end

Search method

EzOnRails uses ransack to build the search form. Ransack enables us to pass a combination of field names and search methods to search fields. For instance, by passing "name_cont" to a search field, the field will cause a search for "name contains" after submitting the form. This search method can be overwritten by the render info, by passing it:

def render_info_article
  {
    title: {
      search_label: 'Title',
      search_method: 'eq'
    }
    ...
  }
end

In this example, the title will be search with the "equals" instead of the"contains" method. You do not need to specify the search_method, because the default is set to "cont". But there is one special case for associations. If you have an association and use the "label_method" id for this association, the default search method will be set to "eq".

Association search attributes

If you have an association and want to customize the field(s) the association should be searched with, you can define them by using "association_search_attributes". This should be an array of field keys of the association. Those will be combined with "or" and the search method like described in the section before. If you do not define the association_search_attributes, the label_method will be used. If you also did not define a label_method, the id will be used as default.

def render_info_article
  {
    tags: {
      search_label: 'Title',
      association_search_attributes: [:name, :color]
    }
    ...
  }
end

This will now create a search field for tags targeting there name or color value. Note that this currently only works for has_many associations, until https://github.com/activerecord-hackery/ransack/issues/1428 is fixed.

Attribute types

Normally the type of each attribute in the render_info will be automaticly identified to render the correct field.

In some cases this may not work, esepcially if the attribute you defined is not related to the model but is some additional content. Maybe you also just want to change the field type of the attribute, eg. maybe you want to use a number field for some string.

In this case you can define the type of the attribute. This will skip the method that tries to identify the attributes type based on the model information.

def render_info_article
  {
    category: {
      label: Article.human_attribute_name(:category),
      type: :enum
    },
    ...
  }
end

There are some special types ez-on-rails provides that are often used for some cases, like a searchable combobox for select fields. Those types accept a data option holding additional information they need to render the field.

def render_info_article
  {
    author: {
      label: Article.human_attribute_name(:author),
      type: :combobox,
      data: User.all.map { |user| [user.username, user.id] }
    },
    ...
  }
end

As you can see in this example, the data field passed to the combobox holds the selectable items, in this case all users as authors. This is an array of array, holding the labels and identifiers for the selection.

ez-on-rails provides the following special types:

  • :password - Shows a password field holding dots as characters, also hides the field in the show views
  • :select - Shows a dropdown dropdown for selection, the :data option is used to define the selectable items (like in the combobox example above)
  • :combobox - Shows select2 fields (searchable dropdowns) to choose some selection, the :data options is used to define the selectable items (see the example above)
  • :nested_form - Shows a nested form. The :data attribute holds the render_info of the nested model. But note, you will need do some additional work to be able to render nested forms. The [nested form section](## Nested forms) shows the details how use them.
  • :enum - Shows some rails enum as selectable field, where the values are selected from the translation files via default. The :data field is used to define an alternative enum name here. See (enums section)[## Enums] for details
  • :collection_select - Shows a has_many relation as selectable checkbox list. The :data attributes holds an hash having the selectable :items and a :label_method. See (checkbox lists section)[## Checkbox lists] for details.
  • :duration - Shows a duration select. :data is used to limit the selection here. See (durations section)[## Durations] for details.

Enums

Rails enums are safed as integer values hence the method to identify the attributes type will per default only show an integer field. To show the enum as selectable human readable field, you need to specify the type: :enum option to the render info.

def render_info_article
  {
    category: {
      label: Article.human_attribute_name(:category),
      type: :enum,
      data: :optional_other_enum_name  
    }
    ...
  }
end

If the enum you want to provide is not named like the database column of your model, you can pass the alternative name as :data field. This is usually not necessary.

To translate the enum values correctly, you need to provide the translated values in the I18n locale file of your model. The following structure is expected here:

en:
  activerecord:
    enums:
      article:
        category:
          science: "Science"
          economy: "Economy"
          other: "Other"

Nested forms

ez-on-rails is able to show nested forms using cocoon.

There are several steps needed to get this to work. First you need to pass the type: :nested_form option to the target association in the parents render_info. You also have to pass an hash to the data field holding the render_info of the child model.

def render_info_article
  {
    tags: {
      label: Article.human_attribute_name(:tags),
      type: :nested_form,
      data: {
         render_info: render_info_tag
      }
    },
    ...
  }
end

Inside the render_info of the child model you need to pass the option nested: true to every attribute you want to have shown in the nested form.

def render_info_tag
  {
    name: {
      label: Tag.human_attribute_name(:name),
      nested: true
    },
    color: {
      label: Tag.human_attribute_name(:color),
      nested: true
    },
    created_by: {
      label: Tag.human_attribute_name(:created_by)
    }
  }
end

Dont forget to add the accepts_nested_attributes_for hook to your parent model.

class Article < ApplicationRecord
  ...
  accepts_nested_attributes_for :tags, allow_destroy: true
  ...
end

You also need to include the Helper module that holds the render_info method of you child to your parents controller. Otherwise the controller is not able to find the render_info method.

class ArticlesController < EzOnRails::ResourceController
  include TagsHelper
  ...
end

Thats it. Your nested form should now work.

Additional nested forms options

If you want to customize the nested_forms partial you just need to pass the partial name to the data hash of the parents nested form attribute. This is optional, because ez-on-rails uses some own partial to render nested_forms that should have the same render behavior like the default model_form partials.

def render_info_article
  {
    tags: {
      label: Article.human_attribute_name(:tags),
      type: :nested_form,
      data: {
        render_info: render_info_tag,
        partial: 'custom_tag_form'
      }
    },
    ...
  }
end

The links to add or remove associations can be hidden. This is useful if you want to have a fix amount of nested attributes.

def render_info_article
  {
    tags: {
      label: Article.human_attribute_name(:tags),
      type: :nested_form,
      data: {
        render_info: render_info_tag,
        hide_add: true,
        hide_remove: true
      }
    },
    ...
  }
end

Special case: self references in nested forms

In some cases you want to have a self reference, eg. if some article has some referenced articles. The problem here is that if you use the render_info in the nested_form option, you would produce an endless loop ending in a stack overwlow error, because the render_info would call itself recursively. ez-on-rails can handle this by only passing the name of the childs render_info method as string.

def render_info_article
  {
    references_articles: {
      label: Article.human_attribute_name(:references_articles),
      type: :nested_form,
      data: 'render_info_article'
    },
    ...
  }
end

Checkbox lists

You can show a has_many relation as check-list of selectable items.

In this case the render_info expects the data field to hold the information what items should be shown and what method is used to get the human readable labels of this items.

def render_info_article
  {
    tags: {
      label: Article.human_attribute_name(:tags),
      type: :collection_select,
      data: {
        items: Tag.all,
        label_method: :name
      }
    }
  }
end

This results in a list of Tag names having a checkbox to be able to assign them to the articles has_many relation.

Durations

Rails allowes to handle duration attributes using ActiveSupport::Duration. ez-on-rails can render those duration objects. You just need to pass the type: :duration option.

If you want to limit the number of years the user can select, you can pass the max_years option in the data hash. If you dont pass those value, the default maximum selectable duration will be 10 years.

def render_info_article
  {
    lifetime: {
      label: Article.human_attribute_name(:lifetime),
      type: :duration,
      data: {
        max_years: 2,
      }
    },
    ...
  }
end

Custom field rendering

It is possible to define custom blocks to render attributes for each target view. You just need to use the render_blocks option. This option expects a hash having the target views as keys and the custom procs as values.

The procs for all show related targets take the object as parameter. Those that can edit fields take the form object from the surrounding form_with or form_for helper.

The proc for the search form takes a hash as argument, having the :obj_class and :form attributes.

If you dont want to define custom renderes for every view, you can just pass :default to indicate that those view should render the field like the default behavior does.

It is also possible to reference one renderer in another one, just pass the target views key as value.

Note that if you use this render_blocks option and do not define renderers for every target, those who are not defined will just not be rendered. Make use of the :default value to render the other targets.

def render_info_article
  {
    author: {
      label: Article.human_attribute_name(:author),
      render_blocks: {
        index: proc { |obj| link_to 'User profile', alternative_user_profile_path },
        show: :index,
        model_form: :default
      }
    },
    title: {
      label: Article.human_attribute_name(:title),
      render_blocks: {
        index: :default
        show: :default
        model_form: proc { |form| form.text_field :title, class: 'form-control' },
        search_form: proc { |data| data[:form].search_field :title_cont, class: "#{data[:obj_class].underscore}-style" }
      }
    },
    ...
  }
end

Tip: This can also be used to add some content to your pages that is not related to your model. Just add the type: :custom and define the render blocks, and the content will be shown.

def render_info_article
  {
    custom_content: {
      type: :custom,
      render_blocks: {
        model_form: proc { |obj| tag.span 'Some content here' }
      }
    },
    ...
  }
end

Custom renderer

If you eject the helpers using the ezhelpers generator you can see that all files having the rendering methods are located in the folder helper/ez_on_rails/ez_scaff/.

As you can see, those helpers are named like the keywords you can use in the render info to eg. hide fields or do some other stuff. For instance, if you want to define a custom render for the index action or hide a field in the index action, the behavior of the rendering method in the index_helper.rb will be overriden.

The helpers has methods for every type implemented. The methods are named following the convention:

render_<attribute_type>_<target>(data, attribute_key, attribute_render_info)

Hence the method render_boolean_index will render boolean fields on the index targets. The data depends on the target. This is for instance the form builder in case of the model_form target, but is the record in case of the index or show target.

The partials using the render info will try to identify the type of the attribute and call the method belonging to the current type and target. If the method does not exist, the method render_default_ will be called.

You can make use of this principle to define your own target renderers.

If you define your own helper file here and take care of the convention, you can use your custom target renderer in the render info.

If you want to use your new renderer in some view, you just have to call:

render_attribute(:<target>, data, attribute_key, attribute_render_info)

The data you pass here will be passed to the render methods of your target renderer. The attribute_key is the name of the attribute and the attribute_render_info is the attributes hash in the render info of the model.

You can also define the methode filter_attributes!(obj_class, render_info)_. But note that you must use this method on your own in your partial before rendering.