I want to generate a Excel and PDF report using ruby on rails , but I have 24,278 entries to display in both reports so it is showing an error . How to solve this error .
Controller Code - (for excel)
def from_date_wise_xls
#start = params[:day]
#end = params[:to_date]
#present = params[:present]
#employee_attendances = EmployeeAttendance.where(present: #present,day: #start..#end)
respond_to do |format|
format.xls {render template: 'employee_attendances/from_date_wise_xls.xls.erb'}
end
end
(For PDF) -
def from_date_wise_pdf
#start = params[:day]
#end = params[:to_date]
#present = params[:present]
#employee_attendances = EmployeeAttendance.where(present: #present,day: #start..#end)
respond_to do |format|
format.html
format.pdf do
render :pdf => 'from_date_wise_pdf',
layout: '/layouts/pdf.html.erb',
:template => 'employee_attendances/from_date_wise_pdf.pdf.erb',
:orientation => 'Landscape', # default , Landscape
:page_height => 1000,
:dpi => '300',
:margin => {:top => 10, # default 10 (mm)
:bottom => 10,
:left => 20,
:right => 20},
:show_as_html => params[:debug].present?
end
end
end
Related
Is it possible to export in xlsx with multiple sheet using gem axlsx. If not please suggest me the other ways.
I want to export the values of #search, #searchg, #clients, #overtimes in different sheets.
#search = Operation.where(:date => params[:from_date].to_datetime..(params[:to_date].to_datetime + 1.day))
#searchg = Guard.where(:status => true).where(:induction_date => params[:from_date].to_datetime..(params[:to_date].to_datetime + 1.day))
#searcht = Guard.where(:status => false).where(:terminate_date => params[:from_date].to_datetime..(params[:to_date].to_datetime + 1.day))
#clients = Client.where(:status => false).where(:terminate_date => params[:from_date].to_datetime..(params[:to_date].to_datetime + 1.day))
#overtimes = Overtime.where(:date => params[:from_date].to_datetime..(params[:to_date].to_datetime + 1.day))
respond_to do |format|
format.js
format.xlsx {render xlsx: 'report', filename: "Operation_reports_#{params[:from_date]}_#{params[:to_date]}.xlsx"}
end
Yep, axlsx supports multiple sheets. In fact, you can see it in the very first code snippet in its readme:
Axlsx::Package.new do |p|
p.workbook.add_worksheet(:name => "Pie Chart") do |sheet|
sheet.add_row ["Simple Pie Chart"]
%w(first second third).each { |label| sheet.add_row [label, rand(24)+1] }
sheet.add_chart(Axlsx::Pie3DChart, :start_at => [0,5], :end_at => [10, 20], :title => "example 3: Pie Chart") do |chart|
chart.add_series :data => sheet["B2:B4"], :labels => sheet["A2:A4"], :colors => ['FF0000', '00FF00', '0000FF']
end
end
p.serialize('simple.xlsx')
end
I want to make PDF and Excel report code using same method and want to pass parameters only one time ,but it is showing an error
NoMethodError (undefined method `[]' for nil:NilClass) .
Controller Code -
def print_application_report
#from = params[:salary][:from_date]
#to = params[:salary][:to_date]
#company = params[:employee][:company_id]
#company_location = params[:employee][:company_location_id]
#department = params[:employee][:department_id]
#employees = Employee.where(company_id: #company.to_i,company_location_id: #company_location.to_i,department_id: #department).pluck(:id)
#travel_requests = TravelRequest.where(application_date: #from.to_date..#to.to_date,employee_id: #employees)
respond_to do |format|
format.js
format.xls {render template: 'travel_requests/application_datewise_report_xls.xls.erb'}
format.html
format.pdf do
render pdf: 'application_datewise_report_pdf',
layout: 'pdf.html',
orientation: 'Landscape',
template: 'travel_requests/application_datewise_report_pdf.pdf.erb',
# show_as_html: params[:debug].present?,
:page_height => 1000,
:dpi => '300',
:margin => {:top => 10, # default 10 (mm)
:bottom => 10,
:left => 20,
:right => 20},
:show_as_html => params[:debug].present?
end
end
end
you can check the condition in your controller with the terniary operator and check for the presence of params[:salary] and params[:employee],
Controller:
def print_application_report
#from = params[:salary] ? params[:salary][:from_date] : params[:from_date]
#to = params[:salary] ? params[:salary][:to_date] : params[:to_date]
#company = params[:employee] ? params[:employee][:company_id] : params[:company_id]
#company_location = params[:employee] ? params[:employee][:company_location_id] : params[:company_location_id]
#department = params[:employee] ? params[:employee][:department_id] : params[:department_id]
#employees = Employee.where(company_id: #company.to_i,company_location_id: #company_location.to_i,department_id: #department).pluck(:id)
#travel_requests = TravelRequest.where(application_date: #from.to_date..#to.to_date,employee_id: #employees)
respond_to do |format|
format.js
format.xls {render template: 'travel_requests/application_datewise_report_xls.xls.erb'}
format.html
format.pdf do
render pdf: 'application_datewise_report_pdf',
layout: 'pdf.html',
orientation: 'Landscape',
template: 'travel_requests/application_datewise_report_pdf.pdf.erb',
# show_as_html: params[:debug].present?,
:page_height => 1000,
:dpi => '300',
:margin => {:top => 10, # default 10 (mm)
:bottom => 10,
:left => 20,
:right => 20},
:show_as_html => params[:debug].present?
end
end
end
Explanation:
The change I made is,
#from = params[:salary]
to,
#from = params[:salary] ? params[:salary][:from_date] : params[:from_date]
the meaning of params[:salary] ? params[:salary][:from_date] : params[:from_date] is,
if there is params[:salary], then take params[:salary][:from_date], if params[:salary] is not there, then take params[:from_date]
This line can also be written as,
if params[:salary]
#from = params[:salary][:from_date]
else
#from = params[:from_date]
end
Without the actual line of error, I am making a guess here.
you are sending params as:
print_application_report_travel_requests_path(from_date: params[:salary][:from_date],to_date: params[:salary][:to_date], company_id: params[:employee][:company_id]....
Here value of param from_date is params[:salary][:from_date] but you are sending the param directly, not enclosed in salary key. so all of your params will be available directly inside params hash.
you can try changing like this.
#from = params[:from_date]
#to = params[:to_date]
#company = params[:company_id]
#company_location = params[:company_location_id]
#department = params[:department_id]
My code is
class pdfController < ApplicationController
def index
#posts = Post.all
respond_to do |format|
format.html
format.pdf do
pdf = Prawn::Document.new
send_data pdf.render, :filename => "report.pdf", :type => "application/pdf", :disposition => "inline"
end
end
end
end
Now the problem is, pdf file downloaded. But I am getting an empty file, there is no report in that downloaded file.
And the code#posts = Post.all Post is a table, I can get the data from Post to downloaded pdf file.
As suggested in this post: http://adamalbrecht.com/2014/01/14/generate-clean-testable-pdf-reports-in-rails-with-prawn/
You can inherit Prawn::Document class for an easy configuration of your table border color and font size.
class PdfReport < Prawn::Document
# Often-Used Constants
TABLE_ROW_COLORS = ["FFFFFF","DDDDDD"]
TABLE_FONT_SIZE = 9
TABLE_BORDER_STYLE = :grid
def initialize(default_prawn_options={})
super(default_prawn_options)
font_size 10
end
def header(title=nil)
text "My Awesome Invoice!!", size: 18, style: :bold, align: :center
if title
text title, size: 14, style: :bold_italic, align: :center
end
end
def footer
# ...
end
# ... More helpers
end
Now, the above class is handful to use at other places by inheriting to some other classes, for example: posts reports, comments reports, etc. For your posts reports, We could do something like this:
class PostSummaryReportPdf < PdfReport
TABLE_WIDTHS = [20, 100, 30, 60]
TABLE_HEADERS = ["ID", "Name", "Date", "User"]
def initialize(posts=[])
super()
#posts = posts
header "Posts' Invoice Summary Report"
display_event_table
footer
end
private
def display_event_table
if table_data.empty?
text "No Events Found"
else
table table_data,
headers: TABLE_HEADERS,
column_widths: TABLE_WIDTHS,
row_colors: TABLE_ROW_COLORS,
font_size: TABLE_FONT_SIZE
end
end
def table_data
#table_data ||= #posts.map { |p| [p.id, p.name, p.created_at.strftime("%m/%d/%y"), p.created_by.try(:full_name)] }
end
end
Everything is now up and ready to be used in DRY-way within your controllers.
class pdfController < ApplicationController
def index
#posts = Post.all
respond_to do |format|
format.html
format.pdf do
pdf = PostSummaryReportPdf.new(#posts)
send_data pdf.render, :filename => "report.pdf", :type => "application/pdf", :disposition => "inline"
end
end
end
end
I hope that helps.
Is there a best practice for managing objects between model and controller in Rails? I've got code that clumsily hands files from controller to model and back like so:
In document_controller.rb:
tmp_file = Tempfile.new(Digest::MD5.hexdigest(rand(12).to_s))
pdf = Prawn::Document.generate(tmp_file.path(), :page_size => "A4", :skip_page_creation => true) do |posting|
#document.process(posting)
send_data posting.render, :filename => "#{#document.id}", :type => "application/pdf"
end
And in document.rb:
def process(pdf)
pdf.start_new_page
pdf.image self.user.logo.path, :width => pdf.bounds.width
pdf.fill_color '#444444'
pdf.image self.component.image_snapshot.path, :width => pdf.bounds.width
pdf.move_down 20
pdf.indent 20 do
pdf.text self.component.name, :size => 24
end
box_start = pdf.cursor
pdf.reflow_column_box([20, box_start-15], :width => pdf.bounds.width-20) do
format_for_prawn(pdf, self.component.body, self.user.colour1)
end
end
How can I rewrite this code so I don't have to clumsily pass the pdf/posting object around? It feels very un-Rails-like.
Thanks for your input.
EDIT: worse example.
Again in the controller:
tempfile = Magick::Image.read(#document.component.image_newsletter.path).first
overlay = Magick::Image.read(#document.user.logo.path).first.resize_to_fit(tempfile.columns)
rec = Magick::Draw.new
rec.stroke = "##{#document.user.colour1}"
rec.fill = "##{#document.user.colour1}"
rec.rectangle 0, 0, tempfile.columns, 5
lank = tempfile.extent(tempfile.columns, tempfile.rows+overlay.rows, 0 ,0)
final = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(final)
send_data final.to_blob, :filename => "#{#document.user.name}-#{#document.id}.jpg", :type => "image/jpg"
This is in the controller because otherwise send_data can't access final. Any help?
Thanks again.
EDIT
You can create extension methods for inbuilt Class by
#lib/ext/prawn/document.rb
class Prawn::Document
def process(pdf)
pdf = self if pdf.blank?
pdf.start_new_page
pdf.image self.user.logo.path, :width => pdf.bounds.width
pdf.fill_color '#444444'
pdf.image self.component.image_snapshot.path, :width => pdf.bounds.width
pdf.move_down 20
pdf.indent 20 do
pdf.text self.component.name, :size => 24
end
box_start = pdf.cursor
pdf.reflow_column_box([20, box_start-15], :width => pdf.bounds.width-20) do
format_for_prawn(pdf, self.component.body, self.user.colour1)
end
end
end
And in your controller
tmp_file = Tempfile.new(Digest::MD5.hexdigest(rand(12).to_s))
pdf = Prawn::Document.generate(tmp_file.path(), :page_size => "A4", :skip_page_creation => true) do |posting|
posting.process
send_data posting.render, :filename => "#{#document.id}", :type => "application/pdf"
end
To load this class, you will need to require it in your config/application.rb file or in an initializer.
require 'ext/prawn/document'
To get that messy controller code into the model I've done this:
def process_media
temp = "#{::Rails.root}/public/temporary/#{temp_file_name}"
tempfile = Magick::Image.read(self.component.image_newsletter.path).first
overlay = Magick::Image.read(self.user.logo.path).first.resize_to_fit(tempfile.columns)
rec = Magick::Draw.new
rec.stroke = "##{self.user.colour1}"
rec.fill = "##{self.user.colour1}"
rec.rectangle 0, 0, tempfile.columns, 5
lank = tempfile.extent(tempfile.columns, tempfile.rows+overlay.rows, 0 ,0)
final = lank.composite(overlay, Magick::SouthGravity, 0, 0, Magick::OverCompositeOp)
rec.draw(final)
final.to_blob
final.write("#{temp}.jpg")
end
And I'm sending the actual file with send_file from the controller. Hope that helps someone.
Hello!
I have this trouble: I'm searching reports by date and in html view everything is alright, BUT when I'm rendering xls view error appear, because it didn't receive params, so I need to pass them in URL for xls link_to generator.
My controller:
def show
#website = Website.find(params[:id])
if params[:report] && params[:report][:start_date] && params[:report][:end_date]
#search_by_created_at
#performance_reports = #website.performance_reports.where("created_at between ? and ?", params[:report][:start_date].to_date, params[:report][:end_date].to_date)
else
#performance_reports = #website.performance_reports
end
respond_to do |format|
format.html # index.html.erb
format.xls
format.xml { render :xml => #performance_reports }
end
end
and my generated url looks like:
http://127.0.0.1:3000/websites/25/performance_reports/show?utf8=%E2%9C%93&report[end_date]=07%2F09%2F2012&report[start_date]=04%2F09%2F2012&commit=Run+Report
mine xls url is generated like this:
<%= link_to url_for(:format => 'xls') do%>
<%= image_tag("excel.png", :id => "analytics",:size => '21x23')%> <b>Export</b>
<% end %>
result:
http://127.0.0.1:3000/websites/25/performance_reports/show
Any help will be appreciated.
xls in not available by default.
Add this:
gem "spreadsheet"
gem "to_xls", :git => "https://github.com/dblock/to_xls.git", :branch => "to-xls-on-models"
Register the Excel MIME type in config/initializers/mime_types.rb by adding this:
Mime::Type.register "application/vnd.ms-excel", :xls
Add an as_xls method to model that you want to export for the fields you want.
For example for a User model you might have:
def as_xls(options = {})
{
"Id" => id.to_s,
"Name" => name,
"E-Mail" => email,
"Joined" => created_at,
"Last Signed In" => last_sign_in_at,
"Sign In Count" => sign_in_count
}
end
Add code to the controller:
def index
#users = User.all
respond_to do |format|
format.html
format.xls { send_data #users.to_xls, content_type: 'application/vnd.ms-excel', filename: 'users.xls' }
end
end
Provide a link:
= link_to 'Export', users_path(request.parameters.merge({:format => :xls}))
All code should have a test. You could do something like this:
describe "GET index.xls" do
it "creates an Excel spreadsheet with all users" do
user = Fabricate :user
get :index, :format => :xls
response.headers['Content-Type'].should == "application/vnd.ms-excel"
s = Spreadsheet.open(StringIO.new(response.body))
s.worksheets.count.should == 1
w = s.worksheet(0)
w.should_not be_nil
w.row(0)[0].should == "Id"
w.row(1)[0].should == user.id.to_s
w.row(0)[1].should == "Name"
w.row(1)[1].should == user.name
end
end