Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run rails generate commands automatically when Super Scaffolding #547

Merged
merged 20 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
90691fe
First pass on setting up rails g
gazayas Sep 13, 2023
3ac77a2
Fixing Standard Ruby
gazayas Sep 22, 2023
a73edaa
More additions to automate migrations on Super Scaffold
gazayas Sep 22, 2023
0d90b85
Remove generate-migration flag for crud scaffolders
gazayas Sep 22, 2023
eaaabf4
Compensate for image partials
gazayas Sep 25, 2023
5b45c71
Compensate for join-model scaffolder with *_ids attributes
gazayas Sep 25, 2023
4d66db6
Removing options when registering attribute type for generation
gazayas Sep 25, 2023
e6aa173
Compensate for attributes with options
gazayas Sep 25, 2023
9f6b314
Update comment in scaffolding script
gazayas Sep 25, 2023
bd6ff3e
Compensate for matching namespaces, add foreign key to migration for …
gazayas Sep 26, 2023
ab54117
Refactor Super Scaffolding script sightly
gazayas Sep 26, 2023
2396d18
Automate model generation for the join-model scaffolder
gazayas Sep 26, 2023
5a834cd
Fixing Standard Ruby
gazayas Sep 26, 2023
206972c
Add --skip-migration-generation flag
gazayas Sep 26, 2023
79b44f2
Merge branch 'main' into features/generate-models-when-super-scaffolding
gazayas Sep 26, 2023
f2f0338
Update portion of docs for new Super Scaffolding functionality
gazayas Sep 26, 2023
8265e38
Update more documentation
gazayas Sep 27, 2023
3d59e5e
Add skip-migration-generation flag for join-model scaffolder
gazayas Sep 27, 2023
cca2fc5
Update delegated types documentation
gazayas Sep 27, 2023
4d0ffbd
Merge branch 'main' into features/generate-models-when-super-scaffolding
jagthedrummer Oct 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def run
end
end

# get all the attributes.
attributes = argv[2..]

check_required_options_for_attributes("crud", attributes, child, parent)

# `tr` here compensates for namespaced models (i.e. - `Projects::Deliverable` to `projects/deliverable`).
parent_reference = parent_without_namespace.tableize.singularize.tr("/", "_")
tableized_child = child.tableize.tr("/", "_")
Expand Down Expand Up @@ -82,11 +87,6 @@ def run
"bin/super-scaffold crud Section Page,Site,Team title:text body:text\n"
end

# get all the attributes.
attributes = argv[2..]

check_required_options_for_attributes("crud", attributes, child, parent)

transformer = Scaffolding::Transformer.new(child, parents, @options)
transformer.scaffold_crud(attributes)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ def run
# 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.gsub(/_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}") }
Expand Down
122 changes: 100 additions & 22 deletions bullet_train-super_scaffolding/lib/scaffolding/script.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@

require_relative "../bullet_train/terminal_commands"

FIELD_PARTIALS = {
address_field: "string",
boolean: "boolean",
buttons: "string",
cloudinary_image: "string",
color_picker: "string",
date_and_time_field: "datetime",
date_field: "date",
email_field: "string",
emoji_field: "string",
file_field: "attachment",
image: "attachment",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be active_storage_image? Or should we remove cloudinary_image above and then have image be a special case where we don't do a direct lookup, but instead check whether Cloudinary is active?

options: "string",
password_field: "string",
phone_field: "string",
super_select: "string",
text_area: "text",
text_field: "string",
number_field: "integer",
trix_editor: "text"
}

# filter out options.
argv = []
@options = {}
Expand All @@ -29,10 +51,49 @@ def standard_protip
end

def check_required_options_for_attributes(scaffolding_type, attributes, child, parent = nil)
tableized_parent = nil

# Ensure the parent attribute name has the proper namespacing for adding as a foreign key.
if parent.present?
if child.include?("::") && parent.include?("::")
child_parts = child.split("::")
parent_parts = parent.split("::")
child_parts_dup = child_parts.dup
parent_parts_dup = parent_parts.dup

