Skip to content

Commit

Permalink
Jfb/2024 12 25 passdown fixes (#183)
Browse files Browse the repository at this point in the history
* adds  option to supress writing to the _nav template; the _nav template itself can now end with either .html.erb or .erb; Removing feature: optionalized nested params (this was a bad idea); enum partials now correctly render within a namespace; fixes for detecting missing belongs_to relationships; fixes to post-create and post-update parental reloads;

* specs

* v0.6.10

* gemfile.lock
  • Loading branch information
jasonfb authored Dec 26, 2024
1 parent 87706b1 commit 05fefbd
Show file tree
Hide file tree
Showing 15 changed files with 88 additions and 78 deletions.
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
hot-glue (0.6.9.1)
hot-glue (0.6.10)
ffaker (~> 2.16)
kaminari (~> 1.2)
rails (> 5.1)
Expand Down Expand Up @@ -139,7 +139,7 @@ GEM
mini_mime (1.1.2)
mini_portile2 (2.8.4)
minitest (5.16.3)
net-imap (0.5.1)
net-imap (0.5.4)
date
net-protocol
net-pop (0.1.2)
Expand Down Expand Up @@ -235,7 +235,7 @@ GEM
stimulus-rails (1.1.1)
railties (>= 6.0.0)
thor (1.2.1)
timeout (0.4.2)
timeout (0.4.3)
turbo-rails (1.3.2)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
Expand Down
45 changes: 15 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,34 +413,6 @@ Then, finally the @charge will be loaded
This is "starfish access control" or "poor man's access control." It works when the current user has several things they can manage, and by extension can manage children of those things.


#### Optionalized Nested Parents

Add `~` in front of any nested parameter (any parent in the `--nested` list) you want to make optional. This creates a two-headed controller: It can operate with or without that optionalized parameter.

This is an advanced feature. To use, **make duplicative routes to the same controller**. You can only use this feature with Gd controller.

Specify your controller *twice* in your routes.rb. Then, in your `--nested` setting, add `~` to any nested parent you want to **make optional**. "Make optional" means the controller will behave as-if it exists in two places: once, at the normal nest level. Then the same controller will 'exist' again one-level up in your routes. **If the route has sub-routes, you'll need to re-specify the entire subtree also**.
```
namespace :admin
resources :users do
resources :invoices
end
resources :invoices
end
```

Even though we have two routes pointed to **invoices**, both will go to the same controller (`app/controllers/admin/invoices_controller.rb`)

```
./bin/rails generate hot_glue:scaffold User --namespace=admin --gd
./bin/rails generate hot_glue:scaffold Invoice --namespace=admin --gd --nested=~users
```
Notice for the Invoice build, the parent user is *optionalized* (not 'optional'-- optionalized: to be made so it can be made optional).

The Invoices controller, which is a Gd controller, will load the User if a user is specified in the route (`/admin/users/:user_id/invoices/`). It will ALSO work at `/admin/invoices` and will switch back into loading directly from the base class when routed without the parent user.


### `--auth=`

By default, it will be assumed you have a `current_user` for your user authentication. This will be treated as the "authentication root" for the "poor man's auth" explained above.
Expand Down Expand Up @@ -1454,7 +1426,7 @@ Always:

Don't include this last line in your factory code.

## Nav Templates
## Nav Templates and `--no-nav-menu`
At the namespace level, you can have a file called `_nav.html.erb` to create tabbed bootstrap nav

To create the file for the first time (at each namespace), start by running
Expand All @@ -1478,6 +1450,10 @@ Once the file is present, any further builds in this namespace will:
```
(In this example `owner/` is the namespace and `things` is the name of the scaffold being built)

To suppress this behavior, add `--no-nav-menu` to the build command and the _nav template will not be touched.



## Automatic Base Controller

Hot Glue will copy a file named `base_controller.rb` to the same folder where it tries to create any controller (so to the namespace), unless such a file exists there already.
Expand Down Expand Up @@ -1686,10 +1662,19 @@ These automatic pickups for partials are detected at buildtime. This means that


# VERSION HISTORY

#### 2024-12-25 v0.6.10
• adds `--no-nav-menu` option to supress writing to the _nav template
• the _nav template itself can now end with either .html.erb or .erb
• Removing feature: optionalized nested params (this was a bad idea)
• enum partials now correctly render within a namespace
• fixes for detecting missing belongs_to relationships
• fixes to post-create and post-update parental reloads


#### 2024-12-17 v0.6.9.2
• adds alt_lookup to related_set_field.rb and fixes a variable passdown problem in edit.html.erb


#### 2024-12-16 v0.6.9.1
• Fixes hardcoding in #new action

Expand Down
1 change: 1 addition & 0 deletions lib/generators/hot_glue/field_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def initialize(type: , name: , generator: )
attachment_data: generator.attachments[name.to_sym],
sample_file_path: generator.sample_file_path,
modify_as: generator.modify_as[name.to_sym] || nil,
plural: generator.plural,
display_as: generator.display_as[name.to_sym] || nil,
default_boolean_display: generator.default_boolean_display,
namespace: generator.namespace_value,
Expand Down
14 changes: 8 additions & 6 deletions lib/generators/hot_glue/fields/association_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ class AssociationField < Field

attr_accessor :assoc_name, :assoc_class, :assoc, :alt_lookup

def initialize( alt_lookup: , class_name: , default_boolean_display:, display_as: ,
name: , singular: ,
update_show_only: ,
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
form_labels_position:, modify_as: , self_auth: , namespace:, pundit: )
def initialize( alt_lookup: ,
class_name: ,
default_boolean_display:, display_as: ,
name: , singular: ,
update_show_only: ,
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
form_labels_position:, modify_as: , self_auth: , namespace:, pundit: , plural: )
super


Expand Down
1 change: 1 addition & 0 deletions lib/generators/hot_glue/fields/attachment_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class AttachmentField < Field
attr_accessor :attachment_data
def initialize(alt_lookup:,
attachment_data:,
plural:,
auth:,
class_name:,
display_as:, singular:,
Expand Down
2 changes: 1 addition & 1 deletion lib/generators/hot_glue/fields/enum_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def line_field_output
end

def partial_render
"<% if #{singular}.#{name} %><%= render partial: #{singular}.#{name}, locals: { #{singular}: #{singular} } %><% end %>"
"<% if #{singular}.#{name} %><%= render partial: \"#{namespace + "/" if namespace}#{plural}/\#{#{singular}.#{name}}\", locals: { #{singular}: #{singular} } %><% end %>"
end


Expand Down
6 changes: 4 additions & 2 deletions lib/generators/hot_glue/fields/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Field
:hawk_keys, :layout_strategy, :limit, :modify_as, :name, :object, :sample_file_path,
:self_auth,
:singular_class, :singular, :sql_type, :ownership_field,
:update_show_only, :namespace, :pundit
:update_show_only, :namespace, :pundit, :plural

def initialize(
auth: ,
Expand All @@ -26,7 +26,8 @@ def initialize(
update_show_only:,
self_auth:,
namespace:,
pundit:
pundit: ,
plural:
)
@name = name
@layout_strategy = layout_strategy
Expand All @@ -43,6 +44,7 @@ def initialize(
@modify_as = modify_as
@display_as = display_as
@pundit = pundit
@plural = plural

@self_auth = self_auth
@default_boolean_display = default_boolean_display
Expand Down
60 changes: 35 additions & 25 deletions lib/generators/hot_glue/scaffold_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
:singular_class, :smart_layout, :stacked_downnesting,
:update_show_only, :ownership_field,
:layout_strategy, :form_placeholder_labels,
:form_labels_position, :pundit,
:form_labels_position, :no_nav_menu, :pundit,
:self_auth, :namespace_value, :record_scope, :related_sets,
:search_clear_button, :search_autosearch
# important: using an attr_accessor called :namespace indirectly causes a conflict with Rails class_name method
Expand All @@ -37,15 +37,13 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
class_option :singular_class, type: :string, default: nil
class_option :nest, type: :string, default: nil # DEPRECATED —— DO NOT USE
class_option :nested, type: :string, default: ""

class_option :namespace, type: :string, default: nil
class_option :auth, type: :string, default: nil
class_option :auth_identifier, type: :string, default: nil
class_option :exclude, type: :string, default: ""
class_option :include, type: :string, default: ""
class_option :god, type: :boolean, default: false
class_option :gd, type: :boolean, default: false # alias for god

class_option :specs_only, type: :boolean, default: false
class_option :no_specs, type: :boolean, default: false
class_option :no_delete, type: :boolean, default: false
Expand All @@ -58,7 +56,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
class_option :big_edit, type: :boolean, default: false
class_option :show_only, type: :string, default: ""
class_option :update_show_only, type: :string, default: ""

class_option :ujs_syntax, type: :boolean, default: nil
class_option :downnest, type: :string, default: nil
class_option :magic_buttons, type: :string, default: nil
Expand All @@ -71,7 +68,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
class_option :layout, type: :string, default: nil # if used here it will override what is in the config
class_option :hawk, type: :string, default: nil
class_option :with_turbo_streams, type: :boolean, default: false

class_option :label, default: nil
class_option :list_label_heading, default: nil
class_option :new_button_label, default: nil
Expand Down Expand Up @@ -101,8 +97,11 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
class_option :code_before_update, default: nil
class_option :code_after_update, default: nil
class_option :record_scope, default: nil
class_option :no_nav_menu, type: :boolean, default: false # suppress writing to _nav template



# SEARCH OPTIONS
class_option :search, default: nil # set or predicate

# FOR THE SET SEARCH
Expand All @@ -111,11 +110,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
# for the single-entry search box, they will be removed from the list specified above.
class_option :search_query_fields, default: '' # comma separated list of fields to search by single-entry search term
class_option :search_position, default: 'vertical' # choices are vertical or horizontal


class_option :search_clear_button, default: false
class_option :saerch_autosearch, default: false

class_option :search_autosearch, default: false

# FOR THE PREDICATE SEARCH
# TDB
Expand Down Expand Up @@ -343,6 +339,9 @@ def initialize(*meta_args)
@record_scope = options['record_scope']

@pundit = options['pundit']

@no_nav_menu = options['no_nav_menu']

if @pundit.nil?
@pundit = get_default_from_config(key: :pundit_default)
end
Expand Down Expand Up @@ -788,12 +787,7 @@ def identify_object_owner
raise(HotGlue::Error, exit_message)

else
if eval(singular_class + ".reflect_on_association(:#{@object_owner_sym.to_s})").nil? && !eval(singular_class + ".reflect_on_association(:#{@object_owner_sym.to_s.singularize})").nil?
exit_message = "*** Oops: you tried to nest #{singular_class} within a route for `#{@object_owner_sym}` but I can't find an association for this relationship. Did you mean `#{@object_owner_sym.to_s.singularize}` (singular) instead?"
# else # NOTE: not reachable
# exit_message = "*** Oops: Missing relationship from class #{singular_class} to :#{@object_owner_sym} maybe add `belongs_to :#{@object_owner_sym}` to #{singular_class}\n (If your user is called something else, pass with flag auth=current_X where X is the model for your auth object as lowercase. Also, be sure to implement current_X as a method on your controller. If you really don't want to implement a current_X on your controller and want me to check some other method for your current user, see the section in the docs for --auth-identifier flag). To make a controller that can read all records, specify with --god."
end

exit_message = "When trying to nest #{singular_class} within #{@nested_set.last[:plural]}, check the #{singular_class} model for the #{@object_owner_sym} association: \n belongs_to :#{@object_owner_sym}"
raise(HotGlue::Error, exit_message)
end
elsif @object_owner_sym && !@object_owner_eval.include?(".")
Expand Down Expand Up @@ -1011,6 +1005,10 @@ def objest_nest_params_by_id_for_specs
}.join(",\n ")
end

