Module: AggregateColumns::MethodsRails3

Defined in:
lib/aggregate_columns.rb

Instance Method Summary collapse

Instance Method Details

#aggregate_columns(options) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/aggregate_columns.rb', line 50

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: this only handles :conditions and in a specific way - it should
  # really handle other options as well
  assoc_conditions = assoc_reflection.conditions.first.first
  if assoc_conditions
    aggregate_rel = aggregate_rel.where( klass.send( :sanitize_sql, assoc_conditions ))
  end
  if through_reflection
    through_conditions = through_reflection.conditions.first.first
    if through_conditions
      aggregate_rel = aggregate_rel.where( through_reflection.klass.send( :sanitize_sql, through_conditions ))
    end
  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" ) # 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" ) # You might reorder if you want to have it differently
  end
  return rel
end