hot-glue 0.5.8 → 0.5.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -2
  3. data/Gemfile +1 -1
  4. data/README.md +267 -149
  5. data/app/helpers/hot_glue/controller_helper.rb +9 -6
  6. data/config/hot_glue.yml +2 -0
  7. data/lib/generators/hot_glue/direct_upload_install_generator.rb +48 -0
  8. data/lib/generators/hot_glue/dropzone_install_generator.rb +42 -0
  9. data/lib/generators/hot_glue/field_factory.rb +57 -0
  10. data/lib/generators/hot_glue/fields/association_field.rb +76 -0
  11. data/lib/generators/hot_glue/fields/attachment_field.rb +9 -0
  12. data/lib/generators/hot_glue/fields/boolean_field.rb +18 -0
  13. data/lib/generators/hot_glue/fields/date_field.rb +10 -0
  14. data/lib/generators/hot_glue/fields/date_time_field.rb +23 -0
  15. data/lib/generators/hot_glue/fields/enum_field.rb +27 -0
  16. data/lib/generators/hot_glue/fields/field.rb +51 -0
  17. data/lib/generators/hot_glue/fields/float_field.rb +11 -0
  18. data/lib/generators/hot_glue/fields/integer_field.rb +26 -0
  19. data/lib/generators/hot_glue/fields/string_field.rb +33 -0
  20. data/lib/generators/hot_glue/fields/text_field.rb +14 -0
  21. data/lib/generators/hot_glue/fields/time_field.rb +6 -0
  22. data/lib/generators/hot_glue/fields/uuid_field.rb +12 -0
  23. data/lib/generators/hot_glue/layout/builder.rb +15 -6
  24. data/lib/generators/hot_glue/layout_strategy/base.rb +2 -0
  25. data/lib/generators/hot_glue/layout_strategy/bootstrap.rb +4 -0
  26. data/lib/generators/hot_glue/markup_templates/erb.rb +158 -117
  27. data/lib/generators/hot_glue/scaffold_generator.rb +296 -306
  28. data/lib/generators/hot_glue/templates/computer_code.jpg +0 -0
  29. data/lib/generators/hot_glue/templates/controller.rb.erb +15 -9
  30. data/lib/generators/hot_glue/templates/erb/_list.erb +19 -6
  31. data/lib/generators/hot_glue/templates/erb/_show.erb +6 -4
  32. data/lib/generators/hot_glue/templates/javascript/dropzone_controller.js +191 -0
  33. data/lib/generators/hot_glue/templates/system_spec.rb.erb +32 -111
  34. data/lib/hotglue/version.rb +1 -1
  35. data/script/clean_generated_code +1 -1
  36. metadata +22 -4
@@ -1,24 +1,19 @@
1
1
  require 'rails/generators/erb/scaffold/scaffold_generator'
2
2
  require 'ffaker'
3
-
3
+ require_relative './fields/field'
4
+ require_relative './field_factory'
4
5
  require_relative './markup_templates/base'
5
6
  require_relative './markup_templates/erb'
6
- # require_relative './markup_templates/haml'
7
- # require_relative './markup_templates/slim'
8
-
9
7
  require_relative './layout/builder'
10
8
  require_relative './layout_strategy/base'
11
9
  require_relative './layout_strategy/bootstrap'
12
10
  require_relative './layout_strategy/hot_glue'
13
11
  require_relative './layout_strategy/tailwind'
14
12
 
15
-
16
13
  module HotGlue
17
14
  class Error < StandardError
18
15
  end
19
16
 
20
-
21
-
22
17
  def self.construct_downnest_object(input)
23
18
  res = input.split(",").map { |child|
24
19
  child_name = child.gsub("+","")
@@ -70,7 +65,7 @@ module HotGlue
70
65
  with_params: with_params,
71
66
  put_form: put_form,
72
67
  nested_set: rest_of_nest )
73
- return "defined?(#{instance_sym + nested_set[0][:singular]}) ? #{is_present_path} : #{is_missing_path}"
68
+ return "defined?(#{instance_sym + nested_set[0][:singular]}2) ? #{is_present_path} : #{is_missing_path}"
74
69
  end
75
70
  end
76
71
 
@@ -95,8 +90,9 @@ module HotGlue
95
90
  hook_for :form_builder, :as => :scaffold
96
91
 
97
92
  source_root File.expand_path('templates', __dir__)
98
- attr_accessor :path, :singular, :plural, :singular_class, :nest_with
99
- attr_accessor :columns, :downnest_children, :layout_object
93
+ attr_accessor :path, :singular, :plural, :singular_class, :nest_with,
94
+ :columns, :downnest_children, :layout_object, :alt_lookups,
95
+ :update_show_only, :hawk_keys, :auth, :sample_file_path
100
96
 
101
97
  class_option :singular, type: :string, default: nil
102
98
  class_option :plural, type: :string, default: nil
@@ -150,8 +146,8 @@ module HotGlue
150
146
  class_option :inline_list_labels, default: 'omit' # choices are before, after, omit
151
147
  class_option :factory_creation, default: ''
152
148
  class_option :alt_foreign_key_lookup, default: '' #
153
-
154
-
149
+ class_option :attachments, default: ''
150
+ class_option :stacked_downnesting, default: false
155
151
 
156
152
  def initialize(*meta_args)
157
153
  super
@@ -180,11 +176,6 @@ module HotGlue
180
176
 
181
177
  if @stimulus_syntax.nil?
182
178
  @stimulus_syntax = true
183
-
184
- # if Rails.version.split(".")[0].to_i >= 7
185
- # @stimulus_syntax = true
186
- # else
187
- # end
188
179
  end
189
180
 
190
181
  if !options['markup'].nil?
@@ -194,6 +185,7 @@ module HotGlue
194
185
 
195
186
  yaml_from_config = YAML.load(File.read("config/hot_glue.yml"))
196
187
  @markup = yaml_from_config[:markup]
188
+ @sample_file_path = yaml_from_config[:sample_file_path]
197
189
 
198
190
  if options['layout']
199
191
  layout = options['layout']
@@ -215,15 +207,6 @@ module HotGlue
215
207
  LayoutStrategy::HotGlue.new(self)
216
208
  end
217
209
 
