How to "shift" objects from ActiveRecord array - ruby-on-rails

I have this method
def gen_events(score)
events = location.events
(1..rand(5..7)).each do |n|
random = rand(0.139..1).round(3)
rarity = Character.get_rarity(random)
event = events.where(rarity: rarity).shuffle.shift #<-- HERE
self.events << event
end
end
Currently, the shift method only grabs the first element, but doesn't remove it, how can I go about making it so that it does both?

This is not an array: events.where(rarity: rarity), this is an ActiveRecord scope, you can't remove things from it without destroying and erasing them from database. Instead, you should keep an array of object you already found, and use it to filter future results:
def gen_events(score)
events = location.events
new_events = []
(1..rand(5..7)).each do |n|
random = rand(0.139..1).round(3)
rarity = Character.get_rarity(random)
event = events.where(rarity: rarity).where.not(id: new_events.map(&:id).sample
new_events << event
end
self.events << new_events
end

Related

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.

Order model objects by an attr_accessor

I thought that attr_accessor has the same behavior as the other when I have to sort a list of objects, but it seems that is different:
dataRecords = MyData.where("day = ?", Time.now.yesterday.strftime("%Y%m%d").to_i)
dataRecords.each do |data|
data.accessor_var = func(data.x, data.y)
end
#sortedData = dataRecords.order('accessor_var DESC')
but #sortedData is not being sorted...
You need to keep in mind that when you apply a scope or order to an ActiveRecord::Relation the data is reloaded from the table. This means that when you loop through them and change an attribute, unless you save the result the changes will not be available to the next scope call.
You can use sort_by instead which will work on the objects in memory rather than the database.
Option 1: Save as you loop (probably not much use with an accessor!)
dataRecords = MyData.where("day = ?", Time.now.yesterday.strftime("%Y%m%d").to_i)
dataRecords.each do |data|
data.accessor_var = func(data.x, data.y)
data.save
end
#sortedData = dataRecords.order('accessor_var DESC') # Reload from table will include the saved values.
Option 2: sort_by
dataRecords = MyData.where("day = ?", Time.now.yesterday.strftime("%Y%m%d").to_i)
dataRecords.each do |data|
data.accessor_var = func(data.x, data.y)
end
#sortedData = dataRecords.sort_by{|data| data.accessor_var}
Also, toro2k has some nice optimisation for your sorting once you understand the situation.
It doesn't work because accessor_var is not a column in the database. You can use the method sort_by:
dataRecords.each { ... }
#sortedData = dataRecords.sort_by(&:accessor_var).reverse
Or, to save an interation over dataRecords:
#sortedData = dataRecords.sort_by { |data| data.accessor_var = func(data.x, data.y) }.reverse

rails model action check

I have Coupon model and in this model file I have a suitable_for_use method.I want to list Coupons if coupon.suitable_for_use == true.Is there any short way to do this ? I wrote this code but it doesn't work.
#coupons = []
coupons = Coupon.all.each do |coupon|
if coupon.suitable_for_use
#coupons << coupon
end
end
#coupons = coupons
suitable_for_use method
def suitable_for_use
result = true
if is_used?
result = false
elsif self.start > Time.now.in_time_zone
result = false
elsif self.end < Time.now.in_time_zone
result = false
end
return result
end
The problem is your assigning twice to #coupons. The return value from each is the collection it was given. So your last line reassigns the original set of coupons returned by Coupon.all.
#coupons = Coupon.all.select(&:suitable_for_use)
If your not sure what that does, here's the expanded version.
#coupons = Coupon.all.select {|coupon| coupon.suitable_for_select}
Basically, select takes a block that it will iterate over and if the block returns true then it will add that element to the returned collection. So any coupon that returns false will not be returned by select.
The &:suitable_for_use is called a symbol to proc. It literally expands to the block in the second line and is pretty common in ruby one-liners.

Push a hash into an array in a loop rails

I am trying to add hashes to an array whilst iterating through an each loop. Here's my controller code: the line I'm struggling with is setting the #royaltiesbychannel variable in the each loop:
def royalty(isbn)
sales_hash_by_channel = Sale.find_all_by_isbn_id(#isbn).group_by(&:channel_id)
sales_hash_by_channel.each do |ch_id, sale_array|
#royaltiesbychannel = Array.new()
value_total_by_channel = sale_array.sum(&:value)
quantity_total_by_channel = sale_array.sum(&:quantity)
#isbn.rules.each do |rule|
next unless rule.channel_id == ch_id
case quantity_total_by_channel
when 0..5000
#royaltiesbychannel = #royaltiesbychannel << {ch_id => value_total_by_channel * 0.5}
# (some other case-when statements)
end
end
end
In the console, when I set the ch_id and the value to something new and push the new values into the array:
#royaltiesbychannel = #royaltiesbychannel << {ch_id => value_total_by_channel * 0.5}
I get a nice array of hashes:
[{1=>100000.0}, {2=>3000.0}]
However, when I do #royaltiesbychannel.inspect in the view, I get just one key-value pair:
[{2=>3000.0}]
For ref:
#royaltiesbychannel.class = Array
#royaltiesbychannel.class = 1
#sales_hash_by_channel.class = Hash
#sales_hash_by_channel.size = 2
#isbn.rules.size = 4
So it looks like the push into the array is overwriting rather than adding. What am I doing wrong? Have I completely missed the point on how loops and .push work? Many thanks in advance.
You are initializing the array within the loop:
#royaltiesbychannel = Array.new()
It is being re-initialized each time, therefore you get only one result. Move it outside the each loop.
Your #royaltiesbychannel initialization is inside the first loop, so every time it starts that loop again it empties the array. Move it outside the loop and you should get the result you want.
def royalty(isbn)
#royaltiesbychannel = Array.new()
sales_hash_by_channel = Sale.find_all_by_isbn_id(#isbn).group_by(&:channel_id)
sales_hash_by_channel.each do |ch_id, sale_array|
value_total_by_channel = sale_array.sum(&:value)
quantity_total_by_channel = sale_array.sum(&:quantity)
#isbn.rules.each do |rule|
next unless rule.channel_id == ch_id
case quantity_total_by_channel
when 0..5000
#royaltiesbychannel = #royaltiesbychannel << {ch_id => value_total_by_channel * 0.5}
# (some other case-when statements)
end
end
end
You're setting #royaltiesbychannel to a new Array object during each iteration over sales_hash_by_channel should you be instead initialising it once outside of that loop instead?

Ruby on rails save data inside loop

object.each do |info|
#user_work_history.fb_user_id = facebook_session.user.id
#user_work_history.city = info.location.city
#user_work_history.country = info.location.state
#user_work_history.company_name = info.company_name
#user_work_history.description = info.description
#user_work_history.start_date = info.start_date
#user_work_history.end_date = info.end_date
#user_work_history.position = info.position
#user_work_history.updated_at = Time.now
#user_work_history.save
end
here is the code but inside loop data is not saving only one record is saved.
I believe you have to add
#user_work_history = UserWorkHistory.new
to the first line inside the loop. (or whatever the model is)
When you do that kind of loop you need to change the reference to the instance variable #user_work_history. What your loop does is that for each iteration it updates the state of the same object!
What you should do, is to set the #user_work_history before each iteration, like this:
object.each do |info|
#user_work_history = UserWorkHistory.new(:fb_user_id => facebook_session.user.id) # for example
#user_work_history.city = info.location.city
#user_work_history.country = info.location.state
(...)
#user_work_history.save # here it saves new tuple
end

Resources