Ruby - Splititng Array after getting data from Mysql - ruby-on-rails

I have the following code to fetch the data from MySQL database into my rails controller
#main = $connection.execute("SELECT * FROM builds WHERE platform_type IS NOT NULL")
This returns a mysql2 type object which behaves like an array i guess.
I want to split this into 2 arrays, first one where platform_type is 'TOTAL' and everything else in the other array.

It actually returns a Mysql2::Result object. Of course you can do
totals = []
others = []
main.each { |r|
(r['platform_type'] == 'TOTAL' ? totals : others) << r
}
but why not use a rails way with smth like:
Builds.where("platform_type = ?", 'TOTAL')
Builds.where("platform_type NOT IN ?", [nil, 'TOTAL'])

Try array.select. Something like
total = #main.select { |build| build.platform_type == 'TOTAL' }
not_total = #main.reject { |build| build.platform_type == 'TOTAL' }
http://matthewcarriere.com/2008/06/23/using-select-reject-collect-inject-and-detect/
Even better, use Enumerable.partition as per this answer: Ruby Select and Reject in one method

Related

How to calculate specific rating count hash in ruby on rails?

So, I have an after_save hook on review model which calls calculate_specific_rating function of product model. The function goes like this:
def calculate_specific_rating
ratings = reviews.reload.all.pluck(:rating)
specific_rating = Hash.new(0)
ratings.each { |rating| specific_rating[rating] += 1 }
self.specific_rating = specific_rating
save
end
Right now, it returns
specific_rating => {
"2"=> 3, "4"=> 1
}
I want it to return like:
specific_rating => {
"1"=> 0, "2"=>3, "3"=>0, "4"=>1, "5"=>0
}
Also, is it okay to initialize a new hash everytime a review is saved? I want some alternative. Thanks
You can create a range from 1 until the maximum value in ratings plus 1 and start iterating through it, yielding an array where the first element is the current one, and the second element is the total of times the current element is present in ratings. After everything the result is converted to a hash:
self.specific_rating = (1..ratings.max + 1).to_h { |e| [e.to_s, ratings.count(e)] }
save
You could also do something like this -
def calculate_specific_rating
ratings = [1,2,3,4,5]
existing_ratings = reviews.group_by(&:rating).map{|k,v| [k, v.count]}.to_h
Hash[(ratings - existing_ratings.keys).map {|x| [x, 0]}].merge(existing_ratings)
end
which gives
{3=>0, 4=>0, 5=>0, 2=>3, 1=>1}

How to group and count the last N results?

In Rails, you can use:
Model.group(:field).count
to yield something like:
{"a"=>7, "b"=>5, "c"=>3 "d"=>3, "e"=>4}
But how can I count ONLY in the last N lines, not the entire table, with the DATABASE doing the calculations?
Do not work:
Model.limit(100).group(:field).count
limit will limit the hash output keys not the table lines used
Model.last(100).group(:field).count
Last returns a Array and raises an error
I'm using:
* Ruby 2.3.3p222
* Rails 4.2.4
* pg 9.5.6
As you mentioned, the limit is being applied on the grouped instances, not the instances themselves. A simple workaround would be:
Model.where(id: Model.limit(100).select(:id)).group(:field).count
Array objects can also be grouped using group_by:
grouped = Model.last(100).group_by(&:field).map { |k,v| [k, v.length] }
This will return the following matrix:
#=> [["Field value 1", value_1_count], ["Field value 2", value_2_count], etc...]
Matrix can also be turned into hash:
grouped.each_with_object({}) { |value, memo| memo[value[0]] = value[1] }
To sum up, try the following:
Model.last(100)
.group_by(&:field)
.each_with_object({}) { |(key, value), memo| memo[key] = value.length }

Select certain record within response from ActiveRecord

I have a call to ActiveRecord in my controller as so:
#configurations = EmailConfiguration.where(customer_id: '1', email_template: '1')
This will return all EmailConfigurations that have the correct parameters. Each record has a field_id and a the_value. I want to display the value in the view:
#configurations.where(field_id: 1).the_value
What do I need to add to the view to select a certain record within the collection that is returned by the database?
You can use select for a quick filter on arrays
#configurations.select {|c| c.field_id == 1}
that will return all collections with field_id = 1. If you know there is only one, you could chain it for a direct output:
#configurations.select {|c| c.field_id == 1}.first.the_value
#configurations.where(field_id: 1)
returns a collection of objects(array) even if there is only one result. If you would like to show only one you can do as suggested above:
#configurations.select {|c| c.field_id == 1}.first.the_value
If you want to show all of the "the_values" you can do
field_1_configs = #configurations.select do |c| c.field_id == 1
end
field_1_configs.map{|config| config.the_value }

In ruby which is better, detect or index, to find an object in an array?

