В Rails вы хотите создавать тонкие контроллеры, содержащие минимум кода, и единственными c методами должны быть действия, соответствующие вашим маршрутам. Это связано с тем, что контроллеры, как известно, сложно тестировать, и раздувание становится проблемой очень быстро.
HTTP-вызовы, пакетная обработка и другие подобные задачи не относятся к вашему контроллеру. Особенно, если они касаются границы приложения. Вместо этого вы хотите создать клиентский объект, который обрабатывает HTTP-вызов и модели, которые инкапсулируют данные и нормализуют их для вашего приложения.
Итак, давайте начнем с HTTP-вызова:
# app/clients/audio_scobbler_client.rb
class AudioScrobblerClient
include Httparty
format :json
base_uri "http://ws.audioscrobbler.com/2.0/"
def initialize(api_key:)
@base_opts = {
api_key: api_key,
format: "json" # may be redundant
}
end
def album_search(query, limit: 15)
self.class.get(
@base_opts.reverse_merge(
method: 'album.search',
limit: limit
)
)
end
end
Это дает вам объект, который вы можете протестировать отдельно от вашего контроллера, и устраняет беспорядок при построении строк запроса с конкатенацией строк (никогда не делайте этого). Он возвращает JSON от выполнения HTTP-запроса и ничего более.
Затем создайте модель, которая представляет результаты поиска в вашем приложении. Помните, что постоянство - не единственная роль моделей в MVC.
# app/models/album.rb
class Album
include ActiveModel::Model
include ActiveModel::Attributes
attr :artist, String
attr :name, String
attr :image, String
end
Теперь давайте добавим еще один объект в микс - объект службы, который выполняет вызов API и нормализует значения:
# app/services/audio_scrobbler_search.rb
class AudioScrobblerSearch
def perform(query, **options)
api_key = ENV["AUDIOSCROBBER_API_KEY"] # or use the encrypted secrets.
json = AudioScrobblerClient.new(api_key: api_key).album_search(query, options)
json.dig("results", "albummatches", "album").map do |result|
# I have no idea what api response looks
# like but I have no doubt that you can figure this part out
Album.new(
artist: result["name"],
name: result["name"],
image: result["image"]
)
end
end
end
Затем избавимся от всего лишнего в контроллере:
class AlbumsController < ApplicationController
# you don't really need the new action at all since a search form can just loop back on itself
# GET /albums/search?q=believe
def search
@search_query = params[:q]
if @search_query
@albums = AudioScrobblerSearch.perform(query)
end
end
end
И перечислим альбомы в представлении:
<h4>Search for Album</h4>
<%= form_with(url: "/albums/search", method: "get") do %>
<%= f.label(:q, "Search for: ") %>
<%= f.text_field(:q, value: @search_query) %>
<%= f.submit("Search") %>
<% end %>
<% if @albums %>
<table>
<thead>
<tr>
<th>Image</th>
<th>Artist</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<% @albums.each do |album| %>
<tr>
<td><%= tag.img src: album.image, alt: "Cover art for #{album.name}" %></td>
<td><%= album.artist %></td>
<td><%= album.name %></td>
</tr>
<% end %>
</tbody>
</table>
<% elsif @search_query.present? %>
<p>No results to display :(</p>
<% end %>