get dynamically updated stats monthly rails - ruby

I have Rails4 application.
class Developer
has_many :client_applications
class ClientApplication
belongs_to :developer
Among ClientApplication's attributes there is cost.
What I can do is to get developer's total earning by:
def total_earning
self.client_applications.where(status: "Closed").map{|c| c.cost}.inject(&:+).to_i
end
What I want is to have a table of statistics. Table should have month column and total_earning column with corresponding to this month value.
I only want to get columns where total_earning is nonzero value (so that if developer earned $0 in February, this month is not present in table).
I can hardcode the month into total_earning method's query (which is dumb and one-off way), I would like to know if there (I am sure there is) a way to do it nice and smooth.
Would appreciated for any hints!

You could do a callback each time a ClientApplication record is saved to update your stats table like this:
class Developer
has_many :client_applications
Add this:
class ClientApplication
belongs_to :developer
after_save :update_statistics
def update_statistics
if self.status == "Closed"
stats = Statistic.find_or_create_by(developer: self.developer, month: self.created_at)
stats.total_earning += self.cost
stats.save
end
end
class Statisitc
has_many :developers
has_many :client_applications

So the solution I've come up with is following(ActiveAdmin table):
table do
Hash[*developer.client_applications.load.where(status: "Closed").group_by{|a| [a.updated_at.month, a.updated_at.year]}.map{|a,b| ["#{Date::MONTHNAMES[a[0]]} #{a[1]}", b.map(&:cost).map(&:to_i).inject(&:+)]}.reject{|entry| entry[1] == [0, nil]}.flatten].each_pair do |k, v|
tr do
td k
td number_to_currency(v, unit: "$")
end
end
end

Related

You can have_one if you're true

In my website (written with sinatra) I am trying to set up a database. I have 2 tables, here referred to as Table1 and Table2.
models.rb
class Table1 < ActiveRecord::Base
Table1.where(bool:true) has_one :table2 # PSUDO-CODE
# So that every record where bool:true has the relationship
# but every record where bool:false or bool:nil doesn't
end
class Table2 < ActiveRecord::Base
belongs_to :table1
end
I am trying to find a way to make the section labeled PSUDO-CODE into actual code. How can I do that?
You can't do this directly: a class either has a relationship or it doesn't (although of course there may be no associated record)
You can set conditions on an association, but to the best of my knowledge you can only really set conditions on the associated collection (i.e. table 2 in this case)
You can however override the generated method, so for example
class Table1 < ActiveRecord::Base
has_one :table2
def table2(*args)
bool ? super : nil
end
end
This works with current versions of activerecord - not how far back this is supported (older version defined the association methods directly on the class so you couldn't call super)

Get sorted list of the top voted Models from DB

Using: Rails 4.1.4, PostgreSQL 9.1.13
Hi. I'm have a simple problem, but for some reason I can't get it done. The picture is this:
Models
class Article < ActiveRecord::Base
belongs_to :user
has_many :votes
end
class User < ActiveRecord::Base
has_many :articles
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :article
belongs_to :user, scope: :hotel_id
validates_inclusion_of :value, in: 0..5
validates_uniqueness_of :user_id, :article_id
end
Idea
Each User can Vote for each Article but only once (to avoid multiple voting).
Vote model has a 'value' attribute that is the range 0..10.
ArticlesController except standard CRUD methods has action #showcase which must return 5 articles with the top votes rating from the DB and sort them in the descending order (and render the respective view).
So I understand that the proper way is to write the class method in the Article Model (smth. like "by_top_votes") and use it in the ArticlesController#showcase:
def showcase
#top_five_articles = Article.by_top_votes
end
The problem is that I can't write the proper query to the DB which will: 1)find articles, 2)find all votes of the each article, 3) sum all values of the respective article's votes, 4)sort them (this step I know how to do).
Thank you for reading and for the help.
P.S. Maybe my way to solve problem is almost wrong. If this so, please tell my the right one.
Ok, I've done it by myself. If anybody will stuck with the same problem, here is solution for it.
1. In Vote model summarize the vote's values:
def self.sum_value
sum(:value)
end
2. Add new attribute (and column) to Article - user_rating:integer.
3. In the Article model define two class methods:
# assign user_rating attribute with the sum of all votes values
def set_user_rating
user_rating = self.votes.sum_value
self.update_attribute(:user_rating, user_rating)
end
# get top 5 articles by user_rating value from db
def self.top_by_user_rating
Article.order(:user_rating).reverse_order.limit(5)
end
4. In the ArticlesController define showcase action:
def showcase
#top_articles = Article.top_by_user_rating
end
5. In the VotesController define create action:
def create
#article = Article.find(params[:article_id])
#vote = #article.votes.create(vote_params)
if #vote.save
#article.set_user_rating
redirect_to #article, notice: "Thanks for your vote"
else
.
end
end
It works and tests are passing.

How to retrieve related records in Ruby on Rails?

I am trying to find a user's overdue invoices:
class User < ActiveRecord::Base
def overdue_invoices
invoices.where("overdue = ?", true)
end
end
class Invoice < ActiveRecord::Base
def overdue
balance > 0 && Date.today > due_date
end
end
The problem seems to be that overdue is a method on the Invoice model, not a database column.
How can I retrieve those records anyway? And does that even make sense or would it be better to store those true and false values in the database?
You should create an equivalent class method or scope for overdue on Invoice:
class Invoice < ActiveRecord::Base
def self.overdue
where('balance > 0').where('? > due_date', Date.today)
end
end
Then you can call overdue on an Invoice relation:
class User < ActiveRecord::Base
def overdue_invoices
invoices.overdue
end
end
Note that I’m assuming due_date is a database column, if it’s not, you cannot use this method—however, you may be able to replace it with SQL to calculate the due date from data that is in columns.

Rails 3 scope with multiple models

Here are my models:
Patient:
has_many :patient_records
PatientRecord:
belongs_to :patient
has_many :progress_reports
ProgressReport:
has_one :patient_record
The query I am trying to produce is to get all patients where progress_reports are older than or equal to 7 days from now (using a date_of_report column in progress_reports) while including or joining the patient record table... I have been working on this for so long that I have ran into a brick wall.
I would try:
scope :recent_patients, lambda
{ |since_when| join(:progress_reports)
.where("progress_reports.created_at >= ?", since_when)}
in your Patient model
reports = ProgressReport.where(:created_at > 7.days.ago).all
if you want to get each patient that belongs to each record, do:
reports.each do |r|
puts r.patient.name
end

Rails 3.1 distinct find and missing attributes

class State < ActiveRecord::Base
has_many :cities
end
class City < ActiveRecord::Base
belongs_to :state
has_many :companies
end
class Company < ActiveRecord::Base
belongs_to :city
end
I'm trying to list all states, and their respective cities, that contain at least one company registered. My first try was the following query:
states = State.joins(:cities => :companies).includes(:cities)
Which works, but I end up getting duplicates if a state has more than one city with companies in it. I then changed the query to:
states = State.joins(:cities => :companies).includes(:cities).select("distinct(states.id)")
This query almost works. I have access to the cities (states[0].cities), and there are no duplicates, but if I try to access an attribute from the State object, I get the following error:
ruby-1.9.2-p290 :056 >states[0].name
ActiveModel::MissingAttributeError: missing attribute: name
How can I solve this?
Thanks in advance
Your select statement overrides the default (SELECT * FROM ... becomes SELECT distinct(state.id) FROM...) so the results don't include the columns of your state table (where the attributes are inferred from). Try changing your select method to the following:
.select("distinct(states.id), states.*")

Resources