PRAWN - Two variables in same column - ruby-on-rails

I wish to have a Prawn PDF which will contain a column containing two variables. Here is my table definition:
def transactions_table
grid([3,0], [14,3]).bounding_box do
data = [%w(Date Description Amount)]
data += #rows.map{|r| [r.value_date, r.description, r.amount, r.credit]}
options = { header: true, width: 520,
column_widths: {0 => 100, 2 => 200},
row_colors: ['EEEEEE', 'FFFFFF']}
table(data, options) do
cells.padding = 5
cells.border_width = 0.5
cells.border_color = BLACK
row(0).font_weight = 'bold'
row(0).border_color = BLACK
column(2).align = :right
end
end
end
As you can see there are four columns value_date, description, amount and credit. I want to have amount and credit in the same column, however, I'm not sure how to do this. Simply removing the comma does not work. Is there a joiner could someone fill me in. Thanks.

You can combine the two values in one string, for example with string interpolation.
data += #rows.map{|r|
[r.value_date, r.description, "#{r.amount} #{r.credit}"]
}

You can join two strings with the + operator. I'm not sure if the values you want to join are strings, but you can make sure they are with the to_s method:
data += #rows.map{|r| [r.value_date, r.description, r.amount.to_s + ' ' + r.credit.to_s]}

The best solution I could come up with is was to remove borders:
column(3).borders = [:top, :right, :bottom]
column(2).borders = [:top, :left, :bottom]

Related

Prawn/PDF: Multiple formats/text on one line?

When I try the following code:
text "Hello "
text "World"
They render Hello on top of World instead of World right after Hello. I have some complicated formatting (highlighting, different font sizes etc) on text that I need on one line. I know that the :inline_formatting option exists but it seems this is too complicated to use that option.
I have the following code:
highlight_callback.rb:
class HighlightCallback
def initialize(options)
#color = options[:color]
#document = options[:document]
end
def render_behind(fragment)
original_color = #document.fill_color
#document.fill_color = #color
#document.fill_rectangle(fragment.top_left,
fragment.width,
fragment.height)
#document.fill_color = original_color
end
end
order.pdf.prawn:
highlight = HighlightCallback.new(:color => 'ffff00', :document => self)
#code....
text "Authorized Signature: "
formatted_text [{:text => "_" * 15, :callback => highlight }], :size => 20
which is producing the attached image. How can I get the signature line on the same level as the text?
Ruby 2.5.1
Rails 5.2.0
It's enough to change method text to text_box, i.e.:
bounding_box([0, cursor], width: 540, height: 40) do
stroke_color 'FFFF00'
stroke_bounds
date = 'Date: '
text_box date, style: :bold
text_box DateTime.now.strftime('%Y/%m/%d'), at: [bounds.left + width_of(date), cursor]
text_box "Signature ________________", align: :right
end
Example:
To place text at a exact position you can use text_box with the option :at.
You can get the width of your text with pdf.width_of(str) (use the same style optione :size etc. otherwise it will use the default settings to calculate)

Mechanize gem string to decimal over 1000 issue

