Class: BulletTrain::SuperScaffolding::Scaffolders::JoinModelScaffolder

Inherits:
BulletTrain::SuperScaffolding::Scaffolder show all
Defined in:
lib/bullet_train/super_scaffolding/scaffolders/join_model_scaffolder.rb

Instance Attribute Summary

Attributes inherited from BulletTrain::SuperScaffolding::Scaffolder

#argv

Instance Method Summary collapse

Methods inherited from BulletTrain::SuperScaffolding::Scaffolder

#initialize

Constructor Details

This class inherits a constructor from BulletTrain::SuperScaffolding::Scaffolder

Instance Method Details

#runObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/bullet_train/super_scaffolding/scaffolders/join_model_scaffolder.rb', line 5

def run
  unless argv.count >= 3
    puts ""
    puts "🚅  usage: bin/super-scaffold join-model <JoinModel> <left_association> <right_association>"
    puts ""
    puts "E.g. Add project-specific tags to a project:"
    puts ""
    puts "  Given the following example models:".blue
    puts ""
    puts "    bin/super-scaffold crud Project Team name:text_field description:trix_editor"
    puts ""
    puts "    bin/super-scaffold crud Projects::Tag Team name:text_field"
    puts ""
    puts "  1️⃣   Use `join-model` scaffolding to generate the join model for use in `crud-field` scaffolding:".blue
    puts ""
    puts "    bin/super-scaffold join-model Projects::AppliedTag project_id{class_name=Project} tag_id{class_name=Projects::Tag}"
    puts ""
    puts "  2️⃣  Now you can use `crud-field` scaffolding to actually add the field to the form of the parent model:".blue
    puts ""
    puts "    bin/super-scaffold crud-field Project tag_ids:super_select{class_name=Projects::Tag}"
    puts ""
    puts "    👋 Heads up! There will be one follow-up step output by this command that you need to take action on."
    puts ""
    puts "  3️⃣  Now you can run your migrations.".blue
    exit
  end

  child = argv[0]
  check_class_name_for_namespace_conflict(child)

  primary_parent = argv[1].split("class_name=").last.split(",").first.split("}").first
  secondary_parent = argv[2].split("class_name=").last.split(",").first.split("}").first

  # There should only be two attributes.
  attributes = [argv[1], argv[2]]

  unless @options["skip-migration-generation"]
    attributes_without_options = attributes.map { |attribute| attribute.gsub(/{.*}$/, "") }
    attributes_without_id = attributes_without_options.map { |attribute| attribute.delete_suffix("_id") }
    attributes_with_references = attributes_without_id.map { |attribute| attribute + ":references" }

    generation_command = "bin/rails generate model #{child} #{attributes_with_references.join(" ")}"
    puts "Generating model with '#{generation_command}'".green
    `#{generation_command}`
  end

  # Pretend we're doing a `super_select` scaffolding because it will do the correct thing.
  attributes = attributes.map { |attribute| attribute.gsub("{", ":super_select{") }
  attributes = attributes.map { |attribute| attribute.gsub("}", ",required}") }

  transformer = Scaffolding::Transformer.new(child, [primary_parent], @options)

  # We need this transformer to reflect on the class names _just_ between e.g. `Project` and `Projects::Tag`, without the join model.
  has_many_through_transformer = Scaffolding::Transformer.new(secondary_parent, [primary_parent], @options)

  # We need this transformer to reflect on the association between `Projects::Tag` and `Projects::AppliedTag` backwards.
  inverse_transformer = Scaffolding::Transformer.new(child, [secondary_parent], @options)

  # We need this transformer to reflect on the class names _just_ between e.g. `Projects::Tag` and `Project`, without the join model.
  inverse_has_many_through_transformer = Scaffolding::Transformer.new(primary_parent, [secondary_parent], @options)

  # However, for the first attribute, we actually don't need the scope validator (and can't really implement it).
  attributes[0] = attributes[0].gsub("}", ",unscoped}")

  has_many_through_association = has_many_through_transformer.transform_string("completely_concrete_tangible_things")
  source = transformer.transform_string("absolutely_abstract_creative_concept.valid_$HAS_MANY_THROUGH_ASSOCIATION")
  source.gsub!("$HAS_MANY_THROUGH_ASSOCIATION", has_many_through_association)

  # For the second one, we don't want users to have to define the list of valid options in the join model, so we do this:
  attributes[1] = attributes[1].gsub("}", ",source=#{source}}")

  # This model hasn't been crud scaffolded, so a bunch of views are skipped here, but that's OK!
  # It does what we need on the files that exist.
  transformer.add_scaffolding_hooks_to_model

  transformer.suppress_could_not_find = true
  transformer.add_attributes_to_various_views(attributes, type: :crud_field)
  transformer.suppress_could_not_find = false

  # Add the `has_many ... through:` association in both directions.
  # We pass the "opposing" attribute so that both the association name and the
  # class name get wired up correctly in cases where they don't match. For instance
  # if you want an `assigned_to_membership` relationship to the `memberships` table.
  transformer.add_has_many_through_associations(has_many_through_transformer, attributes[1])
  inverse_transformer.add_has_many_through_associations(inverse_has_many_through_transformer, attributes[0])

  additional_steps = (transformer.additional_steps + has_many_through_transformer.additional_steps + inverse_transformer.additional_steps + inverse_has_many_through_transformer.additional_steps).uniq

  additional_steps.each_with_index do |additional_step, index|
    color, message = additional_step
    puts ""
    puts "#{index + 1}. #{message}".send(color)
  end
  puts ""
end