An Easy Way to Setup the gon gem in a Discourse Plugin

If you need to send some data to your javascript files in Discourse and you don't want to do this the long way, winding way through views and parsing and other strange framework headaches, consider using the gon gem.

Here, I will show you a very simple way to set up gon for Discourse.

Step 1: Enable inline javascript in your Discourse site settings

Add 'unsafe-inline' (with single quotes) to your Discourse site settings for content security policy script src. For example my Discourse site setting setup looks like this:

Screen Shot 2020-11-10 at 3.43.06 PM

You must use the single quotes just like in the picture. This will permit inline javascript to be easily added.

Step 2: Add the gon gem in your plugin.rb file

gem 'gon', '6.2.0'
require 'gon'

You are almost there. There is one more step required. The way I do this may not be the "best way" but it works for me.

Step 3: Add <%= include_gon %> to layout/application.html.erb

The way I do this is by using Discourse pups in the container build process by adding this line in my container yaml files:

Modifying the container file below is not required as of version 0.1.6 because the plugin does this for us. See this post.

## Remember, this is YAML syntax - you can only have one block with a name
run:
  - exec: echo "Beginning of custom commands"
  - exec: sed -i '/<\/head>/c\<%= include_gon %>\n</head>' /var/www/discourse/app/views/layouts/application.html.erb

If you have done all of these three steps (and rebuilt your container), you can now write any ruby plugin code you wish and assign ruby values directly to a javascript variable which can be used in both Discourse theme components and in your plugin code including EmberJS code.

Discourse Example

plugin.rb

after_initialize do
     if GlobalSetting.container_main.to_s.length > 1
       Gon.global.container_main = GlobalSetting.container_main.dup
       Gon.global.container_data = GlobalSetting.container_data.dup
     end
end

In the above example, we read two custom site settings we set up in our container YAML file, like this:

env:
  DISCOURSE_CONTAINER_MAIN: 'socket-only2'
  DISCOURSE_CONTAINER_DATA: 'data'

and we read thse value and set a gon global variable in Ruby. The can be just about any Ruby variables, but a typical use case would be a variable from a Rails controller.

Gon.global.container_main
Gon.global.container_data

Now, we can use this the javascript variable just about anywhere we want in Discourse as:

gon.global.container_main
gon.global.container_data

Here is an example from the real world, in Discourse looking at the console:

Vanilla Rails Example

Here is a example from a Rails controller using Bootstrap and chart.js

    gon.myRunLables = ["A", "B", "C", "C"]
    gon.myRunValues = [
      Run.where(grade: "A", season: @this_season).sum(:cwt),
      Run.where(grade: "B", season: @this_season).sum(:cwt),
      Run.where(grade: "C", season: @this_season).sum(:cwt),
      Run.where(grade: "D", season: @this_season).sum(:cwt),
    ]

Now, these gon variables set in the Rails controller can be used directly in chart.js, for example:

var myBarChart = new Chart(ctx, {
  type: "bar",
  data: {
    labels: gon.myRunLables,
    datasets: [
      {
        label: "Revenue",
        backgroundColor: "rgba(0, 97, 242, 1)",
        hoverBackgroundColor: "rgba(0, 97, 242, 0.9)",
        borderColor: "#4e73df",
        data: gon.myRunValues,
      },
    ],

Summary

If you are looking for a very easy way to get your Rails variables into your javascripts, consider the gon gem. It works well and with a little bit of work, it can work well with Discourse.

Reference:

See Also:

2 Likes

Version 0.1.4

Update:

This version eliminate the need to modify the Discourse container file.

The plugin will not do the required layout changes (inject gon), as follows:

class GonLayoutChanges
  def self.add_gon_to_head
    head_file = "#{Rails.root}/app/views/layouts/_head.html.erb"
    if File.readlines(head_file).grep(/include_gon/).size < 1
      tmp_file = "/shared/tmp/work.tmp.txt"
      gon_text = "<%= include_gon if defined? gon && gon.present? %>\n"
      IO.write(tmp_file, gon_text)
      IO.foreach(head_file) do |line|
        IO.write(tmp_file, line, mode: "a")
      end
      FileUtils.mv(tmp_file, head_file)
    end
  end
end