Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ruby SDK support rails render_in interface #617

Merged
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
51 changes: 43 additions & 8 deletions sdk/ruby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ In your Rack handler or Rails controller:

```ruby
# Rails controllers, as well as Sinatra and others,
# already have request and response objects
# already have request and response objects.
# `view_context` is optional and is used to render Rails templates.
# Or view components that need access to helpers, routes, or any other context.

datastar = Datastar.new(request:, response:, view_context: self)
datastar = Datastar.new(request:, response:, view_context:)

# In a Rack handler, you can instantiate from the Rack env
datastar = Datastar.from_rack_env(env)
Expand Down Expand Up @@ -130,7 +132,7 @@ See https://data-star.dev/reference/sse_events#datastar-execute-script

```ruby
sse.execute_scriprt(%(alert('Hello World!'))
```
```

#### `signals`
See https://data-star.dev/guide/getting_started#data-signals
Expand All @@ -139,14 +141,14 @@ Returns signals sent by the browser.

```ruby
sse.signals # => { user: { name: 'John' } }
```
```

#### `redirect`
This is just a helper to send a script to update the browser's location.

```ruby
sse.redirect('/new_location')
```
```

### Lifecycle callbacks

Expand Down Expand Up @@ -198,11 +200,14 @@ Datastar.configure do |config|
end
```

### Rails
### Rendering Rails templates

#### Rendering Rails templates
In Rails, make sure to initialize Datastar with the `view_context` in a controller.
This is so that rendered templates, components or views have access to helpers, routes, etc.

```ruby
datastar = Datastar.new(request:, response:, view_context:)

datastar.stream do |sse|
10.times do |i|
sleep 1
Expand All @@ -212,14 +217,44 @@ datastar.stream do |sse|
end
```

#### Rendering Phlex components
### Rendering Phlex components

`#merge_fragments` supports [Phlex](https://www.phlex.fun) component instances.

```ruby
sse.merge_fragments(UserComponent.new(user: User.first))
```

### Rendering ViewComponent instances

`#merge_fragments` also works with [ViewComponent](https://viewcomponent.org) instances.

```ruby
sse.merge_fragments(UserViewComponent.new(user: User.first))
```

### Rendering `#render_in(view_context)` interfaces

Any object that supports the `#render_in(view_context) => String` API can be used as a fragment.

```ruby
class MyComponent
def initialize(name)
@name = name
end

def render_in(view_context)
"<div>Hello #{@name}</div>""
end
end
```

```ruby
sse.merge_fragments MyComponent.new('Joe')
```



### Tests

```ruby
Expand Down
7 changes: 6 additions & 1 deletion sdk/ruby/lib/datastar/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
module Datastar
class Railtie < ::Rails::Railtie
FINALIZE = proc do |view_context, response|
view_context.response = response
case view_context
when ActionView::Base
view_context.controller.response = response
else
raise ArgumentError, 'view_context must be an ActionView::Base'
end
end

initializer 'datastar' do |_app|
Expand Down
10 changes: 9 additions & 1 deletion sdk/ruby/lib/datastar/server_sent_event_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ def initialize(stream, signals:, view_context: nil)

def merge_fragments(fragments, options = BLANK_OPTIONS)
# Support Phlex components
fragments = fragments.call(view_context:) if fragments.respond_to?(:call)
# And Rails' #render_in interface
fragments = if fragments.respond_to?(:render_in)
fragments.render_in(view_context)
elsif fragments.respond_to?(:call)
fragments.call(view_context:)
else
fragments.to_s
end

fragment_lines = fragments.to_s.split("\n")

buffer = +"event: datastar-merge-fragments\n"
Expand Down
16 changes: 16 additions & 0 deletions sdk/ruby/spec/dispatcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ def self.call(view_context:) = %(<div id="foo">\n<span>#{view_context}</span>\n<
dispatcher.response.body.call(socket)
expect(socket.lines).to eq([%(event: datastar-merge-fragments\nid: 72\nretry: 2000\ndata: settleDuration 1000\ndata: fragments <div id="foo">\ndata: fragments <span>#{view_context}</span>\ndata: fragments </div>\n\n\n)])
end

it 'works with #render_in(view_context, &) interfaces' do
template_class = Class.new do
def self.render_in(view_context) = %(<div id="foo">\n<span>#{view_context}</span>\n</div>\n)
end

dispatcher.merge_fragments(
template_class,
id: 72,
retry_duration: 2000,
settle_duration: 1000
)
socket = TestSocket.new
dispatcher.response.body.call(socket)
expect(socket.lines).to eq([%(event: datastar-merge-fragments\nid: 72\nretry: 2000\ndata: settleDuration 1000\ndata: fragments <div id="foo">\ndata: fragments <span>#{view_context}</span>\ndata: fragments </div>\n\n\n)])
end
end

describe '#remove_fragments' do
Expand Down
Loading