Skip to content

Commit

Permalink
Add react generator
Browse files Browse the repository at this point in the history
  • Loading branch information
skryukov committed Jun 6, 2024
1 parent 9461e94 commit 8de7545
Show file tree
Hide file tree
Showing 26 changed files with 562 additions and 2 deletions.
8 changes: 8 additions & 0 deletions lib/generators/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require "rails/generators/named_base"

require_relative "generator_helpers"
module Inertia
class Base < Rails::Generators::NamedBase
include GeneratorHelpers
end
end
71 changes: 71 additions & 0 deletions lib/generators/generator_helpers.rb
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions lib/generators/inertia/controller/controller_generator.rb
Original file line number Diff line number Diff line change
@@ -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

10 changes: 10 additions & 0 deletions lib/generators/inertia/controller/templates/controller.rb.tt
Original file line number Diff line number Diff line change
@@ -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 -%>
Original file line number Diff line number Diff line change
@@ -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")

Expand Down
18 changes: 18 additions & 0 deletions lib/generators/inertia/scaffold/scaffold_generator.rb
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
103 changes: 103 additions & 0 deletions lib/generators/inertia/scaffold_controller/templates/controller.rb.tt
Original file line number Diff line number Diff line change
@@ -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 -%>
28 changes: 28 additions & 0 deletions lib/generators/react/controller/controller_generator.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions lib/generators/react/controller/templates/view.jsx.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function <%= @action.camelize %>({name}) {
return (
<>
<h1><%= class_name %>#<%= @action %></h1>
<p>Find me in <%= @path %></p>
</>
);
}
40 changes: 40 additions & 0 deletions lib/generators/react/scaffold/scaffold_generator.rb
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 8de7545

Please sign in to comment.