218
-
219
- if @markup == "erb"
220
- @template_builder = HotGlue::ErbTemplate.new(layout_strategy: @layout_strategy)
221
- elsif @markup == "slim"
222
- raise(HotGlue::Error, "SLIM IS NOT IMPLEMENTED")
223
- elsif @markup == "haml"
224
- raise(HotGlue::Error, "HAML IS NOT IMPLEMENTED")
225
- end
226
-
227
210
  args = meta_args[0]
228
211
  @singular = args.first.tableize.singularize # should be in form hello_world
229
212
 
@@ -233,32 +216,21 @@ module HotGlue
233
216
 
234
217
  @plural = options['plural'] || @singular.pluralize # respects what you set in inflections.rb, to override, use plural option
235
218
  @namespace = options['namespace'] || nil
236
-
237
-
238
219
  use_controller_name = plural.titleize.gsub(" ", "")
239
-
240
220
  @controller_build_name = (( @namespace.titleize.gsub(" ","") + "::" if @namespace) || "") + use_controller_name + "Controller"
241
221
  @controller_build_folder = use_controller_name.underscore
242
222
  @controller_build_folder_singular = singular
243
223
 
244
- # if ! @controller_build_folder.ends_with?("s")
245
- # raise HotGlue::Error, "can't build with controller name #{@controller_build_folder} because it doesn't end with an 's'"
246
- # end
247
-
248
224
  @auth = options['auth'] || "current_user"
249
225
  @auth_identifier = options['auth_identifier'] || (! @god && @auth.gsub("current_", "")) || nil
250
226
 
251
-
252
-
253
227
  if options['nest']
254
228
  raise HotGlue::Error, "STOP: the flag --nest has been replaced with --nested; please re-run using the --nested flag"
255
-
256
229
  end
257
-
258
230
  @nested = (!options['nested'].empty? && options['nested']) || nil
259
-
260
231
  @singular_class = args.first # note this is the full class name with a model namespace
261
232
 
233
+ setup_attachments
262
234
 
263
235
  @exclude_fields = []
264
236
  @exclude_fields += options['exclude'].split(",").collect(&:to_sym)
@@ -270,7 +242,6 @@ module HotGlue
270
242
  @include_fields += options['include'].split(":").collect{|x|x.split(",")}.flatten.collect(&:to_sym)
271
243
  end
272
244
 
273
-
274
245
  @show_only = []
275
246
  if !options['show_only'].empty?
276
247
  @show_only += options['show_only'].split(",").collect(&:to_sym)
@@ -282,6 +253,76 @@ module HotGlue
282
253
  end
283
254
 
284
255
 
256
+ # syntax should be xyz_id{xyz_email},abc_id{abc_email}
257
+ # instead of a drop-down for the foreign entity, a text field will be presented
258
+ # You must ALSO use a factory that contains a parameter of the same name as the 'value' (for example, `xyz_email`)
259
+
260
+ alt_lookups_entry = options['alt_foreign_key_lookup'].split(",")
261
+ @alt_lookups = {}
262
+ @alt_foreign_key_lookup = alt_lookups_entry.each do |setting|
263
+ setting =~ /(.*){(.*)}/
264
+ key, lookup_as = $1, $2
265
+ assoc = eval("#{class_name}.reflect_on_association(:#{key.to_s.gsub("_id","")}).class_name")
266
+
267
+ data = {lookup_as: lookup_as.gsub("+",""),
268
+ assoc: assoc,
269
+ with_create: lookup_as.include?("+")}
270
+ @alt_lookups[key] = data
271
+ end
272
+
273
+ puts "------ ALT LOOKUPS for #{@alt_lookups}"
274
+
275
+ @update_alt_lookups = @alt_lookups.collect{|key, value|
276
+ @update_show_only.include?(key) ?
277
+ { key: value }
278
+ : nil}.compact
279
+
280
+ @label = options['label'] || ( eval("#{class_name}.class_variable_defined?(:@@table_label_singular)") ? eval("#{class_name}.class_variable_get(:@@table_label_singular)") : singular.gsub("_", " ").titleize )
281
+ @list_label_heading = options['list_label_heading'] || ( eval("#{class_name}.class_variable_defined?(:@@table_label_plural)") ? eval("#{class_name}.class_variable_get(:@@table_label_plural)") : plural.gsub("_", " ").upcase )
282
+
283
+ @new_button_label = options['new_button_label'] || ( eval("#{class_name}.class_variable_defined?(:@@table_label_singular)") ? "New " + eval("#{class_name}.class_variable_get(:@@table_label_singular)") : "New " + singular.gsub("_", " ").titleize )
284
+ @new_form_heading = options['new_form_heading'] || "New #{@label}"
285
+
286
+
287
+
288
+ setup_hawk_keys
289
+ @form_placeholder_labels = options['form_placeholder_labels'] # true or false
290
+ @inline_list_labels = options['inline_list_labels'] || 'omit' # 'before','after','omit'
291
+
292
+
293
+ @form_labels_position = options['form_labels_position']
294
+ if !['before','after','omit'].include?(@form_labels_position)
295
+ raise HotGlue::Error, "You passed '#{@form_labels_position}' as the setting for --form-labels-position but the only allowed options are before, after (default), and omit"
296
+ end
297
+
298
+ if !['before','after','omit'].include?(@inline_list_labels)
299
+ raise HotGlue::Error, "You passed '#{@inline_list_labels}' as the setting for --inline-list-labels but the only allowed options are before, after, and omit (default)"
300
+ end
301
+
302
+
303
+
304
+ if @markup == "erb"
305
+ @template_builder = HotGlue::ErbTemplate.new(
306
+ layout_strategy: @layout_strategy,
307
+ magic_buttons: @magic_buttons,
308
+ small_buttons: @small_buttons,
309
+ inline_list_labels: @inline_list_labels,
310
+ show_only: @show_only,
311
+ update_show_only: @update_show_only,
312
+ singular_class: singular_class,
313
+ singular: singular,
314
+ hawk_keys: @hawk_keys,
315
+ ownership_field: @ownership_field,
316
+ form_labels_position: @form_labels_position,
317
+ form_placeholder_labels: @form_placeholder_labels,
318
+ alt_lookups: @alt_lookups,
319
+ attachments: @attachments,
320
+ )
321
+ elsif @markup == "slim"
322
+ raise(HotGlue::Error, "SLIM IS NOT IMPLEMENTED")
323
+ elsif @markup == "haml"
324
+ raise(HotGlue::Error, "HAML IS NOT IMPLEMENTED")
325
+ end
285
326
 
