diff --git a/lib/generators/base.rb b/lib/generators/base.rb new file mode 100644 index 0000000..ed9bddc --- /dev/null +++ b/lib/generators/base.rb @@ -0,0 +1,8 @@ +require "rails/generators/named_base" + +require_relative "generator_helpers" +module Inertia + class Base < Rails::Generators::NamedBase + include GeneratorHelpers + end +end diff --git a/lib/generators/generator_helpers.rb b/lib/generators/generator_helpers.rb new file mode 100644 index 0000000..44ecaa0 --- /dev/null +++ b/lib/generators/generator_helpers.rb @@ -0,0 +1,71 @@ +module Inertia + module GeneratorHelpers + def self.guess_the_default_framework + case Rails.root.join("package.json").read + when /@inertiajs\/react/ + "react" + when /@inertiajs\/svelte/ + "svelte" + when /@inertiajs\/vue3/ + "vue" + else + puts "Could not determine the Inertia.js framework you are using." + end + end + + def inertia_base_path + (class_path + [file_name]).map(&:camelize).join("/") + end + + def inertia_component_name + singular_name.camelize + end + + def attributes_to_serialize + [:id] + attributes.reject do |attribute| + attribute.password_digest? || + attribute.attachment? || + attribute.attachments? + end.map(&:column_name) + end + + def js_resource_path + "`#{route_url}/${#{singular_table_name}.id}`" + end + + def js_edit_resource_path + "`#{route_url}/${#{singular_table_name}.id}/edit`" + end + + def js_new_resource_path + "'#{route_url}/new'" + end + + def js_resources_path + "'#{route_url}'" + end + + def input_type(attribute) + case attribute.type + when :string + "text" + when :text, :rich_text + "text_area" + when :integer + "number" + when :float, :decimal + "number" + when :datetime, :timestamp, :time + "datetime-local" + when :date + "date" + when :boolean + "checkbox" + when :attachments, :attachment + "file" + else + "text" + end + end + end +end diff --git a/lib/generators/inertia/controller/controller_generator.rb b/lib/generators/inertia/controller/controller_generator.rb new file mode 100644 index 0000000..9248532 --- /dev/null +++ b/lib/generators/inertia/controller/controller_generator.rb @@ -0,0 +1,19 @@ +require "rails/generators/rails/controller/controller_generator" + +require_relative "../../generator_helpers" + +module Inertia + module Generators + class ControllerGenerator < Rails::Generators::ControllerGenerator + include GeneratorHelpers + + source_root File.expand_path("./templates", __dir__) + + remove_hook_for :template_engine + # TODO: decide on remove_hook_for :test_framework + + hook_for :inertia_framework, required: true, default: GeneratorHelpers.guess_the_default_framework + end + end +end + diff --git a/lib/generators/inertia/controller/templates/controller.rb.tt b/lib/generators/inertia/controller/templates/controller.rb.tt new file mode 100644 index 0000000..53eb0eb --- /dev/null +++ b/lib/generators/inertia/controller/templates/controller.rb.tt @@ -0,0 +1,10 @@ +<% module_namespacing do -%> +class <%= class_name %>Controller < <%= parent_class_name.classify %> +<% actions.each do |action| -%> + def <%= action %> + render inertia: '<%= "#{inertia_base_path}/#{action.camelize}" %>' + end +<%= "\n" unless action == actions.last -%> +<% end -%> +end +<% end -%> diff --git a/lib/generators/inertia_rails_contrib/install_generator.rb b/lib/generators/inertia/install/install_generator.rb similarity index 98% rename from lib/generators/inertia_rails_contrib/install_generator.rb rename to lib/generators/inertia/install/install_generator.rb index e3ccf9f..3373fa9 100644 --- a/lib/generators/inertia_rails_contrib/install_generator.rb +++ b/lib/generators/inertia/install/install_generator.rb @@ -1,6 +1,6 @@ -module InertiaRailsContrib +module Inertia class InstallGenerator < Rails::Generators::Base - source_root File.expand_path("./install", __dir__) + source_root File.expand_path("./templates", __dir__) APPLICATION_LAYOUT = Rails.root.join("app/views/layouts/application.html.erb") diff --git a/lib/generators/inertia_rails_contrib/install/controller.rb b/lib/generators/inertia/install/templates/controller.rb similarity index 100% rename from lib/generators/inertia_rails_contrib/install/controller.rb rename to lib/generators/inertia/install/templates/controller.rb diff --git a/lib/generators/inertia_rails_contrib/install/initializer.rb b/lib/generators/inertia/install/templates/initializer.rb similarity index 100% rename from lib/generators/inertia_rails_contrib/install/initializer.rb rename to lib/generators/inertia/install/templates/initializer.rb diff --git a/lib/generators/inertia_rails_contrib/install/react/InertiaExample.jsx b/lib/generators/inertia/install/templates/react/InertiaExample.jsx similarity index 100% rename from lib/generators/inertia_rails_contrib/install/react/InertiaExample.jsx rename to lib/generators/inertia/install/templates/react/InertiaExample.jsx diff --git a/lib/generators/inertia_rails_contrib/install/react/inertia.js b/lib/generators/inertia/install/templates/react/inertia.js similarity index 100% rename from lib/generators/inertia_rails_contrib/install/react/inertia.js rename to lib/generators/inertia/install/templates/react/inertia.js diff --git a/lib/generators/inertia_rails_contrib/install/svelte/InertiaExample.svelte b/lib/generators/inertia/install/templates/svelte/InertiaExample.svelte similarity index 100% rename from lib/generators/inertia_rails_contrib/install/svelte/InertiaExample.svelte rename to lib/generators/inertia/install/templates/svelte/InertiaExample.svelte diff --git a/lib/generators/inertia_rails_contrib/install/svelte/inertia.js b/lib/generators/inertia/install/templates/svelte/inertia.js similarity index 100% rename from lib/generators/inertia_rails_contrib/install/svelte/inertia.js rename to lib/generators/inertia/install/templates/svelte/inertia.js diff --git a/lib/generators/inertia_rails_contrib/install/svelte/svelte.config.js b/lib/generators/inertia/install/templates/svelte/svelte.config.js similarity index 100% rename from lib/generators/inertia_rails_contrib/install/svelte/svelte.config.js rename to lib/generators/inertia/install/templates/svelte/svelte.config.js diff --git a/lib/generators/inertia_rails_contrib/install/vue/InertiaExample.vue b/lib/generators/inertia/install/templates/vue/InertiaExample.vue similarity index 100% rename from lib/generators/inertia_rails_contrib/install/vue/InertiaExample.vue rename to lib/generators/inertia/install/templates/vue/InertiaExample.vue diff --git a/lib/generators/inertia_rails_contrib/install/vue/inertia.js b/lib/generators/inertia/install/templates/vue/inertia.js similarity index 100% rename from lib/generators/inertia_rails_contrib/install/vue/inertia.js rename to lib/generators/inertia/install/templates/vue/inertia.js diff --git a/lib/generators/inertia/scaffold/scaffold_generator.rb b/lib/generators/inertia/scaffold/scaffold_generator.rb new file mode 100644 index 0000000..96e63d7 --- /dev/null +++ b/lib/generators/inertia/scaffold/scaffold_generator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "rails/generators/rails/resource/resource_generator" + +require_relative "../../generator_helpers" + +module Inertia + module Generators + class ScaffoldGenerator < Rails::Generators::ResourceGenerator # :nodoc: + remove_hook_for :resource_controller + remove_class_option :actions + + class_option :resource_route, type: :boolean + + hook_for :scaffold_controller, required: true + end + end +end diff --git a/lib/generators/inertia/scaffold_controller/scaffold_controller_generator.rb b/lib/generators/inertia/scaffold_controller/scaffold_controller_generator.rb new file mode 100644 index 0000000..02d729e --- /dev/null +++ b/lib/generators/inertia/scaffold_controller/scaffold_controller_generator.rb @@ -0,0 +1,57 @@ +require "rails/generators/resource_helpers" + +require_relative "../../generator_helpers" + +module Inertia + module Generators + # This class is a modified copy of Rails::Generators::ScaffoldControllerGenerator. + # We don't use inheritance because some gems (i.e. jsbuilder) monkey-patch it. + class ScaffoldControllerGenerator < Rails::Generators::NamedBase + include GeneratorHelpers + include Rails::Generators::ResourceHelpers + + source_root File.expand_path("./templates", __dir__) + + check_class_collision suffix: "Controller" + + class_option :helper, type: :boolean + class_option :orm, banner: "NAME", type: :string, required: true, + desc: "ORM to generate the controller for" + + class_option :skip_routes, type: :boolean, desc: "Don't add routes to config/routes.rb." + + argument :attributes, type: :array, default: [], banner: "field:type field:type" + + def create_controller_files + template "controller.rb", File.join("app/controllers", controller_class_path, "#{controller_file_name}_controller.rb") + end + + hook_for :inertia_framework, as: :scaffold, required: true, default: GeneratorHelpers.guess_the_default_framework + + hook_for :resource_route, in: :rails, required: true do |route| + invoke route unless options.skip_routes? + end + + hook_for :test_framework, in: :rails, as: :scaffold + + # Invoke the helper using the controller name (pluralized) + hook_for :helper, in: :rails, as: :scaffold do |invoked| + invoke invoked, [controller_name] + end + + private + + def permitted_params + attachments, others = attributes_names.partition { |name| attachments?(name) } + params = others.map { |name| ":#{name}" } + params += attachments.map { |name| "#{name}: []" } + params.join(", ") + end + + def attachments?(name) + attribute = attributes.find { |attr| attr.name == name } + attribute&.attachments? + end + end + end +end diff --git a/lib/generators/inertia/scaffold_controller/templates/controller.rb.tt b/lib/generators/inertia/scaffold_controller/templates/controller.rb.tt new file mode 100644 index 0000000..861a2fd --- /dev/null +++ b/lib/generators/inertia/scaffold_controller/templates/controller.rb.tt @@ -0,0 +1,103 @@ +<% module_namespacing do -%> +class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: %i[ show edit update destroy ] + +<% if regular_class_path.any? -%> + wrap_parameters :<%= singular_table_name %> + +<% end -%> + # TODO: move to application_controller.rb? + inertia_share flash: -> { flash.to_hash } + + # GET <%= route_url %> + def index + @<%= plural_table_name %> = <%= orm_class.all(class_name) %> + render inertia: '<%= "#{inertia_base_path}/Index" %>', props: { + <%= plural_table_name %>: @<%= plural_table_name %>.map do |<%= singular_table_name %>| + <%= "serialize_#{singular_table_name}" %>(<%= singular_table_name %>) + end + } + end + + # GET <%= route_url %>/1 + def show + render inertia: '<%= "#{inertia_base_path}/Show" %>', props: { + <%= singular_table_name %>: <%= "serialize_#{singular_table_name}" %>(@<%= singular_table_name %>) + } + end + + # GET <%= route_url %>/new + def new + @<%= singular_table_name %> = <%= orm_class.build(class_name) %> + render inertia: '<%= "#{inertia_base_path}/New" %>', props: { + <%= singular_table_name %>: <%= "serialize_#{singular_table_name}" %>(@<%= singular_table_name %>) + } + end + + # GET <%= route_url %>/1/edit + def edit + render inertia: '<%= "#{inertia_base_path}/Edit" %>', props: { + <%= singular_table_name %>: <%= "serialize_#{singular_table_name}" %>(@<%= singular_table_name %>) + } + end + + # POST <%= route_url %> + def create + @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> + + if @<%= orm_instance.save %> + redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully created.") %> + else + redirect_to <%= new_helper %>, inertia: { errors: @<%= singular_table_name %>.errors } + end + end + + # PATCH/PUT <%= route_url %>/1 + def update + if @<%= orm_instance.update("#{singular_table_name}_params") %> + redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully updated.") %> + else + redirect_to <%= edit_helper %>, inertia: { errors: @<%= singular_table_name %>.errors } + end + end + + # DELETE <%= route_url %>/1 + def destroy + @<%= orm_instance.destroy %> + redirect_to <%= index_helper %>_url, notice: <%= %("#{human_name} was successfully destroyed.") %> + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_<%= singular_table_name %> + @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> + end + + # Only allow a list of trusted parameters through. + def <%= "#{singular_table_name}_params" %> + <%- if attributes_names.empty? -%> + params.fetch(:<%= singular_table_name %>, {}) + <%- else -%> + params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>) + <%- end -%> + end + + def <%= "serialize_#{singular_table_name}" %>(<%= singular_table_name %>) + <%= singular_table_name %>.as_json(only: [ + <%= attributes_to_serialize.map { |attribute| ":#{attribute}" }.join(", ") %> + ])<% if attributes.any?(&:attachment?) -%>.tap do |hash| +<% attributes.filter(&:attachment?).map do |attribute| -%> + hash["<%= attribute.column_name %>"] = {filename: <%= singular_table_name %>.<%= attribute.column_name %>.filename, url: url_for(<%= singular_table_name %>.<%= attribute.column_name %>)} if <%= singular_table_name %>.<%= attribute.column_name %>.attached? +<% end -%> + end<% end -%><% if attributes.any?(&:attachments?) -%>.tap do |hash| +<% attributes.filter(&:attachments?).map do |attribute| -%> + hash["<%= attribute.column_name %>"] = + <%= singular_table_name %>.<%= attribute.column_name %>.flat_map do |file| + {filename: file.filename.to_s, url: url_for(file)} + end +<% end -%> + end +<% end -%> + end +end +<% end -%> diff --git a/lib/generators/react/controller/controller_generator.rb b/lib/generators/react/controller/controller_generator.rb new file mode 100644 index 0000000..aa31d4d --- /dev/null +++ b/lib/generators/react/controller/controller_generator.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative "../../base" + +module React + module Generators + class ControllerGenerator < Inertia::Base + source_root File.expand_path("./templates", __dir__) + + argument :actions, type: :array, default: [], banner: "action action" + + def copy_view_files + base_path = File.join("app/frontend/pages", inertia_base_path) + empty_directory base_path + + actions.each do |action| + @action = action + @path = File.join(base_path, "#{action.camelize}.#{extension}") + template "view.#{extension}", @path + end + end + + def extension + :jsx + end + end + end +end diff --git a/lib/generators/react/controller/templates/view.jsx.tt b/lib/generators/react/controller/templates/view.jsx.tt new file mode 100644 index 0000000..e87d982 --- /dev/null +++ b/lib/generators/react/controller/templates/view.jsx.tt @@ -0,0 +1,8 @@ +export default function <%= @action.camelize %>({name}) { + return ( + <> +
Find me in <%= @path %>
+ > + ); +} diff --git a/lib/generators/react/scaffold/scaffold_generator.rb b/lib/generators/react/scaffold/scaffold_generator.rb new file mode 100644 index 0000000..7f41043 --- /dev/null +++ b/lib/generators/react/scaffold/scaffold_generator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require_relative "../../base" +require "rails/generators/resource_helpers" +require_relative "../../generator_helpers" + +module React + module Generators + class ScaffoldGenerator < Inertia::Base + include Rails::Generators::ResourceHelpers + include Inertia::GeneratorHelpers + + source_root File.expand_path("./templates", __dir__) + + argument :attributes, type: :array, default: [], banner: "field:type field:type" + + def copy_view_files + base_path = File.join("app/frontend/pages", inertia_base_path) + empty_directory base_path + + available_views.each do |view| + filename = "#{view}.#{extension}" + template filename, File.join(base_path, filename) + end + + template "One.#{extension}", File.join(base_path, "#{inertia_component_name}.#{extension}") + end + + private + + def extension + :jsx + end + + def available_views + %w[Index Edit Show New Form] + end + end + end +end diff --git a/lib/generators/react/scaffold/templates/Edit.jsx.tt b/lib/generators/react/scaffold/templates/Edit.jsx.tt new file mode 100644 index 0000000..2b60c49 --- /dev/null +++ b/lib/generators/react/scaffold/templates/Edit.jsx.tt @@ -0,0 +1,31 @@ +import { Link } from '@inertiajs/react'; +import Form from './Form'; + +export default function Edit({ <%= singular_table_name %> }) { + return ( + <> +{flash.notice}
)} + ++ }>Show this <%= human_name.downcase %> +
++ <%= attribute.human_name %>: +<% if attribute.attachment? -%> + {<%= singular_table_name %>.<%= attribute.column_name %> && (.<%= attribute.column_name %>.url}> + {<%= singular_table_name %>.<%= attribute.column_name %>.filename} + )} +
+<% elsif attribute.attachments? -%> + + {<%= singular_table_name %>.<%= attribute.column_name %>.map((file, i) => ( +{flash.notice}
)} + + <<%= inertia_component_name %> <%= singular_table_name %>={<%= singular_table_name %>} /> + +