Having some issues putting these puzzle pieces together... I'm scraping a website to get an array of strings and I want the array to get sent back to my React client for use. Here's what I have
index.js
componentDidMount() {
const { restaurant } = this.state
axios.post('/api/scraper', { restaurant: restaurant })
.then((res) => {
console.log(res.data);
})
}
app/controllers/api/scraper_controller.rb
class Api::ScraperController < ApplicationController
respond_to :json
def create
#info = helpers.get_info(params[:restaurant])
respond_with #info
end
end
app/helpers/api/scraper_helper.rb
module Api::ScraperHelper
def get_info(restaurant)
puts restaurant
require 'openssl'
doc = Nokogiri::HTML(open('http://www.subway.com/en-us/menunutrition/menu/all', :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE))
#items = []
doc.css('.menu-cat-prod-title').each do |item|
#items.push(item.text)
end
end
end
The whole idea is to get the #items array sent back to my axios request on my React page
Your actual code will just return 0, because the result of applying each in a Nokogiri::XML::NodeSet in this case is 0, and is what you're leaving as the last executed "piece of code" within your method, so Ruby will return this.
If you add #items in the last line, then this will be returned, and you'll get ["Black Forest Ham", "Chicken & Bacon Ranch Melt", ...] that I guess is what you need:
#items = []
doc.css('.menu-cat-prod-title').each { |item| #items.push(item.text) }
#items
Note you could also do a map operation on doc.css('.menu-cat-prod-title'), which can then be assigned to any instance variable:
def get_info(restaurant)
...
doc.css('.menu-cat-prod-title').map(&:text)
end
I guess to return the data from create you could use something like render json: { items: #items }, as items contains an array of menues.
Related
I'm running a rails application that calls Simplecasts API to display my podcast episodes. I followed a tutorial to setup the API services using Faraday. My question is how to only display published episodes on my index page? Normally, I would add a .where(:status => "live") in my controller, IE #podcasts = Episodes.where(:status => "published") but this doesn't seem to work.
Simplecast's API for the podcast returns a collection that contains all the available episodes, each has a status node.
Any help would be appreciated as I'm new to working with external APIs in Rails
Sample API response
"collection": [
{
"updated_at": "2020-03-25T17:57:00.000000-04:00",
"type": "full",
"token": "lgjOmFwr",
"title": "Test",
"status": "draft",
Episode.rb
module Simplecast
class Episodes < Base
attr_accessor :count,
:slug,
:title,
:status
MAX_LIMIT = 10
def self.episodes(query = {})
response = Request.where('/podcasts/3fec0e0e-faaa-461f-850d-14d0b3787980/episodes', query.merge({ number: MAX_LIMIT }))
episodes = response.fetch('collection', []).map { |episode| Episode.new(episode) }
[ episodes, response[:errors] ]
end
def self.find(id)
response = Request.get("episodes/#{id}")
Episode.new(response)
end
def initialize(args = {})
super(args)
self.collection = parse_collection(args)
end
def parse_collection(args = {})
args.fetch("collection", []).map { |episode| Episode.new(episode) }
end
end
end
Controller
class PodcastsController < ApplicationController
layout "default"
def index
#podcasts, #errors = Simplecast::Episodes.episodes(query)
#podcast, #errors = Simplecast::Podcast.podcast(query)
render 'index'
end
# GET /posts/1
# GET /posts/1.json
def show
#podcast = Simplecast::Episodes.find(params[:id])
respond_to do |format|
format.html
format.js
end
end
private
def query
params.permit(:query, {}).to_h
end
end
Looks like collection is just an array of hashes so rails ActivrRelations methods aka .where are not supported. However It is an array so you can just filter this array:
published_episodes = collection.filter { |episode| episode[:status] == “ published” }
Also look through their API - may be the do support optional filtering params so you would get only published episodes in the first place.
BTW: second thought is to save external API request data in your own DB and then fetch require episodes with standard .where flow.
The search method is non-crud action and map is a private method, restaurant, dish, location, pictures are models. these models data contains an array. so how I write test case for map method and search method. restaurant and location has HABTM association, and also restaurant and dish has HABTM association, restaurant and pictures have a polymorphic association, and also dish and pictures has a polymorphic association
def search
map
if params[:name]
#items = Dish.search(params[:name])
end
if params[:price]
#items = Dish.sortby_price(params[:price]).search(params[:name])
end
if params[:ratings]
#items = Dish.sortby_ratings(params[:name])
end
if params[:rating]
#items = Dish.sortby_rating(params[:rating])
end
if params[:category]
#items= Dish.sortby_dietary(params[:category]).search(params[:name])
end
if params[:restaurant]
#restaurants =
Restaurant.find(params[:restaurant])
#items = #restaurants.dishes
end
end
private
def map
#items = Dish.search(params[:name])
restaurants = []
locations = []
pictures = []
#items.each do |d|
#restaurants = d.restaurants
restaurants.push(#restaurants)
d.restaurants.each do |r|
#pictures = r.pictures
pictures.push(#pictures)
#locations = r.locations
locations.push(#locations)
end
end
gon.restaurants = restaurants
gon.locations = locations
gon.pictures = pictures
x = []
#items.each do |d|
#restaurants = d.restaurants
d.restaurants.each do |r|
x.push(r.id)
end
end
y = []
x.each do |x|
r = Restaurant.find(x)
d = r.dishes.count
y.push(d)
end
gon.dishes_count = y
end
Some people say that there is no need to test private methods. But in a company i'm working for we do test private methods.
For your case I'd recommend to do this:
test method #map separately from action #search. You need to check that gon, #items, #restaurants, #pictures, #locations objects got populated correctly.
You can test private methods by using method #send.
Example:
describe '#map' do
subject { controller.send(:map) }
# you would need to stub params method
before { allow(controller).to receive(:params).and_return({ name: 'my name' }) }
it { expect(controller.instance_variable_get(:#items)).to include/not be_blank/... }
end
Test method #search without actually calling method map.
Example:
describe '#search' do
before { allow(controller).to receive(:map) }
# you can set different context where you test cases with different parameters
context 'when params[:name] and params[:ratings] exist' do
before { get :search, { name: '...', ratings: '...' } }
it {...}
end
end
Hey everyone I am having an issue setting up my app. It uses the shopify API and essentially what it does is grab some data via a view and sends it to the controller but I am having issues passing it to another method in the controller to use the API to save the data.
Here is my code :
Controller
class BuilderController < ShopifyApp::AuthenticatedController
def index
urlval = request.fullpath
#urlCheck = urlval.split('/').last
end
def show
url = request.fullpath
#urlID = url.split('/').last
#customers = ShopifyAPI::Customer.search(query: "id:"+ #urlID)
#need to get a way to retrieve the ajax call info here to pass into the update
end
def updateCustomer(notes)
#customers.each do |cus|
cus.note = notes
cus.save()
end
end
def new
notes = params[:notes]
updateCustomer(notes)
render json: notes
end
end
View
<button id="test">TEST</button>
<script>
var butt = document.getElementById('test');
butt.addEventListener("click",function(){
$.ajax({
url: "/builder/new",
type: "GET",
data: {
"notes": [
"test",
"test2"
]
},
success: function(data,text,xhr) {
console.log(text);
console.log(xhr);
console.log(data);
alert('successfully');
},
error: function(data,error){
console.log(data);
console.log(error);
alert("help");
}
});
});
</script>
Rather than a fully separate method, have you looked into the
respond_to method? http://api.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to
You could do (assuming html is the primary request type, change if it isn't):
def index
respond_to do |format|
format.html { actions }
format.json { actions }
end
end
This the method we use to accommodate different request types within the same action. Please let me know if I've misinterpreted your question.
you can use this
update_all(updates) public
Updates all records with details given if
they match a set of conditions supplied, limits and order can also be
supplied. This method constructs a single SQL UPDATE statement and
sends it straight to the database. It does not instantiate the
involved models and it does not trigger Active Record callbacks or
validations.
http://apidock.com/rails/v4.0.2/ActiveRecord/Relation/update_all
def new
notes = params[:notes]
#customer.update_all({note: notes})
respond_to do |format|
format.html {}
format.json { json: #customer.json }
end
end
so i have this controller and i want to add a dynamic attribute along with the other data in the #events instance variable
i have search and tried things like #events.attributes.merge(appointment: true)
appointment = true is what i want to add to the events object.
def find
params = event_params
current_user = 2
#events = Event.where('date LIKE ?',"%#{params[:month]}%")
def #events.as_json(options = { })
h = super(options)
h[:appointments] = false # Or combine with above h[:appointments] = self.appointments?
h
end
respond_to do |format|
if current_user == 1
if #events
format.json {
render json: #events.to_json
}
else
render 'index'
end
else
format.json {
render json: #events.to_json
}
end
end
end
ajax code here
function retrieve(date_partial) {
var jsondata = {
events: {
month: date_partial,
}
}
$.ajax({
cache: false,
type: "POST",
url: "/events/find",
data: jsondata,
success: function(data) {
console.log(data);
for (var i = 0; i < data.length; i++) {
var day = data[i].date.substring(0, 2);
$("td[data-day='" + day + "']").addClass('added');
}
},
error: function(xhr) {
alert("The error code is: " + xhr.statusText);
}
});
so how can i add that property?
This could work ? But then maybe the JSON output isn't what you expected ?
format.json { render :json => {events: #events, appointments: true} }
Because this property is view oriented, the model should not know about it. A better way to do this, is to use a decorator, which will allow you to add what ever attributes you want in the manner you want, without polluting the model.
you can create a PORO object
like this one
# this is by no means a complete implementation, but just for you
# to get the idea
class EventDecorator
# use ( delegate :event_attribute, to: :event ) to delegate
# all the event attributes and to be able to access them
# as if they were declared on the decorator itself
attr_reader :event
attr_accessor :appointment
def initialize(event)
#event = event
#appointment = false
end
def to_json
event.attributes.merge(appointment: appointment).to_json
end
end
a better way is to use the draper gem. You can find a good explanation in this railscat, #286 Draper
Two ways to do that I can think of: adding an instance variable or a custom method (or something hybrid)
EDIT : Forget what I said about creating an instance variable out of nowhere (see this answer)^^"
Method
#events.define_singleton_method(:appointments?){true/false}
#events.appointments? # => true/false
EDIT 2 : AJAX/JSON override
See this answer
def #events.as_json(options = { })
h = super(options)
h[:appointments] = true/false # Or combine with above h[:appointments] = self.appointments?
h
end
I've looked at similar posts but can't seem to quite figure it out.
I have the following function which works just fine. The Listing model has a foreign key called price_id which maps to the Price model and its price_range column. Price_id is returned as part of the message object in the JSON response.
How can I return the corresponding price_range value from the association instead of the price_id value (as part of the message obj, and keep the other attributes)?
def update
#listing = Listing.find(params[:listing][:id])
#if params were passed in for updating
if #listing.update_attributes(params[:listing])
#should we return the whole thing or just what's needed?
json_response = {
"success" => #listing.save, #save to DB and assign true/false based on success...
"message" => #listing.attributes #USE attributes to show output the content of the #message obj, and not another object called "message"
}
respond_to do |format|
#json response
format.html { render:json => json_response }
format.xml { render :xml => #listing }
#normal response. Consider leaving this for now?
#format.html { render :action => "detail" } #refresh this page, with new data in it. Consider trying to use redirect instead?
#format.xml { head :ok }
end
end #end if
end
add a method in your Listing model with the price_range and call it in serializable_hash
class Listing
def price_range
price.price_range
end
end
Like explain on comment you can use delegate instead this method :
class Listing
delegate :prince_range, :to => price
end
In you controller you can now do :
json_response = {
"success" => #listing.save, #save to DB and assign true/false based on success...
"message" => #listing.serializable_hash(:methods => [:price_range])
}
Based on what I read in this article, you should be able to do this:
class Listing
def as_json
super(:include => :price)
end
end
Then in your controller:
json_response = {
"success" => #listing.save,
"message" => #listing.as_json
}
If I understand correctly, you want to add #listing.price.price_range value to the "message" ?
If so, try this:
"message" => #listing.attributes[:price_range] = #listing.price.price_range