286
327
  @god = options['god'] || options['gd'] || false
287
328
  @specs_only = options['specs_only'] || false
@@ -297,20 +338,8 @@ module HotGlue
297
338
  @no_list = options['no_list'] || false
298
339
  @no_list_label = options['no_list_label'] || false
299
340
  @no_list_heading = options['no_list_heading'] || false
341
+ @stacked_downnesting = options['stacked_downnesting']
300
342
 
301
- @form_labels_position = options['form_labels_position']
302
- if !['before','after','omit'].include?(@form_labels_position)
303
-
304
- raise HotGlue::Error, "You passed '#{@form_labels_position}' as the setting for --form-labels-position but the only allowed options are before, after (default), and omit"
305
- end
306
-
307
- @form_placeholder_labels = options['form_placeholder_labels'] # true or false
308
- @inline_list_labels = options['inline_list_labels'] || 'omit' # 'before','after','omit'
309
-
310
-
311
- if !['before','after','omit'].include?(@inline_list_labels)
312
- raise HotGlue::Error, "You passed '#{@inline_list_labels}' as the setting for --inline-list-labels but the only allowed options are before, after, and omit (default)"
313
- end
314
343
 
315
344
 
316
345
 
@@ -415,15 +444,11 @@ module HotGlue
415
444
  end
416
445
  end
417
446
 
418
- identify_object_owner
419
- setup_hawk_keys
420
447
 
421
448
  @factory_creation = options['factory_creation'].gsub(";", "\n")
422
-
423
-
424
-
425
- # SETUP FIELDS & LAYOUT
449
+ identify_object_owner
426
450
  setup_fields
451
+
427
452
  if (@columns - @show_only - (@ownership_field ? [@ownership_field.to_sym] : [])).empty?
428
453
  @no_field_form = true
429
454
  end
@@ -434,65 +459,19 @@ module HotGlue
434
459
  downnest_object: @downnest_object,
435
460
  buttons_width: buttons_width,
436
461
  columns: @columns,
437
- smart_layout: @smart_layout )
462
+ smart_layout: @smart_layout,
463
+ stacked_downnesting: @stacked_downnesting)
438
464
  @layout_object = builder.construct
439
465
 
440
- @menu_file_exists = true if @nested_set.none? && File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_menu.#{@markup}")
441
466
 
442
- @turbo_streams = !!options['with_turbo_streams']
443
-
444
-
445
- # syntax should be xyz_id{xyz_email},abc_id{abc_email}
446
- # instead of a drop-down for the foreign entity, a text field will be presented
447
- # You must ALSO use a factory that contains a parameter of the same name as the 'value' (for example, `xyz_email`)
448
-
449
- alt_lookups_entry = options['alt_foreign_key_lookup'].split(",")
450
- @alt_lookups = {}
451
- @alt_foreign_key_lookup = alt_lookups_entry.each do |setting|
452
- setting =~ /(.*){(.*)}/
453
- key, lookup_as = $1, $2
454
- assoc = eval("#{class_name}.reflect_on_association(:#{key.to_s.gsub("_id","")}).class_name")
455
-
456
- data = {lookup_as: lookup_as.gsub("+",""),
457
- assoc: assoc,
458
- with_create: lookup_as.include?("+")}
459
- @alt_lookups[key] = data
460
- end
461
-
462
- puts "------ ALT LOOKUPS for #{@alt_lookups}"
463
-
464
- @update_alt_lookups = @alt_lookups.collect{|key, value|
465
- @update_show_only.include?(key) ?
466
- { key: value }
467
- : nil}.compact
468
-
469
- @label = options['label'] || ( eval("#{class_name}.class_variable_defined?(:@@table_label_singular)") ? eval("#{class_name}.class_variable_get(:@@table_label_singular)") : singular.gsub("_", " ").titleize )
470
- @list_label_heading = options['list_label_heading'] || ( eval("#{class_name}.class_variable_defined?(:@@table_label_plural)") ? eval("#{class_name}.class_variable_get(:@@table_label_plural)") : plural.gsub("_", " ").upcase )
471
-
472
- @new_button_label = options['new_button_label'] || ( eval("#{class_name}.class_variable_defined?(:@@table_label_singular)") ? "New " + eval("#{class_name}.class_variable_get(:@@table_label_singular)") : "New " + singular.gsub("_", " ").titleize )
473
- @new_form_heading = options['new_form_heading'] || "New #{@label}"
474
- end
475
467
 
468
+ @menu_file_exists = true if @nested_set.none? && File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_menu.#{@markup}")
476
469
 
477
- def fields_filtered_for_email_lookups
478
- @columns.reject{|c| @alt_lookups.keys.include?(c) } + @alt_lookups.values.map{|v| ("__lookup_#{v[:lookup_as]}").to_sym}
470
+ @turbo_streams = !!options['with_turbo_streams']
479
471
  end
480
472
 
481
473
 
482
- def creation_syntax
483
474
 
484
- merge_with = @alt_lookups.collect{ |key, data|
485
- "#{data[:assoc].downcase}: #{data[:assoc].downcase}_factory.#{data[:assoc].downcase}"
486
- }.join(", ")
487
-
488
- if @factory_creation == ''
489
- "@#{singular_name } = #{ class_name }.create(modified_params)"
490
- else
491
- "#{@factory_creation}\n" +
492
- " @#{singular_name } = #{ class_name }.create(modified_params#{'.merge(' + merge_with + ')' if !merge_with.empty?})"
493
- end
494
- end
495
-
496
475
  def setup_hawk_keys
497
476
  @hawk_keys = {}
498
477
 
@@ -509,24 +488,115 @@ module HotGlue
509
488
  end
510
489
 
511
490
  hawk_scope = key.gsub("_id", "").pluralize
512
- @hawk_keys[key.to_sym] = [hawk_to]
491
+ optional = eval(singular_class + ".reflect_on_association(:#{key.gsub('_id','')})").options[:optional]
492
+
493
+ @hawk_keys[key.to_sym] = {bind_to: [hawk_to], optional: optional}
513
494
  use_shorthand = !options["hawk"].include?("{")
