0

My question is whether there is a simpler or more idiomatic way to implement the check_count method for my Deposit class?

Here's the (long) context. In my current project, I have the classes LineItem:

# == Schema Information
#
# Table name: line_items
#
#  id                 :integer          not null, primary key
#  customer_id        :integer          not null
#  item_type_id       :integer          not null
#  amount_cents       :integer          not null
#  amount_currency    :string           not null
#  check_number       :string
#  lineable_type      :string 
#  lineable_id        :integer
#  created_at         :datetime         not null
#  updated_at         :datetime         not null
#
class LineItem < ActiveRecord::Base

  belongs_to :lineable, polymorphic: true

  validates :lineable_type,
            :lineable_id,
            :customer_id,
            :item_type,
            :amount_cents,
            :amount_currency,
            presence: true

  validates :check_number, presence: true, if: :check?

  enum item_type: {
    check:  0,
    cash:   1
  }

  def check?; item_type == :check end

end

and Deposit:

class Deposit < ActiveRecord::Base

  has_many  :line_items,
            as: :lineable,
            dependent: :destroy

  def check_items
    line_items.where(item_type: LineItem.item_types['check'])
  end

  def check_count
    check_items.
      group_by(&:customer_id).
      transform_values{|v| v.map(&:check_number).uniq.count}.
      values.
      sum
  end

end

So, if in my line_items table, I have (omitting the irrelevant bits):

--------------------------------------------------------------------------------
| id | customer_id | item_type_id | check_number | lineable_type | lineable_id |
--------------------------------------------------------------------------------
| 1  | 1           | 0            | 9989         | 'Deposit'     | 1           |
| 2  | 1           | 0            | 9989         | 'Deposit'     | 1           |
| 3  | 2           | 0            | 9989         | 'Deposit'     | 1           |
--------------------------------------------------------------------------------

I can do:

Deposit.find(1).check_count
 => 2

Which is the correct result.

That check_count method seems clunky to me. Am I missing some cleaner way of doing this?

I'm using rails 4.2.8 and ruby 2.5.1.

jvillian
  • 19,953
  • 5
  • 31
  • 44

1 Answers1

1

As I understand you are summing all unique check numbers per customer.

You can do it by using GROUP BY feature of SQL. Here is a good example of how to do double columns groupings. So to get the same in ActiveRecord(AR) you can do something like this.

  def check_count
    check_items.group(:customer_id, :check_number).count.count
  end

NOTE: as you interested in a number of such groups we are calling #count 2 times. On the first call, the AR relation will be called resulting with

check_items.group(:customer_id, :check_number).count #=> {[1, '9989'] => 2, [2, '9989'] => 1}

the second call to count will calculate the number of the groups

{[1, '9989'] => 2, [2, '9989'] => 1}.count # => 2
mpospelov
  • 1,510
  • 1
  • 15
  • 24
  • Wow! I knew there had to be a better way. I'm going to have to meditate on that for a while. Thank you so much. – jvillian Jun 15 '18 at 01:06