Prawn table not creating in Rails 4.0 and Ruby 2.0 - ruby-on-rails

I have the following table code:
member_id_and_first_name_and_nps_score_and_comments = [["14169021-00",
"Chris", 9, "YMCA in the Tampa, FL area is fantastic. I would like to
see the level of support for other sports also provided to
racquetball."], ["1660592-00", "Nayrone", 9, "There are so many
offerings for activities and there are many convenient locations
throughout the area."], ["16183029-00", "Mary", 9, "I like the staff
but the showers are gross"], ["16257833-00", "Phyllis", 10, "A
comfortable environment, a place where you can meet people and most
importantly the staff is always available."], ["1629846-00", "Joan",
10, "Cost, Distance , Family Friendly"], ["16204559-00", "Lisa-Marie",
5, "The facilities are fairly crowded which makes working out on
equipment or participating in classes cramped and difficult. "],
["16258576-00", "Michael", 7, "yhe cost way to much\r\n"],
["16161644-00", "April", 10, ""], ["1663157-00", "Mariela", 10, "Great
customer service, clean facilities and professional staff "],
["16114120-00", "Alison", 10, "The Y offers great programs and is
active in the local community. "], ["16177159-00", "Sheri", 10, "The Y
is a place for everything. It is a place for fitness, fellowship,
socialization, community, upward progretion of the youth, employment
opps..."]]
table([
["Member ID", "First Name", "NPS Score", "Comments"],
[member_id_and_first_name_and_nps_score_and_comments]
.transpose.reject{ |x| x[2].nil? }
],
:position => :center, :column_widths => {0 => 50, 1 => 60, 2 => 45, 3 => 285}) do
row(0).style :background_color => 'C0C0C0'
end
The table is generating with only the header row (Member ID, First Name, etc.). The actual member_id_and_first_name_and_nps_score_and_comments array is not populating in the column.
Any thoughts as to why this is?

Try:
table([
["Member ID", "First Name", "NPS Score", "Comments"],
*member_id_and_first_name_and_nps_score_and_comments.reject{ |x| x[2].nil? }
],
:position => :center, :column_widths => {0 => 50, 1 => 60, 2 => 45, 3 => 285}) do
row(0).style :background_color => 'C0C0C0'
end

Related

MongoDB Aggregation push zero in keys while doing group

I am not sure that this is a valid question or not. I have started working on mongodb aggregation. I have to make a graph for the data on daily, weekly, monthly basis.
I am using "$dayOfMonth", "$week", "$month" to group by depending on the date provided. ex if from and to dates difference is less or equal to 6 I am grouping on daily basis using "$dayOfMonth",
If from and to dates difference is greater than 6 and less than 30 grouping is done of "$week" and if differece is greater than 30 then grouping is done on monthly basis "$month".
I am passing date in my "$match". Is it possible to push 0 as keys if the gouping is not present.
example - from_date = "01/01/2018" to_date = "30/6/2018"
so grouping will be done on month. and suppose if I dont have date for 3 and 4th & 5th month. I want to push 0 in the nested keys as the value.
output = [
{"_id": "01/01/2018", "counter":12},
{"_id": "01/02/2018", "counter": 15},
{"_id":"01/06/2018", counter: 10}
]
expected_output =
[
{"_id": "01/01/2018", "counter":12},
{"_id": "01/02/2018", "counter": 15},
{"_id":"01/03/2018", counter: 0},
{"_id":"01/04/2018", counter:0},
{"_id":"01/05/2018", counter: 0},
{"_id":"01/06/2018", counter: 10}
]
I am using Rails and Mongoid Gem.
Query That I am using
converted = Analytics::Conversion::PharmacyPrescription.collection.aggregate([
{ "$match" => {
"organisation_id" => org_id.to_s,
"date" => {
"$gte" => from_date,
"$lte" => to_date
},
"role_ids" => {"$in" => [role_id, "$role_ids"]}
}
},{
"$project" => {
"total_count" => 1,
"converted_count" => 1,
"not_converted_count" => 1,
"total_invoice_amount" => 1,
"user_id" => 1,
"facility_id" => 1,
"organisation_id" => 1,
"date" => 1,
}
},{
"$group" => {
"_id" => { "#{groupby}" => "$date" },
"total_count" => {"$sum" => "$total_count"},
"converted_count" => { "$sum" => "$converted_count" },
"not_converted_count" => { "$sum" => "$not_converted_count"},
}
}
]).to_a
The Aggregation Framework can only aggregate the documents you have. You are actually asking it to add groups for documents that do not exist, but it has no way to "know" which groups to add.
What I would do is run the query as you have it, and afterwards "spread" the date units according to the chosen granularity (in your example it will be 01/01/2018, 01/02/2018, 01/03/2018, 01/04/2018, 01/05/2018, 01/06/2018, and run a simple function which will add an entry for each missing unit.

Improving performance of a Rails controller method that returns a serialized hash

Would anyone be willing to give me advice on how I can improve the performance of the following controller method?
def index
#contacts = Hash[current_user.company.contacts.map {|contact| [contact.id, ContactSerializer.new(contact).as_json[:contact]] }]
respond_to do |format|
format.json { render json: { contacts: #contacts } }
end
end
This returns the following data structure:
{
contacts: {
79: {
id: 79,
first_name: "Foo",
last_name: "Bar",
email: "t#t.co",
engagement: "0%",
company_id: 94,
created_at: " 9:41AM Jan 30, 2016",
updated_at: "10:57AM Feb 23, 2016",
published_response_count: 0,
groups: {
test: true,
test23: false,
Test222: false,
Last: false
},
invites: [
{
id: 112,
email: "t#t.co",
status: "Requested",
created_at: "Jan 30, 2016, 8:48 PM",
date_submitted: null,
response: null
}
],
responses: [ ],
promotions: [
{
id: 26,
company_id: 94,
key: "e5cb3bc80b58c29df8a61231d0",
updated_at: "Feb 11, 2016, 2:45 PM",
read: null,
social_media_posts: [ ]
}
]
},
81: {
id: 81,
first_name: "Foo2",
last_name: "Bar2",
email: "foobar2#foobar.com",
engagement: "0%",
company_id: 94,
created_at: "12:55PM Feb 04, 2016",
updated_at: " 4:25PM Feb 19, 2016",
published_response_count: 0,
groups: {
test: true,
test23: true,
Test222: false,
Last: false
},
invites: [
{
id: 116,
email: "foobar2#foobar.com",
status: "Requested",
created_at: "Feb 22, 2016, 9:10 PM",
date_submitted: null,
response: null
}
],
responses: [ ],
promotions: [
{
id: 26,
company_id: 94,
key: "e5cb3bc80b58c29df8a61231d0",
updated_at: "Feb 11, 2016, 2:45 PM",
read: null,
social_media_posts: [ ]
}
]
}
}
}
I need the index method to return a hash where the the keys are the contact IDs, as opposed to an Array, which is what would normally be returned. Additionally, I pass each contact through the serializer so that I get all associated data that my client needs.
This method works fine when there are only a few contacts, however when I have 100 or 1000, it really slows down. I benchmarked it with 100 contacts and it took 4 seconds to finish, which is abysmal. I'm wondering how I can improve my code to get the exact same output in a more performant manner. The key here is that the output needs to remain unchanged. I have no interest in modifying the client-side code (it depends on this data structure for numerous applications), so all changes need to occur on the server-side.
Here is my ContactSerializer for reference:
class ContactSerializer < ActiveModel::Serializer
attributes :id, :first_name, :last_name, :email, :engagement, :company_id, :created_at, :updated_at, :published_response_count, :groups
has_many :invites
has_many :responses
has_many :promotions
def groups
Hash[object.company.groups.map {|group| [group.name, object.groups.include?(group)] }]
end
def published_response_count
object.responses.where(published: true).count
end
def created_at
object.created_at.in_time_zone("Eastern Time (US & Canada)").strftime("%l:%M%p %b %d, %Y")
end
def updated_at
object.updated_at.in_time_zone("Eastern Time (US & Canada)").strftime("%l:%M%p %b %d, %Y")
end
def engagement
object.engagement
end
end
For what it's worth, I am fully aware that returning JSON data like this from Rails is not a great practice and have since moved away from it completely. Unfortunately this piece of code was written quite awhile ago and I can't afford the time to do a full rewrite of the client side to consume a standard output such as an array of contacts.
I started looking into the queries that we're being generated by ActiveRecord.
I realized after a while though that the queries were only accounting for a few ms of the total processing time. I started benchmarking the code starting with the base query company.contacts and then gradually adding on methods I had, such as map and then passing each contact into the serializer, and finally calling as_json[:contact] on the object returned by ContactSerializer.new(contact).
What I found is that calling as_json[:contact] on the serialized object was consuming an average of about 30ms (averaged over 100 runs) per contact. The reason I had that was to remove the root contact node from the JSON that was returned.
When I benchmarked my original code with 198 contacts, it took an average of 10400ms over 10 runs. When I removed as_json[:contact] and set root false on ContactSerializer, as described by "Abusing ActiveModel::Serializers for HAL", I was able to cut the time down from 10400ms to 87ms, while returning the exact same structure to the client, which is astounding.
It might be possible to shave a few ms off that with some query optimizations, but anything else at this point is just icing on the cake.

Replace transpose method?

I have the following code:
table([
["UnitID", "First Name", "NPS Score", "Comments"],
*[invite_unitid_arr, invite_name_arr, nps_score_integers_final, comment_arr]
.transpose.reject{ |x| x[3].empty? }
], :position => :center, :column_widths => {0 => 50, 1 => 60, 2 => 60, 3 => 40, 4 => 150}) do
row(0).style :background_color => 'C0C0C0'
end
I am calling transpose on an array of arrays. I am refactoring this code and I now have an array of model objects:
array = Model.all
How can I rewrite the above, saying "Loop through each model (Model1, Model2, etc.) and create a row with the attributes unit_id,first_name,nps_score,comment like so: Model1[:unit_id],Model1[:first_name],Model1[:nps_score],Model1[:comment]"
If I understand correctly, you have an array of objects like this:
my_models = [ <MyModel id: 1, unit_id: 123, first_name: "Xxx", nps_score: 100, ...>,
<MyModel id: 2, unit_id: 456, first_name: "Yyy", nps_score: 200, ...>,
...
]
And you want an array like this:
[ [ "UnitID", "First Name", "NPS Score", "Comments" ],
[ 123, "Xxx", 100, "..." ],
[ 456, "Yyy", 200, "..." ],
...
]
All you really need to do is this:
headers = [ "UnitID", "First Name", "NPS Score", "Comments" ]
data = my_models.map do |model|
[ model.unit_id, model.first_name, model.nps_score, model.comments ]
end
rows = [ headers, *data ]
Or...
data = my_models.map do |model|
model.attributes.values_at(:unit_id, :first_name, :nps_score, :comments)
end
(Either way you could make this a one-liner, but mind your code's readability.)
Of course, it's always best to select only the columns you're going to use, so you could just do this (adding whatever where, order etc. calls you need):
my_models = MyModel.select([ :unit_id, :first_name, :nps_score, :comments ]).where(...)
data = my_models.map(&:attributes)
# => [ [ 123, "Xxx", 100, "..." ],
# [ 456, "Yyy", 200, "..." ],
# ...
# ]
In Rails 4 the pluck method takes multiple arguments, making this even easier:
data = MyModel.where(...).pluck(:unit_id, :first_name, :nps_score, :comments)
# => [ [ 123, "Xxx", 100, "..." ],
# [ 456, "Yyy", 200, "..." ],
# ...
# ]
I am not entirely sure what are you trying to achieve here, but it seems that you are looking for pluck method. Since rails 4 it allows you to pluck multiple columns at once (and by default will pluck all columns). So it seems that:
Model.pluck(:unit_id, :first_name, :nps_score, :comment)
Is what you are looking for - and is actually much better as it is not instantiating new objects and makes just one call to db. . It will return 2d array, one row for each model. If you rather prefer have different values of same column, add transpose on the above.

Specify styling (font, in particular) for a certain cell in Prawn

I want to specify a font style for a certain cell. What I found in the documentation is the capacity to do it for all the cell, but not for one I need:
table data, :cell_style => { :font => "Times-Roman", :font_style => :italic }
How do I do that for only one cell?
Turn your table into a block to do more configuration and then you can find it by row and column. See line 2 for your font, I included a couple other examples to set styles by rows & columns:
table(invoice_header_data, width: 210) do
style(row(0).column(0), font: "Times-Roman")
style(rows(0..-1), :padding => [2, 10, 2, 10], :borders => [])
style(row(4), padding: [12, 10], :font_style => :bold)
style(columns(1..3), :align => :right)
end

header and footer in Prawn PDF

I have read through all relevant posts on Prawn but found no mentioning (even in Prawn's own documentation) of headers and footers.
However, I did see a demo on Prawnto's own website about headers and footers. I copied the entire source of that demo just to see if it works but an error of undefined method "header" is complained about. Am I to understand that Prawn took out header and footer recently in the gem or is there something else I need to do first to use header and footer?
The demo page:
http://cracklabs.com/prawnto/code/prawn_demos/source/text/flowing_text_with_header_and_footer
the part of code of concern:
Prawn::Document.generate("flow_with_headers_and_footers.pdf") do
header margin_box.top_left do
text "Here's My Fancy Header", :size => 25, :align => :center
end
text "hello world!"
end
And by header, just in case, I mean the snippets of words that appear usually at a corner of every page of a document. Like your account number in your bills pages.
thanks!
The sample you are refering to, from the prawnto plugin, is using an older version of prawn.
Since i also needed header and footer i looked a bit more into this. It seems that that version of prawn had header and footer methods, which were implemented using lazy bounding box. (found by checking the code on github)
In the new prawn version you can do the same thing using repeaters.
Here is the full sample rewritten using the new version:
require "#{File.dirname(__FILE__)}/../example_helper.rb"
Prawn::Document.generate("test.pdf") do
repeat :all do
# header
bounding_box [bounds.left, bounds.top], :width => bounds.width do
font "Helvetica"
text "Here's My Fancy Header", :align => :center, :size => 25
stroke_horizontal_rule
end
# footer
bounding_box [bounds.left, bounds.bottom + 25], :width => bounds.width do
font "Helvetica"
stroke_horizontal_rule
move_down(5)
text "And here's a sexy footer", :size => 16
end
end
bounding_box([bounds.left, bounds.top - 50], :width => bounds.width, :height => bounds.height - 100) do
text "this is some flowing text " * 200
move_down(20)
font "#{Prawn::BASEDIR}/data/fonts/DejaVuSans.ttf"
table [["ὕαλον ϕαγεῖν", "baaar", "1" ],
["This is","a sample", "2" ],
["Table", "dont\ncha\nknow?", "3" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules\nwith an iron fist", "x" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ],
[ "It", "Rules", "4" ]],
:font_size => 24,
:horizontal_padding => 10,
:vertical_padding => 3,
:border_width => 2,
:position => :center,
:headers => ["Column A","Column B","#"]
end
end
you can check the documentation page of repeat for other options which allow you to exactly specify where you want the repeaters.
#GrantSayer thx for the example, but this will only let you show the current page number, not the total number of pages.
You can also use the number_pages function for the footer:
Prawn::Document.generate("page_with_numbering.pdf") do
text "Hai"
start_new_page
text "bai"
start_new_page
text "-- Hai again"
number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
end
However, in my case I also need to format/style and right align the page numbers to match company style guides. I used go_to_page(k) to create my own header and footer functions, which add the header and footer to each page after all the pages are created. This gives me both styling options and the total number of pages:
Prawn::Document.generate("footer_example.pdf", :skip_page_creation => true) do
10.times do
start_new_page
text "Some filler text for the page"
end
# footer
page_count.times do |i|
go_to_page(i+1)
lazy_bounding_box([bounds.right-50, bounds.bottom + 25], :width => 50) {
text "#{i+1} / #{page_count}"
}.draw
end
end
It's little bit different with latest version of Prawn you must passe an hash
Prawn::Document.generate("page_with_numbering.pdf") do
text "Hai"
start_new_page
text "bai"
start_new_page
text "-- Hai again"
number_pages "<page> in a total of <total>", { :start_count_at => 0, :page_filter => :all, :at => [bounds.right - 50, 0], :align => :right, :size => 14 }
end
If you want a footer that do not write stuff over your text, you have to create the bounding_box below the margin of the document using bounds.bottom.
require 'prawn'
file_name = 'hello.pdf'
random_table = (0..50).map{|i|[*('a'..'z')]} # generate a 2D array for example (~2 pages)
Prawn::Document::generate(file_name) do |pdf|
pdf.table random_table
pdf.page_count.times do |i|
pdf.bounding_box([pdf.bounds.left, pdf.bounds.bottom], :width => pdf.bounds.width, :height => 30) {
# for each page, count the page number and write it
pdf.go_to_page i+1
pdf.move_down 5 # move below the document margin
pdf.text "#{i+1}/#{pdf.page_count}", :align => :center # write the page number and the total page count
}
end
end
It should look like that, you can see that the footer is outside the margin bottom :
Hope it help someone
START EDIT
This works in prawn >= 0.12
END EDIT
Here is my solution using repeat, canvas and cell. Essentially I'm drawing my bounding boxes at the absolute top and bottom of every page. I'm using cell to have better styling control over it. Hope this is going to be helpful to someone. ( I used slightly annoying colors to better illustrate how you can control styling of header and footer)
Prawn::Document.generate("headers_and_footers_with_background.pdf") do
repeat :all do
# header
canvas do
bounding_box([bounds.left, bounds.top], :width => bounds.width) do
cell :content => 'Header',
:background_color => 'EEEEEE',
:width => bounds.width,
:height => 50,
:align => :center,
:text_color => "001B76",
:borders => [:bottom],
:border_width => 2,
:border_color => '00FF00',
:padding => 12
end
end
# footer
canvas do
bounding_box [bounds.left, bounds.bottom + 50], :width => bounds.width do
cell :content => 'Footer',
:background_color => '333333',
:width => bounds.width,
:height => 50,
:align => :center,
:text_color => "FFFFFF",
:borders => [:top],
:border_width => 2,
:border_color => 'FF0000',
:padding => 12
end
end
end
# body
bounding_box([bounds.left, bounds.top - 25], :width => bounds.width, :height => bounds.height - 50) do
100.times do
text "Some filler text for the page"
end
end
end
here's the problem when using the bounding_box for creating the custom footer contents... it is still rendering within the bounds of a margin.
I was looking for something that will write the contents in the margin area together with number_pages.(because a footer usually is set in the bottom margin area)... and it seems that there were none.
so instead, I used text_box and place the coordinates outside my main bounding box like so:
repeat :all do
text_box "My custom footer", size: 7, align: :center, :at => [bounds.left, bounds.bottom], :height => 100, :width => bounds.width
end
take note that the repeat :all , will render this footer text to every page.
The only way I've found to get a repeating item on a page is to use the Prawn::Document::LazyBoundingBox method. Basically this allows you to define a bounding box that is only rendered once you call it. So the usual pseudo-code steps are
Define lazy bounding box element assign to somevar
On each new page call the element.
The example from the source shows how
file = "lazy_bounding_boxes.pdf"
Prawn::Document.generate(file, :skip_page_creation => true) do
point = [bounds.right-50, bounds.bottom + 25]
page_counter = lazy_bounding_box(point, :width => 50) do
text "Page: #{page_count}"
end
10.times do
start_new_page
text "Some filler text for the page"
page_counter.draw
end
end
This gives you a Page-count text output on each new page. What would be ideal is if this could be applied in the setup of a page template that is reused without the manual call to render the element. Combined with text flow this would give the traditional solution of headers and footers.

Resources