Releases: hot-glue-for-rails/hot-glue
TinyMCE implementation + more
- TinyMCE implementation. See 'TinyMCE' in the README.
Note: I also plan to implement ActionText as an alternative. However, because TinyMCE is implemented with atext
field type an ActionText is implemented with a Rails-specificrich_text
field type, the two mechanisms will be incompatible with one another. TinyMCE has an annoying drawback in how it works with Turbo refreshes (not very consistently), and style of loading Javascript is discordant with Rails moving forward. So I am leaving this implementation as experimental. - Spec Savestart code: In the behavior specs, there is a code marker (start & end) where you can insert custom code that gets saved between
build. The start code maker has changed from#HOTGLUE-SAVESTART
to# HOTGLUE-SAVESTART
and the end code marker has changed from#HOTGLUE-END
to# HOTGLUE-END
. This now conforms to Rubocop.
Be sure to do find & replace in your existing projects to keep your custom code. - Fix for specs for attachment fields. If you have attachments fields, you must have a sample file at
spec/fixtures/glass_button.png
- Pundit now correctly protects the
index
action (authorize was missing before)
Enums can now render partials
- You can now use the modify flag on enum type fields to display a partial with the same name of that enum type.
--modify=status{partials}
Here, status
is an enum field on your table; you must use the exact string partials
when using this feature.
You're telling Hot Glue to build scaffold that will display the status
enum field as a partial.
It will look for a partial in the same build directory, whose name matches to the value of the enum. You will need to create a partial for each enum option that you have defined.
Remember when defining enums Rails will patch methods on your objects with the name of the enum types, so you must avoid namespace collisions with existing Ruby or Rails methods that are common to all objects -- like, for example, new
.
-
Before this feature, enums always rendered like this on the show page:
<%= domain.status %>
(or, if you use custom labels:)
<%= Domain.status_labels(domain.status)
-
After, you if you use the
--modify
flag and modify the enum to 'partials', your show output will render:
<%= render partial: thing.status, locals: {thing: thing} %>
(In this example Thing
is the name of the model being built.)
Your form will also render the partial, but after the collection_select used to create the drop-down. (So it will render both the drop-down and the partial. This implementation has the drawback of no immediate switch between the partials when changing the drop-down, so it is not as responsive of an interaction design as it could be.)
Assuming your Thing enum is defined like so:
enum status: {abc: 'abc', dfg: 'dfg', hgk: 'hgk'}
You then would create three partials in the things
directory. Make sure to create a partial for each defined enum, or else your app will crash when it tries to render a record with that enum.
_abc.html.erb
_dfg.html.erb
_hgk.html.erb
If your enum is on the show-only list, then the drop-down does not appear (but the partial is rendered).
Proof-of-concept can be found here:
https://github.com/hot-glue-for-rails/HGEnumWithPartials1
Remember to see the section marked 'A Note About Enums' for more about working with Rails 7 enums.
Various Fixes
- adds back magic button tap-away params in the controller (in the
update
method) - changes creation of
flash[:notice]
(inupdate
method`) - adds pundit authorization for edit action in the
edit
method itself - adds a crude
show
method that redirects to theedit
(Hot Glue takes the opinionated stance that show routes should not really exist. This addition makes it so that when the user is on theshow
route and re-loads the browse window, they don't see a route error) - adds RuboCop detection; if you have RuboCop, the regen code at the top of the controller is excluded from the Layout/LineLength cop
- fixes for specs
Adds flags --no-controller --no-list
- Removes
@
symbols using instance variables in_edit
partials and in the enum syntax - fixes in system_spec.rb.erb
- Adds flags
--no-controller
and--no-list
- adds regen code to a file in the views folder REGENERATE.md if you have set --no-controller
Field-level access control with Pundit
--pundit
If you enable Pundit, your controllers will look for a Policy that matches the name of the thing being built.
Realtime Field-level Access
Hot Glue gives you automatic field level access control if you create *_able?
methods for each field you'd like to control on your Pundit policy.
Take note this is not what Pundit recommends in their documentation for field-level access control. Instead, it has been implemented this way to provide field-by-field user feedback to the user shown in red just like Rails validation errors are currently shown. (Remember, we're talking about an access control failure, which may involve conditions that look like what you'd typically see in Rails validations).
You can refine on a field-by-field and role-by-role basis, which users have access to edit which fields on the Hot Glue layout. For everyone else, they see the field as viewable only.
A user inputting a field they don't have access to edit is only hypothetical for Hot Glue, because the interface correctly shows the field non-editable for them anyway. But, if the interface was edited or simply hacked (your web user can add hidden fields using the DevTools to try to set the values they don't have access to), the backend policy will prevent the hacking attempt by applying the access control in the update?
method of the policy. You can set the object errors here when catching for access control errors, as you see in the example below.
The *_able?
method should return true if the field can be edited and false if the field should appear as viewable only (non-editable). No distinction is made between the create/edit contexts. You may check if the record is new using new_record?
. To remove the field completely, either remove it from your --include
list or add it to your --exclude
list, whichever you are using.
The *_able?
method is used by Hot Glue only on the new and edit actions, but you must incorporate it into the policy's update?
method as in the example, which will extend other operations since Hot Glue will use the policy to authorize the action
Add one *_able?
method to the policy for each field you want to allow field-level access control.
Replace *
with the name of the field you want under access control. Remember to include _id
for foreign keys. You do not need it for any field you don't want under access control.
When the method returns true, the field will be displayed to the user (and allowed) for editing.
When the method returns false, the field will be displayed as read-only (viewable) to the user.
Important: These special fields determine only display behavior (new and edit), not create and update.
For create & update field-level access control, you must also implement the update?
method on the Policy. Notice how in the example policy below, the update?
method uses the name_able?
method when it is checking if the name field can be updated, tying the feature together.
You can set Pundit to be enabled globally on the whole project for every build in config/hot_glue.yml
(then you can leave off the --pundit
flag from the scaffold command)
:pundit_default:
(all builds in that project will use Pundit)
Here's an example ThingPolicy
that would allow editing the name field only if:
• the current user is an admin
• the sent_at date is nil (meaning it has not been sent yet)
For your policies, copy the initialize
method of both the outer class (ThingPolicy
) and the inner class (Scope
) exactly as shown below.
If you argue with this implementation and think that there should be a way to make a field invisible based on access control — for example, if you have two different user contexts (roles) with different concerns and you want to use the same Hot Glue controller — instead you should simply make different Hot Glue controllers for the different purposes, hiding or showing the fields applicable to the users in their contexts. A singular application-wide policy is used to enforce consistency across the app, but each area of the app (namespaced Hot Glue controller) has a different user context interaction and, therefore, which fields to include or exclude get defined based on that.
class ThingPolicy < ApplicationPolicy
def initialize(user, record)
@user = user
@record = record
end
def name_able?
@record.sent_at.nil?
end
def update?
if !@user.is_admin?
return false
elsif record.name_changed? && !name_able?
record.errors.add(:name, "cannot be changed.")
return false
else
return true
end
end
class Scope < Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
end
end
Because Hot Glue detects the *_able?
methods at build time, if you add them to your policy, you will have to rebuild your scaffold.
When mixing the show only, update show only, and Pundit features, notice that the show only and update show only will act to override whatever the policy might say, so fields specified in those settings will be shown as viewable (non-editable) even when the policy may allow it.
Remember, the show only list is specified using --show-only
and the update show only list is specified using --update-show-only
.
'Viewable' means it displays as view-only (not editable) even on the form. In this context, 'viewable' means 'read-only'. It does not mean 'visible'.
That's because when the field is not viewable, then it is editable or inputable. This may seem counter-intuitive for a standard interpretation of the word 'viewable,' but consider that Hot Glue has been carefully designed this way. If you do not want the field to appear at all, remove it using the exclude list or don't specify it in your include list.
If the field is being built, Hot Glue assumes your users want to see or edit it. Other special cases are beyond the scope of Hot Glue but can easily be added using direct customization of the code.
Without Pundit:
on new screen | on edit screen | |
---|---|---|
for a field on the show only list | viewable | viewable |
for a field on the update show only list | inputable | viewable |
for all other fields | inputable | inputable |
With Pundit:
on new screen | on edit screen | |
---|---|---|
for a field on the show only list | viewable | viewable |
for a field on the update show only list | check policy | viewable |
for all other fields | check policy | check policy |
Fancy booleans
Given a table generated with this schema:
rails generate model Thing abc:boolean dfg:boolean hij:boolean
• You can now use new flag --display-as
to determine how the booleans will be displayed: checkbox, radio, or switch
rails generate hot_glue:scaffold Thing --include=abc,dfg,hij--god --display-as='abc{checkbox},dfg{radio},hij{switch}'
You may specify a default default_boolean_display
in config/hot_glue.yml
, like so:
:default_boolean_display: 'radio'
(The options are checkbox, radio, or switch.)
If none is given and no default is specified, legacy display behavior will be used (radio buttons)
You still use the --modify
flag to determine the truthy and falsy labels, which are also used as the truth and false when a boolean is displays as radio button, as shown in the dfg
field above. (switches and checkboxes simply display with the field label and do not use the truthy and falsy labels)
Fixes to datetime and timezone aware inputs
-
there are three ways Hot Glue deals with Datetime fields:
-
(1) with current users who have
timezone
method (field or method) -
(2) without current user timezone and no global timezone set for your app
-
(3) without current user timezone and global timezone set for your app
-
For #1, previously this method returned a time zone offset (integer). After v0.5.18, the preferred return value is a Timezone string, but legacy implementation returning offset values will continue to work.
Your user objects should have a field calledtimezone
which should be a string. -
Note that daylight savings time is accounted for in this implementation.
-
For #2 (your user does not have a timezone field and you have not set the timezone globally), all datetimes will display in UTC timezone.
-
For #3 (your user does not have a timezone field but you have set the timezone globally), your datetimes will display in the Rails-specified timezone.
-
be sure to configure in
config/application.rb
this:
config.time_zone = 'Eastern Time (US & Canada)'
This should be your business's default timezone.
(#93)
fixes variables be more clear if they are TimeZone objects (https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) or are UTC offset (integers -/+ from UTC)
- fixes spec assertions for DateTime and Time fields
- removes randomness causing race conditions in the datetime object specs
- fixes issue where setting bootstrap-column-width was not preferred if… (#88) - fixes flash notice output
Generator for `_nav.html.erb`; automatically add tabs to `_nav.html.erb` if it exists in the namespace
• Nav templates (_nav.html.erb
) are now automatically appended to if they exist. Remember nav template live in the views folder at the root of the namespace, which is one folder up from whatever folder is being built.
If a file exists _nav.html.erb
, it will get appnded to with content like this:
<li class="nav-item">
<%= link_to "Domains", domains_path, class: "nav-link #{'active' if nav == 'domains'}" %>
</li>
This append to the _nav.html.erb
template happens in addition to the partial itself being included from the layout.
(Also only if it exists, so be sure create it before running the scaffold generators for the namespace. Of course, you only need to create it once for each namespace)
• To create a new _nav.html.erb
template use
bin/rails generate hot_glue:nav_template --namespace=xyz
Here, you give only the namespace. It will create an empty nav template:
<ul class='nav nav-tabs'>
</ul>
• Fixes to specs for datetimes
• Fixes way the flash notices where created that violated frozen string literal
--modify feature allows you to cast a viewable field as a currency or binary
--modify=field1{...},field2{...}
You can apply the modification to the viewable (non-edit) display of the field using the --modify
switch.
The currency syntax is --modify=cost{$},price{$}
Here, the cost
and price
fields will be displayed as wrapped in number_to_currency()
when displayed on the list view and when displayed as show-only.
You can also use a binary modifier, which can apply to booleans, datetimes, times, dates or anything else. When using the binary modify, a specific value is displayed if the field is truthy and another one is display if the field is falsy.
You specify it using a pipe | character like so:
--modify=paid_at{paid|unpaid}
here, even though paid_at
is a datetime field, it will display as-if it is a binary— showing either the truthy label or the falsy label depending on if paid_at
is or is not null in the database. For all fields except booleans, this affects only the viewable output — what you see on the list page and on the edit page for show-only fields. For booleans, it affects those outputs as well as the normal edit output: The labels you specify are used as the labels for the radio buttons that the user can select.
You must separately specify these as show-only if you want them to be non-editable.
The available modifiers are:
modifier | what it does | can be used on | ||
---|---|---|---|---|
$ | wraps output in number_to_currency() |
float, integer | ||
truthy label|falsy label | specify a binary switch with a pipe (|) character if the value is truthy, it will display as "truthy label" if the value is falsy, it will display as "falsy label" | boolean, datetime, date, time | ||
Big edit automatically updates parent, better test support
- When using big edit, updating a child will now re-render the parent EDIT record automatically.
For example
rails generate hot_glue:scafold Invoice --big-edit
rails generate hot_glue:scafold LineItem --nested=invioce
Whenever the line item is created, updated, or destroyed, the parent invoice record gets (edit action) re-rendered automatically. This happens for the big edit screen of the invoice.
-
Refactors fields into polymoric objects
-
Adds test coverage for Postgres Enums
-
Fixes tests on CI (and switches to CircleCI)