hot-glue 0.6.24 → 0.6.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6f0b7db93d319807a09627a2272bbe451b7de1d6c18a2599a26ebf95183b6cd
4
- data.tar.gz: 8ab6b66aba18ac70f1cbb98dbce90fe92a9c7bf81700e292566c9426504695c4
3
+ metadata.gz: 0cfc8d749ec345dbcb5d02abf0c850e80a4c3265f02c259f719022a8125fdd12
4
+ data.tar.gz: 02ec813fd1529fd21efacab451c9ce1f5c2eaa5704177ade118f9dff0f412449
5
5
  SHA512:
6
- metadata.gz: 194e75f4fff99b371bdd8b0a6130c7bc158ebab17f2daf530e09afaf7844ddb5b40df63c2d51fcd483430beea65c78013eaaa15176f3113d4588c8b069e7fb25
7
- data.tar.gz: f1f665a844f0fd0a33077d2780b215538247d8a4241dcf9dad93a586a545d3a047722e442501a9f7389475db80271a5a7d69a579e58f00075fe4466e609994b3
6
+ metadata.gz: aa56cf2a1e0f84aec69426daab2c7e2d7dc2fcf3771ff2e797a387e3da37995f72f97074b11cc50a90f38ac16f15b10e211b63ef5f2f72f79d51b918456c2ad3
7
+ data.tar.gz: b005066d967a6ed1377b4ecefcd62ca2223c0d20d36861fa1b4a03e04309e84c6675d9a07c00caf831b211fd12d0482d54414573fa6cb44c8a7de39aa2131911
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hot-glue (0.6.24)
4
+ hot-glue (0.6.25)
5
5
  ffaker (~> 2.16)
6
6
  kaminari (~> 1.2)
7
7
  rails (> 5.1)
data/README.md CHANGED
@@ -1657,6 +1657,43 @@ Here's how you would add a search interface to Example #1 in the [Hot Glue Tutor
1657
1657
  bin/rails generate Book --include=name,author_id --search=set --search-fields=name,author_id