I'm trying to use mechanize to pull some prices and I'm to the point where I can handle anything priced under $1,000 but as soon as anything hits above $1,000, the result is that I lose any integers after the thousands integer. For instance, $1,234.99 becomes $1.00.
This is what I have in my controller:
product_price = page.search(store.price_selector).first.text.match(/\b\d[\d,.]*\b/)
product = Product.create!(
price: product_price
...
)
Price is a decimal i.e.
:price, :decimal, :precision => 8, :scale => 2
Displaying in my Product view as such:
<h4><%= number_to_currency(product.price, :unit => "$", :separator => ".") %></h4>
Here is what I tried to use as a fix in the controller - converting to a string first:
product_price_str = page.search(store.price_selector).first.text.match(/\b\d[\d,.]*\b/).to_s
product_price_str = product_price_str.split(".")[0]
product_price = product_price_str.scan(/\d/).join('')
This solved the problem, only to create another - Now any price with a number other than zero for the decimal is rounded down to zero no matter what. For instance, 1,800.99 is displaying as 1,800.00
I know it's an easy answer (at least I hope), but I'm so new to this whole specific combination and rails in general so any help you've got would be much appreciated.
I'm not sure I understand your issue completely, but why not tweak your regex on data import so you have the price and only the price in a format suitable for inserting into your database?
> s = "or instance, $1,234.99 beco"
=> "or instance, $1,234.99 beco"
> s.match(/\$\d[\d,.]+/).to_s.gsub(/[^\d.]/, '')
=> "1234.99"
Here is what I did in the controller to solve the issue, at least for now:
product_price_str = page.search(store.price_selector).first.text.match(/\b\d[\d,.]*\b/).to_s
product_price_str = product_price_str.split(".")[0]
product_price = product_price_str.scan(/\d/).join('')
Changed to
product_price_str_decimal = "00"
product_price_str_decimal = product_price_str.split(".")[1] if product_price_str.split(".")[1]
product_price_str = product_price_str.split(".")[0]
product_price = product_price_str + "." + product_price_str_decimal

Incrementing iteration through a hash in Ruby

What is the best way to incrementally iterate through a pair of hashes in Ruby? Should I convert them to arrays? Should I go an entirely different direction? I am working on a problem where the code is supposed to determine what to bake, and in what quantities, for a bakery given 2 inputs. The number of people to be fed, and their favorite food. They bake 3 things (keys in my_list) and each baked item feeds a set number of people (value in my_list).
def bakery_num(num_of_people, fav_food)
my_list = {"pie" => 8, "cake" => 6, "cookie" => 1}
bake_qty = {"pie_qty" => 0, "cake_qty" => 0, "cookie_qty" => 0}
if my_list.has_key?(fav_food) == false
raise ArgumentError.new("You can't make that food")
end
index = my_list.key_at(fav_food)
until num_of_people == 0
bake_qty[index] = (num_of_people / my_list[index])
num_of_people = num_of_people - bake_qty[index]
index += 1
end
return "You need to make #{pie_qty} pie(s), #{cake_qty} cake(s), and #{cookie_qty} cookie(s)."
end
The goal is to output a list for the bakery that will result in no uneaten food. When doing the math, the modulo would then be divided into the next food item.
Thanks for the help.
What is the best way to incrementally iterate through a pair of hashes in Ruby?
Since the keys of bake_qty conveniently have a '_qty' appended to them from their corresponding keys in my_list, you can use this to your advantage:
max_value = my_list[fav_food]
my_list.each do |key,value|
next if max_value < value
qty = bake_qty[key+'_qty']
...
end
You could use 'inject' method.
until num_of_people == 0
num_of_people = my_list.inject(num_of_people) do |t,(k,v)|
if num_of_people > 0
bake_qty["#{key}_qty"] += num_of_people/v
t - v
end
end
You can sort your hash at the beginning to ensure that your first food is the fav food

Rails: adding to last object if conditions met

