From b4ac3e9bf20bb7a19e7b2005aadb3b2b662a4340 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 8 Aug 2025 10:33:44 -0700 Subject: [PATCH 1/6] Added devcontainer manifest --- .devcontainer/Dockerfile | 38 ++++++++++++++++++++++++++++++++ .devcontainer/devcontainer.json | 18 +++++++++++++++ .devcontainer/docker-compose.yml | 15 +++++++++++++ spec/rails_helper.rb | 2 +- 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..157b88cf --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,38 @@ +# Use the official Ruby image as base +FROM ruby:3.3-slim + +# Install system dependencies +RUN apt-get update -qq && apt-get install -y \ + build-essential \ + git \ + curl \ + sqlite3 \ + libsqlite3-dev \ + libyaml-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js (for documentation site) +RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \ + && apt-get install -y nodejs + +# Create a non-root user +RUN groupadd --gid 1000 user \ + && useradd --uid 1000 --gid user --shell /bin/bash --create-home user + +# Set up the working directory +WORKDIR /workspace + +# Install bundler +RUN gem install bundler + +# Create cache directory for gems +RUN mkdir -p /usr/local/bundle && chown -R user:user /usr/local/bundle + +# Switch to non-root user +USER user + +# Set up git config (helps with development) +RUN git config --global init.defaultBranch main + +# Set the default shell +SHELL ["/bin/bash", "-c"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..7a8f87ac --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,18 @@ +{ + "name": "Inertia Rails Development", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", + "customizations": { + "vscode": { + "extensions": [ + "shopify.ruby-lsp", + "ms-vscode.vscode-json", + "redhat.vscode-yaml" + ] + } + }, + "forwardPorts": [3000, 5173], + "postCreateCommand": "bin/setup", + "remoteUser": "user" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000..38c20171 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,15 @@ +services: + app: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + volumes: + - ..:/workspace:cached + - gem_cache:/usr/local/bundle + command: sleep infinity + environment: + - RAILS_ENV=development + working_dir: /workspace + +volumes: + gem_cache: diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 03e1f42e..484b2a55 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -2,7 +2,7 @@ # Requiring logger fixes an issue between concurrent-ruby and activesupport in Rails < 7.1 # https://github.com/rails/rails/issues/54260 require 'logger' -ENV['RAILS_ENV'] ||= 'test' +ENV['RAILS_ENV'] = 'test' require File.expand_path('../dummy/config/environment', __FILE__) From 3f4c09d41270d04603406bde1442e8e777390848 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 8 Aug 2025 11:10:53 -0700 Subject: [PATCH 2/6] Removed irrelevant section of Dockerfile --- .devcontainer/Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 157b88cf..11244213 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -31,8 +31,5 @@ RUN mkdir -p /usr/local/bundle && chown -R user:user /usr/local/bundle # Switch to non-root user USER user -# Set up git config (helps with development) -RUN git config --global init.defaultBranch main - # Set the default shell SHELL ["/bin/bash", "-c"] From 8caa288a7ca3ae9c9efbdd6610fc6a880441c1e0 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 8 Aug 2025 11:26:10 -0700 Subject: [PATCH 3/6] Added a prop transformer method to the configuration --- lib/inertia_rails/configuration.rb | 7 +++++++ lib/inertia_rails/renderer.rb | 11 +++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/inertia_rails/configuration.rb b/lib/inertia_rails/configuration.rb index 9cd74596..c2415caf 100644 --- a/lib/inertia_rails/configuration.rb +++ b/lib/inertia_rails/configuration.rb @@ -12,6 +12,9 @@ class Configuration # Allows the user to hook into the default rendering behavior and change it to fit their needs component_path_resolver: ->(path:, action:) { "#{path}/#{action}" }, + # A function that transforms the props before they are sent to the client. + prop_transformer: ->(props:) { props }, + # DEPRECATED: Let Rails decide which layout should be used based on the # controller configuration. layout: true, @@ -89,6 +92,10 @@ def component_path_resolver(path:, action:) @options[:component_path_resolver].call(path: path, action: action) end + def prop_transformer(props:) + @options[:prop_transformer].call(props: props) + end + OPTION_NAMES.each do |option| unless method_defined?(option) define_method(option) do diff --git a/lib/inertia_rails/renderer.rb b/lib/inertia_rails/renderer.rb index 12e5bb20..46c7de8c 100644 --- a/lib/inertia_rails/renderer.rb +++ b/lib/inertia_rails/renderer.rb @@ -90,10 +90,13 @@ def merge_props(shared_props, props) end def computed_props - merged_props = merge_props(shared_data, props) - deep_transform_props(merged_props).tap do |transformed_props| - transformed_props[:_inertia_meta] = meta_tags if meta_tags.present? - end + merge_props(shared_data, props) + # This performs the internal work of hydrating/filtering props + .then { |props| deep_transform_props(props) } + # Then we apply the user-defined prop transformer + .then { |props| configuration.prop_transformer(props: props) } + # Then we add meta tags after everything since they must not be transformed + .tap { |props| props[:_inertia_meta] = meta_tags if meta_tags.present? } end def page From b5a83b45daa5c526c4b05bcd1ca3e4d2a35bbb1e Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 8 Aug 2025 11:52:05 -0700 Subject: [PATCH 4/6] Add some tests --- .../inertia_prop_transformer_controller.rb | 32 ++++++++++ spec/dummy/config/routes.rb | 3 + spec/inertia/prop_transformer_spec.rb | 60 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 spec/dummy/app/controllers/inertia_prop_transformer_controller.rb create mode 100644 spec/inertia/prop_transformer_spec.rb diff --git a/spec/dummy/app/controllers/inertia_prop_transformer_controller.rb b/spec/dummy/app/controllers/inertia_prop_transformer_controller.rb new file mode 100644 index 00000000..403d75ee --- /dev/null +++ b/spec/dummy/app/controllers/inertia_prop_transformer_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class InertiaPropTransformerController < ApplicationController + inertia_config( + prop_transformer: lambda do |props:| + props.deep_transform_keys { |key| key.to_s.upcase } + end + ) + + def just_props + render inertia: 'TestComponent', props: { + lower_prop: 'lower_value', + parent_hash: { + lower_child_prop: 'lower_child_value', + }, + } + end + + def props_and_meta + render inertia: 'TestComponent', + props: { + lower_prop: 'lower_value', + }, + meta: [ + { name: 'description', content: "Don't transform me!" } + ] + end + + def no_props + render inertia: 'TestComponent' + end +end diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 717fbaf8..8dae460e 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -42,6 +42,9 @@ get 'instance_props_test' => 'inertia_rails_mimic#instance_props_test' get 'default_render_test' => 'inertia_rails_mimic#default_render_test' get 'transformed_default_render_test' => 'transformed_inertia_rails_mimic#render_test' + get 'prop_transformer_test' => 'inertia_prop_transformer#just_props' + get 'prop_transformer_with_meta_test' => 'inertia_prop_transformer#props_and_meta' + get 'prop_transformer_no_props_test' => 'inertia_prop_transformer#no_props' get 'default_component_test' => 'inertia_rails_mimic#default_component_test' get 'default_component_with_props_test' => 'inertia_rails_mimic#default_component_with_props_test' get 'default_component_with_duplicated_props_test' => 'inertia_rails_mimic#default_component_with_duplicated_props_test' diff --git a/spec/inertia/prop_transformer_spec.rb b/spec/inertia/prop_transformer_spec.rb new file mode 100644 index 00000000..5ccb1fac --- /dev/null +++ b/spec/inertia/prop_transformer_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require_relative '../../lib/inertia_rails/rspec' +RSpec.describe 'props can be transformed', type: :request, inertia: true do + let(:headers) do + { + 'X-Inertia' => true, + 'X-Inertia-Partial-Component' => 'TestComponent', + } + end + + context 'props are provided' do + it 'transforms the props' do + get prop_transformer_test_path, headers: headers + + expect_inertia.to render_component('TestComponent') + .and have_exact_props({ + 'LOWER_PROP' => 'lower_value', + 'PARENT_HASH' => { + 'LOWER_CHILD_PROP' => 'lower_child_value', + }, + }) + end + end + + context 'props and meta are provided' do + it 'transforms the props' do + get prop_transformer_with_meta_test_path, headers: headers + + expect_inertia.to render_component('TestComponent') + .and include_props({ + 'LOWER_PROP' => 'lower_value', + }) + end + + it 'does not transform the meta' do + get prop_transformer_with_meta_test_path, headers: headers + + expect(response.parsed_body['props']['_inertia_meta']).to eq( + [ + { + 'tagName' => 'meta', + 'name' => 'description', + 'content' => "Don't transform me!", + 'headKey' => 'meta-name-description', + } + ] + ) + end + end + + context 'no props are provided' do + it 'does not error' do + get prop_transformer_no_props_test_path, headers: headers + + expect_inertia.to render_component('TestComponent') + .and have_exact_props({}) + end + end +end From 720e3eb045f078f47281aa2b0ab283a8ff7c3db2 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 8 Aug 2025 12:00:36 -0700 Subject: [PATCH 5/6] Added rough-pass documentation --- docs/guide/configuration.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 36f9f8d4..93158f8e 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -44,6 +44,33 @@ Inertia Rails supports setting any configuration option via environment variable Use `component_path_resolver` to customize component path resolution when [`default_render`](#default_render) config value is set to `true`. The value should be callable and will receive the `path` and `action` parameters, returning a string component path. See [Automatically determine component name](/guide/responses#automatically-determine-component-name). +### `prop_transformer` + +**Default**: `->(props:) { props }` + +Use `prop_transformer` to apply a transformation to your props before they're sent to the view. One use-case this enables is to work with `snake_case` props within Rails while working with `camelCase` in your view: + +```ruby + inertia_config( + prop_transformer: lambda do |props:| + props.deep_transform_keys { |key| key.to_s.camelize(:lower) } + end + ) +``` + +> [!NOTE] +> This controls the props provided by Inertia Rails but does not concern itself with props coming _into_ Rails. You may want to add a global `before_action` to `ApplicationController`: + +```ruby +before_action :underscore_params + +# ... + +def underscore_params + params.deep_transform_keys! { |key| key.to_s.underscore } +end +``` + ### `deep_merge_shared_data` **Default**: `false` From 43ec2846d3576cc1063dfca62eed817d753720ed Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 8 Aug 2025 12:01:54 -0700 Subject: [PATCH 6/6] Pluck old PR --- .devcontainer/Dockerfile | 35 -------------------------------- .devcontainer/devcontainer.json | 18 ---------------- .devcontainer/docker-compose.yml | 15 -------------- spec/rails_helper.rb | 2 +- 4 files changed, 1 insertion(+), 69 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/docker-compose.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 11244213..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -# Use the official Ruby image as base -FROM ruby:3.3-slim - -# Install system dependencies -RUN apt-get update -qq && apt-get install -y \ - build-essential \ - git \ - curl \ - sqlite3 \ - libsqlite3-dev \ - libyaml-dev \ - && rm -rf /var/lib/apt/lists/* - -# Install Node.js (for documentation site) -RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \ - && apt-get install -y nodejs - -# Create a non-root user -RUN groupadd --gid 1000 user \ - && useradd --uid 1000 --gid user --shell /bin/bash --create-home user - -# Set up the working directory -WORKDIR /workspace - -# Install bundler -RUN gem install bundler - -# Create cache directory for gems -RUN mkdir -p /usr/local/bundle && chown -R user:user /usr/local/bundle - -# Switch to non-root user -USER user - -# Set the default shell -SHELL ["/bin/bash", "-c"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 7a8f87ac..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "Inertia Rails Development", - "dockerComposeFile": "docker-compose.yml", - "service": "app", - "workspaceFolder": "/workspace", - "customizations": { - "vscode": { - "extensions": [ - "shopify.ruby-lsp", - "ms-vscode.vscode-json", - "redhat.vscode-yaml" - ] - } - }, - "forwardPorts": [3000, 5173], - "postCreateCommand": "bin/setup", - "remoteUser": "user" -} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml deleted file mode 100644 index 38c20171..00000000 --- a/.devcontainer/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - app: - build: - context: .. - dockerfile: .devcontainer/Dockerfile - volumes: - - ..:/workspace:cached - - gem_cache:/usr/local/bundle - command: sleep infinity - environment: - - RAILS_ENV=development - working_dir: /workspace - -volumes: - gem_cache: diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 484b2a55..03e1f42e 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -2,7 +2,7 @@ # Requiring logger fixes an issue between concurrent-ruby and activesupport in Rails < 7.1 # https://github.com/rails/rails/issues/54260 require 'logger' -ENV['RAILS_ENV'] = 'test' +ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../dummy/config/environment', __FILE__)