Complex date find and inject - ruby

I am building a financial reporting app with Ruby on Rails. In the app I have monthly financial statement objects (with 'revenue' as an attribute). For any single financial statement, I want show (1) year-to-date revenue, and (2) last-year-to-date revenue (calendar years are fine). Each object also has a Date (not Datetime) attribute called 'month' (watch out for 'month' variable name vs. 'month' method name confusion...maybe I should change that variable name).
So...
I think I need to (1) 'find' the array of financial statements (i.e., objects) in the appropriate date range, then (2) sum the 'revenue' fields. My code so far is...
def ytd_revenue
# Get all financial_statements for year-to-date.
financial_statements_ytd = self.company.financial_statements.find(:all,
:conditions => ["month BETWEEN ? and ?", "self.month.year AND month.month = 1",
"self.month.year AND self.month.month" ])
# Sum the 'revenue' attribute
financial_statements_ytd.inject(0) {|sum, revenue| sum + revenue }
end
This does not break the app, but returns '0' which cannot be correct.
Any ideas or help would be appreciated!

This statement may do what you want:
financial_statements_ytd.inject(0) {|sum, statement| sum + statement.revenue }

You can also look into ActiveRecord's sum class method - you can pass in the field name and conditions to get the sum.

What is the name of the field in financial_statement object that holds the value you want?
Supposing that the field name is ammount then just modify the inject statement to be:
financial_statements_ytd.inject {|sum, revenue| sum + revenue.ammount }

Related

2 similar LINQ statements with different syntax yielding different output