514
495
 
515
496
  if use_shorthand # only include the hawk scope if using the shorthand
516
- @hawk_keys[key.to_sym] << hawk_scope
497
+ @hawk_keys[key.to_sym][:bind_to] << hawk_scope
517
498
  end
499
+
518
500
  end
519
501
 
520
502
  puts "HAWKING: #{@hawk_keys}"
521
503
  end
522
504
  end
523
505
 
506
+
507
+ def setup_attachments
508
+ @attachments = {}
509
+
510
+ if options["attachments"]
511
+
512
+ options['attachments'].split(",").each do |attachment_entry|
513
+ # format is: avatar{thumbnail|field_for_original_filename}
514
+
515
+ if attachment_entry.include?("{")
516
+ num_params = attachment_entry.split("|").count
517
+ if num_params == 1
518
+ attachment_entry =~ /(.*){(.*)}/
519
+ key, thumbnail = $1, $2
520
+ elsif num_params == 2
521
+ attachment_entry =~ /(.*){(.*)\|(.*)}/
522
+ key, thumbnail, field_for_original_filename = $1, $2, $3
523
+ elsif num_params > 2
524
+ if num_params == 3
525
+ attachment_entry =~ /(.*){(.*)\|(.*)\|(.*)}/
526
+ key, thumbnail, field_for_original_filename, direct_upload = $1, $2, $3, $4
527
+ elsif num_params > 3
528
+ attachment_entry =~ /(.*){(.*)\|(.*)\|(.*)\|(.*)}/
529
+ key, thumbnail, field_for_original_filename, direct_upload, dropzone = $1, $2, $3, $4, $5
530
+ end
531
+
532
+ field_for_original_filename = nil if field_for_original_filename == ""
533
+
534
+ if thumbnail == ''
535
+ thumbnail = nil
536
+ end
537
+
538
+ if !direct_upload.nil? && direct_upload != "direct"
539
+ raise HotGlue::Error, "received 3rd parameter in attachment long form specification that was not 'direct'; for direct uploads, just use 'direct' or leave off to disable"
540
+ end
541
+
542
+ if !dropzone.nil? && dropzone != "dropzone"
543
+ raise HotGlue::Error, "received 4th parameter in attachme long form specification that was not 'dropzone'; for dropzone, just use 'dropzone' or leave off to disable"
544
+ end
545
+
546
+ if dropzone && !direct_upload
547
+ raise HotGlue::Error, "dropzone requires direct_upload"
548
+ end
549
+
550
+ if field_for_original_filename && direct_upload
551
+ raise HotGlue::Error, "Unfortunately orig filename extraction doesn't work with direct upload; please set 2nd parameter to empty string to disable"
552
+ end
553
+
554
+ direct_upload = !!direct_upload
555
+ dropzone = !!dropzone
556
+ end
557
+ else
558
+ key = attachment_entry
559
+
560
+ if !(eval("#{singular_class}.reflect_on_attachment(:#{attachment_entry})"))
561
+ raise HotGlue::Error, "Could not find #{attachment_entry} attachment on #{singular_class}"
562
+ end
563
+ if eval("#{singular_class}.reflect_on_attachment(:#{attachment_entry}).variants.include?(:thumb)")
564
+ thumbnail = "thumb"
565
+ else
566
+ thumbnail = nil
567
+ end
568
+
569
+ direct_upload = nil
570
+ field_for_original_filename = nil
571
+ dropzone = nil
572
+ end
573
+
574
+ if thumbnail && !eval("#{singular_class}.reflect_on_attachment(:#{key}).variants.include?(:#{thumbnail})")
575
+ raise HotGlue::Error, "you specified to use #{thumbnail} as the thumbnail but could not find any such variant on the #{key} attachment; add to your #{singular}.rb file:
576
+ has_one_attached :#{key} do |attachable|
577
+ attachable.variant :#{thumbnail}, resize_to_limit: [100, 100]
578
+ end
579
+ "
580
+ end
581
+
582
+
583
+ @attachments[key.to_sym] = {thumbnail: thumbnail,
584
+ field_for_original_filename: field_for_original_filename,
585
+ direct_upload: direct_upload,
586
+ dropzone: dropzone}
587
+ end
588
+
589
+ puts "ATTACHMENTS: #{@attachments}"
590
+ end
591
+ end
592
+
524
593
  def identify_object_owner
525
594
  auth_assoc = @auth && @auth.gsub("current_","")
526
595
 
527
596
  if @object_owner_sym && ! @self_auth
528
597
  auth_assoc_field = auth_assoc + "_id" unless @god
529
598
  assoc = eval("#{singular_class}.reflect_on_association(:#{@object_owner_sym})")
599
+
530
600
  if assoc
531
601
  @ownership_field = assoc.name.to_s + "_id"
532
602
  elsif ! @nested_set.any?
@@ -548,7 +618,6 @@ module HotGlue
548
618
  end
549
619
 
550
620
  def setup_fields
551
-
552
621
  if !@include_fields
553
622
  @exclude_fields.push :id, :created_at, :updated_at, :encrypted_password,
554
623
  :reset_password_token,
@@ -561,58 +630,86 @@ module HotGlue
561
630
 
562
631
  @columns = @the_object.columns.map(&:name).map(&:to_sym).reject{|field| @exclude_fields.include?(field) }
563
632
 
633
+
564
634
  else
565
635
  @columns = @the_object.columns.map(&:name).map(&:to_sym).reject{|field| !@include_fields.include?(field) }
566
636
  end
567
637
 
638
+ if @attachments.any?
639
+ puts "adding attachments-as-columns: #{@attachments}"
640
+ @attachments.keys.each do |attachment|
641
+ @columns << attachment if !@columns.include?(attachment)
642
+ end
643
+
644
+ check_if_sample_file_is_present
645
+ end
646
+
568
647
 
648
+ # build a new polymorphic object
569
649
  @associations = []
650
+ @columns_map = {}
570
651
  @columns.each do |col|
