Build Google Forms with Apps Script

You can create a fully customized Google Form programmatically using Google Apps Script

Instead of manually designing forms, you can write a script to:

  • Automatically generate a form with multiple sections and questions.

  • Add various question types like text, multiple choice, or checkboxes.

  • Retrieve and log the link to the form for easy sharing.

Here’s an example:

function createTILGoogleForm() {
  // Create a new Google Form
  const form = FormApp.create('Today I Learned Form');
  form.setDescription('A quick example of how to create a Google Form with Apps Script.');

  // Add a section and some questions
  form.addSectionHeaderItem().setTitle('Your TIL Entry');
  form.addTextItem().setTitle('What did you learn today?');
  form.addParagraphTextItem().setTitle('How might you use this knowledge in the future?');

  // Log the form's URL
  Logger.log('Form created! Access it here: ' + form.getPublishedUrl());
}

Steps to create the Google Form using a script:

  1. Open Google Drive and create a new Google Apps Script:
  • Go to New > More > Google Apps Script
  1. Write your script or copy mine, into the Script editor

  2. Save and run the script

When you run the script:

  1. A new Google Form titled “Today I Learned Form” will be created in your Google Drive.

  2. The form’s link is logged in the Apps Script console for sharing.

Why this is cool:

  • Saves time on repetitive tasks.

  • Perfect for automating form creation for surveys, feedback collection, or team updates.

Next time you need to create a form, think about scripting it instead of clicking through the interface

How to re-use styles for components using the sx props with React and Material UI

A really simple way to create reusable components using sx for styling looks like this:

First of all, once you realize that you are creating a similar component more than twice, then it's a good time to create a component with some default properties and then you add some additional properties to make the differentiation, and a quick example would look like this:

import { Chip } from '@mui/material';

type ChipLabelGuidelineProps = {
  label: string;
  borderColor?: string;
  bgcolor?: string;
  padding?: string;
  //..rest of your attributes if you use them directly
}

const ChipLabelGuideline = (props: ChipLabelGuidelineProps) => {
  const { label, borderColor = 'white', ...restSxProps } = props;
  return (
    <Chip
      label={label}
      sx={{
        borderRadius: '8px',
        fontSize: '16px',
        fontWeight: '700',
        border: `2px solid ${borderColor}`,
        padding: '4px 25px',
        color: 'blue',
        ...restSxProps,
      }}
    />
  );
};

Then you can use it like this:

<ChipLabelGuideline label="Label 1" bgcolor='green' />
# Or
<ChipLabelGuideline label="Label 2" bgcolor='black' />
# or if you want to override some data
<ChipLabelGuideline label="Label 3" bgcolor='yellow' color='white' padding: '0 10px'/>

Really simple isn't it?

Fix for Form Post-Submit Redirection Issue with Turbo Drive

With Rails 7 came the addition of Hotwire which contains Turbo. Turbo Drive, which is enabled by default, intercepts link clicks and form submissions. In the case of the latter, some issues may occur if the back end is trying to do a redirect. For instance, let’s suppose we have the following controller code:

class SubscriptionsController < ApplicationController
  def create
    # code...
    redirect_to root_path
  end
end

Then we have the following form:

<%= form_with url: subscriptions_path, method: :post do |f| %>
  <%= # form fields... %>
  <%= f.button 'Subscribe', id: 'submit-button'%>
<% end %>

After submitting the form, we get a “320 found” response because of the redirect the back end is trying to do. Notice we are submitting the form using the POST method. In this case we may encounter some issues like Turbo losing the CSRF token, Caching Issues, etc that may cause some issues like the app not doing the redirect or having some weird partial renderings. It’s worth noting that this issue doesn’t happen if we submit the form using the GET method.

One easy solution to this problem is to disable Turbo using the data-turbo=”false” flag and let the from to be submitted the “old way”:

