Call a variable from another model in Ruby on Rails - ruby-on-rails

I'd like to ask a little question. It's rather trivial I guess but I might just look for the wrong solutions to my issue so I cant get it working.
I have a model called request.rb which has a method called self.dates
def self.dates
from_date = Request.last.date.to_date
to_date = Request.last.todate.to_date
weekdays = (from_date..to_date).select { |d| (1..5).include?(d.wday)}.count
weekend_days = (from_date..to_date).select { |d| [0, 6].include?(d.wday)}.count
end
I have another model called hotels.rb where I'd like to call the variables weekdays and weekend_days for the price calculation.
def self.best_deal_nm
weekday_nm_tot = (Request.dates.weekdays * pricea.wdpricenm) + (Request.dates.weekdays * pricea.wepricenm)
weekend_nm_tot = (Request.dates.weekend_days * priceb.wdpricenm) + (Request.dates.weekend_days * priceb.wepricenm)
[weekday_nm_tot, weekend_nm_tot].min
end
Unfortunately the code above doesnt work. My question is therefore how can I possibly call these two variables in my hotel model.
Thanks a lot for help

Just return all info in last line into your self.dates method like
def self.dates
from_date = Request.last.date.to_date
to_date = Request.last.todate.to_date
weekdays = (from_date..to_date).select { |d| (1..5).include?(d.wday)}.count
weekend_days = (from_date..to_date).select { |d| [0, 6].include?(d.wday)}.count
{'from_date' => from_date, 'to_date' => to_date, 'weekdays' => weekdays, 'weekend_days' => weekend_days}
end
After call Request.dates from hotels.rb you could access to all variables added to hash.
weekday_nm_tot = (Request.dates['weekdays'] * pricea.wdpricenm) + (Request.dates['weekdays'] * pricea.wepricenm)

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.

Finding the months between two dates in rails

