Class: Scaffolding::Transformer

Inherits:
Object
  • Object
show all
Defined in:
lib/scaffolding/transformer.rb

Constant Summary collapse

RUBY_NEW_FIELDS_PROCESSING_HOOK =
"# πŸš… super scaffolding will insert processing for new fields above this line."
RUBY_NEW_ARRAYS_HOOK =
"# πŸš… super scaffolding will insert new arrays above this line."
RUBY_NEW_FIELDS_HOOK =
"# πŸš… super scaffolding will insert new fields above this line."
RUBY_ADDITIONAL_NEW_FIELDS_HOOK =
"# πŸš… super scaffolding will also insert new fields above this line."
RUBY_EVEN_MORE_NEW_FIELDS_HOOK =
"# πŸš… super scaffolding will additionally insert new fields above this line."
RUBY_NEW_API_VERSION_HOOK =
"# πŸš… super scaffolding will insert new api versions above this line."
RUBY_FILES_HOOK =
"# πŸš… super scaffolding will insert file-related logic above this line."
RUBY_FACTORY_SETUP_HOOK =
"# πŸš… super scaffolding will insert factory setup in place of this line."
ERB_NEW_FIELDS_HOOK =
"<%#{RUBY_NEW_FIELDS_HOOK} %>"
CONCERNS_HOOK =
"# πŸš… add concerns above."
ATTR_ACCESSORS_HOOK =
"# πŸš… add attribute accessors above."
BELONGS_TO_HOOK =
"# πŸš… add belongs_to associations above."
HAS_MANY_HOOK =
"# πŸš… add has_many associations above."
OAUTH_PROVIDERS_HOOK =
"# πŸš… add oauth providers above."
HAS_ONE_HOOK =
"# πŸš… add has_one associations above."
SCOPES_HOOK =
"# πŸš… add scopes above."
VALIDATIONS_HOOK =
"# πŸš… add validations above."
CALLBACKS_HOOK =
"# πŸš… add callbacks above."
DELEGATIONS_HOOK =
"# πŸš… add delegations above."
METHODS_HOOK =
"# πŸš… add methods above."

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(child, parents, cli_options = {}) ⇒ Transformer

Returns a new instance of Transformer.



40
41
42
43
44
45
46
47
48
# File 'lib/scaffolding/transformer.rb', line 40

def initialize(child, parents, cli_options = {})
  self.child = child
  self.parent = parents.first
  self.parents = parents
  self.namespace = cli_options["namespace"] || "account"
  self.class_names_transformer = Scaffolding::ClassNamesTransformer.new(child, parent, namespace)
  self.cli_options = cli_options
  self.additional_steps = []
end

Instance Attribute Details

#additional_stepsObject

Returns the value of attribute additional_steps.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def additional_steps
  @additional_steps
end

#childObject

Returns the value of attribute child.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def child
  @child
end

#class_names_transformerObject

Returns the value of attribute class_names_transformer.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def class_names_transformer
  @class_names_transformer
end

#cli_optionsObject

Returns the value of attribute cli_options.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def cli_options
  @cli_options
end

#namespaceObject

Returns the value of attribute namespace.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def namespace
  @namespace
end

#parentObject

Returns the value of attribute parent.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def parent
  @parent
end

#parentsObject

Returns the value of attribute parents.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def parents
  @parents
end

#suppress_could_not_findObject

Returns the value of attribute suppress_could_not_find.



8
9
10
# File 'lib/scaffolding/transformer.rb', line 8

def suppress_could_not_find
  @suppress_could_not_find
end

Instance Method Details

#add_ability_line_to_roles_yml(class_names = nil) ⇒ Object



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/scaffolding/transformer.rb', line 484

def add_ability_line_to_roles_yml(class_names = nil)
  model_names = class_names || [child]
  role_file = "./config/models/roles.yml"
  roles_hash = YAML.load_file(role_file)
  default_role_placements = [
    [:default, :models],
    [:admin, :models]
  ]

  model_names.each do |model_name|
    default_role_placements.each do |role_placement|
      stringified_role_placement = role_placement.map { |placement| placement.to_s }
      if roles_hash.dig(*stringified_role_placement)[model_name].nil?
        role_type = (role_placement.first == :admin) ? "manage" : "read"
        Scaffolding::FileManipulator.add_line_to_yml_file(role_file, "#{model_name}: #{role_type}", role_placement)
      end
    end
  end
end

#add_additional_step(color, message) ⇒ Object



1307
1308
1309
# File 'lib/scaffolding/transformer.rb', line 1307

def add_additional_step(color, message)
  additional_steps.push [color, message]
end

#add_attributes_to_various_views(attributes, scaffolding_options = {}) ⇒ Object



630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
# File 'lib/scaffolding/transformer.rb', line 630

