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

Introduce Phlex::Bucket #674

Merged
merged 4 commits into from Mar 12, 2024
Merged

Introduce Phlex::Bucket #674

merged 4 commits into from Mar 12, 2024

Conversation

joeldrapper
Copy link
Collaborator

@joeldrapper joeldrapper commented Mar 12, 2024

Phlex::Bucket allows you to define a bucket of components which can then be included into your views and other components.

Here’s how it works:

First, you can define a module to hold your components and have it extend Phlex::Bucket. Then define as many components in this module as you like. These should be namespaced directly under your module.

module Components
  extend Phlex::Bucket

  class Card < ApplicationComponent
    def template(&)
      article(class: "card", &)
    end
  end
end

Then include that module in your base component/view classes.

ApplicationComponent.include(Components)
ApplicationView.include(Components)

Now you can use components from this bucket by calling their name as a method.

class Hello < ApplicationView
  def template
    Card { "Hello" }
  end
end

The only real downside to this is all your components need to live in a single namespace. That seems to work okay for React and web components, so maybe it’ll work for you. Of course you can continue to render namespaced components by passing them to render.

One gotcha to look out for is if you have a component that takes no arguments, you’ll need to include empty parentheses to tell Ruby this is a method call, not a constant lookup.

You can create and import as many “buckets” as you like, but they can’t have conflicting names, since they all share a single namespace once the methods are included.

Why can't we do MyNamespace::Card()?

Because that would be calling Card() on MyNamespace, which has no idea which component you’re currently rendering. The only way to support this would be to have a global variable keep track of the currently rendering component, which would be really nasty. When you call Card() on self, it’s easy to pass the rendering context to the new component.

Update: If you want to know how this could be done ^^ here's a rough sketch. f8cca44

def const_added(name)
constant = const_get(name)

return if instance_methods.include?(name)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps should raise an error in this case.

@joeldrapper joeldrapper marked this pull request as ready for review March 12, 2024 19:32
@joeldrapper joeldrapper merged commit e9167c4 into main Mar 12, 2024
17 checks passed
@joeldrapper joeldrapper deleted the bucket branch March 12, 2024 19:32
@ioquatix
Copy link

ioquatix commented Mar 13, 2024

So, considering https://github.com/phlex-ruby/phlex/compare/crazy-globals.

I think there are two ways to achieve this:

  1. Card { "Hello" } - when you pass the block to Card you can actually extract the caller binding to get self or something similar. A similar technique is used here: https://github.com/ioquatix/trenni/blob/86394b0b9680e47eabd463317fac73c3a690b6cf/lib/trenni/template.rb#L40-L42 to get the buffer output from a block. Using self can be sufficient to "connect the dots".
  2. Dynamic variables. As you've shown, you can have "per-thread" dynamically scoped variables, but Fiber[:variable] is as close as we get to properly scoped dynamic variables in Ruby. I would recommend you replace Thread.current[x] with Fiber[x]. Use https://rubygems.org/gems/fiber-storage as a shim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants