Domanda

I found a great railscast that shows how to generate XLS without a gem, but can find precious little documentation that goes more in depth. http://railscasts.com/episodes/362-exporting-csv-and-excel?view=asciicast

I've been asked to create a metrics list that is direct-to-email in the form of an XLS spreadsheet, but have been told that the slug is too large to add a gem.

The rake task:

#scheduler.rake
namespace :scheduler do
...
desc "Email client metrics - 100pm every Sunday"
task :email_client_metrics => :environment do
  # run every Sunday
  Notifications.send_metrics_email('name1@company.com').deliver
  Notifications.send_metrics_email('name2@company.com').deliver
end

The task calls the notifications mailer:

#app/mailers/notifications.rb
class Notifications < ActionMailer::Base
...
def send_metrics_email(email_addr)
  @email = email_addr
  #rather than splitting on @, we are looking for a specific name to avoid mistakes.
  if @email =~ /name1/
    login = "name1"
  elsif @email =~ /name2/
    login = "name2"
  end
  @user = User.find_by_login(login)
  @factor_clients,@active_count,@update_count,@closed_per_month,@incorrect = @user.get_factors_and_counts_for_metrics

  mail(:to => @email, :subject => "Weekly Client Metrics")
end

The mailer calls to a method in the user model:

#user.rb
class User < ActiveRecord::Base
...
def get_factors_and_counts_for_metrics()
  ret_factors,ret_actives,ret_updates,ret_cls_per_mo,ret_incorrect = [],[],[],[],[]
  factors.includes(:factor_clients).where(:status => "a").find_each { |f| ret_factors << f }

  ret_factors = ret_factors.sort_by { |f| f.company }

  ret_factors.each do |f|
    ret_actives << f.factor_clients.where(:status => ['a','u']).count
    ret_updates << f.factor_clients.where("status IN (?) AND (reason_8821 NOT IN (?) OR reason_8821 is ?)",['a'],'current',nil).count
    ret_cls_per_mo << f.factor_clients.joins(:client_status_changes).where("factor_clients.status IN (?) AND client_status_changes.change_type IN (?) AND client_status_changes.change_time BETWEEN (?) AND (?)", ['c'],['a2c','r2c','ia2c','e2c','u2c','o2c'],Time.now.beginning_of_month,Time.now.end_of_month).count
    ret_incorrect << f.factor_clients.where(:status => 'e').count
  end

  return [ret_factors,ret_actives,ret_updates,ret_cls_per_mo,ret_incorrect]
end

And, of course, all of this renders the mailer template:

<div>
  Hello <%= @user.first_name %>,<br />
  <p>
    Here is the breakdown of your client metrics.
  </p>
</div>

<p>
<div>
  <table style="font-size: 12; margin-right: auto; margin-left: auto; width: 90%;"border=1 cellpadding=2 cellspacing=0>
    <tr>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">Account Name</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">Active</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">Need Updated 8821</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">% Outstanding</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">Closed This Month</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">Total Incorrect</th>
    </tr>
  <% shade = "dark" %>
  <% @factor_clients.zip(@active_count,@update_count,@closed_per_month,@incorrect).each do |f,a,u,c,i| %>
    <% if shade == "background: #dddddd;"
         shade = "background: #ffffff;"
       else
         shade = "background: #dddddd;"
       end %>

    <tr style="<%= shade %>">
    <% if f %>
      <td><%= f.company %></td>
      <td><%= a %></td>
      <td><%= u %></td>
      <td><% if a == 0 || u == 0 then %><%= 0 %><% else %><%= ((u.to_f)/(a.to_f) * 100).to_i %><% end %> %</td>
      <td><%= c %></td>
      <td><%= i %></td>
    <% end %>
    </tr>
  <% end %>
    <% if @active_count.any? && @update_count.any? %>
    <tr>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);">SUM</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);"><%= @active_count.sum %></th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);"><%= @update_count.sum %></th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);"><%= (((@update_count.sum.to_f)/(@active_count.sum.to_f)) * 100).to_i %> %</th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);"><%= @closed_per_month.sum %></th>
      <th style="background: #8B8B8B; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E78CC, endColorstr=#0C89E6); background: -webkit-gradient(linear, left top, left bottom, from(#1E78CC), to(#0C89E6)); background: -moz-linear-gradient(top,  #1E78CC,  #0C89E6);"><%= @incorrect.sum %></th>
    </tr>
    <% end %>
  </table>