I've got a method that scans an HTML string and sort of formats it for prawnpdf:
def format_for_prawn(pdf, string, colour)
body = Nokogiri::HTML::DocumentFragment.parse(string)
result = body.xpath('./*|./text()')
result.each do |breaker|
if breaker.name == "h3"
pdf.fill_color colour
pdf.text breaker.text.to_s, :size => 16
pdf.move_down 5
else
pdf.fill_color '#444444'
pdf.text breaker.text.to_s, :size => 10, :leading => 1
pdf.move_down 10
end
end
end
It works great for <h3>s. In the event that some mid-paragraph <b> (or similar) tags are found it starts a new paragraph because that's where Nokogiri broke the string--which is the correct behaviour.
How could I add the bolded string to the last pdf.text function instead of calling a new pdf.text which results in a new paragraph?
I thought about making an array out of it all but then it'll be out of order with the <h3>s.
Any help would be appreciated.
My first thought was to do a negative match :
body.xpath( './node()[not(self::b)]' )
Sadly, this would exclude <b> rather than ignoring it :
> body = Nokogiri::HTML::DocumentFragment.parse %(<h3><b>foo</b></h3><h3>bar</h3>fooz<b>baz</b>whatever); true
> body.xpath( './node()[not(self::b)]' ).to_a
[
[0] <h3>
<b>foo</b>
</h3>,
[1] <h3>bar</h3>,
[2] fooz,
[3] whatever
]
So, you'll have no choice but using a buffer, here : we can iterate through nodes first, to populate a buffer regarding if we should have a new line or not, then iterate this buffer to have your lines added to pdf :
buffer = []
body.xpath( './node()' ).each do |node|
if %w[text b].include? node.name
# add to previous line or create one
buffer << [] unless buffer.count
buffer.last << { node: node }
else
# set content and create a new line
buffer << [ { node: node, title: node.name == 'h3' } ]
buffer << []
end
end
# Now, each first level item in buffer is a line,
# containing elements we just have to concatenate text of
# to pass to `pdf#text`
buffer.each do |line|
text = line.map do |part|
node = part[ :node ]
inner = node.text.to_s
# restore <b> tag if you want bold style in pdf
node.name == 'b' ? "<b>#{inner}</b>" : inner
end.join
if line.first
if line.first[ :title ]
pdf.fill_color colour
pdf.text text, :size => 16
pdf.move_down 5
else
pdf.fill_color '#444444'
# inline_format ensure basic html formating is used, <b> in our case
# See http://prawn.majesticseacreature.com/docs/0.11.1/Prawn/Text.html#method-i-text
pdf.text text, size: 10, leading: 1, inline_format: true
pdf.move_down 10
end
end
end
Of course, all of this is considering you do not control original html. Else, you should place your text nodes inside <p> or something, and there would not be problems anymore.

Return string from multiple array items

I have multiple arrays which have code string items in them. I need to match the code from a given string and then return a class name from the matched array.
Might be better if I show you what I've got. So below are the arrays and underneath this is the string I need to return if the given string matches an item from within the array. So lets say I send a string of '329' this should return 'ss4' as a string:
['392', '227', '179', '176']
= 'ss1'
['389', '386']
= 'ss2'
['371', '338', '335']
= 'ss3'
['368', '350', '332', '329', '323', '185', '182']
= 'ss4'
I need to know what would be the best approach for this. I could create a helper method and have an array for each code block and then check each array to see if the given string code is contained and then return the string, which could be ss1 or ss4. Is this a good idea?
The most efficient approach would be to generate a translator hash once that can perform the lookup super fast:
CODES = {
ss1: ['392', '227', '179', '176'],
ss2: ['389', '386'],
ss3: ['371', '338', '335'],
ss4: ['368', '350', '332', '329', '323', '185', '182']
}
translator = CODES.each_with_object({}){|(s, a), m| a.each{|n| m[n] = s.to_s}}
Now you can simply do:
translator['329']
=> "ss4"
translator['389']
=> "ss2"
def code_to_string(code)
if [395].include? code
"ss1"
elsif [392, 227, 179, 176].include? code
"ss2"
# and so on
end
Note that the codes are integers. to match with a string code, use %w(392 227 179).include? instead of the array
Here's one solution you could try:
CODE_LOOKUP = {
[395] => 'ss1',
[392, 227, 179, 176] => 'ss2',
[389, 386] => 'ss3'
# etc
}
def lookup_code(code)
CODE_LOOKUP.each do |codes_to_test, result|
return result if codes_to_test.include?(code)
end
end
lookup_code(395)
# => "ss1"
lookup_code(179)
# => "ss2"
h = {:ss1 => [395],:ss2 => [392, 227, 179, 176] }
h.key(h.values.find{|x| x.include? "392".to_i})
#=> :ss2
I'd recommend joining all the arrays into a multi-dimensional hash and then searching that.
a1 = ['395']
a2 = ['392', '227', '179', '176']
h = { a1: a1, a2: a2 }
h.select {|a, v| a if v.include?('392') }.keys.first.to_s

Resources