The Books & Authors example is the simplest of all of the examples: It contains only two tables (books & auth, and two scaffoldings. You will need to go through all of the setup steps in “Getting Started” above, except Step 9. For this example, we will use –god (or –gd) controllers, which means that they are like admin interfaces: By default, they will be able to create/read/update/delete all records on the tables we are constructing.
Remember, only use Gd controllers when you also have some kind of admin authentication. We will cover that example later; for Example 1, the Megatron is only a demo app so it will allow any user access to both tables (no authentication).
When you make your new Rails app, make it with
rails new BooksAndAuthors --database=postgresql
Then go through “Getting Started” above (except Step 9).
(Somewhere along the way during the setup make sure you
rails db:create and
rails db:migrate to get your schema started.)
Next, we’ll make two models & migrations using the Rails built-in model generators:
rails generate model Author name:string
Take a look at the 4 files that Rails has created or you:
This is a normal Rails migration that should look familiar. If it doesn’t, take a look at the Rails guide for migrations.
class CreateAuthors < ActiveRecord::Migration[6.1]
create_table :authors do |t|
All we’re doing is creating a table of Authors who have a name. (The t.timestamps tells Rails to create both an updated_at and created_at timestamp for this table.)
rails db:migrate to create the Authors table.
Before we get to building the scaffolding, let’s keep going and build out our Books table. Take a note that this Books table is just a small pretend part of a demo app. Its purpose is to demonstrate one of each type of field so you can see the all the field types in action.
rails generate model Book name:string author_id:integer blurb:string long_description:text cost:float how_many_printed:integer approved_at:datetime release_on:date time_of_day:time selected:boolean genre:enum
Don’t migrate the database again — YET!
We will need to fix the enum field before you can run the migration.
Now go ahead and find the migration file itself and edit it to add the enums.
Fix the Migration
In the Megatrons create migration, add code shown in orange below:
class CreateBooks < ActiveRecord::Migration[6.1] def change create_enum "genres", %w[Fiction Nonfiction Biography Science_Fiction Mystery] create_table :books do |t| t.string :name t.integer :author_id t.string :blurb t.text :long_description t.float :cost t.integer :how_many_printed t.datetime :approved_at t.date :release_on t.time :time_of_day t.boolean :selected t.enum :genre, as: :genres t.timestamps end end end
Now you can run
Add the same Enum to the Model Books
Open app/models/book.rb and add this code:
class Book < ApplicationRecord include PGEnum(genres: %w[Fiction Nonfiction Mystery Romance Novel]) end
Now, while we’re in here, we’ll hook up our Books to Authors. As you might have guessed, Books belong_to an
:author, and an Author has_many
:books. If you aren’t familiar with Rails Active Record relationships, check out the Rails guide here.
class Book < ApplicationRecord include PGEmum(genres: %w[Fiction Nonfiction Mystery Romance Novel]) belongs_to :author end
class Author < ApplicationRecord has_many :books end
Finally, let’s build the scaffolding:
Here are the two Hot Glue commands we’ll need:
rails generate hot_glue:scaffold Author --gd
rails generate hot_glue:scaffold Book --gd
Run them both and see what is output:
For every scaffolding you build, Hot Glue generates up to 16 files:
|A spec file|
By design, the files that begin with underscore (_) are called partials, and they are used as sub-views of top-level views or other partials. Also by design, the top-level views (shown here with *) all expect to have instance variables made available to them (@) by the controller itself. Then, those top-level pages will in turn render lower level partials (which begin with underscores) and when they do, they pass the objects down the chain using local variables passed in-scope when the partial is rendered.
This is important because the lower-level partials (any views that are not top-level) should not reference instance variables. In most cases, the instance variables switch names from the instance variable name (
@abc) to a local variable name (
abc) when you move from the top-level views to the partials. This is by design and facilitates seamless re-use of the views using nesting.
The other thing to note is that the edit template and the new template share a single template: in Hot Glue’s case, it happens to be the _form partial.
This central partial is where both edit & new operations will be rendered from. In fact, it sort of is like a “show-edit-new” partial in that it can do any of the above.
Unlike Rails scaffolding of the past, there is no extra “show only” (non-editable state) template generated as I have found it not often useful. The _show partial displays a “show only” format of this record in list view.
You can specify field-level “show only” (non-editable) functionality— and you can easily make all of your fields “show only”.
However, there is no concept of the controller having a “show” state vs. a “edit” state. The fields that are show-only (non-editable) will display as non-editable on both the create & edit screen, and the controller will not allow those fields to be submitted.
A naive Rails approach to a “visible only” state would easily leave parameters you want to protect from hacking inside of the allow parameters list— a security flaw. If you’re a little fuzzy about when & where you want your users to view-only vs. be able to edit something, I’ve found this leads to quickly opening up field-level security flaws. For example, the user is allowed to have access to the record but they aren’t supposed to be able to update field X, but you accidentally leave field X in the allowed params list even though it doesn’t appear on the display. Hot Glue is encouraging you to think about the simple Rails code you are generating: it forces you to specify that this controller should have this access to these fields.
If you find yourself wanting to give different access in different cases, stop and listen very carefully: You probably want a new controller. One of the most common pitfalls of new Rails developers is Overloaded Controllers. Hot Glue is built to encourage you to keep making more controllers (even though at first it seems like too much code) when you want a new ‘feature’ or a new ‘function’ or a new ‘context’ or a new ‘path’ for that user for that object.
The basic architectural principle is based on Domain Context Interaction, which I recommend you read more about in Jame Coplien’s excellent book Lean Architecture. By building out many controllers you are encouraging yourself to see and identify the commonalities between them — those can be abstracted using mixes and inheritance — and discover the careful art of defining your business domain in conjunction with your code mental models.
Putting lots of operations upfront in the same controller is the enemy of this process, which is why Hot Glue encourages you to stick to the basic CRUD + list operations only for each controller and not make up your own actions.
There is one exception, and that exception is magic buttons, which are implemented as a sort of middle ground between making redundant controllers and overloading your controllers with business-specific actions. Magic buttons are buttons that submit as PATCH http verbs directly onto the existing update methods, which is correct & standard as according to the rules of REST, but instead of just updating data on the object they instead call a bang (
!) method on that object. This bang method in turn invokes or affects some kind of non-idempotent (change-making) action (for example, Activate, Release, Publish, etc) for this context interaction. For more on this topic, see the discussion in Example 6 below.
Although it may seem like Hot Glue is generating too much code, remember that code is cheap and your time is expensive. The scaffold is designed to do what it needs to do today, for the given context it was built in, but be easily disposable and not get in your way to architect the rest of your app.