如何将Ruby服务对象GrabFeedFromJSE的数据传递至控制器?
实现服务对象数据传递到控制器的方案
我来帮你搞定把GrabFeedFromJSE服务中的数据传递到控制器这件事,先梳理下你的现有代码和调试结果,再一步步给出实现步骤:
服务对象代码(路径:app/services/grab_feed_from_jse.rb)
class GrabFeedFromJSE def initialize(url) require 'oga' require 'net/http' body = Net::HTTP.get(URI.parse(url)) document = Oga.parse_html(body) @titles = document.css('li') end end
Pry调试响应
从你用Pry调试的结果来看,@titles已经成功抓取到了页面上的所有li元素,包含了市场指数的名称、成交量、数值、涨跌幅度等关键信息:
[1] pry(#<GrabFeedFromJSE>)> @titles => NodeSet( Element(name: "li" children: NodeSet(Text(" "), Element(name: "input" attributes: [Attribute(name: "class" value: "ticker-status"), Attribute(name: "type" value: "hidden"), Attribute(name: "name" value: "Language"), Attribute(name: "value" value: "summary")]))), Element(name: "li" attributes: [Attribute(name: "class" value: "live-ticker-heading")] children: NodeSet(Text("Market Summary for 2018-05-10 "), Element(name: "br"), Text(" "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/index-data/jse-index")] children: NodeSet(Text("\n\n \n\n\n JSE Index \n \n "), Element(name: "br"), Text("\n\n 5,201,225 Vol\n "), Element(name: "br"), Text("\n\n 299,198.19 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/up.png")]), Text("\n\n \n 22.32\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/index-data/all-jamaican")] children: NodeSet(Text("\n\n \n\n\n JSE All Jamaican Composite Index \n \n "), Element(name: "br"), Text("\n\n 3,842,904 Vol\n "), Element(name: "br"), Text("\n\n 328,387.59 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/up.png")]), Text("\n\n \n 24.50\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/index-data/jse-select")] children: NodeSet(Text("\n\n \n\n\n JSE Select Index \n \n "), Element(name: "br"), Text("\n\n 1,995,939 Vol\n "), Element(name: "br"), Text("\n\n 8,948.62 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/down.png")]), Text("\n\n \n -31.23\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/index-data/jse-junior")] children: NodeSet(Text("\n\n \n\n\n Junior Market Index \n \n "), Element(name: "br"), Text("\n\n 1,858,849 Vol\n "), Element(name: "br"), Text("\n\n 2,917.69 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/down.png")]), Text("\n\n \n -15.45\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/index-data/combined-index")] children: NodeSet(Text("\n\n \n\n\n JSE Combined Index \n \n "), Element(name: "br"), Text("\n\n 7,028,074 Vol\n "), Element(name: "br"), Text("\n\n 306,927.60 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/down.png")]), Text("\n\n \n -148.29\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/index-data/us-equities")] children: NodeSet(Text("\n\n \n\n\n JSE USD Equities Index \n \n "), Element(name: "br"), Text("\n\n 220,397 Vol\n "), Element(name: "br"), Text("\n\n 165.29 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/down.png")]), Text("\n\n \n -8.87\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/listed-companies/trade-information/CCC/latest")] children: NodeSet(Text("\n\n \n\n\n CCC\n \n "), Element(name: "br"), Text("\n\n 979,272 Vol\n "), Element(name: "br"), Text("\n\n $37.25 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: "src" value: "/img/down.png")]), Text("\n\n \n -0.05\n "))), Text("\n\n\n "))), Element(name: "li" children: NodeSet(Text("\n\n "), Element(name: "a" attributes: [Attribute(name: "href" value: "/market-data/listed-companies/trade-information/CAR/latest")] children: NodeSet(Text("\n\n \n\n\n CAR\n \n "), Element(name: "br"), Text("\n\n 109,202 Vol\n "), Element(name: "br"), Text("\n\n $10.60 \n "), Element(name: "img" attributes: [Attribute(name: "style" value: "height:16px"), Attribute(name: [2] pry(#<GrabFeedFromJSE>)> ls @titles ActiveSupport::ToJsonWithActiveSupportEncoder#methods: to_json Enumerable#methods: all? collect_concat each_slice find_index group_by_day_of_month group_by_mon
实现数据传递到控制器的步骤
1. 给服务对象添加公共方法暴露数据
目前你的服务只有初始化方法,@titles是私有实例变量,控制器无法直接访问。我们可以添加一个公共方法,既可以直接返回原始的NodeSet,也可以把数据整理成更易用的哈希数组(推荐后者,这样视图层处理起来更方便)。
修改后的服务代码:
class GrabFeedFromJSE def initialize(url) require 'oga' require 'net/http' body = Net::HTTP.get(URI.parse(url)) document = Oga.parse_html(body) @titles = document.css('li') end # 整理并返回结构化的市场数据 def market_data # 过滤掉不需要的li元素(比如标题、隐藏输入框所在的li) valid_list_items = @titles.reject do |li| li.attributes['class'] == 'live-ticker-heading' || li.children.any? { |child| child.name == 'input' } end # 把每个有效li转换成哈希 valid_list_items.map do |li| a_tag = li.at_css('a') next unless a_tag # 跳过没有a标签的li # 提取a标签中的文本内容,清理掉多余的换行和空格 text_parts = a_tag.text.strip.split("\n").map(&:strip).reject(&:empty?) { name: text_parts[0], volume: text_parts[1].gsub('Vol', '').strip, current_value: text_parts[2].strip, change_amount: text_parts[3].strip, change_direction: a_tag.at_css('img')&.attributes['src']&.value == '/img/up.png' ? :up : :down, detail_url: a_tag.attributes['href'].value } end.compact # 移除nil值 end # 如果需要直接获取原始的NodeSet,添加这个方法 attr_reader :titles end
2. 在控制器中调用服务并获取数据
在你的控制器里,实例化服务对象,调用market_data(或者titles)方法获取数据,然后赋值给实例变量,这样视图就能访问到:
class MarketsController < ApplicationController def index # 替换成实际的JSE市场数据URL jse_feed_url = 'https://www.jamse.com/market-summary' jse_grabber = GrabFeedFromJSE.new(jse_feed_url) # 获取结构化的市场数据 @market_summary = jse_grabber.market_data # 如果需要原始的NodeSet,用下面这行 # @raw_titles = jse_grabber.titles end end
3. 在视图中渲染数据
现在你可以在对应的视图文件(比如app/views/markets/index.html.erb)里使用@market_summary来展示数据:
<h1>JSE Market Summary</h1> <div class="market-cards"> <% @market_summary.each do |item| %> <div class="card"> <h3><%= item[:name] %></h3> <p class="value"><%= item[:current_value] %></p> <p class="change <%= item[:change_direction] %>"> <%= item[:change_amount] %> <%= item[:change_direction] == :up ? '↑' : '↓' %> </p> <p class="volume">Volume: <%= item[:volume] %></p> <%= link_to 'View Details', item[:detail_url], class: 'btn' %> </div> <% end %> </div>
内容的提问来源于stack exchange,提问作者marcamillion
相关产品推荐
相关产品推荐

