Hot Glue Example #10: Alt Lookups

This example demonstrates the usage of the --alt-foreign-key-lookup flag in the settings. Using this flag, you can specify related entities to be displayed as lookups (where the user must supply an email, for example, to look up the related record) instead of drop-downs.

Let’s assume a Company has_many :company_users and also a Company has_many :users, through: :company_users

rails generate model Company name:string
rails generate model User name:string email:string
rails generate model CompanyUser company_id:integer user_id:integer

Now modify your models like so:

app/models/company.rb

class Company < ApplicationRecord
has_many :company_users
has_many :users, through: :company_users
end

app/models/company_user.rb

class CompanyUser < ApplicationRecord
  belongs_to :company
  belongs_to :user

  def to_label
    "#{user.email} for #{company.name}"
  end
end

app/models/user.rb

class User < ApplicationRecord
  has_many :company_user
end

config/routes.rb

Rails.application.routes.draw do
  resources :companies do
    resources :company_users
  end
end

Typically, you would be constructing a CompanyUsers portal on the Company page. (Showing you only CompanyUsers associated with that company.)

Let’s do that now with Hot Glue

rails generate hot_glue:scaffold Company --downnest=company_users --gd --smart-layout
rails generate hot_glue:scaffold CompanyUser --nested=company --gd --smart-layout

(This does not use the --alt-foreign-key-lookup option. We’ll throw away this code and regenerate it later using a lookup later in this demo.)

Before we continue, let’s make 10 fake users. Drop into rails console and type:

10.times{faker = FFaker::Name.name; User.create!(email: "#{faker.gsub(' ','_').downcase}@#{FFaker::Internet.email.split('@')[1]}", name: faker)}

You will now have 10 fake users in the database.

If you start your Rails app and navigate to /companies, here’s what you find:

Notice that I can associate any of the 10 users in the database to this company using the drop-down list. Still, all I am doing is creating and deleting join records for CompanyUser.

You could use Hawking to limit what is shown in the drop-down (covered in Examples #3 and #4), but instead, the Alternative Lookup Mechanism allows you to give lookup access to the operator so they can look up a remote record by uniquely identifying it. For example, instead of picking from a drop-down, they must specify a the complete email address of the associated user.

What if we don’t want all users to appear here in a list? Instead, we want the user to input the unique email address which can be found on the related user record.

rails generate hot_glue:scaffold CompanyUser --alt-foreign-key-lookup=user_id{email} --nested=company --gd --smart-layout

Here, we are telling Hot Glue that for the foreign key user_id relationship (to the User object), we want to look up a related value email on the user object instead of displaying a drop-down.

Now, we see a text input field to enter the associated email address. Under the hood, we are using a .find_by, so if the associated thing isn’t found, we get this:

An error message tells the user it must belong to the company (it behaves just like the normal user_id field would, but instead it looks up the specified email address of the Users table).

Here, we see that if we correctly enter the email addresses of the associated objects, we can use the lookup mechanism to create join records to those objects:

You get an error if you enter an email that isn’t found.

With Automatic Creation

Instead of failing when it doesn’t find a matching User by email, we can modify the Alt Lookup to use a .find_or_create_by(...)instead of .find_by(...) by adding a plus (+) to the lookup field. This will have the effect of creating new User records if they aren’t found (and also otherwise valid):

rails generate hot_glue:scaffold CompanyUser --alt-foreign-key-lookup=user_id{email+} --nested=company --gd --smart-layout

Now, with this special syntax using the plus symbol (+), the underlying code will use .find_or_create_by(email: __) so if you enter an email that isn’t found, a new User record will be created.