直接将 API 数据添加到哈希中

Directly adding API data into a hash

我正在使用这个 GiantBomb API(https://github.com/games-directory/api-giantbomb) 来获取游戏列表。用户搜索游戏,然后将其添加到他们的库中。用户还可以查看其他人的库。我让它工作,除了我添加到库中的项目无法调用所有 API 数据。我想将 GiantBomb::Game 数据放入 has 中,这样我就可以在视图中调用数据,例如 @game.description 等等。

这是我的游戏控制器。有一个搜索功能,一个个人游戏的展示页面,以及我的图书馆功能,可以将个人游戏添加到用户的图书馆页面。

class GamesController < ApplicationController

#Users search for games
  def index
  @games = GiantBomb::Search.new().query(params[:query]).resources('game').limit(100).fetch
  end

#individual game profile page
  def show
  @game = GiantBomb::Game.detail(params[:id])
  end

#Adding games to user libraries
  def library
    type = params[:type]
    @game = Game.new(game_params)

    if type == "add"
      current_user.library_additions << @game
      redirect_to user_library_path(current_user), notice: "Game was added to your library"

    elsif type == "remove"
      current_user.library_additions.delete(@game)
      redirect_to root_path, notice: "Game was removed from your library"
    else
      # Type missing, nothing happens
      redirect_to game_path(@game), notice: "Looks like nothing happened. Try once more!"
    end
  end


private

  def game_params
    params.require(:game).permit(:name, :id)
  end
end

游戏添加到库中查看此代码在视图中。

 <% if user_added_to_library?(current_user, game) %>

            <%= link_to 'Remove from library', add_game_path(game['id'], type: "remove", game: game), method: :put %>

          <% else %>
            <%= link_to 'Add to library', add_game_path(game['id'], type: "add", game: game), method: :put %>
          <% end %>

在我的展示页面上,我通过 GiantBomb::Game.detail(params[:id]) 直接从 API 拉取,因此我可以调用如下参数:

<div class="cards">

  <div class="card">
  <%= image_tag @game.image['medium_url'], class: "cover"%>
  <div class="container">
    <h2><%= @game.name %></h2>
    <p><%= @game.deck %></p>
    <p><%= @game.id %></p>
    <% if @game.platforms === nil %>
    <p>Platform Unknown</p>
    <% else %>
    <p><%= @game.platforms[0]['name'] %></p>
    <% end %>
    <p><%= @game.original_release_date.to_s[0..3] %></p>
  </div>
</div>
  </div>

但是,我不能为我的库函数使用@game = GiantBomb::Game.detail(params[:id]),我必须使用@game = Game.new(game_params).我的游戏模型只有name和id相关联,所以我好像只能在图书馆页面上显示这些信息。

这是我的图书馆管理员。

class LibraryController < ApplicationController
  #find user id through params
  def index
    @library_games = User.find(params[:id]).library_additions
  end
end

它的索引页:

<h1>Library</h1>

<% if @library_games.exists? %>
<% @library_games.each do |game| %>
    <div class="container">
      <p><%= game.name%></p>
      <p><%= game.id %></p>

    </div>
  <% end %>
  <% else %>
  <div class="container">
    <div class="message-body">You haven't added any games to your library yet. <%= link_to 'Add some', root_path %>.</div>
  </div>
  <% end %>

如果我使用 game.deck 我会得到一个未定义的方法,但是如果我使用游戏 ['deck'] 我不会收到错误,但是没有显示与我的显示页面不同。

那么我是否需要为我的游戏模型(甲板、图像、平台等)的每个单独数据块添加一个迁移,或者有没有办法编辑我的库方法以便我可以通过所有GiantBomb::Game 数据到我的 library_additions 这样我就可以通过 @game.insertdatahere?

调用任何数据

如您所述,您的游戏模型仅存储 nameid,因为这是 game_params 包含的所有内容,并且可能也是您在底层定义的唯一列table。如果您查看您的数据库 (rails db) 和 运行 SELECT * FROM games;,您可以看到实际保存到您的数据库中的数据。

因此,如果您想存储完整的数据集,最快的答案是将其存储为任意散列。 GiantBomb::Game 记录没有提供获取其中数据散列的简单方法,但您可以使用一小段元编程将其中的所有内容提取出来:

data = Hash[game.instance_variables.map { |var| [var.to_s[1..-1], game.instance_variable_get(var)] } ]

然后,您可以将散列存储在新的 TEXT 列中并告诉 Rails 到 serialize 它,或者如果您使用的数据库支持它,直接进入 JSON/JSONB 列。由于我不知道你的数据库,这里有一个序列化方法的例子:

# in a new migration
change_table :games do |t|
  t.text :data
end

# game.rb
class Game < ApplicationRecord
  serialize :data

  # this method will overwrite the `data` column with the latest game data from the
  # API. I've extracted it so refreshing the data if it becomes stale is easier.
  def fetch_data
    game = GiantBomb::Game.detail(id)
    self.data = Hash[game.instance_variables.map { |var| [var.to_s[1..-1], game.instance_variable_get(var)] } ]
  end

  # ...
end

# in your controller
game = Game.new(game_params)
game.fetch_data

# in wherever you want to access the data
platforms = game.data["platforms"]

现在,我稍微看了看 GiantBomb 代码,它仅从纯哈希(我假设是从 JSON API 调用中解析)实例化自己的记录,因此您可以也从您存储的数据中重新实例化它们:

giant_bomb_game = GiantBomb::Game.new(game.data)

这可能会帮助您解决倒数第二段中的问题:您的视图都可以处理 GiantBomb::Game 类型的实例,而不必处理有时获取 Game 有时获取GiantBomb::Game.

例如,如果您将以上内容作为新方法添加到游戏中:

class Game < ApplicationRecord
  # ...
 
  def to_giant_bomb_game
    GiantBomb::Game.new(data)
  end
end

然后您的视图可以使用该方法将对象简洁地再水化为您想要的:

<h1>Library</h1>

<% if @library_games.exists? %>
<% @library_games.map(&:to_giant_bomb_game).each do |game| %>
    <div class="container">
      <p><%= game.name%></p>
      <p><%= game.id %></p>
      <p><%= game.deck %></p>
//etc

我可能应该就此打住,但因为我们在 Ruby, 可以(而且容易)更进一步,让游戏充当如果它是 GiantBomb::Game。如果这是您付费开发的生产应用程序,请立即返回,否则:

class Game < ApplicationRecord
  def method_missing(*args)
    if (gbgame = to_giant_bomb_game) && gbgame.respond_to?(args.first)
      return gbgame.public_send(*args)    
    end

    super(*args)
  end
end

这将生成一个 Game 实例,该实例接收一个它不知道的方法,例如 deck,在它知道如何构建的 GiantBomb::Game 实例上调用该方法。因此,无论 @game 是游戏还是 GiantBomb::Game,您的 @game.deck 调用都将发挥相同的作用。这很好,但也可能导致一些超级混乱的错误......这就是元编程的诅咒。