I'm fetching rss data from a news API. However, sometimes the entries have certain fields such as image or summary, and sometimes they do not. How can I check if the object is empty before calling it?
url = "http://rss.cnn.com/rss/cnn_topstories.rss"
feed = Feedjira::Feed.fetch_and_parse url
api_json = JSON.parse(feed.to_json)
data_array = []
feed.entries.each_with_index do |entry,index|
data_array << { title: entry.title, link: entry.url, image_url: entry.image, summary: entry.summary }.as_json
end
In the above code, sometimes entry.image and entry.summary are empty and it returns an error such as:
undefined method `image' for #<Feedjira::Parser::ITunesRSSItem:0x007fb25c452688>
Current Attempt:
One obvious way is to check every object before saving it as a variable. But is this the best approach?
if entry.image.exists?
image = entry.image
else
image = ""
end
if entry.summary.exists?
summary = entry.summary
else
summary = ""
end
Use try(), it will return nil if the associated property is missing
entry.try(:image)
In your code, you can do like this
data_array << { title: entry.try(:title), link: entry.try(:url), image_url: entry.try(:image), summary: entry.try(:summary) }.as_json
Hope this helps!
If you are using Ruby 2.3.0 or higher you can make use of the brand new Safe Navigation Operator:
entry&.image # same thing as entry.try(:image) but shorter
You can even navigate as deep as you want, e.g.:
entry&.image&.urls&.small # Works as a charm even if "urls" is not present
Related
In an API processing a large number of attributes, the following pattern is frequent
if !article[:ingredients].nil?
clean_ingredients = article[:ingredients].tr('*$+!##Â', ' ')
ingredients = clean_ingredients.downcase.capitalize
else
ingredients = nil
end
for a JSON string as: { "id": "YYYYYY", "article": [ { "ingredients": "long string", [...]
Unfortunately, a method defined as
def empty_and_clean(array_element, element_attribute)
if !array_element[:element_attribute].nil?
clean_ingredients = array_element[:element_attribute].tr('*$+!##Â', ' ')
ingredients = clean_ingredients.downcase.capitalize
else
ingredients = nil
end
end
cannot be called in the method as empty_and_clean(article, ingredients) as it returns
undefined local variable or method 'ingredients'
What syntax allows to refactor this pattern?
You can call your empty_and_clean method this way:
empty_and_clean(article, :ingredients)
Just modify empty_and_clean to use element_attribute directly rather than the symbol :element_attribute.
I suggest you read more about symbols in Ruby to understand how this works.
Also, array_element is a misleading name because it is an array, not an element of an array. array would be slightly better, but is still too generic. Maybe objects or something else that describes what is actually in the array.
I have a concept called snapshot which basically stores a snapshot of how data looked at a certain period of time. What I'm building is a method that loops through the snapshots for each events, and builds a small hash outlining the ownership over time for a given shareholder.
def fetch_ownership_over_time(shareholder, captable)
#shareholder = Shareholder.find(shareholder.id)
#captable = Captable.find(captable.id)
#company = #captable.company.id
#ownership_over_time = []
#captable.events.collect(&:snapshot).each do |snapshot|
parsed_snapshot = JSON.parse(snapshot)
#ownership_over_time.push(parsed_snapshot["event"]["name"])
#ownership_over_time.push(parsed_snapshot["event"]["date"])
parsed_snapshot["shareholders"].each do |shareholder|
if shareholder["id"] == #shareholder.id
#ownership_over_time.push(shareholder["ownership_percentage"])
end
end
end
return #ownership_over_time
end
I then call this method in my view which successfully retrieves the correct values however they are not structured in any way:
["Event 1 ", "2018-11-19", "0.666666666666667", "Event 2 ", "2018-11-19", "0.333333333333333", "4th event ", "2018-11-19", "0.315789473684211"]
What I'd like to do now though is construct my hash so that each separate snapshot event contains a name, date and ownership_percentage.
Perhaps something like this:
ownership_over_time = [
{
event_name = "Event 1" #parsed_snapshot["event"]["name"]
event_date = "20180202" #parsed_snapshot["event"]["date"]
ownership_percentage = 0.37 #shareholder["ownership_percentage"]
},
{
event_name = "Event 2" #parsed_snapshot["event"]["name"]
event_date = "20180501" #parsed_snapshot["event"]["date"]
ownership_percentage = 0.60 #shareholder["ownership_percentage"]
}
]
My challenge though is that the ["event"]["name"] an ["event"]["date"] attributes I need to fetch when looping over my snapshots i.e. the first loop (.each do |snapshot|) whereas I get my ownership_percentage when looping over shareholders - the second loop (.each do |shareholder|).
So my question is - how can I build this hash in "two" places so I can return the hash with the 3 attributes?
Appreciative of guidance/help - thank you!
You have to create a new hash for the object and append that hash to the array of objects you are creating.
def fetch_ownership_over_time(shareholder, captable)
#shareholder = Shareholder.find(shareholder.id)
#captable = Captable.find(captable.id)
#company = #captable.company.id
#ownership_over_time = []
#captable.events.collect(&:snapshot).each do |snapshot|
parsed_snapshot = JSON.parse(snapshot)
shareholder = parsed_snapshot['shareholders'].select { |s| s['id'] == #shareholder.id }.first
local_snapshot = {
'event_name' => parsed_snapshot['event']['name'],
'event_date' => parsed_snapshot['event']['date'],
'ownership_percentage' => shareholder.try(:[], "ownership_percentage") || 0
}
#ownership_over_time.push local_snapshot
end
return #ownership_over_time
end
Notice that I changed your second loop to a select. As you currently have it, you risk on pushing two percentages if the id is found twice.
EDIT:
Added functionality to use a default value if no shareholder is found.
I want to make an array of hashes. But the problem is after first iteration when code goes to next line then it directly replaces the content of array.
#item_name =[]
item = {}
#invoiceinfo.each do |invoice|
item[:name] = Invoiceinfo.find(#invoiceinfo.id).item.name
item[:desc] = Invoiceinfo.find(#invoiceinfo.id).desc
item[:unit_price] = Invoiceinfo.find(#invoiceinfo.id).unit_price
byebug
#item_name.push (item)
end
This is what i am getting
after first iteration suppose i have this data
#item_name = [{:name=>"usman", :desc=>"sample ", :unit_price=>100}]
As soon as next line is executed it directly changes #item_name(name variable)
After executing item[:name] = Invoiceinfo.find(#invoiceinfo.id).item.name
the content of the #item_name is changed
#item_name = [{:name=>"next_name", :desc=>"sample ", :unit_price=>100}]
Any help would be appreciated.
Thannks
Try something like this
#item_name = []
#invoiceinfo.each do |invoice|
invoice_info = Invoiceinfo.find(#invoiceinfo.id)
item = {}
item[:name] = invoice_info.item.name
item[:desc] = invoice_info.desc
item[:unit_price] = invoice_info.unit_price
#item_name.push(item)
end
If you consider using ruby paradigms and best practices in ruby code, this mistake won’t happen in the future.
#item_name = #invoiceinfo.each_with_object([]) do |invoice, acc|
invoice_info = Invoiceinfo.find(#invoiceinfo.id)
acc.push(
name: invoice_info.item.name,
desc: invoice_info.desc
unit_price: invoice_info.unit_price
)
end
I have the following nested hash (from Ominauth-Facebook) captured in an object called myAuth
<Ominauth::AuthHash credentials
extra=#<Hashie:: Mash
raw_info=#<Hashie::Mash email="myemail#gmail.com">>>
I would like to extract email, so I use:
myAuth['extra']['raw_info']['email']
However, I would like to search the entire hash and get the value for key email without knowing exact hash structure. How should I go about it?
Thank you.
Don't know if this is the best solution, but i would do:
h = {seal: 5, test: 3, answer: { nested: "damn", something: { email: "yay!" } } }
def search_hash(h, search)
return h[search] if h.fetch(search, false)
h.keys.each do |k|
answer = search_hash(h[k], search) if h[k].is_a? Hash
return answer if answer
end
false
end
puts search_hash(h, :email)
This will return the value if the key exists or false.
I have an array (coming from a file_field, :multiple => true) in my params that I want to turn into a hash so I can build associated models for each element and process in my create action.
Currently receiving:
{"gallery"=>{"name"=>"A Gallery", "photos_attributes"=>{"0"=>{"image"=>[#<1st Image data removed for brevity>, #<2nd Image data removed for brevity>]}}}, "commit"=>"Save"}
I'd like to turn it into something like:
{"gallery"=>{"name"=>"A Gallery", "photos_attributes"=>{"0"=>{"image"=>#<1st Image data removed for brevity>}, "1"=>{"image"=>#<1st Image data removed for brevity>}}}, "commit"=>"Save"}
considered something like this but it's clearly wrong:
i = 0
params[:gallery][:photos_attributes]["0"][:image].reduce({}) do |result, element|
result[i++.to_s] = element
end
What's the "Rail's Way"?
You need to return the result hash at the end of each iteration.
i = 0
params[:gallery][:photos_attributes]["0"][:image].reduce({}) do |result, element|
result[(i += 1).to_s] = element
result
end
I've done something similar when receiving data from an iOS device. But, if I understand what you want and what your model(s) look like, to get nested attributes to work you don't want it to look like:
{ "photos_attributes" => { "0" => <image1>, "1" => <image2>, ... }
You want it to look like:
{ "photos_attributes" => [ <image1>, <image2>, ... ] }
And to do that all you need to do is:
params["gallery"]["photos_attributes"] = params["gallery"]["photos_attributes"]["0"]["image"]
Now, if I've misunderstood what you need, to get what you've asked for what you have might work (I don't use much reduce aka inject) or you could use tap:
i = 0
params["gallery"]["photos_attributes"] = {}.tap do |hash|
params["gallery"]["photos_attributes"]["0"]["image"].each do |image|
hash[i.to_s] = image
i = i + 1
end
end
Not a whole lot better IMO.