def add_attributes_to_various_views(attributes, scaffolding_options = {})
  sql_type_to_field_type_mapping = {
    # 'binary' => '',
    "boolean" => "options",
    "date" => "date_field",
    "datetime" => "date_and_time_field",
    "decimal" => "text_field",
    "float" => "text_field",
    "integer" => "text_field",
    "bigint" => "text_field",
    # 'primary_key' => '',
    # 'references' => '',
    "string" => "text_field",
    "text" => "text_area"
    # 'time' => '',
    # 'timestamp' => '',
  }

  # add attributes to various views.
  attributes.each_with_index do |attribute_definition, index|
    attribute = Scaffolding::Attribute.new(attribute_definition, scaffolding_options[:type], index)

    if attribute.is_first_attribute? && ["trix_editor", "ckeditor", "text_area"].include?(attribute.type)
      puts ""
      puts "The first attribute of your model cannot be any of the following types:".red
      puts "1. trix_editor"
      puts "2. ckeditor"
      puts "3. text_area"
      puts ""
      puts "Please ensure you have another attribute type as the first attribute for your model and try again."

      exit
    end

    if sql_type_to_field_type_mapping[attribute.type]
      attribute.type = sql_type_to_field_type_mapping[attribute.type]
    end

    cell_attributes = if attribute.is_boolean?
      ' class="text-center"'
    end

    # don't do table columns for certain types of fields and attribute partials
    if ["trix_editor", "ckeditor", "text_area"].include?(attribute.type) || ["html", "has_many"].include?(attribute.partial_name)
      cli_options["skip-table"] = true
    end

    if attribute.type == "none"
      cli_options["skip-form"] = true
    end

    if attribute.partial_name == "none"
      cli_options["skip-show"] = true
      cli_options["skip-table"] = true
    end

    #
    # MODEL VALIDATIONS
    #

    unless cli_options["skip-form"] || attribute.is_unscoped?

      file_name = "./app/models/scaffolding/completely_concrete/tangible_thing.rb"

      if attribute.is_association?
        field_content = if attribute.options[:source]
          <<~RUBY
            def valid_#{attribute.collection_name}
              #{attribute.options[:source]}
            end

          RUBY
        else
          add_additional_step :yellow, transform_string("You'll need to implement the `valid_#{attribute.collection_name}` method of `Scaffolding::CompletelyConcrete::TangibleThing` in `./app/models/scaffolding/completely_concrete/tangible_thing.rb`. This is the method that will be used to populate the `#{attribute.type}` field and also validate that users aren't trying to exploit multitenancy.")

          <<~RUBY
            def valid_#{attribute.collection_name}
              raise "please review and implement `valid_#{attribute.collection_name}` in `app/models/scaffolding/completely_concrete/tangible_thing.rb`."
              # please specify what objects should be considered valid for assigning to `#{attribute.name_without_id}`.
              # the resulting code should probably look something like `team.#{attribute.collection_name}`.
            end

          RUBY
        end

        scaffold_add_line_to_file(file_name, field_content, METHODS_HOOK, prepend: true)

        if attribute.is_belongs_to?
          scaffold_add_line_to_file(file_name, "validates :#{attribute.name_without_id}, scope: true", VALIDATIONS_HOOK, prepend: true)
        end
      end

    end

    #
    # FORM FIELD
    #

    unless cli_options["skip-form"] || attribute.options[:readonly]

      # add `has_rich_text` for trix editor fields.
      if attribute.type == "trix_editor"
        file_name = "./app/models/scaffolding/completely_concrete/tangible_thing.rb"
        scaffold_add_line_to_file(file_name, "has_rich_text :#{attribute.name}", HAS_ONE_HOOK, prepend: true)
      end

      # field on the form.
      field_attributes = {method: ":#{attribute.name}"}
      field_options = {}
      options = {}

      if attribute.is_first_attribute?
        field_options[:autofocus] = "true"
      end

      if attribute.is_id? && attribute.type == "super_select"
        options[:include_blank] = "t('.fields.#{attribute.name}.placeholder')"
        # add_additional_step :yellow, transform_string("We've added a reference to a `placeholder` to the form for the select or super_select field, but unfortunately earlier versions of the scaffolded locales Yaml don't include a reference to `fields: *fields` under `form`. Please add it, otherwise your form won't be able to locate the appropriate placeholder label.")
      end

      field_options[:multiple] = "true" if attribute.is_multiple?

      valid_values = if attribute.is_id?
        "valid_#{attribute.name_without_id.pluralize}"
      elsif attribute.is_ids?
        "valid_#{attribute.collection_name}"
      end

      # https://stackoverflow.com/questions/21582464/is-there-a-ruby-hashto-s-equivalent-for-the-new-hash-syntax
      if field_options.any? || options.any?
        field_options_key = if attribute.type == "super_select"
          if options.any?
            field_attributes[:options] = "{" + field_options.map { |key, value| "#{key}: #{value}" }.join(", ") + "}"
          end

          :html_options
        else
          field_options.merge!(options)

          :options
        end

        field_attributes[field_options_key] = "{" + field_options.map { |key, value| "#{key}: #{value}" }.join(", ") + "}"
      end

      if attribute.is_association?
        short = attribute.association_class_name
        case attribute.type
        when "buttons", "options"
          field_attributes["\n  options"] = "@tangible_thing.#{valid_values}.map { |#{short}| [#{short}.id, #{short}.#{attribute.options[:label]}] }"
        when "super_select"
          field_attributes["\n  choices"] = "@tangible_thing.#{valid_values}.map { |#{short}| [#{short}.#{attribute.options[:label]}, #{short}.id] }"
        end
      end

      if attribute.type == "color_picker"
        field_attributes[:color_picker_field_options] = "t('#{child.pluralize.underscore}.fields.#{attribute.name}.options')"
      end

      field_content = "<%= render 'shared/fields/#{attribute.type}'#{", " if field_attributes.any?}#{field_attributes.map { |key, value| "#{key}: #{value}" }.join(", ")} %>"

      # TODO Add more of these from other packages?
      is_core_model = ["Team", "User", "Membership"].include?(child)

      scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_form.html.erb", field_content, ERB_NEW_FIELDS_HOOK, prepend: true, suppress_could_not_find: is_core_model)
      scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_fields.html.erb", field_content, ERB_NEW_FIELDS_HOOK, prepend: true, suppress_could_not_find: !is_core_model)
    end

    #
    # SHOW VIEW
    #

    unless cli_options["skip-show"]

      if attribute.is_id?
        <<~ERB
          <% if @tangible_thing.#{attribute.name_without_id} %>
            <div class="form-group">
              <label class="col-form-label"><%= t('.fields.#{attribute.name}.heading') %></label>
              <div>
                <%= link_to @tangible_thing.#{attribute.name_without_id}.#{attribute.options[:label]}, [:account, @tangible_thing.#{attribute.name_without_id}] %>
              </div>
            </div>
          <% end %>
        ERB
      elsif attribute.is_ids?
        <<~ERB
          <% if @tangible_thing.#{attribute.collection_name}.any? %>
            <div class="form-group">
              <label class="col-form-label"><%= t('.fields.#{attribute.name}.heading') %></label>
              <div>
                <%= @tangible_thing.#{attribute.collection_name}.map { |#{attribute.name_without_ids}| link_to #{attribute.name_without_ids}.#{attribute.options[:label]}, [:account, #{attribute.name_without_ids}] }.to_sentence.html_safe %>
              </div>
            </div>
          <% end %>
        ERB
      end

      # this gets stripped and is one line, so indentation isn't a problem.
      field_content = <<-ERB
        <%= render 'shared/attributes/#{attribute.partial_name}', attribute: :#{attribute.is_vanilla? ? attribute.name : attribute.name_without_id_suffix} %>
      ERB

      if attribute.type == "password_field"
        field_content.gsub!(/\s%>/, ", options: { password: true } %>")
      end

      show_page_doesnt_exist = child == "User"
      scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/show.html.erb", field_content.strip, ERB_NEW_FIELDS_HOOK, prepend: true, suppress_could_not_find: show_page_doesnt_exist)

    end

    #
    # INDEX TABLE
    #

    unless cli_options["skip-table"]

      # table header.
      field_content = "<th#{cell_attributes.present? ? " " + cell_attributes : ""}><%= t('.fields.#{attribute.is_vanilla? ? attribute.name : attribute.name_without_id_suffix}.heading') %></th>"

      unless ["Team", "User"].include?(child)
        scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb", field_content, "<%# πŸš… super scaffolding will insert new field headers above this line. %>", prepend: true)
      end

      # If these strings are the same, we get duplicate variable names in the _index.html.erb partial,
      # so we account for that here. Run the Super Scaffolding test setup script and check the index partial
      # of models with namespaced parents for reference (i.e. - Objective, Projects::Step).
      transformed_abstract_str = transform_string("absolutely_abstract_creative_concept")
      transformed_concept_str = transform_string("creative_concept")
      transformed_file_name = transform_string("./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb")
      if (transformed_abstract_str == transformed_concept_str) && File.exist?(transformed_file_name)
        replace_in_file(
          transformed_file_name,
          "#{transformed_abstract_str} = @#{transformed_abstract_str} || @#{transformed_concept_str}",
          "#{transformed_abstract_str} = @#{transformed_concept_str}"
        )
      end

      table_cell_options = []

      if attribute.is_first_attribute?
        table_cell_options << "url: [:account, tangible_thing]"
      end

      # this gets stripped and is one line, so indentation isn't a problem.
      field_content = <<-ERB
        <td#{cell_attributes}><%= render 'shared/attributes/#{attribute.partial_name}', attribute: :#{attribute.is_vanilla? ? attribute.name : attribute.name_without_id_suffix}#{", #{table_cell_options.join(", ")}" if table_cell_options.any?} %></td>
      ERB

      case attribute.type
      when "password_field"
        field_content.gsub!(/\s%>/, ", options: { password: true } %>")
      when "address_field"
        field_content.gsub!(/\s%>/, ", one_line: true %>")
      end

      unless ["Team", "User"].include?(child)
        scaffold_add_line_to_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_tangible_thing.html.erb", field_content.strip, ERB_NEW_FIELDS_HOOK, prepend: true)
      end

    end

    #
    # LOCALIZATIONS
    #

    unless cli_options["skip-locales"]

      yaml_template = <<~YAML

        <%= attribute.name %>: <% if attribute.is_association? %>&<%= attribute.name_without_id_suffix %><% end %>
          _: &#{attribute.name} #{attribute.title_case}
          label: *#{attribute.name}
          heading: *#{attribute.name}
          api_title: *#{attribute.name}
          api_description: *#{attribute.name}

          <% if attribute.type == "super_select" %>
          <% if attribute.is_required? %>
          placeholder: Select <% attribute.title_case.with_indefinite_article %>
          <% else %>
          placeholder: None
          <% end %>
          <% end %>

          <% if attribute.is_boolean? %>

          options:
            yes: "Yes"
            no: "No"

          <% elsif ["buttons", "super_select", "options"].include?(attribute.type) && !attribute.is_association? %>

          options:
            one: One
            two: Two
            three: Three

          <% end %>

          <% if attribute.type == "color_picker" %>
          options:
            - '#9C73D2'
            - '#48CDFE'
            - '#53F3ED'
            - '#47E37F'
            - '#F2593D'
            - '#F68421'
            - '#F9DE00'
            - '#929292'
          <% end %>

        <% if attribute.is_association? %>
        <%= attribute.name_without_id_suffix %>: *<%= attribute.name_without_id_suffix %>
        <% end %>
      YAML

      field_content = ERB.new(yaml_template).result(binding).lines.select(&:present?).join

      scaffold_add_line_to_file("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml", field_content, RUBY_NEW_FIELDS_HOOK, prepend: true)

      # active record's field label.
      scaffold_add_line_to_file("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml", "#{attribute.name}: *#{attribute.name}", "# πŸš… super scaffolding will insert new activerecord attributes above this line.", prepend: true)

    end

    #
    # STRONG PARAMETERS
    #

    unless cli_options["skip-form"] || attribute.options[:readonly]

      # add attributes to strong params.
      [
        "./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb",
        "./app/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller.rb"
      ].each do |file|
        if attribute.is_ids? || attribute.is_multiple?
          scaffold_add_line_to_file(file, "#{attribute.name}: [],", RUBY_NEW_ARRAYS_HOOK, prepend: true)
          if attribute.file_field? || attribute.active_storage_image?
            scaffold_add_line_to_file(file, "#{attribute.name}_removal: [],", RUBY_NEW_ARRAYS_HOOK, prepend: true)
          end
        elsif attribute.type == "address_field"
          address_strong_params = <<~RUBY
            #{attribute.name}_attributes: [
              :id,
              :_destroy,
              :address_one,
              :address_two,
              :city,
              :country_id,
              :region_id,
              :postal_code
            ],
          RUBY
          scaffold_add_line_to_file(file, address_strong_params, RUBY_NEW_ARRAYS_HOOK, prepend: true)
        else
          scaffold_add_line_to_file(file, ":#{attribute.name},", RUBY_NEW_FIELDS_HOOK, prepend: true)
          if attribute.file_field? || attribute.active_storage_image?
            scaffold_add_line_to_file(file, ":#{attribute.name}_removal,", RUBY_NEW_FIELDS_HOOK, prepend: true)
          end
        end
      end

      scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", attribute.special_processing, RUBY_NEW_FIELDS_PROCESSING_HOOK, prepend: true) if attribute.special_processing
    end

    #
    # ASSOCIATED MODELS
    #

    unless cli_options["skip-form"] || attribute.options[:readonly]

      # set default values for associated models.
      case attribute.type
      when "address_field"
        scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", "before_action :set_default_#{attribute.name}, except: :index", "ApplicationController", increase_indent: true)

        method_content = <<~RUBY

          def set_default_#{attribute.name}
            @tangible_thing.#{attribute.name} ||= Address.new
          end
        RUBY
        scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", method_content, "end", prepend: true, increase_indent: true, exact_match: true)
      end

    end

    #
    # API SERIALIZER
    #

    unless cli_options["skip-api"]

      # TODO The serializers can't handle these `has_rich_text` attributes.
      unless attribute.type == "trix_editor"
        unless attribute.type == "file_field"
          scaffold_add_line_to_file("./app/views/api/v1/scaffolding/completely_concrete/tangible_things/_tangible_thing.json.jbuilder", ":#{attribute.name},", RUBY_NEW_FIELDS_HOOK, prepend: true, suppress_could_not_find: true)
        end

        assertion = case attribute.type
        when "date_field"
          "assert_equal_or_nil Date.parse(tangible_thing_data['#{attribute.name}']), tangible_thing.#{attribute.name}"
        when "date_and_time_field"
          "assert_equal_or_nil DateTime.parse(tangible_thing_data['#{attribute.name}']), tangible_thing.#{attribute.name}"
        when "file_field"
          if attribute.is_multiple?
            "assert_equal tangible_thing_data['#{attribute.name}'], @tangible_thing.#{attribute.name}.map{|file| rails_blob_path(file)} unless controller.action_name == 'create'"
          else
            "assert_equal tangible_thing_data['#{attribute.name}'], rails_blob_path(@tangible_thing.#{attribute.name}) unless controller.action_name == 'create'"
          end
        else
          "assert_equal_or_nil tangible_thing_data['#{attribute.name}'], tangible_thing.#{attribute.name}"
        end
        scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb", assertion, RUBY_NEW_FIELDS_HOOK, prepend: true)
      end

      # File fields are handled in a specific way when using the jsonapi-serializer.
      if attribute.type == "file_field"
        jbuilder_content = if attribute.is_multiple?
          <<~RUBY
            json.#{attribute.name} do 
              json.array! tangible_thing.#{attribute.name}.map { |file| url_for(file)  }
            end if tangible_thing.#{attribute.name}.attached?
          RUBY
        else
          "json.#{attribute.name} url_for(tangible_thing.#{attribute.name}) if tangible_thing.#{attribute.name}.attached?"
        end

        scaffold_add_line_to_file("./app/views/api/v1/scaffolding/completely_concrete/tangible_things/_tangible_thing.json.jbuilder", jbuilder_content, RUBY_FILES_HOOK, prepend: true, suppress_could_not_find: true)
        # We also want to make sure we attach the dummy file in the API test on setup
        file_name = "./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb"
        content = if attribute.is_multiple?
          <<~RUBY
            @#{child.underscore}.#{attribute.name} = [Rack::Test::UploadedFile.new("test/support/foo.txt")]
            @another_#{child.underscore}.#{attribute.name} = [Rack::Test::UploadedFile.new("test/support/foo.txt")]
          RUBY
        else
          <<~RUBY
            @#{child.underscore}.#{attribute.name} = Rack::Test::UploadedFile.new("test/support/foo.txt")
            @another_#{child.underscore}.#{attribute.name} = Rack::Test::UploadedFile.new("test/support/foo.txt")
          RUBY
        end
        scaffold_add_line_to_file(file_name, content, RUBY_FILES_HOOK, prepend: true)
      end

      if attribute.default_value
        unless attribute.options[:readonly]
          scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb", "#{attribute.name}: #{attribute.default_value},", RUBY_ADDITIONAL_NEW_FIELDS_HOOK, prepend: true)
          scaffold_add_line_to_file("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb", "assert_equal @tangible_thing.#{attribute.name}, #{attribute.default_value}", RUBY_EVEN_MORE_NEW_FIELDS_HOOK, prepend: true)
        end
      end
    end

    #
    # OPENAPI DOCUMENTS
    #

    unless cli_options["skip-api"]
      # We always want to suppress this error for this file, since it doesn't exist by default. We reset this below.
      suppress_could_not_find_state = suppress_could_not_find
      self.suppress_could_not_find = true

      # It's OK that this won't be found most of the time.
      scaffold_add_line_to_file(
        "./app/views/api/v1/open_api/scaffolding/completely_concrete/tangible_things/_components.yaml.erb",
        "<%= attribute :#{attribute.name} %>",
        "<%# πŸš… super scaffolding will insert new attributes above this line. %>",
        prepend: true
      )

      # It's OK that this won't be found most of the time.
      scaffold_add_line_to_file(
        "./app/views/api/v1/open_api/scaffolding/completely_concrete/tangible_things/_components.yaml.erb",
        "<%= parameter :#{attribute.name} %>",
        "<%# πŸš… super scaffolding will insert new parameter above this line. %>",
        prepend: true
      )

      self.suppress_could_not_find = suppress_could_not_find_state
    end

    #
    # MODEL ASSOCATIONS
    #

    unless cli_options["skip-model"]

      if attribute.is_belongs_to?
        unless attribute.options[:class_name]
          attribute.options[:class_name] = attribute.name_without_id.classify
        end

        file_name = "app/models/#{attribute.options[:class_name].underscore}.rb"
        unless File.exist?(file_name)
          raise "You'll need to specify a `class_name` option for `#{attribute.name}` because there is no `#{attribute.options[:class_name].classify}` model defined in `#{file_name}`. Try again with `#{attribute.name}:#{attribute.type}[class_name=SomeClassName]`."
        end

        modified_migration = false

        # find the database migration that defines this relationship.
        expected_reference = "add_reference :#{class_names_transformer.table_name}, :#{attribute.name_without_id}"
        migration_file_name = `grep "#{expected_reference}" db/migrate/*`.split(":").first

        # if that didn't work, see if we can find a creation of the reference when the table was created.
        unless migration_file_name
          confirmation_reference = "create_table :#{class_names_transformer.table_name}"
          confirmation_migration_file_name = `grep "#{confirmation_reference}" db/migrate/*`.split(":").first

          fallback_reference = "t.references :#{attribute.name_without_id}"
          fallback_migration_file_name = `grep "#{fallback_reference}" db/migrate/* | grep #{confirmation_migration_file_name}`.split(":").first

          if fallback_migration_file_name == confirmation_migration_file_name
            migration_file_name = fallback_migration_file_name
          end
        end

        unless attribute.is_required?

          if migration_file_name
            replace_in_file(migration_file_name, ":#{attribute.name_without_id}, null: false", ":#{attribute.name_without_id}, null: true")
            modified_migration = true
          else
            add_additional_step :yellow, "We would have expected there to be a migration that defined `#{expected_reference}`, but we didn't find one. Where was the reference added to this model? It's _probably_ the original creation of the table, but we couldn't find that either. Either way, you need to rollback, change 'null: false' to 'null: true' for this column, and re-run the migration (unless, of course, that attribute _is_ required, then you need to add a validation on the model)."
          end

        end

        # unless the table name matches the association name.
        unless attribute.class_name_matches?
          if migration_file_name
            # There are two forms this association creation can take.
            replace_in_file(migration_file_name, "foreign_key: true", "foreign_key: {to_table: \"#{attribute.options[:class_name].tableize.tr("/", "_")}\"}", /t\.references :#{attribute.name_without_id}/)
            replace_in_file(migration_file_name, "foreign_key: true", "foreign_key: {to_table: \"#{attribute.options[:class_name].tableize.tr("/", "_")}\"}", /add_reference :#{child.underscore.pluralize.tr("/", "_")}, :#{attribute.name_without_id}/)

            modified_migration = true
          else
            add_additional_step :yellow, "We would have expected there to be a migration that defined `#{expected_reference}`, but we didn't find one. Where was the reference added to this model? It's _probably_ the original creation of the table. Either way, you need to rollback, change \"foreign_key: true\" to \"foreign_key: {to_table: '#{attribute.options[:class_name].tableize.tr("/", "_")}'}\" for this column, and re-run the migration."
          end
        end

        optional_line = ", optional: true" unless attribute.is_required?

        # if the `belongs_to` is already there from `rails g model`..
        scaffold_replace_line_in_file(
          "./app/models/scaffolding/completely_concrete/tangible_thing.rb",
          attribute.class_name_matches? ?
            "belongs_to :#{attribute.name_without_id}#{optional_line}" :
            "belongs_to :#{attribute.name_without_id}, class_name: \"#{attribute.options[:class_name]}\"#{optional_line}",
          "belongs_to :#{attribute.name_without_id}"
        )

        # if it wasn't there, the replace will not have done anything, so we insert it entirely.
        # however, this won't do anything if the association is already there.
        scaffold_add_line_to_file(
          "./app/models/scaffolding/completely_concrete/tangible_thing.rb",
          attribute.class_name_matches? ?
            "belongs_to :#{attribute.name_without_id}#{optional_line}" :
            "belongs_to :#{attribute.name_without_id}, class_name: \"#{attribute.options[:class_name]}\"#{optional_line}",
          BELONGS_TO_HOOK,
          prepend: true
        )

        if modified_migration
          add_additional_step :yellow, "If you've already run the migration in `#{migration_file_name}`, you'll need to roll back and run it again."
        end
      end

      # Add `default: false` to boolean migrations.
      if attribute.is_boolean?
        # Give priority to crud-field migrations if they exist.
        add_column_reference = "add_column :#{class_names_transformer.table_name}, :#{attribute.name}"
        create_table_reference = "create_table :#{class_names_transformer.table_name}"
        confirmation_migration_file_name = `grep "#{add_column_reference}" db/migrate/*`.split(":").first
        confirmation_migration_file_name ||= `grep "#{create_table_reference}" db/migrate/*`.split(":").first

        old_line, new_line = nil
        File.open(confirmation_migration_file_name) do |migration_file|
          old_lines = migration_file.readlines
          old_lines.each do |line|
            target_attribute = line.match?(/:#{class_names_transformer.table_name}, :#{attribute.name}, :boolean/) || line.match?(/\s*t\.boolean :#{attribute.name}/)
            if target_attribute
              old_line = line
              new_line = "#{old_line.chomp}, default: false\n"
            end
          end
        end
        replace_in_file(confirmation_migration_file_name, old_line, new_line)
      end

    end

    #
    # MODEL HOOKS
    #

    unless cli_options["skip-model"]

      if attribute.is_required? && !attribute.is_belongs_to?
        scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "validates :#{attribute.name}, presence: true", VALIDATIONS_HOOK, prepend: true)
      end

      case attribute.type
      when "file_field", "image"
        remove_file_methods = if attribute.is_multiple?
          <<~RUBY
            def #{attribute.name}_removal?
              #{attribute.name}_removal&.any?
            end

            def remove_#{attribute.name}
              #{attribute.name}_attachments.where(id: #{attribute.name}_removal).map(&:purge)
            end

            def #{attribute.name}=(attachables)
              attachables = Array(attachables).compact_blank

              if attachables.any?
                attachment_changes["#{attribute.name}"] =
                  ActiveStorage::Attached::Changes::CreateMany.new("#{attribute.name}", self, #{attribute.name}.blobs + attachables)
              end
            end

          RUBY
        else
          <<~RUBY
            def #{attribute.name}_removal?
              #{attribute.name}_removal.present?
            end

            def remove_#{attribute.name}
              #{attribute.name}.purge
            end

          RUBY
        end

        # Generating a model with an `attachment(s)` data type (i.e. - `rails g ModelName file:attachment`)
        # adds `has_one_attached` or `has_many_attached` to our model, just not directly above the
        # HAS_ONE_HOOK or the HAS_MANY_HOOK. We move the string here so it's scaffolded above the proper hook.
        model_file_path = transform_string("./app/models/scaffolding/completely_concrete/tangible_thing.rb")
        model_contents = File.readlines(model_file_path)
        reflection_declaration = attribute.is_multiple? ? "has_many_attached :#{attribute.name}" : "has_one_attached :#{attribute.name}"

        # Save the file without the hook so we can write it via the `scaffold_add_line_to_file` method below.
        model_without_attached_hook = model_contents.reject.each { |line| line.include?(reflection_declaration) }
        File.open(model_file_path, "w") do |f|
          model_without_attached_hook.each { |line| f.write(line) }
        end

        # Cloudinary images don't need all of the removal boilerplate since that's handled by the cloudinary JS
        unless attribute.cloudinary_image?
          hook_type = attribute.is_multiple? ? HAS_MANY_HOOK : HAS_ONE_HOOK
          scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", reflection_declaration, hook_type, prepend: true)

          # TODO: We may need to edit these depending on how we save multiple files.
          scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "attr_accessor :#{attribute.name}_removal", ATTR_ACCESSORS_HOOK, prepend: true)
          scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", remove_file_methods, METHODS_HOOK, prepend: true)
          scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "after_validation :remove_#{attribute.name}, if: :#{attribute.name}_removal?", CALLBACKS_HOOK, prepend: true)
        end
      when "trix_editor"
        scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_rich_text :#{attribute.name}", HAS_ONE_HOOK, prepend: true)
      when "address_field"
        scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_one :#{attribute.name}, class_name: \"Address\", as: :addressable", HAS_ONE_HOOK, prepend: true)
        scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "accepts_nested_attributes_for :#{attribute.name}", HAS_ONE_HOOK, prepend: true)
      when "buttons"
        if attribute.is_boolean?
          scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "validates :#{attribute.name}, inclusion: [true, false]", VALIDATIONS_HOOK, prepend: true)
        end
      end

    end
  end