1658
1658
  ```
1659
1659
 
1660
+
1661
+ `--phantom-search='{type}_{name}[All|choice A:scope_a|choice B:scope_b],radio_yyyy[choice C:scope_c|]`
1662
+
1663
+ A phantom search is a search we are doing on this result set that doesn't correspond to a single field. Currently, the only available implementation is for scopes with no arguments, as in the example below. It is called 'phantom' because it could be (probably is) querying fields within the scope, but the search doesn't match up with a single field on your model. (So it's like creating 'phantom' criteria.). Only RADIO type is implemented, dropdown & checkboxes an a way to input a search value passed into the scope as an argument is TBD.
1664
+
1665
+ {type} is any of: radio, dropdown, checkboxes
1666
+
1667
+ {name} is a designation for this phantom search. Should NOT match any field name on your table. This should describe the kind of categorization we are performing.
1668
+
1669
+
1670
+ Your phantom search selector will be appended to the search fields and will be treated like a first-class search input, able to be combined with any of the other fields specified in a set search.
1671
+
1672
+ After the type & name, comes a block marked by square braces [ ... ] . Within the square braces, each search option is separated by a pipe (|) character. Within each option is a label & ruby scope, separated by a colon (:). The label comes before the colon the ruby scope. The scope should be specified here without a dot should be defined on your model. If there is scope specified, we assume "all", but we still need to specify a label for "All", which is why in the example above "All" has no colon after it.
1673
+
1674
+
1675
+ ### `--phantom-search='radio_status[Pending:pending|Rejected:rejected|Accepted:accepted]|All'`
1676
+
1677
+ On my model, I have these scopes defined:
1678
+
1679
+ scope :pending_review, -> { not_approved.not_rejected }
1680
+ scope :approved, -> { where.not(approved_at: nil) }
1681
+ scope :rejected, -> { where.not(rejected_at: nil) }
1682
+ scope :not_rejected, -> { where(rejected_at: nil) }
1683
+ scope :not_approved, -> { where(approved_at: nil) }
1684
+
1685
+
1686
+ This produces a search interface with four options listed as radio buttons:
1687
+ ° Pending
1688
+ ° Approved
1689
+ ° Rejected
1690
+ ° All
1691
+
1692
+ (Notice that my search options are called 'approved' and 'rejected' but the field names are slightly different: approved_at, rejected_at)
1693
+
1694
+ The pending, approved, and rejected options will return search results with the corresponding scopes applied. The 'All' option will behave as a no-op, leaving the root search intact (giving all of the other modifications that Hot glue provides in different functionality).
1695
+
1696
+
1660
1697
  #### Predicate Search
1661
1698
  NOT IMPLEMENTED YET
1662
1699
  TODO: implement me
@@ -2042,6 +2079,11 @@ has_many :users, through: :account_users
2042
2079
  ```
2043
2080
 
2044
2081
 
2082
+ Notice that from the table of things referencing the typeahead thing,
2083
+ the `--modify` takes a 3rd parameter in `[...]` to specify the nested set if the typeahead itself is nested.
2084
+
2085
+ As with this example, this means that the object referencing the typeahead object must be in the same namespace, but does not need to be at the same nest level.
2086
+
2045
2087
  `bin/rails generate hot_glue:scaffold Member --auth='current_user' --auth-identifier='user' --auth-identifier=user --modify='user_id{typeahead}[account]'`
2046
2088
 
2047
2089
  in our routes.rb file, we have
@@ -2161,6 +2203,25 @@ These automatic pickups for partials are detected at build time. This means that
2161
2203
 
2162
2204
 
2163
2205
  # VERSION HISTORY
2206
+ #### 2025-09-16 - v0.6.26
2207
+ • Phantom Searching
2208
+ `--phantom-search='{type}_{name}[All|choice A:scope_a|choice B:scope_b],radio_yyyy[choice C:scope_c|]`
2209
+
2210
+ A phantom search is a search we are doing on this result set that doesn't correspond to a single field. Currently, the only available implementation is for scopes with no arguments, as in the example below. It is called 'phantom' because it could be (probably is) querying fields within the scope, but the search doesn't match up with a single field on your model. (So it's like creating 'phantom' criteria.). Only RADIO type is implemented, dropdown & checkboxes an a way to input a search value passed into the scope as an argument is TBD.
2211
+
2212
+ {type} is any of: radio, dropdown, checkboxes
2213
+
2214
+ {name} is a designation for this phantom search. Should NOT match any field name on your table. This should describe the kind of categorization we are performing.
2215
+ see `--phantom-search` above for details.
2216
+
2217
+ • Fixing duplicitous creation of Stimulus JS files when installing the search features
2218
+
2219
+
2220
+ #### 2025-08-31 - v0.6.25
2221
+ - very small fix to typeahead controller supporting typeaheads and nested routes
2222
+ - updates documentation for typeaheads
2223
+
2224
+
2164
2225
  #### 2025-08-22 - v0.6.24
2165
2226
  `--related-sets` fixes issue with related sets due to ruby syntax
2166
2227
 
@@ -100,6 +100,7 @@ class AssociationField < Field
100
100
  parts = name.split('_')
101
101
  "<%= f.text_field :__lookup_#{assoc}_#{lookup_field}, value: @#{singular}.#{assoc_name}&.#{lookup_field}, placeholder: \"#{lookup_field}\" " + (stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + "%>"
102
102
  elsif modify_as && modify_as[:typeahead]
103
+
103
104
  search_url = "#{namespace ? namespace + "_" : ""}" +
104
105
  modify_as[:nested].join("_") + ( modify_as[:nested].any? ? "_" : "") +
105
106
  + "#{assoc.class_name.downcase.pluralize}_typeahead_index_url"
@@ -12,7 +12,7 @@ module HotGlue
12
12
  :search, :search_fields, :search_query_fields, :search_position,
13
13
  :form_path, :layout_object, :search_clear_button, :search_autosearch,
14
14
  :stimmify, :stimmify_camel, :hidden_create, :hidden_update, :invisible_create,
15
- :invisible_update, :plural
15
+ :invisible_update, :plural, :phantom_search
16
16
 
17
17
 
18
18
  def initialize(singular:, singular_class: ,
@@ -25,7 +25,7 @@ module HotGlue
25
25
  search:, search_fields:, search_query_fields: , search_position:,
26
26
  search_clear_button:, search_autosearch:, layout_object:,
27
27
  form_path: , stimmify: , stimmify_camel:, hidden_create:, hidden_update: ,
28
- invisible_create:, invisible_update: , plural: )
28
+ invisible_create:, invisible_update: , plural: , phantom_search:)
29
29
 
30
30
 
31
31
  @form_path = form_path
@@ -65,6 +65,7 @@ module HotGlue
65
65
  @update_show_only = update_show_only
66
66
  @attachments = attachments
67
67
  @related_sets = related_sets
68
+ @phantom_search = phantom_search
68
69
  end
69
70
 
70
71
  def add_spaces_each_line(text, num_spaces)
@@ -130,18 +131,43 @@ module HotGlue
130
131
  search_field_result = columns_map[col].search_field_output
131
132
 
132
133
  add_spaces_each_line( "\n <span class='' >\n" +
133
- add_spaces_each_line( (form_labels_position == 'before' ? the_label || "" : "") +
134
- + " <br />\n" + search_field_result +
135
- (form_labels_position == 'after' ? the_label : "") , 4) +
134
+ add_spaces_each_line( (form_labels_position == 'before' ? the_label || "" : "") +
135
+ + " <br />\n" + search_field_result +
136
+ (form_labels_position == 'after' ? the_label : "") , 4) +
136
137
  "\n </span>\n <br />", 2)
137
138
  end
138
139
  }.compact.join("\n")
139
140
 
141
+
142
+
140
143
  size = layout_object[:columns][:bootstrap_column_width][columns.index(column)]
141
144
  " <div class='#{layout_strategy.column_classes_for_form_fields(size)} search-cell--#{singular}--#{column.join("-")}' >" +
142
145
  cols_result + "</div>"
143
146
 
144
147
  }.join("\n")