652
+ if !(@the_object.columns_hash.keys.include?(col.to_s) || @attachments.keys.include?(col))
653
+ raise "couldn't find #{col} in either field list or attachments list"
654
+ end
655
+
571
656
  if col.to_s.starts_with?("_")
572
657
  @show_only << col
573
658
  end
574
659
 
575
- if @the_object.columns_hash[col.to_s].type == :integer
576
- if col.to_s.ends_with?("_id")
577
- # guess the association name label
578
- assoc_name = col.to_s.gsub("_id","")
660
+ if @the_object.columns_hash.keys.include?(col.to_s)
661
+ type = @the_object.columns_hash[col.to_s].type
662
+ elsif @attachments.keys.include?(col)
663
+ type = :attachment
664
+ end
665
+ this_column_object = FieldFactory.new(name: col.to_s,
666
+ generator: self,
667
+ type: type)
668
+ field = this_column_object.field
669
+ if field.is_a?(AssociationField)
670
+ @associations << field.assoc_name.to_sym
671
+ end
672
+ @columns_map[col] = this_column_object.field
579
673
 
580
674
 
581
- assoc_model = eval("#{singular_class}.reflect_on_association(:#{assoc_name})")
582
675
 
583
- if assoc_model.nil?
584
- exit_message = "*** Oops: The model #{singular_class} is missing an association for :#{assoc_name} or the model #{assoc_name.titlecase} doesn't exist. TODO: Please implement a model for #{assoc_name.titlecase}; or add to #{singular_class} `belongs_to :#{assoc_name}`. To make a controller that can read all records, specify with --god."
585
- puts exit_message
586
- raise(HotGlue::Error, exit_message)
587
- end
676
+ end
677
+ end
588
678
 
589
- begin
590
- assoc_class = eval(assoc_model.try(:class_name))
591
- @associations << assoc_name.to_sym
592
- name_list = [:name, :to_label, :full_name, :display_name, :email]
593
-
594
- rescue
595
- # unreachable(?)
596
- # if eval("#{singular_class}.reflect_on_association(:#{assoc_name.singularize})")
597
- # raise(HotGlue::Error,"*** Oops: #{singular_class} has no association for #{assoc_name.singularize}")
598
- # else
599
- # raise(HotGlue::Error,"*** 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.")
600
- # end
601
- end
602
679
 
603
- if assoc_class && name_list.collect{ |field|
604
- assoc_class.column_names.include?(field.to_s) || assoc_class.instance_methods.include?(field)
605
- }.any?
606
- # do nothing here
607
- else
608
- exit_message = "Oops: Missing a label for `#{assoc_class}`. Can't find any column to use as the display label for the #{assoc_name} association on the #{singular_class} model. TODO: Please implement just one of: 1) name, 2) to_label, 3) full_name, 4) display_name 5) email. You can implement any of these directly on your`#{assoc_class}` model (can be database fields or model methods) or alias them to field you want to use as your display label. Then RERUN THIS GENERATOR. (Field used will be chosen based on rank here.)"
609
- raise(HotGlue::Error,exit_message)
610
- end
611
- end
612
- end
680
+ def check_if_sample_file_is_present
681
+ if sample_file_path.nil?
682
+ puts "you have no sample file path set in config/hot_glue.yml"
683
+ settings = File.read("config/hot_glue.yml")
684
+ @sample_file_path = "spec/files/computer_code.jpg"
685
+ added_setting = ":sample_file_path: #{sample_file_path}"
686
+ File.open("config/hot_glue.yml", "w") { |f| f.write settings + "\n" + added_setting }
687
+
688
+ puts "adding `#{added_setting}` to config/hot_glue.yml"
689
+ elsif ! File.exist?(sample_file_path)
690
+ puts "NO SAMPLE FILE FOUND: adding sample file at #{sample_file_path}"
691
+ template "computer_code.jpg", File.join("#{filepath_prefix}spec/files/", "computer_code.jpg")
613
692
  end
693
+
694
+ puts ""
614
695
  end
615
696
 
697
+ def fields_filtered_for_email_lookups
698
+ @columns.reject{|c| @alt_lookups.keys.include?(c) } + @alt_lookups.values.map{|v| ("__lookup_#{v[:lookup_as]}").to_sym}
699
+ end
700
+
701
+ def creation_syntax
702
+ merge_with = @alt_lookups.collect{ |key, data|
703
+ "#{data[:assoc].downcase}: #{data[:assoc].downcase}_factory.#{data[:assoc].downcase}"
704
+ }.join(", ")
705
+
706
+ if @factory_creation == ''
707
+ "@#{singular } = #{ class_name }.create(modified_params)"
708
+ else
709
+ "#{@factory_creation}\n" +
710
+ " @#{singular } = #{ class_name }.create(modified_params#{'.merge(' + merge_with + ')' if !merge_with.empty?})"
711
+ end
712
+ end
616
713
 
617
714
  def auth_root
618
715
  "authenticate_" + @auth_identifier.split(".")[0] + "!"
@@ -626,22 +723,25 @@ module HotGlue
626
723
  nil
627
724
  end
628
725
 
726
+ def filepath_prefix
727
+ 'spec/dummy/' if Rails.env.test?
728
+ end
729
+
629
730
  def copy_controller_and_spec_files
630
731
  @default_colspan = @columns.size
631
732
  unless @specs_only
632
- template "controller.rb.erb", File.join("#{'spec/dummy/' if Rails.env.test?}app/controllers#{namespace_with_dash}", "#{@controller_build_folder}_controller.rb")
733
+ template "controller.rb.erb", File.join("#{filepath_prefix}app/controllers#{namespace_with_dash}", "#{@controller_build_folder}_controller.rb")
633
734
  if @namespace
634
735
  begin
635
736
  eval(controller_descends_from)
636
- # puts " skipping base controller #{controller_descends_from}"
637
737
  rescue NameError => e
638
- template "base_controller.rb.erb", File.join("#{'spec/dummy/' if Rails.env.test?}app/controllers#{namespace_with_dash}", "base_controller.rb")
738
+ template "base_controller.rb.erb", File.join("#{filepath_prefix}app/controllers#{namespace_with_dash}", "base_controller.rb")
639
739
  end
640
740
  end
641
741
  end
642
742
 
643
743
  unless @no_specs