end

#add_has_many_associationObject



581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
# File 'lib/scaffolding/transformer.rb', line 581

def add_has_many_association
  has_many_line = ["has_many :completely_concrete_tangible_things"]

  # Specify the class name if the model is namespaced.
  if child.match?("::")
    has_many_line << "class_name: \"Scaffolding::CompletelyConcrete::TangibleThing\""
  end

  has_many_line << "dependent: :destroy"

  # Specify the foreign key if the parent is namespaced.
  if parent.match?("::")
    has_many_line << "foreign_key: :absolutely_abstract_creative_concept_id"

    # And if we need `foreign_key`, we should also specify `inverse_of`.
    has_many_line << "inverse_of: :absolutely_abstract_creative_concept"
  end

  has_many_string = transform_string(has_many_line.join(", "))
  add_line_to_file(transform_string("./app/models/scaffolding/absolutely_abstract/creative_concept.rb"), has_many_string, HAS_MANY_HOOK, prepend: true)

  # Return the name of the has_many association.
  has_many_string.split(",").first.split(":").last
end

#add_has_many_through_associations(has_many_through_transformer, attribute_definition) ⇒ Object



606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/scaffolding/transformer.rb', line 606

def add_has_many_through_associations(has_many_through_transformer, attribute_definition)
  attribute = Scaffolding::Attribute.new(attribute_definition, :crud_field, 0)
  has_many_association = add_has_many_association
  has_many_through_parts = [
    "has_many :completely_concrete_tangible_things",
    "through: :$HAS_MANY_ASSOCIATION"
  ]

  unless attribute.class_name_matches?
    has_many_through_parts << "class_name: \"Scaffolding::CompletelyConcrete::TangibleThing\""
  end
  has_many_through_string = has_many_through_transformer.transform_string(has_many_through_parts.join(", "))
  has_many_through_string.gsub!("$HAS_MANY_ASSOCIATION", has_many_association)
  unless attribute.class_name_matches?
    # This handles the case where you're generating a join model where you want association names
    # to be different than the class name, so it'll transform something like this:
    # has_many :memberships, through: :assignments, class_name: "Membership"
    # into something like this:
    # has_many :assigned_to_memberships, through: :assignments, class_name: "Membership"
    has_many_through_string.gsub!("has_many :#{attribute.plural_association_name}", "has_many :#{attribute.name_without_id.tableize}")
  end
  add_line_to_file(transform_string("./app/models/scaffolding/absolutely_abstract/creative_concept.rb"), has_many_through_string, HAS_MANY_HOOK, prepend: true)