<%= form_with url: subscriptions_path, data: { turbo: false }, method: :post do |f| %>
  <%= # form fields... %>
  <%= f.button 'Subscribe', id: 'submit-button'%>
<% end %>

InstantClick behavior in Turbo

Today I learned about the new default InstantClick behavior for Turbo. Turbo now prefetches a link when hovering it and changes the page's contents after clicking on it, resulting in instantaneous page changes most of the time. There's a time window of ~300ms between the hover and the click event, so if you want to optimize for this make sure your backend can respond inside that time window.

This is enabled by default but you can opt out of it by adding data-turbo-prefetch="false" to specific links or to whole containers. Add it to your main container to disable it completely.

More info and demos here: https://github.com/hotwired/turbo/pull/1101

An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'

If you're working with APIs like the OpenAI one and you're passing tools to your bots, make sure the results of a tool call are always right next to the tool call message! Otherwise the API will return a 400 response. Heads up! This a recent change in their API (they use to have functions but is now deprecated) and the mandatory order is not mentioned in the official docs!

// It always needs to be like this
[
  { 
    "role": "user", 
    "content": "Tell me the current time", 
    "tools": [{ "name": "current_time", "description": "Gives you the current time" }]
  },
  {
    "role": "assistant",
    "content": "",
    "tool_calls": [{
      "type": "function",
      "id": "d8cdd56d-98e1-4dea-ae3a-2b23",
      "function": { "name": "current_time", "arguments": "{}" }
    }]
  },
  {
    "role": "assistant",
    "content": "Current time is 10:22 AM",
    "tool_call_id": "d8cdd56d-98e1-4dea-ae3a-2b23"
  },
  {
    "role": "system",
    "content": "Just a message after the tool result"
  },
]

Never do this!

[
  { 
    "role": "user", 
    "content": "Tell me the current time", 
    "functions": [{ "name": "current_time", "description": "Gives you the current time" }]
  },
  {
    "role": "assistant",
    "content": "",
    "tool_calls": [{ /* omitted */ }]
  },
  {
    "role": "system",
    "content": "Just a message between the tool call and the tool result"
  },
  {
    "role": "assistant",
    "content": "Current time is 10:22 AM",
    "tool_call_id": "d8cdd56d-98e1-4dea-ae3a-2b23"
  }
]

:use_route in controller specs inside engines

If you have controller specs inside your Rails engine, you'll need to pass use_route: :my_engine when doing the request. Otherwise you'll get an UrlGenerationError since the dummy app is not running and the engine is not mounted!

describe MyEngine::MainController, type: :controller do
  it "does something" do
    get :index, params: { use_route: :my_engine }
    # ...
  end
end

Reduce your slug size on Heroku!

You don't need app/javascript and node_modules after compiling assets. Enhance the assets:precompile task and delete them so they don't get added to your Heroku slug! Small slugs allow for fast boot up times and better scaling. This also helps stay below the 500MB size limit!

Rake::Task["assets:precompile"].enhance do
  next unless Rails.env.production?

  ["#{Dir.pwd}/app/javascript", "#{Dir.pwd}/node_modules"].each do |dir_path|
    FileUtils.rm_rf(dir_path)
  end
end

Mastering extend and when you want class methods and instance methods for your class

Mastering extend and when you want class methods and instance methods for your class

I just realized something cool, which is the underlying of including same module for a class and share instance and class methods with that class.

So here is the trick

You have this simple module:

module TestModule
  def test_name
    'test_name'
  end
end

And this class

class Test
  def name
    'Test'
  end
end
Test.extend(TestModule)
irb(main):033> Test.test_name
=> "test_name"
irb(main):034> Test.new.name
=> "Test"

So that is why if you want to include both instance and class methods, with something like this

class Test
  include TestModule
end

How would you include some as class methods, and the answer is by defining those methods in a module or subgroup within the module, and it would look like this