148
+
149
+ # phantom searches
150
+ @phantom_search.each_key do |search_field|
151
+ data = @phantom_search[search_field]
152
+ if data[:type] == "radio"
153
+ res << "<div>"
154
+
155
+ res << "<label>#{data[:name]}</label><br />"
156
+
157
+ data[:choices].each do |choice|
158
+ res << "\n<input type='radio'
159
+ id='#{search_field}_search__#{choice[:label]}'
160
+ name='q[0][#{search_field}_search]' value='#{choice[:label]}'
161
+ <%= 'checked' if @q['0'][:#{search_field}_search] == \"#{choice[:label]}\" %> />"
162
+ res << "\n<label for='#{search_field}_search__#{choice[:label]}'>#{choice[:label]}</label> <br/>"
163
+ end
164
+
165
+ res << "</div>"
166
+
167
+ end
168
+ end
169
+
170
+
145
171
  res << "</div>"
146
172
  res << "<div class='#{layout_strategy.column_classes_for_form_fields(nil)}'>"
147
173
  if @search_clear_button
@@ -134,6 +134,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
134
134
  # TDB
135
135
 
136
136
 
137
+ class_option :phantom_search, default: nil
138
+
139
+
140
+
137
141
  def initialize(*meta_args)
138
142
  super
139
143
 
@@ -449,6 +453,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
449
453
  end
450
454
 
451
455
 
456
+
457
+
452
458
  @include_object_names = options['include_object_names'] || get_default_from_config(key: :include_object_names)
453
459
 
454
460
 
@@ -699,11 +705,56 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
699
705
  end
700
706
  end
701
707
 
708
+
709
+ if options['phantom_search']
710
+ ps_input = options['phantom_search']
711
+
712
+ ps_input =~ /(.*)\[(.*)\]/
713
+ type_and_label, settings = $1, $2
714
+
715
+
716
+ type = type_and_label.split("_")[0]
717
+ label = type_and_label.split("_")[1]
718
+
719
+ @phantom_search = {}
720
+ choices = settings.split("|")
721
+
722
+
723
+ @phantom_search[label.to_sym] = {
724
+ type: type,
725
+ name: label.humanize,
726
+ choices: []
727
+ }
728
+
729
+ choices.each do |choice|
730
+ choice_label = choice.split(":")[0]
731
+ choice_scope = choice.split(":")[1]
732
+ if choice_scope.nil?
733
+ choice_scope = "all"
734
+ end
735
+
736
+ choice_scope = ".#{choice_scope}" if !choice_scope.start_with?(".")
737
+ @phantom_search[label.to_sym][:choices] << {
738
+ label: choice_label,
739
+ scope: choice_scope,
740
+ }
741
+ end
742
+ puts "phantom search #{@phantom_search}"
743
+ else
744
+ @phantom_search = {}
745
+ end
746
+
702
747
  # search
703
748
  @search = options['search']
749
+
704
750
  if @search == 'set'
705
751
  if options['search_fields'].nil?
706
- @search_fields = @columns
752
+ if !@phantom_search
753
+ puts "Since you did not specify search fields or phantom search fields I am including all columns in search"
754
+ @search_fields = @columns
755
+ else
756
+ @search_fields = []
757
+ end
707
758
  else
708
759
  @search_fields = options['search_fields'].split(',')
709
760
  end
@@ -715,12 +766,6 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
715
766
 
716
767
  @search_fields = @search_fields - @search_query_fields
717
768
 
718
- @search_fields.each do |field|
719
- if !@columns.include?(field.to_sym)
720
- raise "You specified a search field for #{field} but that field is not in the list of columns"
721
- end
722
- end
723
-
724
769
  @search_clear_button = !!options['search_clear_button']
725
770
  @search_autosearch = !!options['search_autosearch']
726
771
 
@@ -728,22 +773,24 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
728
773
 
729
774
  end
730
775
 
776
+ if @search_fields
777
+ @search_fields.each do |field|
778
+ if !@columns.include?(field.to_sym) && !@phantom_search.include?(field.to_sym)
779
+ raise "You specified a search field for #{field} but that field is not in the list of columns"
780
+ end
781
+ end
782
+ end
783
+
731
784
  builder = HotGlue::Layout::Builder.new(generator: self,
732
785
  include_setting: options['include'],
733
786
  buttons_width: buttons_width)
734
787
 
735
788
  @layout_object = builder.construct
736
789
 
737
-
738
-
739
790
  # syntax should be xyz_id{xyz_email},abc_id{abc_email}
740
791
  # instead of a drop-down for the foreign entity, a text field will be presented
741
792
  # You must ALSO use a factory that contains a parameter of the same name as the 'value' (for example, `xyz_email`)
742
793
 
743
-
744
-
745
-
746
-
747
794
  # create the template object
748
795
 
749
796
 
@@ -780,6 +827,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
780
827
  hidden_update: @hidden_update,
781
828
  invisible_create: @invisible_create,
782
829
  invisible_update: @invisible_update,
830
+ phantom_search: @phantom_search
783
831
  )