# Pop off however many spaces match.
child_parts_dup.each.with_index do |child_part, idx|
if child_part == parent_parts_dup[idx]
child_parts.shift
parent_parts.shift
else
tableized_parent = parent_parts.map(&:downcase).join("_")
break
end
end
end
# In case we're not working with namespaces, just tableize the parent as is.
tableized_parent ||= parent.tableize.singularize.tr("/", "_") if parent.present?
end

generation_command = case scaffolding_type
when "crud"
"bin/rails generate model #{child} #{tableized_parent}:references"
when "crud-field"
"" # This is blank so we can create the proper migration name first after we get the attributes.
end

# Even if there are attributes passed to the scaffolder,
# They may already exist in previous migrations, so we
# only register ones that need to be generated.
# i.e. - *_ids attributes in the join-model scaffolder.
attributes_to_generate = []

attributes.each do |attribute|
parts = attribute.split(":")
name = parts.shift
type = parts.join(":")
type_without_option = type.gsub(/{.*}/, "")

unless Scaffolding.valid_attribute_type?(type)
raise "You have entered an invalid attribute type: #{type}. General data types are used when creating new models, but Bullet Train " \
Expand All @@ -53,6 +114,19 @@ def check_required_options_for_attributes(scaffolding_type, attributes, child, p
{}
end

data_type = if type == "image" && cloudinary_enabled?
"string"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, here's that special case I mentioned above. Maybe we remove cloudinary_image from that list? (Or do we want to leave it for backwards compatibility?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we remove cloudinary_image from that list? (Or do we want to leave it for backwards compatibility?)

Right, I was planning on leaving it here for backwards compatibility. However, if we were to delete it, it technically wouldn't be breaking anything so I don't feel too strongly either way right now about leaving it or deleting it.

elsif attribute_options[:multiple]
case type
when "file"
"attachments"
else
"jsonb"
end
else
FIELD_PARTIALS[type_without_option.to_sym]
end

if name.match?(/_id$/) || name.match?(/_ids$/)
attribute_options ||= {}
unless attribute_options[:vanilla]
Expand Down Expand Up @@ -85,6 +159,30 @@ def check_required_options_for_attributes(scaffolding_type, attributes, child, p
end
end
end

# TODO: Is there ever a case that we want this to be a string?
data_type = "references" if name.match?(/_id$/)

# For join models, we don't want to generate a migration when
# running the crud-field scaffolder in the last step, so we skip *_ids.
unless name.match?(/_ids$/)
generation_command += " #{name_without_id || name}:#{data_type}"
attributes_to_generate << name
end
end

# Generate the models/migrations with the attributes passed.
if attributes_to_generate.any?
case scaffolding_type
# "join-model" is not here because the `rails g` command is written inline in its own scaffolder.
when "crud"
puts "Generating #{child} model with '#{generation_command}'".green
when "crud-field"
generation_command = "bin/rails generate migration add_#{attributes_to_generate.join("_and_")}_to_#{child.tableize.tr("/", "_")}#{generation_command}"
puts "Adding new fields to #{child} with '#{generation_command}'".green
end
puts ""
`#{generation_command}` unless @options["skip-migration-generation"]
end
end

Expand Down Expand Up @@ -120,36 +218,16 @@ def show_usage
when "--field-partials"
puts "Bullet Train uses the following field partials for Super Scaffolding".blue
puts ""
field_partials = {
address_field: "string",
boolean: "boolean",
buttons: "string",
cloudinary_image: "string",
color_picker: "string",
date_and_time_field: "datetime",
date_field: "date_field",
email_field: "string",
emoji_field: "string",
file_field: "attachment",
options: "string",
password_field: "string",
phone_field: "string",
super_select: "string",
text_area: "text",
text_field: "string",
number_field: "integer",
trix_editor: "text"
}

max_name_length = 0
field_partials.each do |key, value|
FIELD_PARTIALS.each do |key, value|
if key.to_s.length > max_name_length
max_name_length = key.to_s.length
end
end

printf "\t%#{max_name_length}s:Data Type\n".bold, "Field Partial Name"
field_partials.each { |key, value| printf "\t%#{max_name_length}s:#{value}\n", key }
FIELD_PARTIALS.each { |key, value| printf "\t%#{max_name_length}s:#{value}\n", key }

puts ""
puts "For more details, check out the documentation:"
Expand Down
2 changes: 0 additions & 2 deletions bullet_train/docs/action-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ bin/super-scaffold action-model:targets-one-parent
### 1. Generate and scaffold an example `Project` model.

```
rails g model Project team:references name:string
bin/super-scaffold crud Project Team name:text_field
```

Expand Down Expand Up @@ -104,7 +103,6 @@ Because Action Models are just regular models, you can add new fields to them wi
For example:

```
rails g migration add_notify_users_to_projects_archive_actions notify_users:boolean
# side quest: update the generated migration with `default: false` on the new boolean field.
bin/super-scaffold crud-field Projects::ArchiveAction notify_users:boolean
```
Expand Down
9 changes: 6 additions & 3 deletions bullet_train/docs/field-partials.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,15 @@ Certain form field partials like `buttons` and `super_select` can also have thei
For Cloudinary you should use `string`, and for ActiveStorage you should use `attachment`.

## A Note On Data Types
Set the data type to `jsonb` whenever passing the `multiple` option to a new attribute.
When creating a `multiple` option attribute, Bullet Train generates these values as a `jsonb`.
```
> rails generate model Project team:references multiple_buttons:jsonb
> bin/super-scaffold crud Project Team multiple_buttons:buttons{multiple}
bin/super-scaffold crud Project Team multiple_buttons:buttons{multiple}
```

This will run the following rails command.
```
rails generate model Project team:references multiple_buttons:jsonb
```
## Formating `date` and `date_and_time`
After Super Scaffolding a `date` or `date_and_time` field, you can pass a format for the object like so:

Expand Down
1 change: 0 additions & 1 deletion bullet_train/docs/field-partials/file-field.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,5 @@ Run the following command to generate the scaffolding for the `documents` field
If you're starting fresh, and don't have an existing model you can do something like this:

```
rails g model Project team:references name:string specification:attachment documents:attachments
bin/super-scaffold crud Project Team name:text_field specification:file_field documents:file_field{multiple}
```
5 changes: 2 additions & 3 deletions bullet_train/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ Whether you want to build a new application with Bullet Train or contribute to B

If you're using Bullet Train for the first time, begin by learning these five important techniques:

1. Use `rails g model` to create and `bin/super-scaffold crud` to scaffold a new model:
1. Use `bin/super-scaffold crud` to scaffold a new model:

```
rails g model Project team:references name:string
bin/super-scaffold crud Project Team name:text_field
```

In this example, `Team` refers to the immediate parent of the `Project` resource. For more details, just run `bin/super-scaffold` or [read the documentation](/docs/super-scaffolding.md).

2. Use `rails g migration` and `bin/super-scaffold crud-field` to add a new field to a model you've already scaffolded:
2. Use `bin/super-scaffold crud-field` to add a new field to a model you've already scaffolded:

```
rails g migration add_description_to_projects description:text
Expand Down
2 changes: 1 addition & 1 deletion bullet_train/docs/modeling.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Even if you know there's an attribute or model that you're going to want to poli

## A Systematic Approach

### 1. Write `rails g` and `bin/super-scaffold` commands in a scratch file.
### 1. Write `bin/super-scaffold` commands in a scratch file.

See the [Super Scaffolding documentation](/docs/super-scaffolding.md) for more specific guidance. Leave plenty of comments in your scratch file describing anything that isn't obvious and providing examples of values that might populate attributes.

Expand Down
Loading
Loading