end

#add_line_to_file(file, content, hook, options = {}) ⇒ Object



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/scaffolding/transformer.rb', line 307

def add_line_to_file(file, content, hook, options = {})
  increase_indent = options[:increase_indent]
  add_before = options[:add_before]
  add_after = options[:add_after]

  transformed_file_name = file
  transformed_content = content
  transform_hook = hook

  begin
    target_file_content = File.read(transformed_file_name)
  rescue Errno::ENOENT => _
    puts "Couldn't find '#{transformed_file_name}'".red unless suppress_could_not_find || options[:suppress_could_not_find]
    return false
  end

  # When Super Scaffolding strong parameters, if an attribute named :project exists for a model `Project`,
  # the `account_load_and_authorize_resource :project,` code prevents the attribute from being scaffolded
  # since the transformed content is `:project,`. We bypass that here with this check.
  content_matches_model_name = transformed_content.gsub(/[:|,]/, "").capitalize == child

  if target_file_content.include?(transformed_content) && !content_matches_model_name
    puts "No need to update '#{transformed_file_name}'. It already has '#{transformed_content}'." unless silence_logs?

  else

    new_target_file_content = []

    target_file_content.split("\n").each do |line|
      if options[:exact_match] ? line == transform_hook : line.match(/#{Regexp.escape(transform_hook)}\s*$/)

        if add_before
          new_target_file_content << "#{line} #{add_before}"
        else
          unless options[:prepend]
            new_target_file_content << line
          end
        end

        line =~ /^(\s*).*#{Regexp.escape(transform_hook)}.*/
        leading_whitespace = $1

        incoming_leading_whitespace = nil
        transformed_content.lines.each do |content_line|
          content_line.rstrip
          content_line =~ /^(\s*).*/
          # this ignores empty lines.
          # it accepts any amount of whitespace if we haven't seen any whitespace yet.
          if content_line.present? && $1 && (incoming_leading_whitespace.nil? || $1.length < incoming_leading_whitespace.length)
            incoming_leading_whitespace = $1
          end
        end

        incoming_leading_whitespace ||= ""

        transformed_content.lines.each do |content_line|
          new_target_file_content << "#{leading_whitespace}#{"  " if increase_indent}#{content_line.gsub(/^#{incoming_leading_whitespace}/, "").rstrip}".presence
        end

        new_target_file_content << "#{leading_whitespace}#{add_after}" if add_after

        if options[:prepend]
          new_target_file_content << line
        end

      else

        new_target_file_content << line

      end
    end

    puts "Updating '#{transformed_file_name}'." unless silence_logs?

    File.write(transformed_file_name, new_target_file_content.join("\n").strip + "\n")

  end
end

#add_locale_helper_export_fixObject



554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/scaffolding/transformer.rb', line 554

def add_locale_helper_export_fix
  namespaced_locale_export_hook = "# πŸš… super scaffolding will insert the export for the locale view helper here."

  spacer = "  "
  indentation = spacer * 3
  namespace_elements = child.underscore.pluralize.split("/")
  last_element = namespace_elements.shift
  lines_to_add = [last_element + ":"]
  namespace_elements.map do |namespace_element|
    lines_to_add << indentation + namespace_element + ":"
    last_element = namespace_element
    indentation += spacer
  end
  lines_to_add << lines_to_add.pop + " *#{last_element}"

  scaffold_replace_line_in_file("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml", lines_to_add.join("\n"), namespaced_locale_export_hook)
end

#add_scaffolding_hooks_to_modelObject



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# File 'lib/scaffolding/transformer.rb', line 448

def add_scaffolding_hooks_to_model
  before_scaffolding_hooks = <<~RUBY
    #{CONCERNS_HOOK}

    #{ATTR_ACCESSORS_HOOK}

  RUBY

  after_scaffolding_hooks = <<-RUBY
    #{BELONGS_TO_HOOK}

    #{HAS_MANY_HOOK}

    #{HAS_ONE_HOOK}

    #{SCOPES_HOOK}

    #{VALIDATIONS_HOOK}

    #{CALLBACKS_HOOK}

    #{DELEGATIONS_HOOK}

    #{METHODS_HOOK}
  RUBY

  # add scaffolding hooks to the model.
  unless File.readlines(transform_string("./app/models/scaffolding/completely_concrete/tangible_thing.rb")).join.include?(CONCERNS_HOOK)
    scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", before_scaffolding_hooks, "ApplicationRecord", increase_indent: true)
  end

  unless File.readlines(transform_string("./app/models/scaffolding/completely_concrete/tangible_thing.rb")).join.include?(BELONGS_TO_HOOK)
    scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", after_scaffolding_hooks, "end", prepend: true, increase_indent: true, exact_match: true)
  end
end

#approved_by_reference(approved_by_index_name) ⇒ Object



16
17
# File 'lib/scaffolding/transformer.rb', line 16

def approved_by_reference(approved_by_index_name)
end

#build_ability_line(class_names = nil) ⇒ Object

if class_name isn’t specified, we use β€˜child`. if class_name is specified, then `child` is assumed to be a parent of `class_name`. returns an array with the ability line and a boolean indicating whether the ability line should be inserted among the abilities for admins only. (this happens when building an ability line for a resources that doesn’t ultimately belong to a Team or a User.)



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/scaffolding/transformer.rb', line 407

def build_ability_line(class_names = nil)
  # e.g. ['Conversations::Message', 'Conversation']
  if class_names
    # e.g. 'Conversations::Message'
    class_name = class_names.shift
    # e.g. ['Conversation', 'Deliverable', 'Phase', 'Project', 'Team']
    working_parents = class_names + [child] + parents
  else
    # e.g. 'Deliverable'
    class_name = child
    # e.g. ['Phase', 'Project', 'Team']
    working_parents = parents.dup
  end

  case working_parents.last
  when "User"
    working_parents.pop
    ability_line = "user_id: user.id"
  when "Team"
    working_parents.pop
    ability_line = "team_id: user.team_ids"
  else
    # if a resources is specified that isn't ultimately owned by a team or a user, then only admins can manage it.
    return ["can :manage, #{class_name}", true]
  end

  # e.g. ['Phase', 'Project']
  while working_parents.any?
    current_parent = working_parents.pop
    current_transformer = Scaffolding::ClassNamesTransformer.new(working_parents.last || class_name, current_parent, namespace)
    ability_line = "#{current_transformer.parent_variable_name_in_context}: {#{ability_line}}"
  end

  # e.g. "can :manage, Deliverable, phase: {project: {team_id: user.team_ids}}"
  ["can :manage, #{class_name}, #{ability_line}", false]
end

#build_conversation_ability_lineObject



444
445
446
# File 'lib/scaffolding/transformer.rb', line 444

def build_conversation_ability_line
  build_ability_line(["Conversations::Message", "Conversation"])
end

#build_factory_setupObject



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/scaffolding/transformer.rb', line 504

def build_factory_setup
  class_name = child
  working_parents = parents.dup
  current_parent = working_parents.pop
  current_transformer = Scaffolding::Transformer.new(working_parents.last || class_name, [current_parent])

  setup_lines = []

  unless current_parent == "Team" || current_parent == "User"
    setup_lines << current_transformer.transform_string("@absolutely_abstract_creative_concept = create(:scaffolding_absolutely_abstract_creative_concept)")
  end

  previous_assignment = current_transformer.transform_string("absolutely_abstract_creative_concept: @absolutely_abstract_creative_concept")

  current_parent = working_parents.pop

  while current_parent
    current_transformer = Scaffolding::Transformer.new(working_parents.last || class_name, [current_parent])
    setup_lines << current_transformer.transform_string("@absolutely_abstract_creative_concept = create(:scaffolding_absolutely_abstract_creative_concept, #{previous_assignment})")
    previous_assignment = current_transformer.transform_string("absolutely_abstract_creative_concept: @absolutely_abstract_creative_concept")

    current_parent = working_parents.pop
  end

  setup_lines << current_transformer.transform_string("@tangible_thing = build(:scaffolding_completely_concrete_tangible_thing, #{previous_assignment})")

  setup_lines
end

#created_by_reference(created_by_index_name) ⇒ Object



13
14
# File 'lib/scaffolding/transformer.rb', line 13

def created_by_reference(created_by_index_name)
end

#decode_double_replacement_fix(string) ⇒ Object



75
76
77
# File 'lib/scaffolding/transformer.rb', line 75

def decode_double_replacement_fix(string)
  string.gsub("~!@BT@!~", "")
end

#encode_double_replacement_fix(string) ⇒ Object



71
72
73
# File 'lib/scaffolding/transformer.rb', line 71

def encode_double_replacement_fix(string)
  string.chars.join("~!@BT@!~")
end

#get_transformed_file_content(file) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/scaffolding/transformer.rb', line 167

def get_transformed_file_content(file)
  transformed_file_content = []

  skipping = false
  gathering_lines_to_repeat = false

  parents_to_repeat_for = []
  gathered_lines_for_repeating = nil

  File.open(resolve_template_path(file)).each_line do |line|
    if line.include?("# πŸš… skip when scaffolding.")
      next
    end

    if line.include?("# πŸš… skip this section if resource is nested directly under team.")
      skipping = true if parent == "Team"
      next
    end

    if line.include?("# πŸš… skip this section when scaffolding.")
      skipping = true
      next
    end

    if line.include?("# πŸš… stop any skipping we're doing now.")
      skipping = false
      next
    end

    if line.include?("# πŸš… for each child resource from team down to the resource we're scaffolding, repeat the following:")
      gathering_lines_to_repeat = true
      parents_to_repeat_for = ([child] + parents.dup).reverse
      gathered_lines_for_repeating = []
      next
    end

    if line.include?("# πŸš… stop repeating.")
      gathering_lines_to_repeat = false

      while parents_to_repeat_for.count > 1
        current_parent = parents_to_repeat_for[0]
        current_child = parents_to_repeat_for[1]
        current_transformer = self.class.new(current_child, current_parent)
        transformed_file_content << current_transformer.transform_string(gathered_lines_for_repeating.join)
        parents_to_repeat_for.shift
      end

      next
    end

    if gathering_lines_to_repeat
      gathered_lines_for_repeating << line
      next
    end

    if skipping
      next
    end

    # remove lines with 'remove in scaffolded files.'
    unless line.include?("remove in scaffolded files.")

      # only transform it if it doesn't have the lock emoji.
      if line.include?("πŸ”’")
        # remove any comments that start with a lock.
        line.gsub!(/\s+?#\s+πŸ”’.*/, "")
      else
        line = transform_string(line)
      end

      transformed_file_content << line

    end
  end

  transformed_file_content.join
end

#last_joinable_parentObject



23
24
25
# File 'lib/scaffolding/transformer.rb', line 23

def last_joinable_parent
  "Team"
end

#no_parent?Boolean

We write an explicit method here so we know we aren’t handling β€˜parent` in this situation as `nil`.

Returns:

  • (Boolean)


33
34
35
# File 'lib/scaffolding/transformer.rb', line 33

def no_parent?
  parent == "None"
end

#permit_parentsObject



19
20
21
# File 'lib/scaffolding/transformer.rb', line 19

def permit_parents
  ["Team"]
end

#replace_in_file(file, before, after, target_regexp = nil) ⇒ Object



533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/scaffolding/transformer.rb', line 533

def replace_in_file(file, before, after, target_regexp = nil)
  puts "Replacing in '#{file}'." unless silence_logs?
  if target_regexp.present?
    target_file_content = ""
    File.open(file).each_line do |l|
      l.gsub!(before, after) if !!l.match(target_regexp)
      target_file_content += l
    end
  else
    target_file_content = File.read(file)
    target_file_content.gsub!(before, after)
  end
  File.write(file, target_file_content)
end

#resolve_target_path(file) ⇒ Object



152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/scaffolding/transformer.rb', line 152

def resolve_target_path(file)
  # Only do something here if they are trying to specify a target directory.
  return file unless ENV["TARGET"]

  # If the file exists in the application repository, we want to target it there.
  return file if File.exist?(file)

  ENV["OTHER_TARGETS"]&.split(",")&.each do |possible_target|
    candidate_path = "#{possible_target}/#{file}".gsub("//", "/")
    return candidate_path if File.exist?(candidate_path)
  end

  "#{ENV["TARGET"]}/#{file}".gsub("//", "/")
end

#resolve_template_path(file) ⇒ Object



143
144
145
146
147
148
149
150
# File 'lib/scaffolding/transformer.rb', line 143

def resolve_template_path(file)
  # Figure out the actual location of the file.
  BulletTrain::SuperScaffolding.template_paths.map do |base_path|
    base_path = Pathname.new(base_path)
    resolved_path = base_path.join(file).to_s
    File.exist?(resolved_path) ? resolved_path : nil
  end.compact.first || raise("Couldn't find the Super Scaffolding template for `#{file}` in any of the following locations:\n\n#{BulletTrain::SuperScaffolding.template_paths.join("\n")}")
end

#restart_serverObject



548
549
550
551
552
# File 'lib/scaffolding/transformer.rb', line 548

def restart_server
  # restart the server.
  puts "Restarting the server so it picks up the new localization .yml file."
  `./bin/rails restart`
end

#scaffold_add_line_to_file(file, content, hook, options = {}) ⇒ Object



386
387
388
389
390
391
# File 'lib/scaffolding/transformer.rb', line 386

def scaffold_add_line_to_file(file, content, hook, options = {})
  file = resolve_target_path(transform_string(file))
  content = transform_string(content)
  hook = transform_string(hook)
  add_line_to_file(file, content, hook, options)
end

#scaffold_crud(attributes) ⇒ Object



1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
# File 'lib/scaffolding/transformer.rb', line 1311

def scaffold_crud(attributes)
  if cli_options["only-index"]
    cli_options["skip-table"] = false
    cli_options["skip-views"] = true
    cli_options["skip-controller"] = true
    cli_options["skip-form"] = true
    cli_options["skip-show"] = true
    cli_options["skip-form"] = true
    cli_options["skip-api"] = true
    cli_options["skip-model"] = true
    cli_options["skip-parent"] = true
    cli_options["skip-locales"] = true
    cli_options["skip-routes"] = true
  end

  if cli_options["namespace"]
    cli_options["skip-api"] = true
    cli_options["skip-model"] = true
    cli_options["skip-locales"] = true
  end

  # TODO fix this. we can do this better.
  files = if cli_options["only-index"]
    [
      "./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb",
      "./app/views/account/scaffolding/completely_concrete/tangible_things/index.html.erb",
      "./app/views/account/scaffolding/completely_concrete/tangible_things/_tangible_thing.html.erb"
    ]
  else
    # copy a ton of files over and do the appropriate string replace.
    [
      "./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb",
      "./app/views/account/scaffolding/completely_concrete/tangible_things",
      "./app/views/api/v1/scaffolding/completely_concrete/tangible_things",
      ("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml" unless cli_options["skip-locales"]),
      ("./app/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller.rb" unless cli_options["skip-api"]),
      ("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb" unless cli_options["skip-api"])
      # "./app/filters/scaffolding/completely_concrete/tangible_things_filter.rb"
    ].compact
  end

  files.each do |name|
    if File.directory?(resolve_template_path(name))
      scaffold_directory(name)
    else
      scaffold_file(name)
    end
  end

  unless cli_options["skip-model"]
    # find the database migration that defines this relationship.
    migration_file_name = `grep "create_table :#{class_names_transformer.table_name}.*do |t|$" db/migrate/*`.split(":").first
    unless migration_file_name.present?
      raise "No migration file seems to exist for creating the table `#{class_names_transformer.table_name}`.\n" \
        "Please run the following command first and try Super Scaffolding again:\n" \
        "rails generate model #{child} #{parent.underscore}:references #{attributes.join(" ")}"
    end

    # if needed, update the reference to the parent class name in the create_table migration
    current_transformer = Scaffolding::ClassNamesTransformer.new(child, parent, namespace)
    unless current_transformer.parent_variable_name_in_context.pluralize == current_transformer.parent_table_name
      replace_in_file(migration_file_name, "foreign_key: true", "foreign_key: {to_table: '#{current_transformer.parent_table_name}'}")
    end

    # update the factory generated by `rails g`.
    content = if transform_string(":absolutely_abstract_creative_concept") == transform_string(":scaffolding_absolutely_abstract_creative_concept")
      transform_string("    association :absolutely_abstract_creative_concept")
    else
      transform_string("    association :absolutely_abstract_creative_concept, factory: :scaffolding_absolutely_abstract_creative_concept")
    end

    scaffold_replace_line_in_file("./test/factories/scaffolding/completely_concrete/tangible_things.rb", content, "    absolutely_abstract_creative_concept { nil }")

    add_has_many_association

    if class_names_transformer.belongs_to_needs_class_definition?
      scaffold_replace_line_in_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", transform_string("belongs_to :absolutely_abstract_creative_concept, class_name: \"Scaffolding::AbsolutelyAbstract::CreativeConcept\"\n"), transform_string("belongs_to :absolutely_abstract_creative_concept\n"))
    end

    update_models_abstract_class

    # add user permissions.
    add_ability_line_to_roles_yml
  end

  # Add factory setup in API controller test.
  unless cli_options["skip-api"]
    test_name = transform_string("./test/controllers/api/v1/scaffolding/completely_concrete/tangible_things_controller_test.rb")
    test_lines = File.open(test_name).readlines

    # Ensure variables built with factories are indented properly.
    factory_hook_index = Scaffolding::FileManipulator.find(test_lines, RUBY_FACTORY_SETUP_HOOK)
    factory_hook_indentation = Scaffolding::BlockManipulator.indentation_of(factory_hook_index, test_lines)
    indented_factory_lines = build_factory_setup.map { |line| "#{factory_hook_indentation}#{line}\n" }
    scaffold_replace_line_in_file(test_name, indented_factory_lines.join, test_lines[factory_hook_index])
  end

  # add children to the show page of their parent.
  unless cli_options["skip-parent"] || parent == "None"
    scaffold_add_line_to_file(
      "./app/views/account/scaffolding/absolutely_abstract/creative_concepts/show.html.erb",
      "<%= render 'account/scaffolding/completely_concrete/tangible_things/index', tangible_things: @creative_concept.completely_concrete_tangible_things, hide_back: true %>",
      "<%# πŸš… super scaffolding will insert new children above this line. %>",
      prepend: true
    )
  end

  unless cli_options["skip-api"]
    # add children to the show page of their parent.
    scaffold_add_line_to_file(
      "./app/views/api/#{BulletTrain::Api.current_version}/open_api/index.yaml.erb",
      "<%= automatic_components_for Scaffolding::CompletelyConcrete::TangibleThing %>",
      "<%# πŸš… super scaffolding will insert new components above this line. %>",
      prepend: true
    )

    # add children to the show page of their parent.
    scaffold_add_line_to_file(
      "./app/views/api/#{BulletTrain::Api.current_version}/open_api/index.yaml.erb",
      "<%= automatic_paths_for Scaffolding::CompletelyConcrete::TangibleThing, Scaffolding::AbsolutelyAbstract::CreativeConcept %>",
      "<%# πŸš… super scaffolding will insert new paths above this line. %>",
      prepend: true
    )
  end

  unless cli_options["skip-model"]
    add_scaffolding_hooks_to_model
  end

  #
  # DELEGATIONS
  #

  unless cli_options["skip-model"]

    if ["Team", "User"].include?(parents.last) && parent != parents.last
      scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "has_one :#{parents.last.underscore}, through: :absolutely_abstract_creative_concept", HAS_ONE_HOOK, prepend: true)
    end

  end

  add_attributes_to_various_views(attributes, type: :crud)

  unless cli_options["skip-locales"]
    add_locale_helper_export_fix
  end

  # add sortability.
  if cli_options["sortable"]
    scaffold_replace_line_in_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_index.html.erb", transform_string("<tbody data-controller=\"sortable\" data-sortable-reorder-path-value=\"<%= url_for [:reorder, :account, context, collection] %>\">"), "<tbody>")

    unless cli_options["skip-model"]
      scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "def collection\n  absolutely_abstract_creative_concept.completely_concrete_tangible_things\nend\n\n", METHODS_HOOK, prepend: true)
      scaffold_add_line_to_file("./app/models/scaffolding/completely_concrete/tangible_thing.rb", "include Sortable\n", CONCERNS_HOOK, prepend: true)

      migration = Dir.glob("db/migrate/*").last
      migration_lines = File.open(migration).readlines
      parent_line_idx = Scaffolding::FileManipulator.find(migration_lines, "t.references :#{parent.underscore}")
      new_lines = Scaffolding::BlockManipulator.insert_line("t.integer :sort_order", parent_line_idx, migration_lines, false)
      Scaffolding::FileManipulator.write(migration, new_lines)
    end

    unless cli_options["skip-controller"]
      scaffold_add_line_to_file("./app/controllers/account/scaffolding/completely_concrete/tangible_things_controller.rb", "include SortableActions\n", "Account::ApplicationController", increase_indent: true)
    end
  end

  # titleize the localization file.
  unless cli_options["skip-locales"]
    replace_in_file(transform_string("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml"), child, child.underscore.humanize.titleize)
  end

  # apply routes.
  unless cli_options["skip-routes"]
    routes_namespace = cli_options["namespace"] || "account"

    begin
      routes_path = if routes_namespace == "account"
        "config/routes.rb"
      else
        "config/routes/#{routes_namespace}.rb"
      end
      routes_manipulator = Scaffolding::RoutesFileManipulator.new(routes_path, child, parent, cli_options)
    rescue Errno::ENOENT => _
      puts "Creating '#{routes_path}'.".green

      unless File.directory?("config/routes")
        FileUtils.mkdir_p("config/routes")
      end

      File.write(routes_path, <<~RUBY)
        collection_actions = [:index, :new, :create]

        # πŸš… Don't remove this block, it will break Super Scaffolding.
        begin
          namespace :#{routes_namespace} do
            shallow do
              resources :teams do
              end
            end
          end
        end
      RUBY

      retry
    end

    begin
      routes_manipulator.apply([routes_namespace])
      Scaffolding::FileManipulator.write(routes_path, routes_manipulator.lines)
    rescue => _
      add_additional_step :red, "We weren't able to automatically add your `#{routes_namespace}` routes for you. In theory this should be very rare, so if you could reach out on Slack, you could probably provide context that will help us fix whatever the problem was. In the meantime, to add the routes manually, we've got a guide at https://blog.bullettrain.co/nested-namespaced-rails-routing-examples/ ."
    end

    # If we're using a custom namespace, we have to make sure the newly
    # scaffolded routes are drawn in the `config/routes.rb` and API routes files.
    if cli_options["namespace"]
      draw_line = "draw \"#{routes_namespace}\""

      [
        "config/routes.rb",
        "config/routes/api/#{BulletTrain::Api.current_version}.rb"
      ].each do |routes_file|
        original_lines = File.readlines(routes_file)

        # Define which line we want to place the draw line under in the original routes files.
        insert_line = if routes_file.match?("api")
          draw_line = "  #{draw_line}" # Add necessary indentation.
          "namespace :v1 do"
        else
          "draw \"sidekiq\""
        end

        new_lines = Scaffolding::BlockManipulator.insert(draw_line, lines: original_lines, within: insert_line)
        Scaffolding::FileManipulator.write(routes_file, new_lines)
      end
    end

    unless cli_options["skip-api"]
      begin
        api_routes_manipulator = Scaffolding::RoutesFileManipulator.new("config/routes/api/#{BulletTrain::Api.current_version}.rb", child, parent, cli_options)
        api_routes_manipulator.apply([BulletTrain::Api.current_version.to_sym])
        Scaffolding::FileManipulator.write("config/routes/api/#{BulletTrain::Api.current_version}.rb", api_routes_manipulator.lines)
      rescue => _
        add_additional_step :red, "We weren't able to automatically add your `api/#{BulletTrain::Api.current_version}` routes for you. In theory this should be very rare, so if you could reach out on Slack, you could probably provide context that will help us fix whatever the problem was. In the meantime, to add the routes manually, we've got a guide at https://blog.bullettrain.co/nested-namespaced-rails-routing-examples/ ."
      end
    end
  end

  unless cli_options["skip-parent"]

    if top_level_model?
      icon_name = nil
      if cli_options["navbar"].present?
        icon_name = if cli_options["navbar"].match?(/^ti/)
          "ti #{cli_options["navbar"]}"
        elsif cli_options["navbar"].match?(/^fa/)
          "fal #{cli_options["navbar"]}"
        else
          puts ""
          puts "'#{cli_options["navbar"]}' is not a valid icon.".red
          puts "Please refer to the Themify or Font Awesome documentation and pass the value like so:"
          puts "--navbar=\"ti-world\""
          exit
        end
      else
        puts ""
        # TODO: Update this help text letting developers know they can Super Scaffold
        # models without a parent after the `--skip-parent` logic is implemented.
        puts "Hey, models that are scoped directly off of a Team are eligible to be added to the navbar."
        puts "Do you want to add this resource to the navbar menu? (y/N)"
        response = $stdin.gets.chomp
        if response.downcase[0] == "y"
          puts ""
          puts "OK, great! Let's do this! By default these menu items appear as a #{font_awesome? ? "puzzle piece" : "gift icon"},"
          puts "but after you hit enter I'll open #{font_awesome? ? "two different pages" : "a page"} where you can view other icon options."
          puts "When you find one you like, hover your mouse over it and then come back here and"
          puts "enter the name of the icon you want to use."
          puts "(Or hit enter when choosing to skip this step.)"
          $stdin.gets.chomp
          if TerminalCommands.can_open?
            TerminalCommands.open_file_or_link("https://themify.me/themify-icons")
            if font_awesome?
              TerminalCommands.open_file_or_link("https://fontawesome.com/icons?d=gallery&s=light")
            end
          else
            puts "Sorry! We can't open these URLs automatically on your platform, but you can visit them manually:"
            puts ""
            puts "  https://themify.me/themify-icons"
            if font_awesome?
              puts "  https://fontawesome.com/icons?d=gallery&s=light"
            end
            puts ""
          end
          puts ""

          loop do
            puts "Did you find an icon you wanted to use?"
            puts "Enter the full CSS class here (e.g. 'ti ti-world'#{" or 'fal fa-puzzle-piece'" if font_awesome?}) or hit enter to just use the #{font_awesome? ? "puzzle piece" : "gift icon"}:"
            icon_name = $stdin.gets.chomp
            unless icon_name.match?(/ti\s.*/) || icon_name.match?(/fal\s.*/) || icon_name.strip.empty?
              puts ""
              puts "Please enter the full CSS class or hit enter."
              next
            end
            break
          end
          puts ""
          unless icon_name.length > 0 || icon_name.downcase == "y"
            icon_name = "fal fa-puzzle-piece ti ti-gift"
          end
        end
      end
      if icon_name.present?
        replace_in_file(transform_string("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml"), "fal fa-puzzle-piece", icon_name)
        scaffold_add_line_to_file("./app/views/account/shared/_menu.html.erb", "<%= render 'account/scaffolding/completely_concrete/tangible_things/menu_item' %>", "<% # added by super scaffolding. %>")
      end
    end
  end

  add_additional_step :yellow, transform_string("If you would like the table view you've just generated to reactively update when a Tangible Thing is updated on the server, please edit `app/models/scaffolding/absolutely_abstract/creative_concept.rb`, locate the `has_many :completely_concrete_tangible_things`, and add `enable_cable_ready_updates: true` to it.")

  restart_server unless ENV["CI"].present?