I can currently set a time range like so:
start_date: "2018-09-11"
end_date: "2018-11-19"
How can I do this for start to end of months? Examples:
time_range = ["2018-09-11".."2018-09-30"]
time_range = ["2018-10-01".."2018-10-31"]
time_range = ["2018-11-01".."2018-11-19"]
I'm not sure what's exactly your desired outcome but, given start date and end date as Date objects, you can perform
(start_date..end_date).to_a.group_by(&:month).values
and at the end what you get is a three element array, and each element contains an array with all the dates in that range for a month
I do not know if I understand very well what you asked, but I'll try to help you.
The Date class has several methods that will help you to work with dates.
Date < Object
Examples
my_date_range_array = [Date.today.beginning_of_year..Date.today.end_of_year]
my_date_time_range_array = [Time.now.beginning_of_year..Time.now.end_of_year]
my_date_range_array = [6.months.ago..Date.today]
YourModel.where date: Date.today.beginning_of_month..Date.today
YourModel.where date: 6.months.ago..Date.today
If you need every single date in the range, you can use something like this:
(Date.today.beginning_of_year..Date.today.end_of_year).map{ |date| date }
I hope that my answer helps you
This is a pure Ruby solution, but I believe (though I don't know Rails) it can be simplified slightly by replacing my methods first_day_of_month and first_day_of_month with Rails methods beginning_of_month and end_of_month, respectively. I designed the method for efficiency over simplicity.
require 'date'
DATE_FMT = "%Y-%m-%d"
def date_ranges(start_date_str, end_date_str)
start_date = Date.strptime(start_date_str, DATE_FMT)
end_date = Date.strptime(end_date_str, DATE_FMT)
return [start_date_str..end_date_str] if
[start_date.year, start_date.month] == [end_date.year, end_date.month]
d = start_date
ranges = [start_date_str..last_day_of_month(d)]
loop do
d = d >> 1
break if [d.year, d.month] == [end_date.year, end_date.month]
ranges << (first_day_of_month(d)..last_day_of_month(d))
end
ranges << (first_day_of_month(d)..end_date_str)
end
def first_day_of_month(d)
(d - d.day + 1).strftime(DATE_FMT)
end
def last_day_of_month(d)
((d >> 1)-d.day).strftime(DATE_FMT)
end
date_ranges("2018-09-11", "2019-02-11")
#=> ["2018-09-11".."2018-09-30", "2018-10-01".."2018-10-31",
# "2018-11-01".."2018-11-30", "2018-12-01".."2018-12-31",
# "2019-01-01".."2019-01-31", "2019-02-01".."2019-02-11"]
date_ranges("2018-09-08", "2018-09-23")
#=> ["2018-09-08".."2018-09-23"]
With the information provided by the OP, this is what I understand he is looking for.
Given a set range for example:
time_range = "2018-09-11".."2018-09-19"
new_range_min = time_range.min.to_date.beginning_of_month
new_range_max = time_range.max.to_date.end_of_month
new_range = new_range_min..new_range_max

Nested ActiveRecords: Find many childrens of many parents

In my Rails 3.2 app a Connector has_many Incidents.
To get all incidents of a certain connector I can do this:
(In console)
c = Connector.find(1) # c.class is Connector(id: integer, name: string, ...
i = c.incidents.all # all good, lists incidents of c
But how can I get all incidents of many connectors?
c = Connector.find(1,2) # works fine, but c.class is Array
i = c.incidents.all #=> NoMethodError: undefined method `incidents' for #<Array:0x4cc15e0>
Should be easy! But I don't get it!
Here’s the complete code in my statistics_controller.rb
class StatisticsController < ApplicationController
def index
#connectors = Connector.scoped
if params['connector_tokens']
logger.debug "Following tokens are given: #{params['connector_tokens']}"
#connectors = #connectors.find_all_by_name(params[:connector_tokens].split(','))
end
#start_at = params[:start_at] || 4.weeks.ago.beginning_of_week
#end_at = params[:end_at] || Time.now
##time_line_data = Incident.time_line_data( #start_at, #end_at, 10) #=> That works, but doesn’t limit the result to given connectors
#time_line_data = #connectors.incidents.time_line_data( #start_at, #end_at, 10) #=> undefined method `incidents' for #<ActiveRecord::Relation:0x3f643c8>
respond_to do |format|
format.html # index.html.haml
end
end
end
Edit with reference to first 3 answers below:
Great! With code below I get an array with all incidents of given connectors.
c = Connector.find(1,2)
i = c.map(&:incidents.all).flatten
But idealy I'd like to get an Active Records object instead of the array, because I'd like to call where() on it as you can see in methode time_line_data below.
I could reach my goal with the array, but I would need to change the whole strategy...
This is my time_line_data() in Incidents Model models/incidents.rb
def self.time_line_data(start_at = 8.weeks.ago, end_at = Time.now, lim = 10)
total = {}
rickshaw = []
arr = []
inc = where(created_at: start_at.to_time.beginning_of_day..end_at.to_time.end_of_day)
# create a hash, number of incidents per day, with day as key
inc.each do |i|
if total[i.created_at.to_date].to_i > 0
total[i.created_at.to_date] += 1
else
total[i.created_at.to_date] = 1
end
end
# create a hash with all days in given timeframe, number of incidents per day, date as key and 0 as value if no incident is in database for this day
(start_at.to_date..end_at.to_date).each do |date|
js_timestamp = date.to_time.to_i
if total[date].to_i > 0
arr.push([js_timestamp, total[date]])
rickshaw.push({x: js_timestamp, y: total[date]})
else
arr.push([js_timestamp, 0])
rickshaw.push({x: js_timestamp, y: 0})
end
end
{ :start_at => start_at,
:end_at => end_at,
:series => rickshaw #arr
}
end
As you only seem to be interested in the time line data you can further expand the map examples given before e.g.:
#time_line_data = #connectors.map do |connector|
connector.incidents.map do |incident|
incident.time_line_data(#start_at, #end_at, 10)
end
end
This will map/collect all the return values of the time_line_data method call on all the incidents in the collection of connectors.
Ref:- map
c = Connector.find(1,2)
i = c.map(&:incidents.all).flatten

Using scope to return results within multiple DateTime ranges in ActiveRecord

I've got a Session model that has a :created_at date and a :start_time date, both stored in the database as :time. I'm currently spitting out a bunch of results on an enormous table and allowing users to filter results by a single date and an optional range of time using scopes, like so:
class Session < ActiveRecord::Base
...
scope :filter_by_date, lambda { |date|
date = date.split(",")[0]
where(:created_at =>
DateTime.strptime(date, '%m/%d/%Y')..DateTime.strptime(date, '%m/%d/%Y').end_of_day
)
}
scope :filter_by_time, lambda { |date, time|
to = time[:to]
from = time[:from]
where(:start_time =>
DateTime.strptime("#{date} #{from[:digits]} #{from[:meridian]}", '%m/%d/%Y %r')..
DateTime.strptime("#{date} #{to[:digits]} #{to[:meridian]}", '%m/%d/%Y %r')
)
}
end
The controller looks more or less like this:
class SessionController < ApplicationController
def index
if params.include?(:date) ||
params.include?(:time) &&
( params[:time][:from][:digits].present? && params[:time][:to][:digits].present? )
i = Session.scoped
i = i.filter_by_date(params[:date]) unless params[:date].blank?
i = i.filter_by_time(params[:date], params[:time]) unless params[:time].blank? || params[:time][:from][:digits].blank? || params[:time][:to][:digits].blank?
#items = i
#items.sort_by! &params[:sort].to_sym if params[:sort].present?
else
#items = Session.find(:all, :order => :created_at)
end
end
end
I need to allow users to filter results using multiple dates. I'm receiving the params as a comma-separated list in string format, e.g. "07/12/2012,07/13/2012,07/17/2012", and need to be able to query the database for several different date ranges, and time ranges within those date ranges, and merge those results, so for example all of the sessions on 7/12, 7/13 and 7/17 between 6:30 pm and 7:30 pm.
I have been looking everywhere and have tried several different things but I can't figure out how to actually do this. Is this possible using scopes? If not what's the best way to do this?
My closest guess looks like this but it's not returning anything so I know it's wrong.
scope :filter_by_date, lambda { |date|
date = date.split(",")
date.each do |i|
where(:created_at =>
DateTime.strptime(i, '%m/%d/%Y')..DateTime.strptime(i, '%m/%d/%Y').end_of_day
)
end
}
scope :filter_by_time, lambda { |date, time|
date = date.split(",")
to = time[:to]
from = time[:from]
date.each do |i|
where(:start_time =>
DateTime.strptime("#{i} #{from[:digits]} #{from[:meridian]}", '%m/%d/%Y %r')..
DateTime.strptime("#{i} #{to[:digits]} #{to[:meridian]}", '%m/%d/%Y %r')
)
end
}
Another complication is that the start times are all stored as DateTime objects so they already include a fixed date, so if I want to return all sessions started between 6:30 pm and 7:30 pm on any date I need to figure something else out too. A third party is responsible for the data so I can't change how it's structured or stored, I just need to figure out how to do all these complex queries. Please help!
EDIT:
Here's the solution I've come up with by combining the advice of Kenichi and Chuck Vose below:
scope :filter_by_date, lambda { |dates|
clauses = []
args = []
dates.split(',').each do |date|
m, d, y = date.split '/'
b = "#{y}-#{m}-#{d} 00:00:00"
e = "#{y}-#{m}-#{d} 23:59:59"
clauses << '(created_at >= ? AND created_at <= ?)'
args.push b, e
end
where clauses.join(' OR '), *args
}
scope :filter_by_time, lambda { |times|
args = []
[times[:from], times[:to]].each do |time|
h, m, s = time[:digits].split(':')
h = (h.to_i + 12).to_s if time[:meridian] == 'pm'
h = '0' + h if h.length == 1
s = '00' if s.nil?
args.push "#{h}:#{m}:#{s}"
end
where("CAST(start_time AS TIME) >= ? AND
CAST(start_time AS TIME) <= ?", *args)
}
This solution allows me to return sessions from multiple non-consecutive dates OR return any sessions within a range of time without relying on dates at all, OR combine the two scopes to filter by non-consecutive dates and times within those dates. Yay!
An important point I overlooked is that the where statement must come last -- keeping it inside of an each loop returns nothing. Thanks to both of you for all your help! I feel smarter now.
something like:
scope :filter_by_date, lambda { |dates|
clauses = []
args = []
dates.split(',').each do |date|
m, d, y = date.split '/'
b = "#{y}-#{m}-#{d} 00:00:00"
e = "#{y}-#{m}-#{d} 23:59:59"
clauses << '(start_time >= ? AND start_time <= ?)'
args.push b, e
end
where clauses.join(' OR '), *args
}
and
scope :filter_by_time, lambda { |dates, time|
clauses = []
args = []
dates.split(',').each do |date|
m, d, y = date.split '/'
f = time[:from] # convert to '%H:%M:%S'
t = time[:to] # again, same
b = "#{y}-#{m}-#{d} #{f}"
e = "#{y}-#{m}-#{d} #{t}"
clauses << '(start_time >= ? AND start_time <= ?)'
args.push b, e
end
where clauses.join(' OR '), *args
}
So, the easy part of the question is what to do about datetimes. The nice thing about DateTimes is that they can be cast to times really easily with this:
CAST(datetime_col AS TIME)
So you can do things like:
i.where("CAST(start_time AS TIME) IN(?)", times.join(", "))
Now, the harder part, why aren't you getting any results. The first thing to try is to use i.to_sql to decide whether the scoped query looks reasonable. My guess is that when you print it out you'll find that all those where are chaining together with AND. So you're asking for objects with a date that is on 7/12, 7/13, and 7/21.
The last part here is that you've got a couple things that are concerning: sql injections and some overeager strptimes.
When you do a where you should never use #{} in the query. Even if you know where that input is coming from your coworkers may not. So make sure you're using ? like in the where I did above.
Secondly, strptime is extremely expensive in every language. You shouldn't know this, but it is. If at all possible avoid parsing dates, in this case you can probably just gsub / into - in that date and everything will be happy. MySQL expects dates in m/d/y form anyways. If you're still having trouble with it though and you really need a DateTime object you can just as easily do: Date.new(2001,2,3) without eating your cpu.

Searching and comparing ActiveRecord attributes to find largest value

I have a model that would look something like:
my_diet = Diet.new
my_diet.food_type_1 = "beef"
my_diet.food_type_1_percentage = 40
my_diet.food_type_2 = "carrots"
my_diet.food_type_2_percentage = 50
my_diet.food_type_3 = "beans"
my_diet.food_type_3_percentage = 5
my_diet.food_type_4 = "chicken"
my_diet.food_type_4_percentage = 5
I need to find which food_type has the highest percentage. So far I've tried creating a hash out of the attibutes and percentages then sorting the hash (see below) but it feels like there must be a cleaner way to do it.
food_type_percentages = { :food_type_1 => my_diet.foo_type_percentage_1_percentage.nil? ? 0 : my_dient.food_type_1_percentage,
:food_type_2 => my_diet.foo_type_percentage_2_percentage.nil? ? 0 : my_dient.food_type_2_percentage,
:food_type_3 => my_diet.foo_type_percentage_3_percentage.nil? ? 0 : my_dient.food_type_3_percentage,
:food_type_4 => my_diet.foo_type_percentage_4_percentage.nil? ? 0 : my_dient.food_type_4_percentage
}
food_type_percentages.sort {|a,b| a[1]<=>b[1]}.last
Any ideas?
Thanks!
To find the max value amongst columns of an existent row in the DB, do the following:
d = Diet.first(:select => "*, GREATEST(
food_type_1_percentage,
food_type_2_percentage,
food_type_3_percentage,
food_type_4_percentage) AS top_food_type_percentage,
CASE GREATEST(
food_type_1_percentage,
food_type_2_percentage,
food_type_3_percentage,
food_type_4_percentage)
WHEN food_type_1_percentage THEN food_type_1
WHEN food_type_2_percentage THEN food_type_2
WHEN food_type_3_percentage THEN food_type_3
WHEN food_type_4_percentage THEN food_type_4
END AS top_food_type")
d.top_food_type # carrots
d.top_food_type_percentage # 50
If you are trying to find the top food type in the current model instance then
class Diet < ActiveRecord::Base
def top_food_type
send(top_food_type_col)
end
def top_food_type_percentage
send("#{top_food_type_col}_percentage")
end
FOOD_TYPE_COL = %w(food_type_1 food_type_2 food_type_3 food_type_4)
def top_food_type_col
#top_food_type_col ||= FOOD_TYPE_COL.sort do |a, b|
send("#{a}_percentage") <=> send("#{b}_percentage")
end.last
end
end
Now you can do the following:
d = Diet.new
....
....
....
d.top_food_type # carrots
d.top_food_type_percentage # 50
I assume food_percentage is the column
if you just want to find out ref this
Diet.maximum('food_percentage') # gives 50
OR you want complete record use this
Diet.find(:first, :order=> 'food_percentage DESC', :limit=>1)

Resources