hot-glue 0.4.7 → 0.4.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/.gitignore +3 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +11 -1
- data/README.md +261 -252
- data/README2.md +79 -0
- data/lib/generators/hot_glue/install_generator.rb +0 -1
- data/lib/generators/hot_glue/layout/builder.rb +34 -25
- data/lib/generators/hot_glue/markup_templates/erb.rb +81 -81
- data/lib/generators/hot_glue/scaffold_generator.rb +155 -35
- data/lib/generators/hot_glue/templates/controller.rb.erb +31 -33
- data/lib/generators/hot_glue/templates/erb/_line.erb +1 -1
- data/lib/generators/hot_glue/templates/erb/_list.erb +5 -2
- data/lib/generators/hot_glue/templates/erb/_new_form.erb +2 -2
- data/lib/generators/hot_glue/templates/erb/_show.erb +9 -5
- data/lib/generators/hot_glue/templates/erb/create.turbo_stream.erb +4 -4
- data/lib/generators/hot_glue/templates/erb/destroy.turbo_stream.erb +2 -2
- data/lib/generators/hot_glue/templates/erb/edit.erb +2 -2
- data/lib/generators/hot_glue/templates/erb/index.erb +1 -1
- data/lib/generators/hot_glue/templates/erb/new.erb +1 -1
- data/lib/generators/hot_glue/templates/erb/update.turbo_stream.erb +2 -2
- data/lib/hotglue/version.rb +3 -1
- metadata +3 -4
- data/lib/generators/hot_glue/markup_templates/haml.rb +0 -243
- data/lib/generators/hot_glue/markup_templates/slim.rb +0 -9
data/README2.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
|
2
|
+
## LEGACY SETUP FOR RAILS 6
|
3
|
+
|
4
|
+
## Legacy Step #1. ADD HOTWIRE
|
5
|
+
(RAILS 6 ONLY— SKIP THIS STEP FOR RAILS 7)
|
6
|
+
```
|
7
|
+
yarn add @hotwired/turbo-rails
|
8
|
+
```
|
9
|
+
or `npm install @hotwired/turbo-rails`
|
10
|
+
|
11
|
+
|
12
|
+
## Legacy Step #2. SWITCH FROM TurblLinks to Turbo-Rails
|
13
|
+
(RAILS 6 ONLY — SKIP FOR RAILS 7)
|
14
|
+
(THIS WAS AUTOMATICALLY DONE BY THE HOT GLUE INSTALLATION -- CONFIRM CHANGES ONLY)
|
15
|
+
- Add `gem 'turbo-rails'` to your Gemfile & `bundle install`
|
16
|
+
- Then install it with `rails turbo:install`
|
17
|
+
- The Turbo install has switched your action cable settings from 'async' to Redis, so be sure to start a redis server
|
18
|
+
- in `app/javascript/packs/application.js` remove this line
|
19
|
+
```
|
20
|
+
import Turbolinks from "turbolinks"
|
21
|
+
```
|
22
|
+
and replace it with
|
23
|
+
```
|
24
|
+
import { Turbo } from "@hotwired/turbo-rails"
|
25
|
+
```
|
26
|
+
|
27
|
+
|
28
|
+
Also replace
|
29
|
+
```
|
30
|
+
Turbolinks.start()
|
31
|
+
```
|
32
|
+
with:
|
33
|
+
```
|
34
|
+
Turbo.start()
|
35
|
+
```
|
36
|
+
|
37
|
+
|
38
|
+
## Legacy Step #3. INSTALL WEBPACKER
|
39
|
+
(_SKIP FOR RAILS 7_ unless you want to use Webpacker with Rails 7)
|
40
|
+
|
41
|
+
** For webpacker, you must be using Node version ^12.13.0 || ^14.15.0 || >=16 **
|
42
|
+
|
43
|
+
I recommend Node Version Manager (NVM) to switch between nodes. You will not be able to get through the following command with a Node version that does not match above.
|
44
|
+
|
45
|
+
Check your node version with `node -v`
|
46
|
+
|
47
|
+
```
|
48
|
+
`yarn add @rails/webpacker`
|
49
|
+
```
|
50
|
+
|
51
|
+
|
52
|
+
rails webpacker:install
|
53
|
+
|
54
|
+
## Legacy Step #4: Postgresql Enum Support for Rail 6
|
55
|
+
For Enum support, I recommend activerecord-pg_enum
|
56
|
+
Instructions for Rails 6 are here:
|
57
|
+
https://jasonfleetwoodboldt.com/courses/stepping-up-rails/enumerated-types-in-rails-and-postgres/
|
58
|
+
|
59
|
+
_This functionality is now built-in to Rails 7._
|
60
|
+
|
61
|
+
|
62
|
+
## Legacy Step #5: Fix Devise if adding Turbo To Your Project
|
63
|
+
## IMPORTANT: Devise currently has serious compatibility issues with Turbo Rails. In particular, your log-in screens do not work out of the box. Follow the next step to fix them.
|
64
|
+
|
65
|
+
Manually port the Devise views into your app with
|
66
|
+
|
67
|
+
`rails generate devise:views`
|
68
|
+
|
69
|
+
Edit `devise/registrations/new`, `devise/sessions/new`, `devise/passwords/new` and `devise/confirmations/new` modifying all four templates like so:
|
70
|
+
|
71
|
+
form_for(resource, as: resource_name, url: session_path(resource_name) ) do |f|
|
72
|
+
|
73
|
+
add the data-turbo false option in the html key:
|
74
|
+
```bigquery
|
75
|
+
|
76
|
+
```
|
77
|
+
form_for(resource, as: resource_name, **html: {'data-turbo' => "false"},** url: session_path(resource_name) ) do |f|
|
78
|
+
|
79
|
+
This tells Devise to fall back to non-Turbo interaction for the log-in and registration. For the rest of the app, we will use Turbo Rails interactions.
|
@@ -17,7 +17,6 @@ module HotGlue
|
|
17
17
|
super
|
18
18
|
@layout = options['layout'] || "hotglue"
|
19
19
|
@theme = options['theme']
|
20
|
-
|
21
20
|
if @layout == "hotglue" && options['theme'].nil?
|
22
21
|
puts "You have selected to install Hot Glue without a theme. You can either use the --layout=bootstrap to install NO HOT GLUE THEME, or to use a Hot Glue theme please choose: like_boostrap, like_menlo_park, like_cupertino, like_mountain_view, dark_knight"
|
23
22
|
return
|
@@ -3,7 +3,10 @@
|
|
3
3
|
module HotGlue
|
4
4
|
module Layout
|
5
5
|
class Builder
|
6
|
-
attr_reader :include_setting,
|
6
|
+
attr_reader :include_setting,
|
7
|
+
:downnest_children,
|
8
|
+
:buttons_width, :columns,
|
9
|
+
:smart_layout, :specified_grouping_mode
|
7
10
|
|
8
11
|
def initialize(params)
|
9
12
|
@include_setting = params[:include_setting]
|
@@ -14,12 +17,13 @@ module HotGlue
|
|
14
17
|
@no_buttons = @buttons_width == 0
|
15
18
|
@columns = params[:columns]
|
16
19
|
@smart_layout = params[:smart_layout]
|
20
|
+
@specified_grouping_mode = include_setting.include?(":")
|
17
21
|
end
|
18
22
|
|
19
23
|
def construct
|
20
24
|
layout_object = {
|
21
25
|
columns: {
|
22
|
-
size_each: nil,
|
26
|
+
size_each: smart_layout ? 2 : (specified_grouping_mode ? nil : 1),
|
23
27
|
container: [] # array of arrays
|
24
28
|
},
|
25
29
|
portals: {
|
@@ -46,9 +50,6 @@ module HotGlue
|
|
46
50
|
@downnest_children_width = []
|
47
51
|
@downnest_children.each_with_index{ |child, i| @downnest_children_width[i] = 4}
|
48
52
|
|
49
|
-
if include_setting.nil?
|
50
|
-
|
51
|
-
end
|
52
53
|
|
53
54
|
if smart_layout
|
54
55
|
# automatic control
|
@@ -68,23 +69,29 @@ module HotGlue
|
|
68
69
|
}
|
69
70
|
layout_object[:columns][:container].reject!{|x| x == [nil]}
|
70
71
|
end
|
71
|
-
elsif !
|
72
|
+
elsif ! specified_grouping_mode
|
73
|
+
# not smart and no specified grouping
|
74
|
+
#
|
72
75
|
layout_object[:columns][:container] = columns.collect{|col| [col]}
|
73
76
|
|
74
|
-
else
|
77
|
+
else # specified grouping mode -- the builder is given control
|
75
78
|
|
76
79
|
(0..available_columns-1).each do |int|
|
77
80
|
layout_object[:columns][:container][int] = []
|
78
81
|
end
|
79
82
|
|
80
83
|
# input control
|
81
|
-
|
84
|
+
|
85
|
+
user_layout_columns = @include_setting.split(":")
|
86
|
+
size_each = (bootstrap_columns / user_layout_columns.count).floor # this is the bootstrap size
|
87
|
+
|
88
|
+
layout_object[:columns][:size_each] = size_each
|
82
89
|
|
83
90
|
if user_layout_columns.size > available_columns
|
84
|
-
raise "Your include statement #{@include_setting }
|
91
|
+
raise "Your include statement #{@include_setting } has #{user_layout_columns.size} columns, but I can only construct up to #{available_columns}"
|
85
92
|
end
|
86
93
|
user_layout_columns.each_with_index do |column,i|
|
87
|
-
layout_object[:columns][:container][i] = column.split(",")
|
94
|
+
layout_object[:columns][:container][i] = column.split(",").collect(&:to_sym)
|
88
95
|
end
|
89
96
|
|
90
97
|
if user_layout_columns.size < layout_object[:columns][:container].size
|
@@ -92,21 +99,23 @@ module HotGlue
|
|
92
99
|
end
|
93
100
|
end
|
94
101
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
102
|
+
# TODO: do I want this code that expands the downnest portal
|
103
|
+
# maybe refactor into a setting on the --downnest flag itself somehow
|
104
|
+
# if layout_object[:columns][:container].size < available_columns
|
105
|
+
# available = available_columns - layout_object[:columns][:container].size
|
106
|
+
# downnest_child_count = 0
|
107
|
+
#
|
108
|
+
# while(available > 0)
|
109
|
+
# if (downnest_child_count <= downnest_children.size-1)
|
110
|
+
# layout_object[:portals][downnest_children[downnest_child_count]][:size] = layout_object[:portals][downnest_children[downnest_child_count]][:size] + 2
|
111
|
+
# else
|
112
|
+
# # leave as-is
|
113
|
+
# end
|
114
|
+
# downnest_child_count = downnest_child_count + 1
|
115
|
+
# available = available - 1
|
116
|
+
# end
|
117
|
+
# # give some space back to the downnest
|
118
|
+
# end
|
110
119
|
|
111
120
|
puts "*** constructed layout columns #{layout_object.inspect}"
|
112
121
|
layout_object
|
@@ -13,20 +13,21 @@ module HotGlue
|
|
13
13
|
attr_accessor :singular
|
14
14
|
|
15
15
|
def field_output(col, type = nil, width, col_identifier )
|
16
|
-
" <%= f.text_field :#{col
|
16
|
+
" <%= f.text_field :#{col}, value: @#{@singular}.#{col}, autocomplete: 'off', size: #{width}, class: 'form-control', type: '#{type}' %>\n "+
|
17
17
|
"\n"
|
18
18
|
end
|
19
19
|
|
20
20
|
|
21
21
|
def magic_button_output(*args)
|
22
|
-
|
23
|
-
|
22
|
+
path = args[0][:path]
|
23
|
+
# path_helper_singular = args[0][:path_helper_singular]
|
24
|
+
# path_helper_args = args[0][:path_helper_args]
|
24
25
|
singular = args[0][:singular]
|
25
26
|
magic_buttons = args[0][:magic_buttons]
|
26
27
|
small_buttons = args[0][:small_buttons]
|
27
28
|
|
28
29
|
magic_buttons.collect{ |button_name|
|
29
|
-
"<%= form_with model: #{singular}, url: #{
|
30
|
+
"<%= form_with model: #{singular}, url: #{path}, html: {style: 'display: inline', data: {\"turbo-confirm\": 'Are you sure you want to #{button_name} this #{singular}?'}} do |f| %>
|
30
31
|
<%= f.hidden_field :#{button_name}, value: \"#{button_name}\" %>
|
31
32
|
<%= f.submit '#{button_name.titleize}'.html_safe, disabled: (#{singular}.respond_to?(:#{button_name}able?) && ! #{singular}.#{button_name}able? ), class: '#{singular}-button btn btn-primary #{"btn-sm" if small_buttons}' %>
|
32
33
|
<% end %>"
|
@@ -39,7 +40,7 @@ module HotGlue
|
|
39
40
|
lines = 5
|
40
41
|
end
|
41
42
|
|
42
|
-
"<%= f.text_area :#{col
|
43
|
+
"<%= f.text_area :#{col}, class: 'form-control', autocomplete: 'off', cols: 40, rows: '#{lines}' %>"
|
43
44
|
end
|
44
45
|
|
45
46
|
def list_column_headings(*args)
|
@@ -68,89 +69,88 @@ module HotGlue
|
|
68
69
|
col_identifier = args[0][:col_identifier]
|
69
70
|
ownership_field = args[0][:ownership_field]
|
70
71
|
|
71
|
-
|
72
|
-
# TODO: CLEAN ME
|
73
72
|
@singular = args[0][:singular]
|
74
73
|
singular = @singular
|
75
|
-
|
76
74
|
result = layout_columns.map{ |column|
|
77
75
|
" <div class='#{col_identifier}' >" +
|
78
|
-
|
79
76
|
column.map { |col|
|
77
|
+
field_result =
|
78
|
+
if show_only.include?(col.to_sym)
|
79
|
+
"<%= @#{singular}.#{col} %>"
|
80
|
+
else
|
81
|
+
type = eval("#{singular_class}.columns_hash['#{col}']").type
|
82
|
+
limit = eval("#{singular_class}.columns_hash['#{col}']").limit
|
83
|
+
sql_type = eval("#{singular_class}.columns_hash['#{col}']").sql_type
|
84
|
+
|
85
|
+
case type
|
86
|
+
when :integer
|
87
|
+
# look for a belongs_to on this object
|
88
|
+
if col.to_s.ends_with?("_id")
|
89
|
+
assoc_name = col.to_s.gsub("_id","")
|
90
|
+
assoc = eval("#{singular_class}.reflect_on_association(:#{assoc_name})")
|
91
|
+
if assoc.nil?
|
92
|
+
exit_message = "*** Oops. on the #{singular_class} object, there doesn't seem to be an association called '#{assoc_name}'"
|
93
|
+
exit
|
94
|
+
end
|
95
|
+
|
96
|
+
is_owner = col == ownership_field
|
97
|
+
display_column = HotGlue.derrive_reference_name(assoc.class_name)
|
98
|
+
|
99
|
+
# TODO: add is_owner && check if this nested arg is optional
|
100
|
+
(is_owner ? "<% unless @#{assoc_name} %>\n" : "") +
|
101
|
+
" <%= f.collection_select(:#{col}, #{assoc.class_name}.all, :id, :#{display_column}, {prompt: true, selected: @#{singular}.#{col} }, class: 'form-control') %>\n" +
|
102
|
+
(is_owner ? "<% else %>\n <%= @#{assoc_name}.#{display_column} %>" : "") +
|
103
|
+
(is_owner ? "\n<% end %>" : "")
|
104
|
+
|
105
|
+
else
|
106
|
+
"<%= f.text_field :#{col}, value: #{singular}.#{col}, class: 'form-control', size: 4, type: 'number' %>"
|
80
107
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
case type
|
89
|
-
when :integer
|
90
|
-
# look for a belongs_to on this object
|
91
|
-
if col.to_s.ends_with?("_id")
|
92
|
-
assoc_name = col.to_s.gsub("_id","")
|
93
|
-
assoc = eval("#{singular_class}.reflect_on_association(:#{assoc_name})")
|
94
|
-
if assoc.nil?
|
95
|
-
exit_message = "*** Oops. on the #{singular_class} object, there doesn't seem to be an association called '#{assoc_name}'"
|
96
|
-
exit
|
108
|
+
end
|
109
|
+
when :string
|
110
|
+
if sql_type == "varchar" || sql_type == "character varying"
|
111
|
+
field_output(col, nil, limit || 40, col_identifier)
|
112
|
+
else
|
113
|
+
text_area_output(col, 65536, col_identifier)
|
97
114
|
end
|
98
115
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
116
|
+
when :text
|
117
|
+
if sql_type == "varchar"
|
118
|
+
field_output(col, nil, limit, col_identifier)
|
119
|
+
else
|
120
|
+
text_area_output(col, 65536, col_identifier)
|
121
|
+
end
|
122
|
+
when :float
|
123
|
+
field_output(col, nil, 5, col_identifier)
|
124
|
+
when :datetime
|
125
|
+
"<%= datetime_field_localized(f, :#{col}, #{singular}.#{col}, '#{ col.to_s.humanize }', #{@auth ? @auth+'.timezone' : 'nil'}) %>"
|
126
|
+
when :date
|
127
|
+
"<%= date_field_localized(f, :#{col}, #{singular}.#{col}, '#{ col.to_s.humanize }', #{@auth ? @auth+'.timezone' : 'nil'}) %>"
|
128
|
+
when :time
|
129
|
+
"<%= time_field_localized(f, :#{col}, #{singular}.#{col}, '#{ col.to_s.humanize }', #{@auth ? @auth+'.timezone' : 'nil'}) %>"
|
130
|
+
when :boolean
|
131
|
+
" " +
|
132
|
+
" <span>#{col.to_s.humanize}</span>" +
|
133
|
+
" <%= f.radio_button(:#{col}, '0', checked: #{singular}.#{col} ? '' : 'checked') %>\n" +
|
134
|
+
" <%= f.label(:#{col}, value: 'No', for: '#{singular}_#{col}_0') %>\n" +
|
135
|
+
" <%= f.radio_button(:#{col}, '1', checked: #{singular}.#{col} ? 'checked' : '') %>\n" +
|
136
|
+
" <%= f.label(:#{col}, value: 'Yes', for: '#{singular}_#{col}_1') %>\n" +
|
137
|
+
""
|
138
|
+
when :enum
|
139
|
+
enum_type = eval("#{singular_class}.columns.select{|x| x.name == '#{col}'}[0].sql_type")
|
140
|
+
"<%= f.collection_select(:#{col}, enum_to_collection_select( #{singular_class}.defined_enums['#{enum_type}']), :key, :value, {selected: @#{singular}.#{col} }, class: 'form-control') %>"
|
121
141
|
end
|
122
|
-
when :float
|
123
|
-
field_output(col, nil, 5, col_identifier)
|
124
|
-
when :datetime
|
125
|
-
"<%= datetime_field_localized(f, :#{col.to_s}, #{singular}.#{col.to_s}, '#{ col.to_s.humanize }', #{@auth ? @auth+'.timezone' : 'nil'}) %>"
|
126
|
-
when :date
|
127
|
-
"<%= date_field_localized(f, :#{col.to_s}, #{singular}.#{col.to_s}, '#{ col.to_s.humanize }', #{@auth ? @auth+'.timezone' : 'nil'}) %>"
|
128
|
-
when :time
|
129
|
-
"<%= time_field_localized(f, :#{col.to_s}, #{singular}.#{col.to_s}, '#{ col.to_s.humanize }', #{@auth ? @auth+'.timezone' : 'nil'}) %>"
|
130
|
-
when :boolean
|
131
|
-
" " +
|
132
|
-
" <span>#{col.to_s.humanize}</span>" +
|
133
|
-
" <%= f.radio_button(:#{col.to_s}, '0', checked: #{singular}.#{col.to_s} ? '' : 'checked') %>\n" +
|
134
|
-
" <%= f.label(:#{col.to_s}, value: 'No', for: '#{singular}_#{col.to_s}_0') %>\n" +
|
135
|
-
" <%= f.radio_button(:#{col.to_s}, '1', checked: #{singular}.#{col.to_s} ? 'checked' : '') %>\n" +
|
136
|
-
" <%= f.label(:#{col.to_s}, value: 'Yes', for: '#{singular}_#{col.to_s}_1') %>\n" +
|
137
|
-
""
|
138
|
-
when :enum
|
139
|
-
enum_type = eval("#{singular_class}.columns.select{|x| x.name == '#{col.to_s}'}[0].sql_type")
|
140
|
-
"<%= f.collection_select(:#{col.to_s}, enum_to_collection_select( #{singular_class}.defined_enums['#{enum_type}']), :key, :value, {selected: @#{singular}.#{col.to_s} }, class: 'form-control') %>"
|
141
|
-
end
|
142
142
|
|
143
|
-
|
143
|
+
end
|
144
144
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
145
|
+
if (type == :integer) && col.to_s.ends_with?("_id")
|
146
|
+
field_error_name = col.to_s.gsub("_id","")
|
147
|
+
else
|
148
|
+
field_error_name = col
|
149
|
+
end
|
150
150
|
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
add_spaces_each_line( "\n <span class='<%= \"alert-danger\" if #{singular}.errors.details.keys.include?(:#{field_error_name}) %>' #{ 'style="display: inherit;"'} >\n" +
|
152
|
+
add_spaces_each_line(field_result + "\n<label class='small form-text text-muted'>#{col.to_s.humanize}</label>", 4) +
|
153
|
+
"\n </span>\n <br />", 2)
|
154
154
|
|
155
155
|
}.join("") + "\n </div>"
|
156
156
|
}.join("\n")
|
@@ -172,20 +172,20 @@ module HotGlue
|
|
172
172
|
singular = args[0][:singular]
|
173
173
|
perc_width = args[0][:perc_width]
|
174
174
|
layout = args[0][:layout]
|
175
|
+
col_identifier = args[0][:col_identifier] || (layout == "bootstrap" ? "col-md-2" : "scaffold-cell")
|
176
|
+
|
175
177
|
|
176
178
|
columns_count = layout_columns.count + 1
|
177
179
|
perc_width = (perc_width).floor
|
178
180
|
|
179
181
|
if layout == "bootstrap"
|
180
|
-
col_identifer = "col-md-2"
|
181
182
|
style_with_flex_basis = ""
|
182
183
|
else
|
183
184
|
style_with_flex_basis = " style='flex-basis: #{perc_width}%'"
|
184
|
-
col_identifer = "scaffold-cell"
|
185
185
|
end
|
186
186
|
|
187
187
|
result = layout_columns.map{ |column|
|
188
|
-
"<div class='#{
|
188
|
+
"<div class='#{col_identifier}'#{style_with_flex_basis}>" +
|
189
189
|
|
190
190
|
|
191
191
|
column.map { |col|
|
@@ -196,7 +196,7 @@ module HotGlue
|
|
196
196
|
case type
|
197
197
|
when :integer
|
198
198
|
# look for a belongs_to on this object
|
199
|
-
if col.
|
199
|
+
if col.ends_with?("_id")
|
200
200
|
|
201
201
|
assoc_name = col.to_s.gsub("_id","")
|
202
202
|
|
@@ -254,7 +254,7 @@ module HotGlue
|
|
254
254
|
<% end %>
|
255
255
|
|
256
256
|
" when :enum
|
257
|
-
enum_type = eval("#{singular_class}.columns.select{|x| x.name == '#{col
|
257
|
+
enum_type = eval("#{singular_class}.columns.select{|x| x.name == '#{col}'}[0].sql_type")
|
258
258
|
|
259
259
|
"
|
260
260
|
<% if #{singular}.#{col}.nil? %>
|