Converting an array with only one object in Ruby - ruby-on-rails

Say I have an array, and I use the keep_if or select methods to delete everything but one object for the array -- is there a way to take that object out of the array, so it's not an array any more?
For example, the way my models are set up, previous and current will only ever have one object.
def more_or_less?(project, current_day)
words = project.words
current = words.select {|w| w.wrote_on == current_day}
previous = words.select {|w| w.wrote_on == (current_day - 1.day)}
end
So, if I want to do a comparison, like if current.quantity > previous.quantity -- I've resorted to actually writing if current.last.quantity > previous.last.quantity but I'm assuming there's a more idiomatic way?

If you're deleting all but one entries, why not use find to choose the one thing you care about? Something like this:
def more_or_less?(project, current_day)
words = project.words
current = words.find { |w| w.wrote_on == current_day }
previous = words.find { |w| w.wrote_on == (current_day - 1.day) }
if current.quantity > previous.quantity
#...
end
end
If you're in ActiveRecord land, then you can use detect as derp says or to_a to get a plain array that you can use find on:
def more_or_less?(project, current_day)
words = project.words
current = words.detect { |w| w.wrote_on == current_day }
previous = words.to_a.find { |w| w.wrote_on == (current_day - 1.day) }
if current.quantity > previous.quantity
#...
end
end

detect is an alias for find, maybe that would work

Related

Ruby array reject elements based on condition

I am trying to reject array items based on multiple conditions.
The code is as follows
def fetch_items
items= line_items.reject(&driving?)
if new_order_history_enabled?
items = items.reject{ |li| li.expenses == 0 }
end
items
end
def driving?
proc { |line_item| LineItemType.new(line_item, segment).drive? }
end
Is there a one liner or a more cleaner way to write this?
Something like
items= line_items.reject { |li| li.driving? && ( new_order_history_enabled? && li.expenses == 0)}
items= line_items.reject { |li| li.driving? || (new_order_history_enabled? && li.expenses == 0)}
Since you want both to apply here, I think you should use || instead of &&
That way, you are actually doing what you describe in your method. (and you only iterate once over the array, which is cool :) )
Although, and this is stylistic preference. I would prefer to do:
items = line_items.reject do |li|
li.driving? ||
(new_order_history_enabled? && li.expenses == 0)
end
since it might be clearer at a glance what we are doing
Personally I don't think a one-liner is always cleaner, especially when it's a long one-liner. The style that (to me) is cleaner, is to write:
def fetch_items
items= line_items.reject(&:driving?)
items= items.reject(&:zero_expenses?) if new_order_history_enabled?
end
def driving?
proc { |line_item| LineItemType.new(line_item, segment).drive? }
end
# in the LineItem class, define the zero_expenses? method:
def zero_expenses?
expenses.zero?
end

Adding values to a hash within/over multiple each loops

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.

Is there a way I can use a rake task or method that can rewrite an HTML partial using erb?