end

#scaffold_directory(directory) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/scaffolding/transformer.rb', line 267

def scaffold_directory(directory)
  transformed_directory_name = transform_string(directory)
  begin
    Dir.mkdir(transformed_directory_name)
  rescue Errno::EEXIST => _
    puts "The directory #{transformed_directory_name} already exists, skipping generation.".yellow
  rescue Errno::ENOENT => _
    puts "Proceeding to generate '#{transformed_directory_name}'."
  end

  Dir.foreach(resolve_template_path(directory)) do |file|
    file = "#{directory}/#{file}"

    next if file.match?("/_menu_item.html.erb") && !top_level_model?

    unless File.directory?(resolve_template_path(file))
      scaffold_file(file)
    end
  end

  # Allow local developers to override just certain files of a directory.
  override_path = begin
    resolve_template_path(directory + "_overrides")
  rescue RuntimeError
    nil
  end

  if override_path
    Dir.foreach(override_path) do |file|
      file = "#{directory}_overrides/#{file}"

      next if file.match?("/_menu_item.html.erb") && !top_level_model?

      unless File.directory?(resolve_template_path(file))
        scaffold_file(file, overrides: true)
      end
    end
  end
end

#scaffold_file(file, overrides: false) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/scaffolding/transformer.rb', line 245

def scaffold_file(file, overrides: false)
  transformed_file_content = get_transformed_file_content(file)
  transformed_file_name = resolve_target_path(transform_string(file))

  # Remove `_overrides` from the file name if we're sourcing from a local override folder.
  transformed_file_name.gsub!("_overrides", "") if overrides

  transformed_directory_name = File.dirname(transformed_file_name)
  unless File.directory?(transformed_directory_name)
    FileUtils.mkdir_p(transformed_directory_name)
  end

  puts "Writing '#{transformed_file_name}'." unless silence_logs?

  File.write(transformed_file_name, transformed_file_content.strip + "\n")

  if transformed_file_name.split(".").last == "rb"
    puts "Fixing Standard Ruby on '#{transformed_file_name}'." unless silence_logs?
    # `standardrb --fix #{transformed_file_name} 2> /dev/null`
  end
end

#scaffold_new_breadcrumbs(child, parents) ⇒ Object



572
573
574
575
576
577
578
579
# File 'lib/scaffolding/transformer.rb', line 572

def scaffold_new_breadcrumbs(child, parents)
  scaffold_file("./app/views/account/scaffolding/completely_concrete/tangible_things/_breadcrumbs.html.erb")
  puts
  puts "Heads up! We're only able to generate the new breadcrumb views, so you'll have to edit `#{transform_string("./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml")}` and add the label. You can look at `./config/locales/en/scaffolding/completely_concrete/tangible_things.en.yml` for an example of how to do this, but here's an example of what it should look like:".yellow
  puts
  puts transform_string("en:\n  scaffolding/completely_concrete/tangible_things: &tangible_things\n    label: &label Things\n    breadcrumbs:\n      label: *label").yellow
  puts