I have an array of objects.
I want to find an object in the array based on some property of the object.
I can do
array.detect {|x| x.name=="some name"}
or I could do
ind=array.index {|x| x.name=="some name"}
array[ind] unless ind.nil?
Is there any reason to choose one over the other?
If you aren't interested in finding the index value of the object you're searching for, I would suggest detect. It'll save you from having to do that nil check before accessing the array.
From a performance standpoint, I imagine it's relatively comparable, but that could help your decision too. That would require benchmarking as Niels B. mentioned in his comment.
If you want to find an element in a collection, it's important to use collections made for fast retrieval. Arrays are not made for that, nor are they particularly convenient unless you are making a stack or a queue.
Here's some code to show ways to improve the storage/retrieval speed over what you can get using find, detect or other normal array-based methods:
require 'fruity'
require 'digest'
class Foo
attr_reader :var1, :var2
def initialize(var1, var2)
#var1, #var2 = var1, var2
end
end
START_INT = 1
START_CHAR = 'a'
END_INT = 10
END_CHAR = 'z'
START_MD5 = Digest::MD5.hexdigest(START_INT.to_s + START_CHAR)
END_MD5 = Digest::MD5.hexdigest(END_INT.to_s + END_CHAR)
ary = []
hsh = {}
hsh2 = {}
START_INT.upto(END_INT) do |i|
(START_CHAR .. END_CHAR).each do |j|
foo = Foo.new(i, j)
ary << foo
hsh[[i, j]] = foo
hsh2[Digest::MD5.hexdigest(i.to_s + j)] = foo
end
end
compare do
array_find {
ary.find { |a| (a.var1 == START_INT) && (a.var2 == START_CHAR) }
ary.find { |a| (a.var1 == END_INT) && (a.var2 == END_CHAR) }
}
hash_access_with_array {
hsh[[START_INT, START_CHAR]]
hsh[[END_INT, END_CHAR]]
}
hash_access_with_digest {
hsh2[START_MD5]
hsh2[END_MD5]
}
end
Which results in:
Running each test 16384 times. Test will take about 17 seconds.
hash_access_with_digest is faster than hash_access_with_array by 10x ± 1.0
hash_access_with_array is faster than array_find by 16x ± 1.0
There are three different tests, and I'm looking for the first, and last elements in the array ary, and the corresponding objects in the hashes. The result of looking for the first and last elements in the array will be an average time for that search. For comparison I'm searching for the same objects in the hashes.
If we had some advance knowledge of which array index the object is in, retrieving the object from the array would be faster, but that's the problem, and making another container to keep track of that information would be slower than using the hash.
See for yourself!
require 'benchmark'
array = (1..1000000).to_a
Benchmark.bmbm do |x|
x.report("#index for 1") {
array.index(1)
}
x.report("#detect 1") {
array.detect { |i| i == 1 }
}
x.report("#index for 500k") {
array.index(500000)
}
x.report("#detect 500k") {
array.detect { |i| i == 500000 }
}
x.report("#index for 1m") {
array.index(1000000)
}
x.report("#detect 1m") {
array.detect { |i| i == 1000000 }
}
end
Put the code above in a file and execute it from the console with ruby <file>
Ignore the top block, that is rehearsal, the bottom block should look something like this:
user system total real
#index for 1 0.000005 0.000002 0.000007 ( 0.000004)
#detect 1 0.000007 0.000002 0.000009 ( 0.000006)
#index for 500k 0.003274 0.000049 0.003323 ( 0.003388)
#detect 500k 0.029870 0.000200 0.030070 ( 0.030872)
#index for 1m 0.005866 0.000009 0.005875 ( 0.005880)
#detect 1m 0.059819 0.000520 0.060339 ( 0.061340)
Running on my mac and Ruby 2.5.0, the numbers seem to suggest that #detect is an order of magnitude slower than #index.

Best way to analyse data using ruby

I would like to analyse data in my database to find out how many times certain words appear.
Ideally I would like a list of the top 20 words used in a particular column.
What would be the easiest way of going about this.
Create an autovivified hash and then loop through the rows populating the hash and incrementing the value each time you get the same key (word). Then sort the hash by value.
A word counter...
I wasn't sure if you were asking how to get rails to work on this or how to count words, but I went ahead and did a column-oriented ruby wordcounter anyway.
(BTW, at first I did try the autovivified hash, what a cool trick.)
# col: a column name or number
# strings: a String, Array of Strings, Array of Array of Strings, etc.
def count(col, *strings)
(#h ||= {})[col = col.to_s] ||= {}
[*strings].flatten.each { |s|
s.split.each { |s|
#h[col][s] ||= 0
#h[col][s] += 1
}
}
end
def formatOneCol a
limit = 2
a.sort { |e1,e2| e2[1]<=>e1[1] }.each { |results|
printf("%9d %s\n", results[1], results[0])
return unless (limit -= 1) > 0
}
end
def formatAllCols
#h.sort.each { |a|
printf("\n%9s\n", "Col " + a[0])
formatOneCol a[1]
}
end
count(1,"how now")
count(1,["how", "now", "brown"])
count(1,[["how", "now"], ["brown", "cow"]])
count(2,["you see", "see you",["how", "now"], ["brown", "cow"]])
count(2,["see", ["see", ["see"]]])
count("A_Name Instead","how now alpha alpha alpha")
formatAllCols
$ ruby count.rb
Col 1
3 how
3 now
Col 2
5 see
2 you
Col A_Name Instead
3 alpha
1 how
$
digitalross answer looks too verbose to me, also, as you tag ruby-on-rails and said you use DB.. i'm assuming you need an activerecord model so i'm giving you a full solution
in your model:
def self.top_strs(column_symbol, top_num)
h = Hash.new(0)
find(:all, :select => column_symbol).each do |obj|
obj.send(column_symbol).split.each do |word|
h[word] += 1
end
end
h.map.sort_by(&:second).reverse[0..top_num]
end
for example, model Comment, column body:
Comment.top_strs(:body, 20)

Resources