Sorry for the confusing title, but I will elaborate here.
ok so on the users index page of my site I have a list of Top Trending songs. The list is ordered by user rankings and this list changes dynamically as each songs aggregate ranking changes relative to each other.
class SongratingsController < ApplicationController
#Top100 = Rails.cache.read('Top100')
lastSpot = #Top100.last
def reCalcTop100
#Top100 = Song.where('num_stars > ?', 0 ).order('num_stars desc, total_score desc').limit(100)
Rails.cache.fetch('Top100'){#Top100}
end
def addRatingToSong
userID = params[:uid].to_i
songId = params[:sid].to_i
rVal = params[:valR].to_i
#averageS = []
songRate = Songrating.find_by(:user_id => userID, :song_id => songId)
if songRate != nil
oldScore = songRate.rating
songRate.update_attributes(:rating => rVal)
#song = Song.find(songId)
score = #song.total_score - oldScore
newScore = score + rVal
averageScore = newScore/#song.songratings_count
#song.update_attributes(:total_score => newScore,:num_stars => averageScore)
#averageS[0] = averageScore
#averageS[1] = #song.songratings_count
else
Songrating.create!(:user_id => userID, :song_id => songId,:rating => rVal)
#song = Song.find(songId)
newScore = #song.total_score + rVal
averageScore = newScore/#song.songratings_count
#song.update_attributes(:total_score => newScore,:num_stars => averageScore)
#averageS[0] = averageScore
#averageS[1] = #song.songratings_count
end
if newScore > lastSpot.total_score && averageScore > lastSpot.num_stars
reCalcTop100
end
if request.xhr?
render :json => {
:starData => #averageS
}
end
end
end
As you can see in these two photos below I have a view partial that shows this list, but right now I have the list generated each time a user logs into the main page. But since this list is not unique to the user, I feel I am wasting time regenerating this list.
ideally I would like to generate and write a static HTML partial only when the top100 list changes, but I don't really know how to accomplish this.
thanks.
Yep just use erb
vars = OpenStruct.new({some_var: some_val})
rendered_html = ERB.new(File.read("#{Rails.root}/app/templates/embed_code.html.erb")).result(vars.instance_eval { binding })
This will put the rendered html in the rendered_html variable from there you can write it to a file or do anything you want. This should work in the context of a ruby class or rake task afaik.
The vars are passed to the template and can be used as <%= some_var %> in the template.
Now that i've answered you actual question, i think the better solution is to just use Rails.cache to cache the rendered data.
Anything that takes a long time can be cached with
result = Rails.cache.fetch "some_cache_key" do
# things you want to cache
end
this will cache the block and return it to result. if unstale cached data exisits in the future it will just return it from cache, if cache is empty or stale it will re-execute the block and return it into result.
Finally in the context fo a controller you can just use action caching which is a bit more hands off.
See: http://guides.rubyonrails.org/caching_with_rails.html for more details.

Put specific collection/array item on the last position in rails

I have a collection/array in rails, transformed to json it looks like this:
#collection = [{"order_number":"123","item":"Paper"},{"order_number":"567","item":"Ruler"},{"order_number":"344","item":"Pen"},{"order_number":"342","item":"Pencil"},{"order_number":"877","item":"Keyboard"}]
I would like to pick the item with the order_number "342" and put it at the last position of the collection, so the new collection looks like this:
#collection = [{"order_number":"123","item":"Paper"},{"order_number":"567","item":"Ruler"},{"order_number":"344","item":"Pen"},{"order_number":"877","item":"Keyboard"},{"order_number":"342","item":"Pencil"}]
In theory, it would look like this:
#collection.last = #collection[3]
but that is obviously not fancy ruby style nor would it re-sort the array as in my example.
Also I don't know the index of the item as it can change depending on what the user shops.
how about:
#collection << #collection.delete_at[#collection.index{|x| x[:order_number] == "342"}]
This basically searches the index of element with :order_number 342 first, uses that index to delete it, and then store the deleted element at the end again.
You can also use the partition method:
#collection = #collection.partition { |h| h['order_number'] != '342' }.flatten
Just split your collection on two (without 342 order and with 342 order), then just join them. It should looks like:
#collection = #collection.select {|e| e[:order_number] != '342' } + #collection.select {|e| e[:order_number] == '342' }
If you have an index of an item it boils down to
#collection << #collection.delete_at(3)
If you don't, you could try finding it using
#collection.find_index{ |el| el["order_number"] == "123" }
Alternative you can try this too:
> #collection.each_with_index{ |key,value| #collection.push(#collection.delete_at(value)) if key[:order_number] == "344" }
#=>[{:order_number=>"123", :item=>"Paper"}, {:order_number=>"567", :item=>"Ruler"}, {:order_number=>"342", :item=>"Pencil"}, {:order_number=>"877", :item=>"Keyboard"}, {:order_number=>"344", :item=>"Pen"}]

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.

Resources