644
- dest_file = File.join("#{'spec/dummy/' if Rails.env.test?}spec/features#{namespace_with_dash}", "#{plural}_behavior_spec.rb")
744
+ dest_file = File.join("#{filepath_prefix}spec/features#{namespace_with_dash}", "#{plural}_behavior_spec.rb")
645
745
 
646
746
  if File.exist?(dest_file)
647
747
  existing_file = File.open(dest_file)
@@ -663,29 +763,26 @@ module HotGlue
663
763
  template "system_spec.rb.erb", dest_file
664
764
  end
665
765
 
666
- template "#{@markup}/_errors.#{@markup}", File.join("#{'spec/dummy/' if Rails.env.test?}app/views#{namespace_with_dash}", "_errors.#{@markup}")
766
+ template "#{@markup}/_errors.#{@markup}", File.join("#{filepath_prefix}app/views#{namespace_with_dash}", "_errors.#{@markup}")
667
767
  end
668
768
 
669
769
  def spec_foreign_association_merge_hash
670
770
  ", #{testing_name}: #{testing_name}1"
671
771
  end
672
772
 
773
+ def testing_name
774
+ singular_class.gsub("::","_").underscore
775
+ end
776
+
673
777
  def spec_related_column_lets
674
- (@columns - @show_only).map { |col|
675
- type = eval("#{singular_class}.columns_hash['#{col}']").type
676
- if (type == :integer && col.to_s.ends_with?("_id") || type == :uuid)
677
- assoc = "#{col.to_s.gsub('_id','')}"
678
- the_foreign_class = eval(@singular_class + ".reflect_on_association(:" + assoc + ")").class_name.split("::").last.underscore
679
- hawk_keys_on_lets = (@hawk_keys["#{assoc}_id".to_sym] ? ", #{@auth.gsub('current_', '')}: #{@auth}": "")
680
-
681
- " let!(:#{assoc}1) {create(:#{the_foreign_class}" + hawk_keys_on_lets + ")}"
682
- end
683
- }.compact.join("\n")
778
+ @columns_map.collect { |col, col_object|
779
+ col_object.spec_related_column_lets
780
+ }.join("\n")
684
781
  end
685
782
 
686
783
  def list_column_headings
