|
3 | 3 |
|
4 | 4 | # Inertia.js Rails Adapter
|
5 | 5 |
|
6 |
| -Visit [inertiajs.com](https://inertiajs.com/) for installation instructions, documentation, and to learn more. |
| 6 | +## Installation |
| 7 | + |
| 8 | +### Backend |
| 9 | + |
| 10 | +Just add the inertia rails gem to your Gemfile |
| 11 | +```ruby |
| 12 | +gem 'inertia_rails' |
| 13 | +``` |
| 14 | + |
| 15 | +### Frontend |
| 16 | + |
| 17 | +Rails 7 specific frontend docs coming soon. For now, check out the official Inertia docs at https://inertiajs.com/ or see an example using React/Vite [here](https://github.com/BrandonShar/inertia-rails-template) |
| 18 | + |
| 19 | +## Usage |
| 20 | + |
| 21 | +### Responses |
| 22 | + |
| 23 | +Render Inertia responses is simple, just use the inertia renderer in your controller methods. The renderer accepts two arguments, the first is the name of the component you want to render from within your pages directory (without extension). The second argument is an options hash where you can provide `props` to your components. This options hash also allows you to pass `view_data` to your layout, but this is much less common. |
| 24 | + |
| 25 | +```ruby |
| 26 | +def index |
| 27 | + render inertia: 'Event/Index', props: { |
| 28 | + events: Event.all, |
| 29 | + } |
| 30 | +end |
| 31 | +``` |
| 32 | + |
| 33 | +#### Rails Component and Instance Props |
| 34 | + |
| 35 | +Starting in version 3.0, Inertia Rails allows you to provide your component name and props via common rails conventions. |
| 36 | + |
| 37 | +```ruby |
| 38 | +class EventsController < ApplicationController |
| 39 | + use_inertia_instance_props |
| 40 | + |
| 41 | + def index |
| 42 | + @events = Event.all |
| 43 | + end |
| 44 | + |
| 45 | +end |
| 46 | +``` |
| 47 | + |
| 48 | +is the same as |
| 49 | + |
| 50 | + |
| 51 | +```ruby |
| 52 | +class EventsController < ApplicationController |
| 53 | + def index |
| 54 | + render inertia: 'events/index', props: { |
| 55 | + events: Event.all |
| 56 | + } |
| 57 | + end |
| 58 | +end |
| 59 | +``` |
| 60 | + |
| 61 | +#### Instance Props and Default Render Notes |
| 62 | + |
| 63 | +In order to use instance props, you must call `use_inertia_instance_props` on the controller (or a base controller it inherits from). If any props are provided manually, instance props |
| 64 | +are automatically disabled for that response. Instance props are only included if they are defined after the before filter is set from `use_inertia_instance_props`. |
| 65 | + |
| 66 | +Automatic component name is also opt in, you must set the `default_render` config value to `true`. Otherwise, you can simply `render inertia: true` for the same behavior explicitly. |
| 67 | + |
| 68 | +### Layout |
| 69 | + |
| 70 | +Inertia layouts use the rails layout convention and can be set or changed in the same way. The original `layout` config option is still functional, but will likely be deprecated in the future in favor |
| 71 | +of using rails layouts. |
| 72 | + |
| 73 | +```ruby |
| 74 | +class EventsController < ApplicationController |
| 75 | + layout 'inertia_application' |
| 76 | +end |
| 77 | +``` |
| 78 | + |
| 79 | + |
| 80 | +### Shared Data |
| 81 | + |
| 82 | +If you have data that you want to be provided as a prop to every component (a common use-case is information about the authenticated user) you can use the `shared_data` controller method. |
| 83 | + |
| 84 | +```ruby |
| 85 | +class EventsController < ApplicationController |
| 86 | + # share syncronously |
| 87 | + inertia_share app_name: env['app.name'] |
| 88 | + |
| 89 | + # share lazily, evaluated at render time |
| 90 | + inertia_share do |
| 91 | + if logged_in? |
| 92 | + { |
| 93 | + user: logged_in_user, |
| 94 | + } |
| 95 | + end |
| 96 | + end |
| 97 | + |
| 98 | + # share lazily alternate syntax |
| 99 | + inertia_share user_count: lambda { User.count } |
| 100 | + |
| 101 | +end |
| 102 | +``` |
| 103 | + |
| 104 | +#### Deep Merging Shared Data |
| 105 | + |
| 106 | +By default, Inertia will shallow merge data defined in an action with the shared data. You might want a deep merge. Imagine using shared data to represent defaults you'll override sometimes. |
| 107 | + |
| 108 | +```ruby |
| 109 | +class ApplicationController |
| 110 | + inertia_share do |
| 111 | + { basketball_data: { points: 50, rebounds: 100 } } |
| 112 | + end |
| 113 | +end |
| 114 | +``` |
| 115 | + |
| 116 | +Let's say we want a particular action to change only part of that data structure. The renderer accepts a `deep_merge` option: |
| 117 | + |
| 118 | +```ruby |
| 119 | +class CrazyScorersController < ApplicationController |
| 120 | + def index |
| 121 | + render inertia: 'CrazyScorersComponent', |
| 122 | + props: { basketball_data: { points: 100 } }, |
| 123 | + deep_merge: true |
| 124 | + end |
| 125 | +end |
| 126 | + |
| 127 | +# The renderer will send this to the frontend: |
| 128 | +{ |
| 129 | + basketball_data: { |
| 130 | + points: 100, |
| 131 | + rebounds: 100, |
| 132 | + } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +Deep merging can be set as the project wide default via the InertiaRails configuration: |
| 137 | + |
| 138 | +```ruby |
| 139 | +# config/initializers/some_initializer.rb |
| 140 | +InertiaRails.configure do |config| |
| 141 | + config.deep_merge_shared_data = true |
| 142 | +end |
| 143 | + |
| 144 | +``` |
| 145 | + |
| 146 | +If deep merging is enabled by default, it's possible to opt out within the action: |
| 147 | + |
| 148 | +```ruby |
| 149 | +class CrazyScorersController < ApplicationController |
| 150 | + inertia_share do |
| 151 | + { |
| 152 | + basketball_data: { |
| 153 | + points: 50, |
| 154 | + rebounds: 10, |
| 155 | + } |
| 156 | + } |
| 157 | + end |
| 158 | + |
| 159 | + def index |
| 160 | + render inertia: 'CrazyScorersComponent', |
| 161 | + props: { basketball_data: { points: 100 } }, |
| 162 | + deep_merge: false |
| 163 | + end |
| 164 | +end |
| 165 | + |
| 166 | +# Even if deep merging is set by default, since the renderer has `deep_merge: false`, it will send a shallow merge to the frontend: |
| 167 | +{ |
| 168 | + basketball_data: { |
| 169 | + points: 100, |
| 170 | + } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +### Lazy Props |
| 175 | + |
| 176 | +On the front end, Inertia supports the concept of "partial reloads" where only the props requested are returned by the server. Sometimes, you may want to use this flow to avoid processing a particularly slow prop on the intial load. In this case, you can use Lazy props. Lazy props aren't evaluated unless they're specifically requested by name in a partial reload. |
| 177 | + |
| 178 | +```ruby |
| 179 | + inertia_share some_data: InertiaRails.lazy(lambda { some_very_slow_method }) |
| 180 | +``` |
| 181 | + |
| 182 | +### Routing |
| 183 | + |
| 184 | +If you don't need a controller to handle a static component, you can route directly to a component with the inertia route helper |
| 185 | + |
| 186 | +```ruby |
| 187 | +inertia 'about' => 'AboutComponent' |
| 188 | +``` |
| 189 | + |
| 190 | +### SSR |
| 191 | + |
| 192 | +Enable SSR via the config settings for `ssr_enabled` and `ssr_url`. |
| 193 | + |
| 194 | +When using SSR, don't forget to add `<%= inertia_headers %>` to the `<head>` of your `application.html.erb`. |
| 195 | + |
| 196 | +## Configuration |
| 197 | + |
| 198 | +Inertia Rails has a few different configuration options that can be set anywhere, but the most common location is from within an initializer. |
| 199 | + |
| 200 | +The default config is shown below |
| 201 | +```ruby |
| 202 | +InertiaRails.configure do |config| |
| 203 | + |
| 204 | + # set the current version for automatic asset refreshing. A string value should be used if any. |
| 205 | + config.version = nil |
| 206 | + # enable default inertia rendering (warning! this will override rails default rendering behavior) |
| 207 | + config.default_render = true |
| 208 | + |
| 209 | + # ssr specific options |
| 210 | + config.ssr_enabled = false |
| 211 | + config.ssr_url = 'http://localhost:13714' |
| 212 | + |
| 213 | + config.deep_merge_shared_data = false |
| 214 | + |
| 215 | +end |
| 216 | +``` |
| 217 | + |
| 218 | +## Testing |
| 219 | + |
| 220 | +If you're using Rspec, Inertia Rails comes with some nice test helpers to make things simple. |
| 221 | + |
| 222 | +To use these helpers, just add the following require statement to your `spec/rails_helper.rb` |
| 223 | + |
| 224 | +```ruby |
| 225 | +require 'inertia_rails/rspec' |
| 226 | +``` |
| 227 | + |
| 228 | +And in any test you want to use the inertia helpers, add the inertia flag to the describe block |
| 229 | + |
| 230 | +```ruby |
| 231 | +RSpec.describe EventController, type: :request do |
| 232 | + describe '#index', inertia: true do |
| 233 | + # ... |
| 234 | + end |
| 235 | +end |
| 236 | +``` |
| 237 | + |
| 238 | +### Assertions |
| 239 | + |
| 240 | +```ruby |
| 241 | +RSpec.describe EventController, type: :request do |
| 242 | + describe '#index', inertia: true do |
| 243 | + |
| 244 | + # check the component |
| 245 | + expect_inertia.to render_component 'Event/Index' |
| 246 | + |
| 247 | + # access the component name |
| 248 | + expect(inertia.component).to eq 'TestComponent' |
| 249 | + |
| 250 | + # props (including shared props) |
| 251 | + expect_inertia.to have_exact_props({name: 'Brandon', sport: 'hockey'}) |
| 252 | + expect_inertia.to include_props({sport: 'hockey'}) |
| 253 | + |
| 254 | + # access props |
| 255 | + expect(inertia.props[:name]).to eq 'Brandon' |
| 256 | + |
| 257 | + # view data |
| 258 | + expect_inertia.to have_exact_view_data({name: 'Brian', sport: 'basketball'}) |
| 259 | + expect_inertia.to include_view_data({sport: 'basketball'}) |
| 260 | + |
| 261 | + # access view data |
| 262 | + expect(inertia.view_data[:name]).to eq 'Brian' |
| 263 | + |
| 264 | + end |
| 265 | +end |
| 266 | + |
| 267 | +``` |
7 | 268 |
|
8 | 269 | *Maintained and sponsored by the team at [bellaWatt](https://bellawatt.com/)*
|
9 | 270 |
|
|
0 commit comments