ruby-on-rails Rails:JS事件处理程序中的嵌入式Ruby未按预期运行

ztyzrc3y  于 2023-04-08  发布在  Ruby
关注(0)|答案(1)|浏览(120)

我目前非常精通Ruby和JS,并开始使用Rails。我已经创建了一个最小的Rails应用程序,我正在从JavaScript事件处理程序(按钮单击事件处理程序)调用Ruby方法。该方法应该对一组卡片进行采样并显示结果。
我通过发出这些命令创建了应用程序:

rails new blackjack
cd blackjack
bin/rails generate controller Games blackjack
rails s

我在这里提到了两个文件:./app/views/games/blackjack.html.erb./app/controllers/games_controller.rb。下面是这两个文件中的代码:

games_controller.rb

class GamesController < ApplicationController
  def blackjack
    @game = Game.new
  end
end

class Deck
  CARD_VALUES = {
    '2'  => 2,
    '3'  => 3,
    '4'  => 4,
    '5'  => 5,
    '6'  => 6,
    '7'  => 7,
    '8'  => 8,
    '9'  => 9,
    '10' => 10,
    'J'  => 10,
    'Q'  => 10,
    'K'  => 10,
    'A'  => 11
  }.freeze

  RANKS = %w(2 3 4 5 6 7 8 9 10 J Q K A).freeze

  # UTF codes for suit symbols
  SUITS = ["\u2660", "\u2661", "\u2662", "\u2663"].freeze

  attr_reader :cards

  def initialize
    new_deck
  end

  def new_deck
    @cards = RANKS.product(SUITS).shuffle
  end
end

class Game
  attr_reader :deck

  def initialize
    @deck = Deck.new
  end
end

二十一点.html.erb

<p id="card" style="font-size:48px">Card: <%= @game.deck.cards.sample %></p>

<button id="button-card">Card</button>

<script>
document.getElementById('button-card').addEventListener('click', e => {
  let card = `<%= @game.deck.cards.sample %>`;
  card = card.replace(/&quot;/g, '"');
  document.getElementById('card').innerText = 'Card: ' + card;
});
</script>

发生的情况是这样的。当我打开页面时,当第一行的#sample方法被调用时,它会显示一张随机卡片,例如["6", "♢"]。这与我预期的一样。当我单击Card按钮时,我会得到另一张随机卡片,也与我预期的一样。但是当我再次单击Card按钮时,或者任何次数,卡片保持不变。我已经确定,每次单击Card按钮时,都会调用click处理程序中的sample方法,但相同的卡片值不断插入到p标记中。此外,如果我重新加载页面,我会得到一张新卡片。
所以,我可能忽略了一些基本的东西,关于嵌入式Ruby如何在客户端与JavaScript一起工作。
我确信这不是一种传统的在Rails中做事情的方式,但是我试图更好地理解嵌入式Ruby在Rails中是如何工作的。有人能启发我吗?

hgqdbh6s

hgqdbh6s1#

这个问题与ERB无关,它只是另一个模板引擎。
问题是Turbo创建了一个跨页面的持久会话(就像许多现代的javascript框架一样),并且像这样编写非幂等的代码在今天是不会解决这个问题的。无论你在后端使用什么语言,你都会遇到同样的问题。
脚本标签的情况是,每次Turbolinks访问页面时,它都会重新评估。事件处理程序将彼此堆叠,最后一个获胜。这会产生典型的“但是哇!当我重新加载页面时,它会工作!”场景。
相反,只需在外部脚本中创建委托幂等事件处理程序:

# Interpolate data into your HTML - not your JS.
<%= tag.button "Card",
    class: "button-card",
    data: {
      target: "#card",
      foo: "rab".reverse
    }
%>
// please put this JS in your assets pipeline
document.addEventListener('click', function(event){
  if (!event.target.matches('button-card')) return;
  let data = event.target.dataset;
  let targetCard = document.querySelector(data.target);
  targetCard.innerText(data.foo);
});

由于事件处理程序是绑定到文档而不是单个元素的,所以即使在页面访问时也可以这样做,而且正如您所看到的,您的脚本中不需要ERB插值。
支持Turbo的Stimulus.js library基于相同的基本原理工作-委托事件处理程序和数据属性作为传递状态的方法。
如果你真的想继续用ERB将数据插入JS的死胡同,你至少需要确保删除任何以前的事件处理程序,这样你的脚本的效果就不会叠加。

相关问题