Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ GEM
zeitwerk (2.6.6)

PLATFORMS
arm64-darwin-20
x86_64-darwin-19
x86_64-linux

Expand Down
47 changes: 39 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,25 @@ If bundler is not being used to manage dependencies, install the gem by executin

## Note: Usage in Rails

In order to use `turbo-ruby` in Rails with the Rails `render` method you have to install the `phlex-rails` gem in your app.
In order to use `turbo-ruby` in Rails with the Rails `render` method you have to install the `phlex-rails` gem in your app.

### Regular Element

```ruby
# Ruby
Turbo::Elements::TurboStream.new(action: "console_log", message: "Hello World").to_html
Turbo.stream(action: "console_log", message: "Hello World").to_html
```

```html+erb
<!-- Rails -->
<%= render Turbo::Elements::TurboStream.new(action: "console_log", message: "Hello World") %>
<%= render Turbo.stream(action: "console_log", message: "Hello World") %>
```



### Blocks

```ruby
# Ruby
Turbo::Elements::TurboStream.new(action: "morph", target: "post_1") do
Turbo.stream(action: "morph", target: "post_1") do
%(<div id="post_id">
<h1>Post 1</h1>
</div>)
Expand All @@ -41,18 +39,51 @@ end.to_html

```html+erb
<!-- Rails -->
<%= render Turbo::Elements::TurboStream.new(action: "morph", target: "post_1") do %>
<%= render Turbo.stream(action: "morph", target: "post_1") do %>
<div id="post_id">
<h1>Post 1</h1>
</div>
<% end %>
```

### Registering custom stream actions

It's also possible to register custom stream actions:

```ruby
Turbo::Ruby.stream_actions do
# Can either register via the shorthand helper:
register :morph

def log(message, **options, &block)
stream(action: "console_log", message: message, **options, &block)
end

# Or define a custom action that must convert any positional arguments into the
# appropriate keyword arguments and must call `stream`.
def custom_action(*arguments, **options, &block)
stream(**options, &block)
end
end
```

Now the examples from above can be:

```ruby
Turbo.log("Hello world").to_html

Turbo.morph target: "post_1" do
%(<div id="post_id">
<h1>Post 1</h1>
</div>)
end.to_html
```

### Partials (Rails only)

```html+erb
<!-- Rails -->
<%= render Turbo::Elements::TurboStream.new(action: "morph", target: "post_1", view_context: self, partial: "posts/post", locals: { post: @post } %>
<%= render turbo.morph(@post, partial: "posts/post") %>
```

## Development
Expand Down
2 changes: 1 addition & 1 deletion lib/turbo/elements/turbo_stream.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def render_template(&block)
@view_context.capture(&block)
elsif @rendering.any?
throw "no view_context error" if @view_context.nil?
@view_context.render(formats: [:html], **@rendering)
@view_context.render(formats: [:html], object: @target, **@rendering)
elsif @allow_inferred_rendering
render_record(@target)
end
Expand Down
99 changes: 99 additions & 0 deletions lib/turbo/ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,104 @@

module Turbo
module Ruby
module StreamActionsContext
def self.register(name, action = name)
define_method name do |*arguments, **options, &block|
stream(*arguments, action: action, **options, &block)
end
end

# turbo.targets(@posts).morph
def targets(targets)
if block_given?
targets.each { yield Turbo::Ruby::Target.new(self, _1) }
else
Turbo::Ruby::Targets.new(self, targets)
end
end
alias for targets # Turbo.for(@post).morph or Turbo.for(@posts).morph
alias call targets # Turbo.(@post).morph or Turbo.(@posts).morph

def target(record)
Turbo::Ruby::Target.new(self, record).tap { yield _1 if block_given? }
end

# <%= turbo.frame "post_1" do %>
# <% end %>
def frame(record)
Turbo::Elements::TurboFrame.new(to_dom_id(record)).tap { yield record if block_given? }
end

def to_dom_id(record)
record
end

def stream(**options, &block)
Turbo::Elements::TurboStream.new(**options, &block)
end
end

class << self
def stream_actions_context
StreamActionsContext
end

def stream_actions(&block)
stream_actions_context.module_eval(&block)
end
end

stream_actions do
register :morph

def log(message, **options, &block)
stream(action: "console_log", message: message, **options, &block)
end
end
end

# Make `Turbo.morph` etc. and `Turbo.stream` available.
include Ruby.stream_actions_context

class Targets
include Turbo::Ruby.stream_actions_context

undef_method :targets
undef_method :target

def initialize(context, targets)
@context = context
@targets = targets.map { context.to_dom_id(_1) }
end

def frame(&block)
@targets.map { @context.frame(_1, &block) }
end

def stream(**options, &block)
@context.stream(targets: @targets, **options, &block)
end
end

class Target
include Turbo::Ruby.stream_actions_context

undef_method :targets
undef_method :target

def initialize(context, target)
@context = context
@target = context.to_dom_id(target)
end

def frame(&block)
@context.frame(@target, &block)
end

def stream(**options, &block)
@context.stream(target: @target, **options, &block)
end
end
end

require_relative "ruby/railtie" if defined?(Rails::Railtie)
28 changes: 28 additions & 0 deletions lib/turbo/ruby/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Turbo
module Ruby
class Context
include Turbo::Ruby.stream_actions_context

attr_reader :view_context

def initialize(view_context)
@view_context = view_context
end

def to_dom_id(record)
record.respond_to?(:to_key) ? view_context.dom_id(record) : record
end

# turbo.morph(@posts) or turbo.morph(@post)
def stream(records, **options, &block)
if records.respond_to?(:each)
super(targets: records, view_context: view_context, **options, &block)
else
super(target: records, view_context: view_context, **options, &block)
end
end
end
end
end
13 changes: 13 additions & 0 deletions lib/turbo/ruby/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Turbo
module Ruby
class Railtie < Rails::Railtie
initializer "turbo.ruby.view_helpers" do
ActiveSupport.on_load :action_view do
include Turbo::Ruby::ViewContextHelper
end
end
end
end
end
11 changes: 11 additions & 0 deletions lib/turbo/ruby/view_context_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Turbo
module Ruby
module ViewContextHelper
def turbo
@turbo ||= Turbo::Ruby::Context.new(self)
end
end
end
end