Handle bad JSON gracefully with Hash#fetch - ruby-on-rails

I'm trying to fetch some products from this JSON API so I can display them in my views, with bad JSON gracefully handled using Hash#fetch to declare a default value if I get nil.
But why am I getting:
can't convert String into Integer
or if I set #json_text to {}:
undefined method 'fetch' for `nil:NilClass`
Live app: http://runnable.com/U-QEWAnEtDZTdI_g/gracefully-handle-bad-json
class MainController < ApplicationController
require 'hashie'
def index
#json_text = <<END
{
"products": []
}
END
hashes = JSON.parse(#json_text)
mashes = Hashie::Mash.new(hashes)
products = []
mashes.products.fetch('products', []).each do |mash|
puts "YOLO"
end
end
end

The right way to fetch is by calling fetch on mashes variable:
mashes.fetch('products', []).each do |mash|
puts "YOLO"
end
#fetch is a Hash method and in this case is the same as mashes['product'], except when product is nil, using the fetch example will return []

Related

Filter data in JSON string

I have a JSON string as pulled from some API
[{"_id"=>"56aefb3b653762000b400000",
"checkout_started_at"=>"2016-02-01T07:32:09.120+01:00",
"created_at"=>"2016-02-01T07:29:15.695+01:00", ...}]
I want to filter data in this string based on created_at, e.g. letting the user chose a specific date-range and then only show the data from this range.
E.g.
#output = my_json.where(created_at: start_date..end_date)
My first thought was to somehow transfer the JSON string to Hashie, to interact with JSON as the data were objects:
my_json = (my_json).map { |hash| Hashie::Mash.new(hash) }
but that didn't work out
undefined method `where' for Array:0x007fd0bdeb84e0
How can I filter out data from a JSON string based on specific criteria or even SQL queries?
This simplest possible way would be to use Enumberable#select directly on the array of hashes:
require 'time'
myjson.select { |o| Time.iso8601(o["created_at"]).between?(start_date, end_date) }
If you want a fancy interface surrounding the raw data:
require 'time' # not needed in rails
class Order < Hashie::Mash
include Hashie::Extensions::Coercion
coerce_key :created_at, ->(v) do
return Time.iso8601(v)
end
end
class OrderCollection
def initialize(json)
#storage = json.map { |j| Order.new(j) }
end
def between(min, max)
#storage.select { |t| t.created_at.between?(min, max) }
end
end
orders = OrderCollection.new(myjson)
orders.between(Time.yesterday, Time.now)

TypeError: no implicit conversion of Symbol into Integer

I encounter a strange problem when trying to alter values from a Hash. I have the following setup:
myHash = {
company_name:"MyCompany",
street:"Mainstreet",
postcode:"1234",
city:"MyCity",
free_seats:"3"
}
def cleanup string
string.titleize
end
def format
output = Hash.new
myHash.each do |item|
item[:company_name] = cleanup(item[:company_name])
item[:street] = cleanup(item[:street])
output << item
end
end
When I execute this code I get: "TypeError: no implicit conversion of Symbol into Integer" although the output of item[:company_name] is the expected string. What am I doing wrong?
Your item variable holds Array instance (in [hash_key, hash_value] format), so it doesn't expect Symbol in [] method.
This is how you could do it using Hash#each:
def format(hash)
output = Hash.new
hash.each do |key, value|
output[key] = cleanup(value)
end
output
end
or, without this:
def format(hash)
output = hash.dup
output[:company_name] = cleanup(output[:company_name])
output[:street] = cleanup(output[:street])
output
end
This error shows up when you are treating an array or string as a Hash. In this line myHash.each do |item| you are assigning item to a two-element array [key, value], so item[:symbol] throws an error.
You probably meant this:
require 'active_support/core_ext' # for titleize
myHash = {company_name:"MyCompany", street:"Mainstreet", postcode:"1234", city:"MyCity", free_seats:"3"}
def cleanup string
string.titleize
end
def format(hash)
output = {}
output[:company_name] = cleanup(hash[:company_name])
output[:street] = cleanup(hash[:street])
output
end
format(myHash) # => {:company_name=>"My Company", :street=>"Mainstreet"}
Please read documentation on Hash#each
myHash.each{|item|..} is returning you array object for item iterative variable like the following :--
[:company_name, "MyCompany"]
[:street, "Mainstreet"]
[:postcode, "1234"]
[:city, "MyCity"]
[:free_seats, "3"]
You should do this:--
def format
output = Hash.new
myHash.each do |k, v|
output[k] = cleanup(v)
end
output
end
Ive come across this many times in my work, an easy work around that I found is to ask if the array element is a Hash by class.
if i.class == Hash
notation like i[:label] will work in this block and not throw that error
end

parsing json object and accessing a specific element

I am trying to parse this json object.
{"coord"=>{"lon"=>-0.13, "lat"=>51.51}, "sys"=>{"message"=>0.2063, "country"=>"GB", "sunrise"=>1387958729, "sunset"=>1387986980}, "weather"=>[{"id"=>801, "main"=>"Clouds", "description"=>"few clouds", "icon"=>"02n"}], "base"=>"gdps stations", "main"=>{"temp"=>276.49, "pressure"=>983, "temp_min"=>275.37, "temp_max"=>277.59, "humidity"=>95}, "wind"=>{"speed"=>0.51, "gust"=>1.54, "deg"=>229}, "rain"=>{"3h"=>0}, "clouds"=>{"all"=>24}, "dt"=>1387950679, "id"=>2643743, "name"=>"London", "cod"=>200}
Here is my code:
class WeatherController < ApplicationController
def index
require 'json'
response = HTTParty.get('http://api.openweathermap.org/data/2.5/weather?q=London,uk')
response.each do |key, value|
puts "key = #{key}, value = #{value}"
end
response.each do |item|
puts item
end
end
end
The two loops work and go through the object. In the item loop shouldn't I be able to do something like puts item.coord to just get that element?
I would use directly response['coord'] in your code, you don't need to do any looks. And you can check if its nil and handle this situation

Add extra filter parameters in RoR

Suppose I have a method in a controller:
def my_find(is_published, count)
items = Idea.where(published: is_published)
#......
end
Sometimes I want to pass some extra filter arguments
def my_find(is_published, count, some_extra_filter = nil)
items = Idea.where(published: is_published) #.where (some_extra_filter)
#......
end
where some_extra_filter can be lambda or just an plain sql "where" string and it can also be nil or "".
So how do I concatenate .where(published: is_published) with where (some_extra_filter) to get what I need?
This is actually very easy using scopes:
def my_find
#items = Idea.scoped
#items = #items.where(published: is_published) unless is_published.nil?
#items = #items.where(other: other_param) if other_params < 10
# etc, etc
end

Using a method while looping through an array in ruby

I am using ruby-aaws to return Amazon Products and I want to enter them into my DB. I have created a model Amazonproduct and I have created a method get_amazon_data to return an array with all the product information. When i define the specific element in the array ( e.g. to_a[0] ) and then use ruby-aaws item_attributes method, it returns the name I am searching for and saves it to my DB. I am trying to iterate through the array and still have the item_attributes method work. When i don't define the element, i get this error: undefined method `item_attributes' for #Array:0x7f012cae2d68
Here is the code in my controller.
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each { |name|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = #arr.item_attributes.title.to_s
}
EDIT: Code in my model to see if that helps:
class Amazonproduct < ActiveRecord::Base
def self.get_amazon_data(r)
resp = Amazon::AWS.item_search('GourmetFood', { 'Keywords' => 'Coffee Maker' })
items = resp.item_search_response.items.item
end
end
Thanks for any help/advice.
I'm not familiar with the Amazon API, but I do observe that #arr is an array. Arrays do not usually have methods like item_attributes, so you probably lost track of which object was which somewhere in the coding process. It happens ;)
Try moving that .item_attributes call onto the object that supports that method. Maybe amazonproduct.get_amazon_data(:r), before its being turned into an array with to_a, has that method?
It's not quite clear to me what your classes are doing but to use #each, you can do something like
hash = {}
[['name', 'Macbook'], ['price', 1000]].each do |sub_array|
hash[sub_array[0]] = sub_array[1]
end
which gives you a hash like
{ 'name' => 'Macbook', 'price' => 1000 }
This hash may be easier to work with
#product = Product.new
#product.name = hash[:name]
....
EDIT
Try
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each do |aws_object|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = aws_object.item_attributes.title.to_s
end
end

Resources