I have to modify some old code in an application that someone before me made. Looking at the variable below whose result goes into "test", I have two tables (which are set up with relational models). In the variable "test2", I have rewritten the query in the more SQL syntax (which I'm used to). I want to join on the Lines and Shifts table where the LineId's match. When I view the "test2" output, I get 6 values where the end time is 2-28-2017 8:30, 9:30 ... 1:30, and 2:30. That makes sense. When I view the "test" output, I see one Line with around 500 Shift entries associated to it. Inspecting those elements yields end times that go back to 2017. Should I not get the same 6 entries in the "test" output that I got back in the "test2" output? Is there something that I'm missing behind the scenes that linq is doing different in the "test" output? Any help would be greatly appreciated!
var test = entityFrameworkDateModel.Lines.Where(line => line.Shifts.Any(s => shift.EndTime >= DateTime.Now));
var test2 = from line in entityFrameworkDateModel.Lines
join shift in entityFrameworkDateModel.Shifts on line.LineID equals shift.LineId
where shift.EndTime >= DateTime.Now
select new
{
line.LineID,
shift.EndTime
};
test is a collection of Line objects that has 0 to many Shift objects. I would expect that
test.SelectMany(t => t.Shifts).Count() == 500 // approx. 500 anyways
test2 is a collection of AnonymousObjects. test2 is flattening your data with one object per LineId / Shift End Time pair. Where as test is keeping your data in a hierarchy.
Inspecting those elements yields end times that go back to 2017.
Test can and will contain shifts that are not matching your where criteria. Since you are only returning Line objects that have shifts with an end time greater than now. So your Line object will have 1 or more shifts matching EndTime >= DateTime.Now. But the .Any() does not filter out the other Shift objects Where EndTime < DateTime.Now.
You can add a SelectMany then Where to return all Shift objects matching your criteria:
var test = entityFrameworkDateModel.Lines
.SelectMany(line => line.Shifts)
.Where(shift => shift.EndTime >= DateTime.Now);
Those 2 are not the same, even though they feel similar. For the first query, the nested "any" filtering isn't needed. The "where" alone is enough. The any is actually going to return just true, which will short circuit the where. I'd lay out the correct syntax for the where clause, but I'm on mobile SO and can't see the question while I'm answering

How do I use the on_change method to calculate total current value

How do I make a value change in real time after I input a specific field value in a form? e.g from the screenshot below , if I enter Quantity recieved as 10000 the Actual stock should compute to 80500.
so far this is the code for the on_change method I came up with :
I would like to know whether this is the correct approach
#api.one
#api.onchange('qnty_recieved', 'init_stock')
def _compute_current_stock(self):
qnty_recieved = self.qnty_recieved
init_stock = self.init_stock
current_quantity = self.current_quantity
self.current_quantity = self.qnty_recieved + self.init_stock
Below is a screenshot of what I am trying to achieve.
If i'm not wrong you want to change your actual stock in real time based on quantity received field.
This can be best achieved by using depends method.
#api.one
#api.depends('qnty_recieved')
def _compute_current_stock(self):
# Assuming current_quantity as the field name of actual stock
self.current_quantity += self.qnty_recieved
You should also add
compute=_compute_current_stock, store=True keyword arguments to your actual stock field.

How to merge ActiveRecord::Relation objects and add only the value, not the key

I have a simple question about programming in Ruby. I'm a newbie to Ruby, so if somebody can help me, I will really appreciate it.
Assume a system lets users have buyer and seller feedback ratings. I want to add/merge the buy and sell feedback ratings for a user into one consolidated rating, so only the rating needs to be added from the two Relation objects. The user id is only used as the key, but is not added.
buy_rating = user_object.group(buy_feedback_rating).select('buy_feedback_rating, COUNT(id) as count')
sell_rating = user_object.group(sell_feedback_rating).select('sell_feedback_rating, COUNT(id) as count')
buy_rating and sell_rating are histograms of the user's buy/sell rating, with 1=Terrible, 2=Poor, 3=Average, 4=Good, 5=Very Good.
The following is a sample array with (key,value) pairs where key=rating from 1 to 5, and value=number of ratings
buy rating = [(1,2),(2,5),(3,1),(4,7),(5,6)]
sell rating = [(1,3),(2,2),(3,7),(4,4),(5,7)]
Desired output = [(1,5),(2,7),(3,8),(4,11),(5,13)]
(obtained by adding only the second values from each array, not the first values).
The buy_rating and sell_rating arrays will only have the the key->value pair if the value>0. Meaning, if a buyer has no buyer rating=1, then the pair (1,0) will not be present in the buy_rating array. This means the arrays could be as follows:
buy_rating = [[2,5],[3,1],[4,7]]
sell_rating = [[1,3],[2,2],[5,7]]
Question is, how do I achieve the desired result? I want to add only the second column, not the first, from each array. Object returned should be of the same data type as buy_rating and sell_rating, i.e. buy_rating and sell_rating are both ActiveRecord::Relation objects, and the result should also be an ActiveRecord::Relation object.
You can make a map of values, sum based on the first, index, and then convert back to an array
buy_rating = [[1,2],[2,5],[3,1],[4,7],[5,6]]
sell_rating = [[1,3],[2,2],[3,7],[4,4],[5,7]]
merged_ratings = buy_rating + sell_rating
composite_ratings = Hash.new(0)
merged_ratings.each do |rating|
composite_ratings[rating[0]]+=rating[1]
end
composite_ratings.to_a
Check this fiddle: http://rubyfiddle.com/riddles/2d0f9/2

ActiveRecord count of distinct days from created_at?

Is it possible to retrieve a count of distinct records based on a field value if the field needs to be interrogated (ideally, using ActiveRecord alone)?
For example, the following returns a count of unique records based on the 'created_at' field:
Record.count('created_at', :distinct => true)
However, is it possible to get a count of, say, unique days based on the 'created_at' field in a similar way?
A naive ActiveRecord example to explain my intent being:
Record.count('created_at'.day, :distinct => true)
(I know the string 'created_at' isn't a 'Time', but that's the sort of query I'd like to ask ActiveRecord.)
You need to group the records. For example
Record.group('DATE(created_at)').count('created_at')
tells you the number of rows created on each particular date, or
Record.group('DAYOFWEEK(created_at)').count('created_at')
would tell you the number of rows created on individual days of the week.
Beware that this with the usual active record setup this will do date calculations in UTC, if you want to do your calculations in a specific timezone you'll have to add that conversion to the group statement.

Calculating an Average from booleans with Ruby on Rails

I have a setup in which there are 10 attributes that accept a float in a rails form. Each attribute also is associated with a value in my model. If a number is entered on the form for more than one attribute, I need to create a weighted average.
An example would be if I have 10 products, each having a price in my model. In the form, a user can enter in the amount (number of products) for each product. I'd like to calculate a weighted price for those products that have an amount entered.
So how can I create a weighted average that checks which products have amounts entered?
columns_names = ['a','b','c','d'] # array of name of the columns
obj = Model.find(:id) # find the object with id
# loop and get column values that are set
values = columns_names.inject([]) do |arr,column_name|
arr << obj.column_name if params[column_name].eql?"true" # collect the values if the column set
arr
end
#get average
if values.blank?
# no column selected
else
avg = values.reduce(:+)/values.size
end
check this for help on weighted average
http://www.dzone.com/snippets/weighted-mean
This code retrieves all attributes that are true on your model:
#model = Model.find(params[:id)
#model.attributes.select{|k,v| (v.is_a?(TrueClass) || v.is_a?(FalseClass)) && v}
If you want the false ones just do a:
#model.attributes.select{|k,v| (v.is_a?(TrueClass) || v.is_a?(FalseClass)) && !v}
Don't know if this is what you are looking for, but maybe it can clear your head a little bit.

Resources