784
832
  elsif @markup == "slim"
785
833
  raise(HotGlue::Error, "SLIM IS NOT IMPLEMENTED")
@@ -1741,12 +1789,11 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
1741
1789
  if !@columns_map[field.to_sym].load_all_query_statement.empty?
1742
1790
  @columns_map[field.to_sym].load_all_query_statement
1743
1791
  end
1744
- }.compact.join("\n" + spaces(4))
1745
- res << "\n"
1792
+ }.compact.join("\n" + spaces(4)) + "\n"
1746
1793
  end
1747
1794
 
1748
1795
  if pundit
1749
- res << " @#{ plural_name } = policy_scope(#{ object_scope })#{record_scope}.page(params[:page])#{ n_plus_one_includes }#{ ".per(per)" if @paginate_per_page_selector }"
1796
+ res << " @#{ plural_name } = policy_scope(#{ object_scope })#{record_scope}\n"
1750
1797
  else
1751
1798
  if !@self_auth
1752
1799
 
@@ -1760,22 +1807,37 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
1760
1807
  end
1761
1808
  }.compact.join
1762
1809
  end
1763
- res << ".page(params[:page])#{ '.per(per)' if @paginate_per_page_selector }"
1810
+
1811
+
1812
+
1813
+ # res << "\n @#{plural} = @#{plural}.page(params[:page])#{ '.per(per)' if @paginate_per_page_selector }"
1764
1814
 
1765
1815
  elsif @nested_set[0] && @nested_set[0][:optional]
1766
1816
  res << "@#{ plural_name } = #{ class_name }.#{record_scope}.all"
1767
1817
  else
1768
1818
  res << "@#{ plural_name } = #{ class_name }.#{record_scope}.where(id: #{ auth_object.gsub("@",'') }.id)#{ n_plus_one_includes }"
1769
1819
 
1770
- res << "#{record_scope}.page(params[:page])#{ ".per(per)" if @paginate_per_page_selector }"
1820
+ # res << "#{record_scope}"
1771
1821
  end
1822
+ res << "\n"
1823
+
1772
1824
  end
1773
- res << "\n"
1774
1825
  if @search_fields
1826
+ res << "\n"
1775
1827
  res << @search_fields.collect{ |field|
1776
1828
  spaces(4) + "@#{plural_name} = @#{plural_name}" + @columns_map[field.to_sym].where_query_statement + " if #{field}_query"
1777
- }.join("\n")
1829
+ }.join("\n") + "\n"
1830
+ end
1831
+
1832
+ @phantom_search.each do |phantom_key, phantom_data|
1833
+ phantom_data[:choices].each do |choice|
1834
+ unless choice[:scope] == ".all"
1835
+ res << "\n @#{plural} = @#{plural}#{choice[:scope]} if @q['0'][:#{phantom_key}_search] == \"#{choice[:label]}\""
1836
+ end
1837
+ end
1778
1838
  end
