Sylar Sylar - 1 year ago 59
Ruby Question

Sum of all amount if created_at dates are the same in Rails

I'm trying to replicate this with the Rails database (pg) query but not having much luck.


table has many columns but I'm interested in

Foo.all.where(client_id: 4)
looks like this:

[0] {:created_at => <date>, :amount => 20},
[1] {:created_at => <different date>, :amount => 5},

Stripe Charge Object is in json so I thought I could:

f = Foo.all.where(client_id: 4).as_json # could I?

Anyhow, my controller:

# As per first link above
def each_user_foo
starting_after = nil
loop do
f = Foo.all.where(client_id: 4).as_json
#f_hash = Hash[f.each_slice(2).to_a] # I was trying something here
break if f.none?
f.each do |bar|
yield bar
starting_after =

foo_by_date =

each_user_foo do |f|
# Parses date object. `to_date` converts a DateTime object to a date (daily resolution).
amount_date = DateTime.strptime(f.created_at.to_s, "%s").to_date # line 50
amount = f.amount

# Initialize the amount for this date to 0.
foo_by_date[amount_date] ||= 0

foo_by_date[amount_date] += amount

My error at line 50 is:

undefined method `created_at' for Hash

I guess an object is still in an array somewhere. Also, is there an equivalent for the JS
in Rails? Would be handy.

Answer Source

It looks like f is stored as a Hash object by calling as_json, yet you're treating it like a plain-old Ruby object in your each_user_foo block by using the . operator.

The solution is to use the hash accessor operator instead (eg f[:created_at]). You also don't need to stringify and parse the date because it's already a DateTime object:

amount_date = f[:created_at].to_date # line 50
amount = f[:amount]

Also, Foo is a model class right? If so, I'd recommend using pluck to get the attributes you need, which will be more efficient and cleaner than calling as_json:

# As per first link above
def each_user_foo
    f = Foo.where(client_id: 4).pluck(:created_at, :amount)
    f.each do |bar|
      yield(bar.first, bar.last)

foo_by_date =

each_user_foo do |created_at, amount|
  # Parses date object. `to_date` converts a DateTime object to a date (daily resolution).
  amount_date = created_at.to_date # line 50
  foo_by_date[amount_date] += amount

You can remove this line as well:

foo_by_date[amount_date] ||= 0 

Since you're using an initializer in your foo_by_date = variable assignment).


If you're using Postgres, just do:

Foo.where(client_id: 4).group("date_trunc('day', created_at)").sum(:amount)

Or for MySQL:

Foo.where(client_id: 4).group("DAY(created_at)").sum(:amount)

Much, much simpler than your current approach.

The equivalent of console.log() in Rails is, you can also use Rails.logger.warn() and Rails.logger.error(). Really, any function that outputs to STDOUT, such as puts and print will output to your console.