I'm working with the LinkedIn API to get companies' details. They are sending an XML response, so I simply converted the XML to a hash using the .to_hash method
This is a sample hash I'm getting: http://pastebin.com/1bXtHZ2F
in some companies they have more than one locations and contact information, i want to parse this data and get the details like phone number, city, postal_code etc.
The structure of the response is not consistent. Sometimes location field itself is missing or the postal_code is available only at the fourth location.
I tried two ways:
1.
def phone(locations)
(locations && locations["values"][0]["contactInfo"]["phone1"]) || nil
end
This is not working if the phone number is not available in the first array
2.
def phone(locations)
if locations["locations"]["total"].to_i == 1
locations["locations"]["location"]["contact_info"]["phone1"]
else
locations["locations"]["location"].each do |p|
if (!p["contact_info"]["phone1"].nil? || !p['contact_info'].nil?)
return p["contact_info"]["phone1"]
break
end
end
end
end
This is not working if the "location" hash itself is missing from the response. I need a solution where I can search with the keys "city", "phone" and "postal_code" and update if it is present. If it returns an array, parse the array and get the non-empty data.
I've also read this StackOverflow answer.
I see this as a question about code confidence. That is, I'm betting you can figure out how to guess your way through all the possible conditions... but that will create a mess of unconfident code. Confident code states what it wants and it gets it and moves on. (Note: I get all of my inspiration on this topic from this wonderful book: http://www.confidentruby.com/ by Avdi Grimm).
That said, I'd recommend the following.
Install the naught gem: https://github.com/avdi/naught
In your code, utilize the Maybe conversion function (read through the gem documetnation for info) to confidently arrive at your values:
At the top of your class or controller:
NullObject = Naught.build
include NullObject::Conversions
In your method:
def phone(locations)
return {} if locations["location"].blank?
Maybe(locations["locations"])["location"].to_a.inject({}) do |location, acc|
contact_info = Maybe(location["contact_info"])
acc[location][:city] = contact_info["city1"].to_s
acc[location][:phone] = contact_info["phone1"].to_i
acc[location][:postal_code] = contact_info["postal_code1"].to_s
acc
end
end
I'm not sure exactly what you're trying to accomplish but the above may be a start. It is simply attempting to assume all of the keys exist. Whether they do or they don't they get converted to a object (an array, a string or an integer). And then, ultimately, collected into a hash (call acc -- short for "accumulator" -- internal to the loop above) to be returned.
If any of the above needs clarification let me know and we can chat.
Ok, this code basically works through the hash and isn't concerned about node names (other than the specific nodes it's searching for)
the find_and_get_values method takes two arguments: object to search, and an array of nodes to find. It will only return a result if all nodes in the array are siblings under the same parent node. (so "city" and "postal_code" must be under the same parent otherwise neither is returned)
The data returned is a simple hash.
The get_values method takes one argument (the company hash) and calls find_and_get_values twice, once for %w(city postal_code) and once for %w(phone1) and merges the hash results into one hash.
def get_values(company)
answer = {}
answer.merge!(find_and_get_values(company["locations"], %w(city postal_code))
answer.merge!(find_and_get_values(company["locations"], ["phone1"]))
answer
end
def find_and_get_values(source, match_keys)
return {} if source.nil?
if source.kind_of?(Array)
source.each do |sub_source|
result = find_and_get_values(sub_source, match_keys)
return result unless result.empty?
end
else
result = {}
if source.kind_of?(Hash)
match_keys.each do |key|
result[key] = source[key] unless source[key].nil?
end
return result if result.count == match_keys.count
source.each do |sub_source|
result = find_and_get_values(sub_source, match_keys)
return result unless result.empty?
end
end
end
return {}
end
p get_values(company)
Related
I'm passing a hash to this function that either a) has keys that are strings along with values that are ints OR b) it is an empty hash.
The point of the function is to return nil if the hash is empty and return the key associated with the lowest int.
def key_for_min_value(name_hash)
if name_hash == nil
return nil
else
lowest_value = nil
lowest_value_name = nil
name_hash.collect do |name, value|
if lowest_value > value
lowest_value = value
lowest_value_name = name
end
end
return lowest_value_name
end
end
The error I'm receiving is:
1) smallest hash value does not call the `#keys` method
Failure/Error: key_for_min_value(hash)
NoMethodError:
undefined method `>' for nil:NilClass`
You can't compare nil to anything using >, it's not allowed, so you either have to avoid that test or use tools like min_by to get the right value instead of this collect approach.
One way to make your unit test happy might be:
def key_for_min_value(name_hash)
return unless (name_hash)
name_hash.keys.min_by do |key|
name_hash[key]
end
end
Ruby leans very heavily on the Enumerable library, there's a tool in there for nearly every job, so when you have some free time have a look around there, lots of things to discover.
Now Ruby is very strict about comparisons, and in particular a nil value can't be "compared" (e.g. > or < and such) to other values. You'll need to populate that minimum with the first value by default, not nil, then the comparisons work out, but doing that completely is pretty ugly:
def key_for_min_value(name_hash)
return unless (name_hash)
min_key, min_value = name_hash.first
name_hash.each do |key, value|
next unless (value < min_value)
min_key = key
min_value = value
end
min_key
end
So that approach is really not worth it. Enumerable makes it way easier and as a bonus your intent is clear. One thing you'll come to appreciate is that in Ruby if your code looks like code then you're probably going about it the wrong way, over-complicating things.
Ruby is an unusually expressive language, and often there's a very minimal form to express just about anything.
I have some code that is chugging through a set of Rails Active Record models, and setting an attribute based on a related value from a 2D Array.
I am essentially setting a US State abbreviation code in a table of US States which was previously only storing the full names. A library of state names is being used to derive the abbreviations, and it contains a 2D Array with each sub-array having a full name, and an abbreviation (i.e., [['New York', 'NY']['Pennsylvania', 'PA'][etc]]). I compare the state name from each record in the database to each full text name in this Array, then grab the corresponding sibling Array cell when there is a match.
This code works fine, and produces the correct results, but its frumpy looking and not easily understood without reading many lines:
# For the following code, StatesWithNames is an Active Record model, which is
# having a new column :code added to its table.
# Sates::USA represents a 2D Array as: [['StateName', 'NY']], and is used to
# populate the codes for StatesWithNames.
# A comparison is made between StatesWithNames.name and the text name found in
# States::USA, and if there is a match, the abbreviation from States::USA is
# used
if StatesWithNames.any?
StatesWithNames.all.each do |named_state|
if named_state.code.blank?
States::USA.each do |s|
if s[0] == named_state.name
named_state.update_column(:code, s[1])
break
end
end
end
end
end
What is the most Ruby style way of expressing assignments with logic like this? I experimented with a few different procs / blocks, but arrived at even cludgier expressions, or incorrect results. Is there a more simple way to express this in fewer lines and/or if-end conditionals?
Yea, there is a few ifs and checks, that are not needed.
Since it is Rails even though it does not state so in question's tags, you might want to use find_each, which is one of the most efficient way to iterate over a AR collection:
StatesWithNames.find_each do |named_state|
next unless named_state.code.blank?
States::USA.each do |s|
named_state.update_column(:code, s[1]) if s[0] == named_state.name
end
end
Also be aware, that update_column bypasses any validations, and if you wish to keep your objects valid, stick to update!.
And last thing - wrap it all in transaction, so if anything goes wrong all the way - it would rollback any changes.
StatesWithNames.transaction do
StatesWithNames.find_each do |named_state|
next unless named_state.code.blank?
States::USA.each do |s|
named_state.update!(:code, s[1]) if s[0] == named_state.name
end
end
end
You might use a different data structure for this.
With your existing 2D array, you can call to_h on it to get a Hash where
a = [['California', 'CA'], ['Oregon', 'OR']].to_h
=> { 'California' => 'CA', 'Oregon' => 'OR' }
Then in your code you can do
state_hash = States::USA.to_h
if StatesWithNames.any?
StatesWithNames.all.each do |named_state|
if named_state.code.blank?
abbreviation = state_hash[named_state.name]
if !abbreviation.nil?
named_state.update_column(:code, abbreviation)
end
end
end
end
the first thing you want to do is convert the lookup from an array of arrays to a hash.
state_hash = States::USA.to_h
if StatesWithNames.any?
StatesWithNames.all.select{|state| state.code.blank?}.each do |named_state|
named_state.update_column(:code, state_hash[named_state.name]) if state_hash[named_state.name]
end
end
Ok so I have an app that allows users to pull App Store data, specifically top free top paid etc. The various attributes are quite limited, but users can filter by category and country. So obviously this leads to a lot of repeated queries, now normally this wouldn't be a problem, but I also use this data with google api which has a credits system. So What I want to do is save these results in my database if the results are unique. I have this all set up and fine but my only hang up is how I determine if a query has been made before, so my solution is to make a hashtable that stores all queries that have been made before and if not NULL(nil) then I call the api to fetch the data then create a new record.
Issue is the App Store refreshes every day or so(not exactly sure the schedule but will look it up later). I would like to have this Hashtable reference function refresh or reset itself to all NULL at this interval.
What would be the most efficient or simple way to start a refresh for this? Additionally I am kinda new to rails, so where should I place this function? In the helper modules? Controller?
Thanks!
Edit:
ok so here is my HashTable helper module
module MapsHelper
queryHistoryLookUp = {}
i = 0
31.times do |i|
queryTableLookup.merge!(i =>[] )
end
def queryTableLookup(asciiNum, queryString)
if queryTableLookup[asciiNum % 31].size == 0
queryTableLookup[asciiNum % 31].push(queryString)
else
a = queryTableLookup[asciiNum % 31].size
arrayOfQueries = queryTableLookup[asciiNum % 31]
a.times do |i|
if arrayOfQueries[i] == queryString
return true
else
return false
end
end
end
end
end
def queryHash(query)
asciSum = 0
query.each_char do |i|
asciSum += i.sum
end
queryTableLookup(asciSum, query)
end
end
additionally I am kinda new to rails, can I interact with these functions using Javascript, since on the client side I create the string query.
In my opinion, your best bet would be to use the Rails cache system. It provides a method of caching data, with an optional expires_in time.
From the docs:
http://guides.rubyonrails.org/caching_with_rails.html#low-level-caching
class MyModel < ActiveRecord::Base
def self.get_api_data(key)
Rails.cache.fetch("my_model/api_data:#{key}", expires_in: 12.hours) do
SomeService::API.get_data(key)
end
end
end
In your hash (which I think it could exist in a class variable) you can store both the query and the last access datetime:
Suppose you have a hash as class variable to the Foo class with name cache and that the query variable is your current query that you want to check.
if Foo.cache[query].nil? || (DateTime.now - Foo.cache[query].last_fetch).to_i > 0
results = your_method_to_fetch_data_for(query)
Foo.cache[query] = {:results => results, :last_fetch => Datetime.now}
else
results = Foo.cache[query][:results]
end
I am using a recursive function.
def abc
get_course_to_be_deleted( courses, array_course_name, nil )
end
def self.get_course_to_be_deleted( courses, array_course_name, course )
if array_course_name.blank?
#### array_course_name = []
course
else
#### array_course_name = ['Science', 'Maths']
array_course_name.each do |course_name|
course = courses.detect{ |course| course.name == course_name }
course_names = array_course_name - [ course_name ]
get_course_to_be_deleted( course.children, course_names, course )
end
end
end
Tried .empty? its not working! array_course_name is always an array, assume a case i have three courses in array_course_names say [ 'Science', 'Botany', 'Zoology']. For the first time the course object will be Science object, course_names would be ['Botany', 'Zoology'], course.children would be botany object. as same it continues to execute the loop.At the last cycle array_course_names would be blank, course would be Zoology object, in that case i would like to return the found zoology object to calling function, but it is NOT getting returned to calling function instead it goes to else block with array_course_names as ['Botany', 'Zoology'] and which throws an error 'undefined method children for nil class' since there is no course exists. How to exit from recursive function when a condition is satisfied??
In your last line, where you perform the recursion, you are submitting an Array (formed by the line course_names = array_course_name - [ course_name ]) rather than a String. However, in your test of if array_course_name.blank?, you test as though you passed a String.
Either pass a String on the recursion or change your test to see if the Array is empty (or some other similar base case that meets your needs) instead of checking if a String is blank?.
This next bit is beyond the likely scope of your OP, but just in case it's an interest of yours: if you want to support both Arrays and Strings as the type of second parameter, you'll have to add .class/.kind_of? support for that.
Given this model:
class User < ActiveRecord::Base
has_many :things
end
Then we can do this::
#user = User.find(123)
#user.things.find_each{ |t| print t.name }
#user.thing_ids.each{ |id| print id }
There are a large number of #user.things and I want to iterate through only their ids in batches, like with find_each. Is there a handy way to do this?
The goal is to:
not load the entire thing_ids array into memory at once
still only load arrays of thing_ids, and not instantiate a Thing for each id
Rails 5 introduced in_batches method, which yields a relation and uses pluck(primary_key) internally. And we can make use of the where_values_hash method of the relation in order to retrieve already-plucked ids:
#user.things.in_batches { |batch_rel| p batch_rel.where_values_hash['id'] }
Note that in_batches has order and limit restrictions similar to find_each.
This approach is a bit hacky since it depends on the internal implementation of in_batches and will fail if in_batches stops plucking ids in the future. A non-hacky method would be batch_rel.pluck(:id), but this runs the same pluck query twice.
You can try something like below, the each slice will take 4 elements at a time and them you can loop around the 4
#user.thing_ids.each_slice(4) do |batch|
batch.each do |id|
puts id
end
end
It is, unfortunately, not a one-liner or helper that will allow you to do this, so instead:
limit = 1000
offset = 0
loop do
batch = #user.things.limit(limit).offset(offset).pluck(:id)
batch.each { |id| puts id }
break if batch.count < limit
offset += limit
end
UPDATE Final EDIT:
I have updated my answer after reviewing your updated question (not sure why you would downvote after I backed up my answer with source code to prove it...but I don't hold grudges :)
Here is my solution, tested and working, so you can accept this as the answer if it pleases you.
Below, I have extended ActiveRecord::Relation, overriding the find_in_batches method to accept one additional option, :relation. When set to true, it will return the activerecord relation to your block, so you can then use your desired method 'pluck' to get only the ids of the target query.
#put this file in your lib directory:
#active_record_extension.rb
module ARAExtension
extend ActiveSupport::Concern
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size, :relation)
relation = self
start = options[:start]
batch_size = options[:batch_size] || 1000
unless block_given?
return to_enum(:find_in_batches, options) do
total = start ? where(table[primary_key].gteq(start)).size : size
(total - 1).div(batch_size) + 1
end
end
if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
relation = relation.reorder(batch_order).limit(batch_size)
records = start ? relation.where(table[primary_key].gteq(start)) : relation
records = records.to_a unless options[:relation]
while records.any?
records_size = records.size
primary_key_offset = records.last.id
raise "Primary key not included in the custom select clause" unless primary_key_offset
yield records
break if records_size < batch_size
records = relation.where(table[primary_key].gt(primary_key_offset))
records = records.to_a unless options[:relation]
end
end
end
ActiveRecord::Relation.send(:include, ARAExtension)
here is the initializer
#put this file in config/initializers directory:
#extensions.rb
require "active_record_extension"
Originally, this method forced a conversion of the relation to an array of activrecord objects and returned it to you. Now, I optionally allow you to return the query before the conversion to the array happens. Here is an example of how to use it:
#user.things.find_in_batches(:batch_size=>10, :relation=>true).each do |batch_query|
# do any kind of further querying/filtering/mapping that you want
# show that this is actually an activerecord relation, not an array of AR objects
puts batch_query.to_sql
# add more conditions to this query, this is just an example
batch_query = batch_query.where(:color=>"blue")
# pluck just the ids
puts batch_query.pluck(:id)
end
Ultimately, if you don't like any of the answers given on an SO post, you can roll-your-own solution. Consider only downvoting when an answer is either way off topic or not helpful in any way. We are all just trying to help. Downvoting an answer that has source code to prove it will only deter others from trying to help you.
Previous EDIT
In response to your comment (because my comment would not fit):
calling
thing_ids
internally uses
pluck
pluck internally uses
select_all
...which instantiates an activerecord Result
Previous 2nd EDIT:
This line of code within pluck returns an activerecord Result:
....
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
...
I just stepped through the source code for you. Using select_all will save you some memory, but in the end, an activerecord Result was still created and mapped over even when you are using the pluck method.
I would use something like this:
User.things.find_each(batch_size: 1000).map(&:id)
This will give you an array of the ids.