</div>
</p>

I believe the tricky part will be that the necessary info is pooled together from various includes and joins queries, all initiated separately from the user model, and combined via zip()- so I don't immediately see a way to just say @variable.to_xls. Also, the e-mail is generated via rake task, so I haven't been able to figure out how/where/when to generate the XLS for the attachment.

The mailer successfully generates a list visible in the e-mail. How do I take that list generated by the mailer and turn it into an XLS attachment for the same e-mail, again, without using a gem?

Ruby 1.8.7, Rails 3.0.20

È stato utile?

Soluzione

Found the solution through bashing my head against walls and doing searches for similar issues to find a loop hole.

I've added a block to the notifications mailer...

#app/mailers/notifications.rb
class Notifications < ActionMailer::Base
...
def send_metrics_email(email_addr)
  @email = email_addr
  #rather than splitting on @, we are looking for a specific name to avoid mistakes.
  if @email =~ /name1/
    login = "name1"
  elsif @email =~ /name2/
    login = "name2"
  end
  @user = User.find_by_login(login)
  @factor_clients,@active_count,@update_count,@closed_per_month,@incorrect = @user.get_factors_and_counts_for_metrics

  mail(:to => @email, :subject => "Weekly Client Metrics") do |format|
    format.html { render }
    format.xls { attachments["#{login}#{Time.now}.xls"] = @user.to_csv(:col_sep => "\t") }
  end
end

Which automatically creates an XLS file as an attachment, through @user.to_csv. I added the to_csv method, which is essentially the get_factors_and_counts_for_metrics method from before, but modified.

I had to change the code from the example CSV.generate to FCSV.generate and add some lines to config/application.rb

#app/models/user.rb
class User < ActiveRecord::Base
...
def to_csv(options = {})
  FCSV.generate(options) do |csv|
    csv << ["Account Name","Active","Need Updated 8821","% Outstanding","Closed This Month","Total Incorrect"]
    ret_factors = []
    factors.includes(:factor_clients).where(:status => "a").find_each { |f| ret_factors << f }
    ret_factors = ret_factors.sort_by { |f| f.company }
    ret_factors.each do |f|
      f_c = f.company
      a = f.factor_clients.where(:status => ['a','u']).count
      u = f.factor_clients.where("status IN (?) AND (reason_8821 NOT IN (?) OR reason_8821 is ?)",['a'],'current',nil).count
      if a == 0 || u == 0 
        a_u_per = 0
      else
        a_u_per = ((u.to_f)/(a.to_f) * 100).to_i
      end
      c = f.factor_clients.joins(:client_status_changes).where("factor_clients.status IN (?) AND client_status_changes.change_type IN (?) AND client_status_changes.change_time BETWEEN (?) AND (?)", ['c'],['a2c','r2c','ia2c','e2c','u2c','o2c'],Time.now.beginning_of_month,Time.now.end_of_month).count
      e = f.factor_clients.where(:status => 'e').count
      csv << [f_c,a,u,"%#{a_u_per}",c,e]
    end
  end
end

And the respective config change..

#config/application.rb
require File.expand_path('../boot', __FILE__)

if RUBY_VERSION < "1.9"
  require "rubygems"
  require "faster_csv"
  #CSV = FCSV
else
  require "csv"
end
require 'rails/all'

Added the mime type to initializers/mime_types.rb: Mime::Type.register "application/xls", :xls

And I now have XLS attachments in my e-mail.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top