camertron/rux-rails

Embedding Ruby

Closed this issue · 2 comments

class Ui::Mix::BarComponent::BarComponent < ViewComponent::Base
  include Birdel::Component
  Styles = Data.define(
    :nav_bar,
    :nav_item,
    :btn,
    :btn_active,
    :nav_item__logo
  )

  attr_reader :styles
  def initialize()
  end

  def styles
    @styles ||= Styles.new(
      nav_bar:        'nav-bar',
      nav_item:       'nav-item',
      btn:            'nav-item-btn',
      btn_active:     'nav-item-btn--active',
      nav_item__logo: 'nav-item-logo'
    )
  end

  def call
    <div class={css_class} data-controller={css_class}>
      <div class={styles.nav_bar}>
        <div class="#{styles.nav_item} #{styles.nav_item__logo}">
        </div>
        <div class={styles.nav_item}>
          <div class={styles.btn}>Lefrt</div>
          <div class="{styles.btn} {styles.btn_active}">Center</div>
          <div class={styles.btn}>Right</div>
        </div>
        <div class={styles.nav_item}>
        </div>
      </div>
    </div>
  end
end

I confused about why <div class="#{styles.nav_item} #{styles.nav_item__logo}"> works correct and renders html like:

<div class="nav-item-btn nav-item-btn--active">Home</div>

But "{styles.btn} {styles.btn_active}" renders html like:

<div class="{styles.btn} {styles.btn_active}">Home</div>

Shouldn't it work like class="{styles.btn} {styles.btn_active}" ? When we just double calls embedding Ruby code and should get both returned some objects in this case inside a String ?

Hey @serhiijun great question! The answer lies in how rux generates ruby code.

Here's how your first example transpiles:

# rux
<div class="#{styles.nav_item} #{styles.nav_item__logo}">

# ruby
Rux.tag("div", { class: "#{styles.nav_item} #{styles.nav_item__logo}" })

As you can see, Rux takes the value of the class attribute and uses it verbatim, i.e. without modifying it at all. It is somewhat by accident this works, since the rux parser doesn't know or care what's inside these attribute values.

Here's how your second example transpiles:

# rux
<div class="{styles.btn} {styles.btn_active}">

# ruby
Rux.tag("div", { class: "{styles.btn} {styles.btn_active}" })

Just as in the first example, the class attribute is inserted into the resulting ruby code without modification. By design, rux does not interpret the contents of literal strings, or in this case literal attribute values.

The second example can be rewritten by placing a set of curly braces outside the double quotes, i.e.:

<div class={"#{styles.btn} #{styles.btn_active}"}>

(Or you can leave the curly braces off entirely as you've done in the first example, both are equivalent in this case).

Rux uses curly braces to identify where ruby code starts and ends. In other words, you can write any ruby code you want inside them, including string interpolations.

For what it's worth, I'm pretty sure JSX works this way too.

@camertron Thank you for the detailed explanation😄