Search code examples
rubyruby-on-rails-4actioncontroller

Rails 4...Page reload after send_data


I have a method export_csv in controller.

def export_csv
   if params[:from_date].present? && params[:to_date].present?
   @users = User.where("created_at between ? and ?", params[:from_date], params[:to_date])
        if !@users.blank?
            users_csv = User.to_excel(@users)
            send_data(users_csv, :type => 'text/csv', :filename => 'users.csv')
            flash.now[:success] = "Successfully downloaded the report!"
       else
        flash.now[:notice] = "No records over selected duration!"
       end
   else
    flash.now[:notice] = "Select from and to date.."
   end
end

The file is downloaded but the page is not refreshed or reloaded. Due to this, the flash message stays as it was on the page even after the file is downloaded.

I have gone through few sites and found that send_data automatically renders the view and so other redirect or render can't be used.

Is there a way to reload the page after send_data.?


Solution

  • send_data sets the whole server response, so the browser is just receiving a CSV file, not a web page. This is why your flash message is not displaying. An alternative could be to generate a temporary CSV file (with random name) and provide back a link to it:

    def export_csv
       if params[:from_date].present? && params[:to_date].present?
       @users = User.where("created_at between ? and ?", params[:from_date], params[:to_date])
            if !@users.blank?
                #Create temporary CSV report file and get the path to it.
                csv_file_path = create_csv_file(User.to_excel(@users))
    
                #Change the flash message a bit for requesting the user
                #to click on a link to download the file.                
                flash.now[:success] = "Your report has been successfully generated! Click <a href='#{csv_file_path}'>here</a> to download".html_safe
           else
            flash.now[:notice] = "No records over selected duration!"
           end
       else
        flash.now[:notice] = "Select from and to date.."
       end
    end
    

    Of course you should implement the function create_csv_file. To avoid keeping old files in your server, you could implement a new method, say download_report which would read the file, send back to the client with send_data and finally delete it.

    EDIT

    Pseudocode for the functions mentioned above:

    require 'tempfile'
    
    def create_csv_file(data)       
        #Create a temporary file. If you omit the second argument of Tempfile.new
        #then the OS's temp directory will be used.
        tmp = Tempfile.new('report', 'my/temp/dir')
        tmp.write(data)
        tmp.close
    
        return tmp.path
    end
    
    
    #Method in controller for downloading the file. I omit checks and/or security issues.
    def download_report
        #Warning: a mechanism should be implemented to prevent the remote 
        #client from faking the path and download other files. 
        #A possible solution would be providing not only the file path but also a
        #hash with a secret key to validate the path. Function create_csv_file()
        #should, then, return a hash in addition of a path.
    
        path = params[:report] 
        file = File.open(path, "rb")
        contents = file.read
        file.close
    
        send_data(contents , :type => 'text/csv', :filename => 'users.csv')       
        file.unlink #Delete file
    end