def nest_path
@nested_set.collect{| arg| arg[:singular] }.join("/") + "/" if @nested_set.any?
end

def controller_class_name
@controller_build_name
end
Expand Down Expand Up @@ -1238,7 +1236,8 @@ def nav_template
end

def include_nav_template
File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.html.#{@markup}")
File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.html.#{@markup}") ||
File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.#{@markup}")
end

def copy_view_files
Expand Down Expand Up @@ -1303,11 +1302,16 @@ def append_model_callbacks
end
end

def insert_into_nav_template
# how does this get called(?)
nav_file = "#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.html.#{@markup}"

def insert_into_nav_template # called from somewhere in the generator
return if @no_nav_menu
if include_nav_template
if File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.html.#{@markup}")
nav_file = "#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.html.#{@markup}"
elsif File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.#{@markup}")
nav_file = "#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.#{@markup}"
end


append_text = " <li class='nav-item'>
<%= link_to '#{@list_label_heading.humanize}', #{path_helper_plural(@nested_set.any? ? true: false)}, class: \"nav-link \#{'active' if nav == '#{plural_name}'}\" %>
</li>"
Expand Down Expand Up @@ -1604,20 +1608,26 @@ def any_datetime_fields?

def post_action_parental_updates
if @nested_set.any?
@nested_set.collect { |data|
@nested_set.collect { |data|
parent = data[:singular]
"@#{singular}.#{parent}.reload"
}
"#{parent}.reload"
}
else
[]
end
end