module TestModule
  def instance_method
    'instance method'
  end

  module TheClassMethods
    def class_hello
      'class_hello'
    end
  end

  def self.included(base)
    base.extend(TheClassMethods)
  end
end

class Test
  include TestModule
end

So now you can access both of them

Test.new.instance_method # instance method
Test.class_hello # class_hello

NOTE: remember that it is included in the past

def self.included(base)
  base.extend(YouClassMethodsToShare)
end

And active_support/concern comparison

module M
  def self.included(klass)
    klass.extend ClassMethods
    klass.class_eval do
      scope :disabled, -> { where(disabled: true) }
    end
  end

  def instance_method
    'instance method'
  end

  module ClassMethods do
     def class_method_one
       'class method'
     end
  end
end

Vs

require 'active_support/concern'

module M
  extend ActiveSupport::Concern

  included do
    scope :disabled, -> { where(disabled: true) }

    def self.direct_class_method_here
      ..
    end
  end

class_methods do
    def class_method_one
      'class method one'
    end
  end
end

class YourClass
  include M
end

Setting up multiple Okta orgs with the same Omniauth Oauth2 Strategy in Rails

Hello There,

I want to share what I did in order to support a second Okta Organization in a Ruby on Rails application using omniauth_oktaoauth gem.

I started with a basic spec to make sure I dont break anything:

RSpec.describe 'Sign in with Okta', type: :request do
  describe "Using Okta" do
    let(:user) { User.find_by email: user_email }
    let(:okta_oauth) do
      {
        provider: okta_provider.to_s,
        uid: "123456789",
        info: {
          name: "John Doe",
          email: user_email,
        }
      }
    end

    before(:each) do
      OmniAuth.config.test_mode = true
      OmniAuth.config.mock_auth[okta_provider] = OmniAuth::AuthHash.new(okta_oauth)
    end

    context "When using existing configuration" do
      let(:okta_provider) { :oktaoauth }
      let(:user_email) { "john.okta@existing.domain" }

      it "keeps working" do
        post "/users/auth/oktaoauth"
        follow_redirect!
        expect(user.email).to eq(user_email)
      end
    end
  end
end

With this, I proceed to write another spec to ensure the new option would work:

context "When using second okta org" do
  let(:okta_provider) { :second_okta }
  let(:user_email) { "john.okta@new.domain" }

  it "signs the right user in" do
    post "/users/auth/second_okta"
    follow_redirect!

    expect(user.email).to eq(user_email)
  end
end

After this, I started to use the normal steps:

# user.rb
devise :omniauthable, omniauth_providers: [:oktaoauth, :second_okta]

Then, modify the initializer to tell omniauth about the new strategy

# config/initializers/okta.rb
....
config.omniauth(:second_okta,
                Rails.configuration.second_okta.client_id,
                Rails.configuration.second_okta.client_secret,
                name: "second_okta",
                request_path: "/users/auth/second_okta",
                callback_path: "/users/auth/second_okta/callback",
                scope: "openid profile email",
                fields: %w[profile email],
                client_options: {
                  site: Rails.configuration.second_okta.url,
                  authorize_url: "#{Rails.configuration.second_okta.auth_issuer}/v1/authorize",
                  token_url: "#{Rails.configuration.second_okta.auth_issuer}/v1/token"
                },
                redirect_uri: "#{Rails.configuration.app.base_domain}/users/auth/second_okta/callback",
                issuer: Rails.configuration.second_okta.auth_issuer,
                strategy_class: OmniAuth::Strategies::Oktaoauth)

At the beginning, it looked easy to add a second option using the same strategy, but after dealing with omniauth internals and omniauth_oktaoauth source code itself, I found that I needed to specify name and strategy_class to override defaults, there's something inside the source code that did not work out of the box, I had to specify request_path and callback_path explicitly (I'll dig deeper later and send a patch if it's a bug).

After making those changes, it worked just fine.

Thanks!