end

#scaffold_replace_line_in_file(file, content, content_to_replace) ⇒ Object



393
394
395
396
397
398
399
400
# File 'lib/scaffolding/transformer.rb', line 393

def scaffold_replace_line_in_file(file, content, content_to_replace)
  file = resolve_target_path(transform_string(file))
  # we specifically don't transform the content, we assume a builder function created this content.
  transformed_content_to_replace = transform_string(content_to_replace)
  content_replacement_transformed = content_to_replace != transformed_content_to_replace
  options = {suppress_could_not_find: suppress_could_not_find, content_replacement_transformed: content_replacement_transformed}
  Scaffolding::FileManipulator.replace_line_in_file(file, content, transformed_content_to_replace, options)
end

#top_level_model?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'lib/scaffolding/transformer.rb', line 27

def top_level_model?
  parent == "Team" || no_parent?
end

#transform_string(string) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/scaffolding/transformer.rb', line 79

def transform_string(string)
  full_class_name = [
    "Scaffolding::AbsolutelyAbstract::CreativeConcepts",
    "Scaffolding::CompletelyConcrete::TangibleThings",
    "ScaffoldingAbsolutelyAbstractCreativeConcepts",
    "ScaffoldingCompletelyConcreteTangibleThings",
    "Scaffolding Absolutely Abstract Creative Concepts",
    "Scaffolding Completely Concrete Tangible Things",
    "Scaffolding/Absolutely Abstract/Creative Concepts",
    "Scaffolding/Completely Concrete/Tangible Things",
    "scaffolding/absolutely_abstract/creative_concepts",
    "scaffolding/completely_concrete/tangible_things",
    "scaffolding/completely_concrete/_tangible_things",
    "scaffolding_absolutely_abstract_creative_concepts",
    "scaffolding_completely_concrete_tangible_things",
    "scaffolding-absolutely-abstract-creative-concepts",
    "scaffolding-completely-concrete-tangible-things",
    "scaffolding.completely_concrete.tangible_things"
  ]

  class_name_with_context = [
    "absolutely_abstract_creative_concepts",
    "completely_concrete_tangible_things",
    "absolutely_abstract/creative_concepts",
    "completely_concrete/tangible_things",
    "absolutely-abstract-creative-concepts",
    "completely-concrete-tangible-things"
  ]

  class_name = [
    "creative_concepts",
    "tangible_things",
    "creative-concepts",
    "tangible-things",
    "Creative Concepts",
    "Tangible Things",
    "Creative concepts",
    "Tangible things",
    "creative concepts",
    "tangible things"
  ]

  (
    full_class_name + full_class_name.map(&:singularize) +
    class_name_with_context + class_name_with_context.map(&:singularize) +
    class_name + class_name.map(&:singularize) +
    [":account", "/account/"] # Account namespace vs. others.
  ).each do |needle|
    string = string.gsub(needle, encode_double_replacement_fix(class_names_transformer.replacement_for(needle)))
  end

  {
    "/v1/" => "/#{BulletTrain::Api.current_version}/",
    "::V1::" => "::#{BulletTrain::Api.current_version}::",
    "_v1_" => "_#{BulletTrain::Api.current_version}_",
    ":v1," => ":#{BulletTrain::Api.current_version},"
  }.each do |from, to|
    string = string.gsub(from.upcase, encode_double_replacement_fix(to.upcase))
    string = string.gsub(from.downcase, encode_double_replacement_fix(to.downcase))
  end

  decode_double_replacement_fix(string)
end

#update_action_models_abstract_class(targets_n) ⇒ Object



37
38
# File 'lib/scaffolding/transformer.rb', line 37

def update_action_models_abstract_class(targets_n)
end

#update_models_abstract_classObject



10
11
# File 'lib/scaffolding/transformer.rb', line 10

def update_models_abstract_class
end