1839
+
1840
+ res << " @#{plural} = @#{plural}.page(params[:page])#{ ".per(per)" if @paginate_per_page_selector }"
1779
1841
  res
1780
1842
  end
1781
1843
 
@@ -1830,4 +1892,22 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
1830
1892
  res
1831
1893
  }.join("\n")
1832
1894
  end
1895
+
1896
+
1897
+ def search_default
1898
+ default_fields = @search_fields.collect{ |foo|
1899
+ { "#{foo}_match".to_sym =>
1900
+ (@columns_map[foo.to_sym].modify_as && @columns_map[foo.to_sym].modify_as[:binary]) ? "-1" : "", "#{foo}_search".to_sym => ""
1901
+ }
1902
+ }.reduce({}, :merge)
1903
+
1904
+ phantom_search_fields = @phantom_search.collect{| k,v|
1905
+ default = v[:choices][0]
1906
+ {
1907
+ "#{k}_match".to_sym => "",
1908
+ "#{k}_search".to_sym => "#{default[:label]}"
1909
+ }
1910
+ }.reduce({}, :merge)
1911
+ return {"0" => (default_fields.merge(phantom_search_fields))}
1912
+ end
1833
1913
  end
@@ -12,10 +12,14 @@ module HotGlue
12
12
  def initialize(*args) #:nodoc:
13
13
  super
14
14
 
15
- ['date_range_picker','time_range_picker','search_form'].each do |file|
15
+ ['date_range_picker',
16
+ 'time_range_picker',
17
+ 'search_form'].each do |file|
18
+
19
+
16
20
  system("./bin/rails generate stimulus #{file.titlecase.gsub(" ", "")}")
17
- copy_file "javascript/#{file}_controller.js", "#{filepath_prefix}app/javascript/controllers/#{file}.js"
18
- puts "HOT GLUE --> copying #{file} stimulus controller into app/javascript/controllers/#{file}.js"
21
+ copy_file "javascript/#{file}_controller.js", "#{filepath_prefix}app/javascript/controllers/#{file}_controller.js"
22
+ puts "HOT GLUE --> copying #{file} stimulus controller into app/javascript/controllers/#{file}_controller.js"
19
23
 
20
24
  end
21
25
  end
@@ -75,8 +75,8 @@ class <%= controller_class_name %> < <%= controller_descends_from %>
75
75
  end<% end %>
76
76
  <% unless @no_list %>
77
77
  def load_all_<%= plural %><% if @search == "set" %>
78
- @q = params[:q] || <%= {"0" => @search_fields.collect{|foo| {"#{foo}_match".to_sym => ((@columns_map[foo.to_sym].modify_as && @columns_map[foo.to_sym].modify_as[:binary]) ? "-1" : ""), "#{foo}_search".to_sym => ""}}.reduce({}, :merge) } %> <% end %>
79
- <%= load_all_code %>
78
+ @q = params[:q] || <%= search_default %> <% end %>
79
+ <%= load_all_code %>
80
80
  end
81
81
 
82
82
  def index
@@ -14,7 +14,7 @@ class <%= ((@namespace.titleize.gsub(" ", "") + "::" if @namespace) || "") + @pl
14
14
  <% nest_chain = [] %>
15
15
  <% @nested_set.each { |arg|
16
16
  if @auth_identifier == arg[:singular]
17
- this_scope = auth_object
17
+ this_scope = auth_object
18
18
  elsif nest_chain.empty?
19
19
  this_scope = "#{@auth ? @auth : class_name}.#{arg[:plural]}"
20
20
  else
@@ -103,6 +103,10 @@ module HotGlue
103
103
  end
104
104
 
105
105
 
106
+ def auth_object
107
+ "#{@auth}_id"
108
+ end
109
+
106
110
  def namespace_with_dash
107
111
  if @namespace
108
112
  "/#{@namespace}"
@@ -1,5 +1,5 @@
1
1
  module HotGlue
2
2
  class Version
3
- CURRENT = '0.6.24'
3
+ CURRENT = '0.6.26'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hot-glue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.24
4
+ version: 0.6.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Fleetwood-Boldt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-22 00:00:00.000000000 Z
11
+ date: 2025-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails