Module: AggregateColumns::MethodsRails4

Defined in:
lib/aggregate_columns/methods_rails_4.rb

Instance Method Summary collapse

Instance Method Details

#aggregate_columns(options) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/aggregate_columns/methods_rails_4.rb', line 5

def aggregate_columns( options )
  rel = self.select( "#{table_name}.*" )
  # Process options
  options.assert_valid_keys( :association, :column, :function, :result_column, :join_type, :expression )
  association = options[:association]
  unless association
    raise ArgumentError, "No association given" 
  end
  #field = options[:column] || '*'
  #function = options[:function] || :count
  aggregate_expression = options[:expression]
  result_column = options[:result_column]
  unless aggregate_expression
    field = options[:column] || '*'
    function = options[:function] || :count
    aggregate_expression = "#{function}(#{field})"
    # Defaults:
    # count(*) on comments => comment_count
    # sum(votes) on comment => comments_votes_sum
    result_column ||= field == '*' ? "#{association.to_s.singularize}_#{function}" : "#{association}_#{field}_#{function}"
  end
  # TODO: this only works on ActiveRecord::Base classes - would be nice
  # (and not that complicated) to make it work on ActiveRecord::Relation
  # instances
  assoc_reflection = reflect_on_association( association )
  # TODO: this only handles single :through association
  through_reflection = assoc_reflection.through_reflection
  foreign_key = assoc_reflection.foreign_key
  aggregate_table_name = assoc_reflection.table_name
  klass = assoc_reflection.klass
  join_type = options[:join_type] # TODO: check if within allowed values

  # This is the reflection to use to "join" with main table
  join_reflection = through_reflection || assoc_reflection
  join_foreign_key = join_reflection.foreign_key
  aggregate_rel = klass.select( "#{aggregate_expression} AS #{result_column}" ).from( aggregate_table_name )
  if through_reflection
    aggregate_rel = aggregate_rel.joins( "INNER JOIN #{through_reflection.table_name} ON #{through_reflection.table_name}.id = #{aggregate_table_name}.#{assoc_reflection.foreign_key}" )
  end

  # TODO: I never needed this part, so it's completely untested
  if assoc_reflection.scope
    aggregate_rel = aggregate_rel.merge( assoc_reflection.scope )
  end
  if through_reflection && through_reflection.scope
    aggregate_rel = aggregate_rel.merge( through_reflection.scope )
  end

  if join_type
    aggregate_rel = aggregate_rel.select( join_foreign_key ).group( join_foreign_key )
    aggregate_rel = yield( aggregate_rel ) if block_given?
    rel = rel.
      joins( "#{join_type.to_s.upcase} JOIN (#{aggregate_rel.to_sql}) #{result_column}_join ON #{table_name}.id = #{result_column}_join.#{join_foreign_key}" ).
      select( result_column.to_s ).
      order( "#{result_column} DESC NULLS LAST" ) # You might reorder if you want to have it differently
  else
    aggregate_rel = aggregate_rel.where( "#{table_name}.id = #{join_reflection.quoted_table_name}.#{join_reflection.foreign_key}" )
    aggregate_rel = yield( aggregate_rel ) if block_given?
    rel = rel.
      select( "(#{aggregate_rel.to_sql}) AS #{result_column}" ).
      order( "#{result_column} DESC NULLS LAST" ) # You might reorder if you want to have it differently
  end
  return rel
end