Rails 3 Table with Fields Using Calculated Data

I have a table in my app, called entries. Entries belongs_to :users and has a field (decimal) called hours. There is another decimal field called cost. Cost is not entered by the user, but is calculated by multiplying the value in hours with the value in users named rate. To get this to work properly, I had to do the following.

First, in the entry form, make sure there is no text_field for cost. Cost is only found using a calculation.

In entries_controller.rb, edit create and update as follows:

  def create
    @entry = Entry.new(params[:entry])
    @entry.cost = @entry.user.rate * @entry.hours
    
    respond_to do |format|
      if @entry.save
        format.html { redirect_to(@entry, :notice => 'Entry was successfully created.') }
      else
        format.html { render :action => "new" }
      end
    end
  end

  def update
    @entry = Entry.find(params[:id])
    total_cost = @entry.user.rate * @entry.hours
    @entry.update_attribute(:cost, total_cost)
    respond_to do |format|

      if @entry.update_attributes(params[:entry])
        format.html { redirect_to(@entry, :notice => 'Entry was successfully updated.') }
      else
        format.html { render :action => "edit" }
      end
    end
  end

There should be a way to put a method in the model to calculate the total_cost, but I haven't successfully figured that out yet.

Downloading Spreadsheet from Rails

I have a rails app that needs to provide a spreadsheet of all the data in one of the tables. Specifically, we have a simple signup form for an event. I need to be able to let a subset of users download a spreadsheet of the current people signed up with the information given on the signup form. Here's what I did.

First, use fasterCSV. Add to the Gemfile:

gem 'fastercsv', :require => 'faster_csv'

Basically, all that will happen is that there will be a link on the attendees index page that only logged in users will see (and be allowed to access). This is done in two steps. First the csv file is generated and then it is downloaded.

So, where shall I put the file? Since my development and production workstations have different operating systems, I’m going to have different paths on both of them. I created an yaml file to hold this information.

config/download_csv.yml

development:
    csvfile: /Users/me/Desktop/output.csv

production:
    csvfile: /local/code/web/app/myapp/shared/system/output.csv

In my attendee model, I created a method to generate the csv file.

def self.generate_csv
    csv_settings = YAML.load_file("#{Rails.root.to_s}/
config/download_csv.yml")[Rails.env]
    csvfile = csv_settings['csvfile']
    @attendees = Attendee.order('lastname ASC')
    FasterCSV.open("#{csvfile}",'w') do |csv|
      csv << ['Firstname','Lastname','Institution','Address',
'Email','Number attending']
      @attendees.each do |attendee|
        csv << [attendee.firstname, attendee.lastname, 
attendee.institution, attendee.address, attendee.email, 
attendee.fest_count]
      end
    end
  end

In my attendee controller, I created a method called download_csv. In this method, I actually call the generate_csv method above. (Side note: I originally had it set up so that the generate_csv method was called with each new create method. But then decided that fewer people would be downloading the csv file than would be registering. And I changed things so that the csv is only generated when someone wants to download it.)

def download_csv
    csv_settings = YAML.load_file("#{Rails.root.to_s}/
config/download_csv.yml")[Rails.env]
    csvfile = csv_settings['csvfile']
    Attendee.generate_csv
    send_file "#{csvfile}", :type => 'text/csv'
  end

Since I'm using authlogic and declarative_authorization in this app, I also needed to edit my filter_resource_access line in the method to:

filter_resource_access :additional_collection 
=> [[:download_csv, :edit], :edit]

(I.E. People who are allowed to edit attendees are also allowed to download the file.)

On my view I have the link:

<% if current_user %>
	<%= link_to 'Download Spreadsheet of Attendees', 
:controller => :attendees, :action => :download_csv %>
<% end %>

Lastly, add the route

 resources :attendees do
    collection do
      get 'download_csv', :as => :download_csv
    end
  end

This all worked perfectly on my laptop while I tested it. But when I uploaded it to the server, it kept giving me an empty file, even though the file on the server was not empty. Apparently, this is a common problem. All I needed to do was edit my environments/production.rb file to match this:

 # Specifies the header that your server uses for sending files
  #config.action_dispatch.x_sendfile_header = "X-Sendfile"

  # For nginx:
  config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'

My problem now, as always, is I have no idea how to write a test for this.