687
784
  @template_builder.list_column_headings(
688
- columns: @layout_object[:columns][:container],
785
+ layout_object: @layout_object,
689
786
  col_identifier: @layout_strategy.column_classes_for_column_headings,
690
787
  column_width: @layout_strategy.column_width,
691
788
  singular: @singular
@@ -693,45 +790,37 @@ module HotGlue
693
790
  end
694
791
 
695
792
  def columns_spec_with_sample_data
696
- @columns.map { |c|
697
- type = eval("#{singular_class}.columns_hash['#{c}']").type
698
- random_data = case type
699
- when :integer
700
- rand(1...1000)
701
- when :string
702
- FFaker::AnimalUS.common_name
703
- when :text
704
- FFaker::AnimalUS.common_name
705
- when :datetime
706
- Time.now + rand(1..5).days
707
- end
708
- c.to_s + ": '" + random_data.to_s + "'"
793
+ @columns_map.map { |col, col_object|
794
+ unless col_object.is_a?(AssociationField)
795
+ random_data = col_object.spec_random_data
796
+ col.to_s + ": '" + random_data.to_s + "'"
797
+ end
709
798
  }.join(", ")
710
799
  end
711
800
 
712
-
713
801
  def regenerate_me_code
714
802
  "rails generate hot_glue:scaffold #{ @meta_args[0][0] } #{@meta_args[1].collect{|x| x.gsub(/\s*=\s*([\S\s]+)/, '=\'\1\'')}.join(" ")}"
715
803
  end
716
804
 
717
805
  def object_parent_mapping_as_argument_for_specs
806
+
718
807
  if @self_auth
719
808
  ""
720
- elsif @nested_set.any?
809
+ elsif @nested_set.any? && ! @nested_set.last[:optional]
721
810
  ", " + @nested_set.last[:singular] + ": " + @nested_set.last[:singular]
722
- elsif @auth
811
+ elsif @auth && !@god
723
812
  ", #{@auth_identifier}: #{@auth}"
724
813
  end
725
814
  end
726
815
 
727
816
  def objest_nest_factory_setup
728
- res = " "
817
+ res = ""
729
818
  if @auth
730
819
  last_parent = ", #{@auth_identifier}: #{@auth}"
731
820
  end
732
821
 
733
822
  @nested_set.each do |arg|
734
- res << "let(:#{arg[:singular]}) {create(:#{arg[:singular]} #{last_parent} )}\n"
823
+ res << " let(:#{arg[:singular]}) {create(:#{arg[:singular]} #{last_parent} )}\n"
735
824
  last_parent = ", #{arg[:singular]}: #{arg[:singular]}"
736
825
  end
737
826
  res
@@ -747,18 +836,6 @@ module HotGlue
747
836
  @controller_build_name
748
837
  end
749
838
 
750
- def singular_name
751
- @singular
752
- end
753
-
754
- def testing_name
755
- singular_class_name.gsub("::","_").underscore
756
- end
757
-
758
- def singular_class_name
759
- @singular_class
760
- end
761
-
762
839
  def plural_name
763
840
  plural
764
841
  end
@@ -767,78 +844,18 @@ module HotGlue
767
844
  @auth_identifier
768
845
  end
769
846
 
770
- def test_capybara_block(which_partial = :create)
771
- (@columns - (which_partial == :create ? @show_only : (@update_show_only+@show_only))).map { |col|
772
- type = eval("#{singular_class}.columns_hash['#{col}']").type
773
- case type
774
- when :date
775
- " " + "new_#{col} = Date.current + (rand(100).days) \n" +
776
- ' ' + "find(\"[name='#{testing_name}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
777
- when :time
778
- # " " + "new_#{col} = DateTime.current + (rand(100).days) \n" +
779
- # ' ' + "find(\"[name='#{singular}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
780
-
781
- when :datetime
782
- " " + "new_#{col} = DateTime.current + (rand(100).days) \n" +
783
- ' ' + "find(\"[name='#{testing_name}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
784
-
785
- when :integer
786
- if col.to_s.ends_with?("_id")
787
- capybara_block_for_association(col_name: col, which_partial: which_partial)
788
- else
789
- " new_#{col} = rand(10) \n" +
790
- " find(\"[name='#{testing_name}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
791
- end
792
- when :float
793
- " " + "new_#{col} = rand(10) \n" +
794
- " find(\"[name='#{testing_name}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
795
- when :uuid
796
- capybara_block_for_association(col_name: col, which_partial: which_partial)
797
-
798
- when :enum
799
- " list_of_#{col.to_s} = #{singular_class}.defined_enums['#{col.to_s}'].keys \n" +
800
- " " + "new_#{col.to_s} = list_of_#{col.to_s}[rand(list_of_#{col.to_s}.length)].to_s \n" +
801
- ' find("select[name=\'' + singular + '[' + col.to_s + ']\'] option[value=\'#{new_' + col.to_s + '}\']").select_option'
802
-
803
- when :boolean
804
- " new_#{col} = 1 \n" +
805
- " find(\"[name='#{testing_name}[#{col}]'][value='\#{new_" + col.to_s + "}']\").choose"
806
- when :string
807
- faker_string =
808
- if col.to_s.include?('email')
809
- "FFaker::Internet.email"
810
- elsif col.to_s.include?('domain')
811
- "FFaker::Internet.domain_name"
812
- elsif col.to_s.include?('ip_address') || col.to_s.ends_with?('_ip')
813
- "FFaker::Internet.ip_v4_address"
814
- else
815
- "FFaker::Movie.title"
816
- end
817
- " " + "new_#{col} = #{faker_string} \n" +
818
- " find(\"[name='#{testing_name}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
819
- when :text
820
- " " + "new_#{col} = FFaker::Lorem.paragraphs(1).join("") \n" +
821
- " find(\"[name='#{testing_name}[#{ col.to_s }]']\").fill_in(with: new_#{col.to_s})"
822
- end
847
+ def capybara_make_updates(which_partial = :create)
848
+ @columns_map.map { | col, col_obj|
849
+ show_only_list = which_partial == :create ? @show_only : (@update_show_only+@show_only)
823
850
 
851
+ if show_only_list.include?(col)
852
+ " page.should have_no_selector(:css, \"[name='#{testing_name}[#{ col.to_s }]'\")"
853
+ else
854
+ col_obj.spec_setup_and_change_act(which_partial)
855
+ end
824
856
  }.join("\n")
825
857
  end
826
858
 
827
-
828
- def capybara_block_for_association(col_name: nil , which_partial: nil )
829
- assoc = col_name.to_s.gsub('_id','')
830
- if which_partial == :update && @update_show_only.include?(col_name)
831
- # do not update tests
832
- elsif @alt_lookups.keys.include?(col_name.to_s)
833
- lookup = @alt_lookups[col_name.to_s][:lookup_as]
834
- " find(\"[name='#{singular}[__lookup_#{lookup}]']\").fill_in( with: #{assoc}1.#{lookup} )"
835
- else
836
- " #{col_name}_selector = find(\"[name='#{singular}[#{col_name}]']\").click \n" +
837
- " #{col_name}_selector.first('option', text: #{assoc}1.name).select_option"
838
- end
839
- end
840
-
841
-
842
859
  def path_helper_args
843
860
  if @nested_set.any? && @nested
844
861
  [(@nested_set).collect{|a| "#{a[:singular]}"} , singular].join(",")
@@ -966,11 +983,9 @@ module HotGlue
966
983
  else
967
984
  "@" + @nested_set.last[:singular] + ".#{plural}"
968
985
  end
969
-
970
986
  end
971
987
  end
972
988
 
973
-
974
989
  def all_objects_root
975
990
  if @auth
976
991
  if @self_auth
@@ -1001,7 +1016,6 @@ module HotGlue
1001
1016
  !Gem::Specification.sort_by{ |g| [g.name.downcase, g.version] }.group_by{ |g| g.name }['devise']
1002
1017
  end
1003
1018
 
1004
-
1005
1019
  def magic_button_output
1006
1020
  @template_builder.magic_button_output(
1007
1021
  path: HotGlue.optionalized_ternary(namespace: @namespace,
@@ -1019,7 +1033,6 @@ module HotGlue
1019
1033
  "#{namespace_with_trailing_dash}nav"
1020
1034
  end
1021
1035
 
1022
-
1023
1036
  def include_nav_template
1024
1037
  File.exist?("#{Rails.root}/app/views/#{namespace_with_trailing_dash}_nav.html.#{@markup}")
1025
1038
  end
@@ -1032,7 +1045,7 @@ module HotGlue
1032
1045
  dest_filename = cc_filename_with_extensions("#{view}", "#{@markup}")
1033
1046
 
1034
1047
 
1035
- dest_filepath = File.join("#{'spec/dummy/' if Rails.env.test?}app/views#{namespace_with_dash}",
1048
+ dest_filepath = File.join("#{filepath_prefix}app/views#{namespace_with_dash}",
1036
1049
  @controller_build_folder, dest_filename)
1037
1050
 
1038
1051
 
@@ -1046,7 +1059,7 @@ module HotGlue
1046
1059
  formats.each do |format|
1047
1060
  source_filename = cc_filename_with_extensions( "#{@markup}/#{view}.turbo_stream.#{@markup}")
1048
1061
  dest_filename = cc_filename_with_extensions("#{view}", "turbo_stream.#{@markup}")
1049
- dest_filepath = File.join("#{'spec/dummy/' if Rails.env.test?}app/views#{namespace_with_dash}",
1062
+ dest_filepath = File.join("#{filepath_prefix}app/views#{namespace_with_dash}",
1050
1063
  @controller_build_folder, dest_filename)
1051
1064
 
1052
1065
 
@@ -1055,15 +1068,6 @@ module HotGlue
1055
1068
 
1056
1069
  end
1057
1070
  end
1058
-
1059
- # menu_file = "app/views#{namespace_with_dash}/menu.erb"
1060
- #
1061
- # if File.exist?(menu_file)
1062
- # # TODO: can I insert the new menu item into the menu programatically here?
1063
- # # not sure how i would achieve this without nokogiri
1064
- #
1065
- # end
1066
-
1067
1071
  end
1068
1072
 
1069
1073
  def append_model_callbacks
@@ -1071,7 +1075,7 @@ module HotGlue
1071
1075
 
1072
1076
  if options['with_turbo_streams'] == true
1073
1077
  dest_filename = cc_filename_with_extensions("#{singular_class.underscore}", "rb")
1074
- dest_filepath = File.join("#{'spec/dummy/' if Rails.env.test?}app/models", dest_filename)
1078
+ dest_filepath = File.join("#{filepath_prefix}app/models", dest_filename)
1075
1079
 
1076
1080
 
1077
1081
  puts "appending turbo callbacks to #{dest_filepath}"
@@ -1098,11 +1102,7 @@ module HotGlue
1098
1102
  end
1099
1103
 
1100
1104
  def namespace_with_trailing_dash
1101
- if @namespace
1102
- "#{@namespace}/"
1103
- else
1104
- ""
1105
- end
1105
+ @namespace ? "#{@namespace}/" : ""
1106
1106
  end
1107
1107
 
1108
1108
  def all_views
@@ -1148,25 +1148,13 @@ module HotGlue
1148
1148
  false
1149
1149
  end
1150
1150
 
1151
-
1152
1151
  def model_search_fields # an array of fields we can search on
1153
1152
  []
1154
1153
  end
1155
1154
 
1156
1155
  def form_fields_html
1157
- @template_builder.all_form_fields(
1158
- columns: @layout_object[:columns][:container],
1159
- show_only: @show_only,
1160
- update_show_only: @update_show_only,
1161
- singular_class: singular_class,
1162
- singular: singular,
1163
- hawk_keys: @hawk_keys,
1164
- col_identifier: @layout_strategy.column_classes_for_form_fields,
1165
- ownership_field: @ownership_field,
1166
- form_labels_position: @form_labels_position,
1167
- form_placeholder_labels: @form_placeholder_labels,
1168
- alt_lookups: @alt_lookups
1169
- )
1156
+ @template_builder.all_form_fields(layout_strategy: @layout_strategy,
1157
+ layout_object: @layout_object)
1170
1158
  end
1171
1159
 
1172
1160
  def list_label
@@ -1179,13 +1167,10 @@ module HotGlue
1179
1167
 
1180
1168
  def all_line_fields
1181
1169
  @template_builder.all_line_fields(
1182
- perc_width: @layout_strategy.each_col, #undefined method `each_col'
1183
- columns: @layout_object[:columns][:container],
1184
- show_only: @show_only,
1185
- singular_class: singular_class,
1186
- singular: singular,
1187
1170
  col_identifier: @layout_strategy.column_classes_for_line_fields,
1188
- inline_list_labels: @inline_list_labels
1171
+ perc_width: @layout_strategy.each_col, #undefined method `each_col'
1172
+ layout_strategy: @layout_strategy,
1173
+ layout_object: @layout_object
1189
1174
  )
1190
1175
  end
1191
1176
 
@@ -1197,8 +1182,6 @@ module HotGlue
1197
1182
  end
1198
1183
  end
1199
1184
 
1200
-
1201
-
1202
1185
  def display_class
1203
1186
  me = eval(singular_class)
1204
1187
 
@@ -1216,7 +1199,6 @@ module HotGlue
1216
1199
  elsif me.column_names.include?("email") || me.instance_methods(false).include?(:email)
1217
1200
  "email"
1218
1201
  else
1219
- # NOT UNREACHABLE BUT UNTESTED
1220
1202
  exit_message = "*** Oops: Can't find any column to use as the display label on #{singular_class} model . TODO: Please implement just one of: 1) name, 2) to_label, 3) full_name, 4) display_name, 5) email, or 6) number directly on your #{singular_class} model (either as database field or model methods), then RERUN THIS GENERATOR. (If more than one is implemented, the field to use will be chosen based on the rank here, e.g., if name is present it will be used; if not, I will look for a to_label, etc)"
1221
1203
  raise(HotGlue::Error, exit_message)
1222
1204
  end
@@ -1253,7 +1235,7 @@ module HotGlue
1253
1235
  flash[:notice] = (flash[:notice] || \"\") << (res ? res + \" \" : \"\")
1254
1236
  rescue ActiveRecord::RecordInvalid => e
1255
1237
  @#{singular}.errors.add(:base, e.message)
1256
- flash[:alert] = (flash[:alert] || \"\") << 'There was ane error #{magic_button}ing your #{@singular}: '
1238
+ flash[:alert] = (flash[:alert] || \"\") << 'There was an error #{magic_button}ing your #{@singular}: '
1257
1239
  end
1258
1240
  end"
1259
1241
 
@@ -1282,8 +1264,8 @@ module HotGlue
1282
1264
  end
1283
1265
 
1284
1266
  def n_plus_one_includes
1285
- if @associations.any?
1286
- ".includes(" + @associations.map{|x| ":#{x.to_s}"}.join(", ") + ")"
1267
+ if @associations.any? || @attachments.any?
1268
+ ".includes(" + (@associations.map{|x| x} + @attachments.collect{|k,v| "#{k}_attachment"}).map{|x| ":#{x.to_s}"}.join(", ") + ")"
1287
1269
  else
1288
1270
  ""
1289
1271
  end
@@ -1319,9 +1301,17 @@ module HotGlue
1319
1301
 
1320
1302
  def hawk_to_ruby
1321
1303
  res = @hawk_keys.collect{ |k,v|
1322
- "#{k}: [#{v[0]}, \"#{v[1]}\"]"
1304
+ "#{k.to_s}: [#{v[:bind_to].join(".")}]"
1323
1305
  }.join(", ")
1324
1306
  res
1325
1307
  end
1308
+
1309
+ def controller_attachment_orig_filename_pickup_syntax
1310
+ @attachments.collect{ |key, attachment| "\n" + " modified_params[:#{ attachment[:field_for_original_filename] }] = #{singular}_params['#{ key }'].original_filename" if attachment[:field_for_original_filename] }.compact.join("\n")
1311
+ end
1312
+
1313
+ def any_datetime_fields?
1314
+ (@columns - @attachments.keys.collect(&:to_sym)).collect{|col| eval("#{singular_class}.columns_hash['#{col}']").type}.include?(:datetime)
1315
+ end
1326
1316
  end
1327
1317
  end