def turbo_parental_updates
set_path = @nested_set.collect { |data| "#{data[:singular]}" }.join("/") + "/"
puts "constructing for #{set_path}"
@nested_set.collect { |data|
"<%= turbo_stream.replace \"__#{@namespace if @namespace}\#{dom_id(@#{data[:singular]})}\" do %>
<%= render partial: \"#{@namespace}/#{data[:plural]}/line\", locals: {#{data[:singular]}: @#{singular}.#{data[:singular]}.reload} %>
res = "<%= turbo_stream.replace \"__#{@namespace if @namespace}\#{dom_id(@#{data[:singular]})}\" do %>
<%= render partial: \"#{@namespace}/#{data[:plural]}/line\", locals: {#{@nested_set.collect { |d|
(set_path.index(data[:singular] + "/") > set_path.index(d[:singular] + "/") || d[:singular] == data[:singular] ) ? "#{d[:singular]}: @#{d[:singular]}" : nil
}.compact.join(", ")}} %>
<% end %>"

res
}.join("\n")
end
end
11 changes: 6 additions & 5 deletions lib/generators/hot_glue/templates/controller.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
<% if !@self_auth %>
def load_<%= singular_name %>
<% if @nested_set[0] && @nested_set[0][:optional] %>if params.include?(:<%= @nested_set.last[:singular] %>_id)
@<%= singular_name %> = <%= object_scope.gsub("@",'') %><%= @record_scope %>.find(params[:id])
else <% end %>@<%= singular_name %> = <%= object_scope %><%= @record_scope %>.find(params[:id])<% if @nested_set[0] && @nested_set[0][:optional] %>
@<%= singular_name %> = <%= object_scope.gsub("@",'') %>.find(params[:id])
else <% end %>@<%= singular_name %> = <%= object_scope %>.find(params[:id])<% if @nested_set[0] && @nested_set[0][:optional] %>
end<% end %>
end
<% else %>
def load_<%= singular_name %>
@<%= singular_name %> = (<%= auth_object.gsub("@",'') %><%= " if params.include?(:#{@nested_set[0][:singular]}_id)" if @nested_set.any? && @nested_set[0][:optional] %>)<% if @nested_set.any? && @nested_set[0][:optional] %> || <%= class_name %>.find(params[:id])<% end %><%= @record_scope %>
@<%= singular_name %> = (<%= auth_object.gsub("@",'') %><%= " if params.include?(:#{@nested_set[0][:singular]}_id)" if @nested_set.any? && @nested_set[0][:optional] %>)<% if @nested_set.any? && @nested_set[0][:optional] %> || <%= class_name %>.find(params[:id])<% end %>
end<% end %>
<% if @paginate_per_page_selector %>def per
params[:per] || 10
Expand Down Expand Up @@ -115,7 +115,7 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
<%= @code_before_create ? "\n " + @code_before_create.gsub(";", "\n") : "" %>
if @<%= singular_name %>.save<%= @code_after_create ? ("\n " + @code_after_create.gsub(";", "\n")) : ""%>
flash[:notice] = "Successfully created #{@<%= singular %>.<%= display_class %>}"
<%= post_action_parental_updates.join("\n ") %>
<%= post_action_parental_updates.compact.join("\n ") %>
load_all_<%= plural %>
<% unless @display_edit_after_create %>render :create<% else %>redirect_to <%= HotGlue.optionalized_ternary(namespace: @namespace,
top_level: true,
Expand Down Expand Up @@ -181,10 +181,11 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
authorize @<%= singular_name %>
<%= @code_before_update ? "\n " + @code_before_update.gsub(";", "\n") : "" %>
@<%= singular_name %>.save
<% else %>
<% else %>
<%= @code_before_update ? "\n " + @code_before_update.gsub(";", "\n") : "" %>
if @<%= singular_name %>.update(modified_params)
<% end %>
<%= post_action_parental_updates.compact.join("\n ") %>
<%= @code_after_update ? "\n " + @code_after_update.gsub(";", "\n") : "" %>
<% if @display_list_after_update %> load_all_<%= plural %><% end %>
flash[:notice] << "Saved #{@<%= singular %>.<%= display_class %>}"
Expand Down
2 changes: 2 additions & 0 deletions lib/generators/hot_glue/templates/erb/create.turbo_stream.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<\%= render partial: "list", locals: {<%= plural %>: @<%= plural %>}<%= @nested_set.collect{|arg| ".merge(@" + arg[:singular] + " ? {nested_for: \"" + arg[:singular] + "-\#{@" + arg[:singular] + ".id}\"" + ", " + arg[:singular] + ": @" + arg[:singular] + "} : {})"}.join() %> \%>
<\% end %>
<\% end %>
<!-- parental updated -->
<%= turbo_parental_updates %>
<!-- errors -->
<\%= turbo_stream.replace "<%= @namespace %>__<%= singular %>-new" do %>
<\% if @<%= singular %>.errors.none? %>
<\%= render partial: "new_button", locals: {}<%= @nested_set.collect{|arg| ".merge(@" + arg[:singular] + " ? {" + arg[:singular] + ": @" + arg[:singular] + "} : {})"}.join() %> %>
Expand Down
2 changes: 1 addition & 1 deletion lib/generators/hot_glue/templates/erb/edit.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<% each_downnest_width = @downnest_children.count == 1 ? 33 : (53/@downnest_children.count).floor %>
<% @downnest_object.each do |downnest, size| %>
<div class="row">
<div class="col-md-6">
<div class="col-md-<%= @big_edit ? 12 : 6 %>">
<% downnest_object = eval("#{singular_class}.reflect_on_association(:#{downnest})") %>
<% if downnest_object.nil?; raise "no relationship for downnested portal `#{downnest}` found on `#{singular_class}`; please check relationship for has_many :#{downnest}"; end; %>
<% downnest_class = downnest_object.class_name %>
Expand Down
Loading

0 comments on commit 05fefbd

Please sign in to comment.