diff --git a/Gemfile.lock b/Gemfile.lock
index 96504a4..efeb7be 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -36,6 +36,7 @@ GEM
zeitwerk (2.6.6)
PLATFORMS
+ arm64-darwin-20
x86_64-darwin-19
x86_64-linux
diff --git a/README.md b/README.md
index c8f42c0..8ed2162 100644
--- a/README.md
+++ b/README.md
@@ -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
-<%= 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
%(
Post 1
)
@@ -41,18 +39,51 @@ end.to_html
```html+erb
-<%= render Turbo::Elements::TurboStream.new(action: "morph", target: "post_1") do %>
+<%= render Turbo.stream(action: "morph", target: "post_1") do %>
Post 1
<% 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
+ %(
+
Post 1
+ )
+end.to_html
+```
+
### Partials (Rails only)
```html+erb
-<%= 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
diff --git a/lib/turbo/elements/turbo_stream.rb b/lib/turbo/elements/turbo_stream.rb
index 16b1d83..955af8b 100644
--- a/lib/turbo/elements/turbo_stream.rb
+++ b/lib/turbo/elements/turbo_stream.rb
@@ -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
diff --git a/lib/turbo/ruby.rb b/lib/turbo/ruby.rb
index 238d736..6ed4abc 100644
--- a/lib/turbo/ruby.rb
+++ b/lib/turbo/ruby.rb
@@ -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)
diff --git a/lib/turbo/ruby/context.rb b/lib/turbo/ruby/context.rb
new file mode 100644
index 0000000..27373be
--- /dev/null
+++ b/lib/turbo/ruby/context.rb
@@ -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
diff --git a/lib/turbo/ruby/railtie.rb b/lib/turbo/ruby/railtie.rb
new file mode 100644
index 0000000..c777da3
--- /dev/null
+++ b/lib/turbo/ruby/railtie.rb
@@ -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
diff --git a/lib/turbo/ruby/view_context_helper.rb b/lib/turbo/ruby/view_context_helper.rb
new file mode 100644
index 0000000..23c95ec
--- /dev/null
+++ b/lib/turbo/ruby/view_context_helper.rb
@@ -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