Prevent ActionMailer to strip repeating spaces in plain message - ruby-on-rails

I'm trying to have an table in text mail, so I write some helpers:
module MailerHelper
def field_width(text, width)
' ' * (width - text.length) + text
end
def cell(text, width)
output = '| ' + field_width(text, width-2) + " |\n"
output << '+-' + '-'*(width-2) + '-+'
end
end
Then in view I write it like this:
<%= cell 'Test', 10 %>
But that what I get (according to letter_opener) is:
| Test |
+----------+
As can you see, the spaces that are repeating before Test. My question is how to prevent ActionMailer (or anything else what is destroying my beautiful table) from doing that.
Mailer code:
def remind(client, invoices)
#client = client
#company = #client.company
#invoices = invoices.to_a
days_left = #invoices.first.pay_date - Date.today
message = #client.group.messages.find_by_period days_left.to_i
raise 'No messages for this invoices.' if message.nil?
#template = message.template || if days_left < 0
t 'message.before'
elsif days_left > 0
t 'message.after'
else
t 'message.today'
end
#text = liquid_parse #template
#html = markdown_parse #text
mail(:to => #client.email, :subject => t('message.title'))
end
private
def markdown_parse(text)
markdown = Redcarpet::Markdown.new Redcarpet::Render::HTML,
:autolink => true, :space_after_headers => true
markdown.render text
end
def liquid_parse(text)
renderer = Liquid::Template.parse text
renderer.render 'company' => #company, 'invoice' => #invoice, 'client' => #client
end

I've found bug. It was caused by Premailer what I use to inline CSS in HTML part.
class InlineCSSInterceptor
def self.delivering_email(message)
#message.text_part.body = Premailer.new(message.text_part.body.to_s, with_html_string: true).to_plain_text # this is line causing the problem.
message.html_part.body = Premailer.new(message.html_part.body.to_s, with_html_string: true).to_inline_css
end
end
Mailer.register_interceptor InlineCSSInterceptor

Related

custom will_paginate renderer

Documentation is lacking for will_paginate custom renderers:
There is no documentation how to write your own link renderer, but the source code is pretty self-explanatory. Dive into it, and selectively override methods of the LinkRenderer to adjust them to your needs.
Is there any unofficial documentation?
Found a decent blog post about custom will_paginate renderer
module ApplicationHelper
# change the default link renderer for will_paginate
def will_paginate(collection_or_options = nil, options = {})
if collection_or_options.is_a? Hash
options, collection_or_options = collection_or_options, nil
end
unless options[:renderer]
options = options.merge :renderer => MyCustomLinkRenderer
end
super *[collection_or_options, options].compact
end
end
and then in an initializer
class MyCustomLinkRenderer < WillPaginate::ActionView::LinkRenderer do
def container_attributes
{class: "tc cf mv2"}
end
def page_number(page)
if page == current_page
tag(:span, page, class: 'b bg-dark-blue near-white ba b--near-black pa2')
else
link(page, page, class: 'link ba b--near-black near-black pa2', rel: rel_value(page))
end
end
def gap
text = #template.will_paginate_translate(:page_gap) { '…' }
%(<span class="mr2">#{text}</span>)
end
def previous_page
num = #collection.current_page > 1 && #collection.current_page - 1
previous_or_next_page(num, #options[:previous_label], 'link ba near-black b--near-black pa2')
end
def next_page
num = #collection.current_page < total_pages && #collection.current_page + 1
previous_or_next_page(num, #options[:next_label], 'link ba near-black b--near-black pa2')
end
def previous_or_next_page(page, text, classname)
if page
link(text, page, :class => classname)
else
tag(:span, text, :class => classname + ' bg-dark-blue near-white')
end
end
end
Thanks to previous answer, i wrote this code to use will_paginate with materialize
application_controller.rb
def custom_paginate_renderer
# Return nice pagination for materialize
Class.new(WillPaginate::ActionView::LinkRenderer) do
def container_attributes
{class: "pagination"}
end
def page_number(page)
if page == current_page
"<li class=\"cyan active\">"+link(page, page, rel: rel_value(page))+"</li>"
else
"<li class=\"waves-effect\">"+link(page, page, rel: rel_value(page))+"</li>"
end
end
def previous_page
num = #collection.current_page > 1 && #collection.current_page - 1
previous_or_next_page(num, "<i class=\"material-icons\">chevron_left</i>")
end
def next_page
num = #collection.current_page < total_pages && #collection.current_page + 1
previous_or_next_page(num, "<i class=\"material-icons\">chevron_right</i>")
end
def previous_or_next_page(page, text)
if page
"<li class=\"waves-effect\">"+link(text, page)+"</li>"
else
"<li class=\"waves-effect\">"+text+"</li>"
end
end
end
end
your_controller.rb
# GET /articles/1
def articles
#articles = #articles.paginate(:page => params[:page], :per_page => 20).order(id: :desc)
#custom_paginate_renderer = custom_paginate_renderer
end
your_view.html.erb
<%= will_paginate #articles, renderer: #custom_paginate_renderer %>
Not the most beautiful rails code, but it works
Thanks to the answers which have guided me to write this renderer for Bootstrap 5.
//config/initializers/bootstrap_paginate_renderer.rb
class BootstrapPaginateRenderer < WillPaginate::ActionView::LinkRenderer
def container_attributes
{ class: 'pagination' }
end
def html_container(html)
child = tag(:ul, html, container_attributes)
tag(:nav, child)
end
def page_number(page)
if page == current_page
'<li class="page-item active">' + link(page, page, rel: rel_value(page),class: 'page-link') + '</li>'
else
'<li class="page-item">' + link(page, page, rel: rel_value(page),class: 'page-link') + '</li>'
end
end
def previous_page
num = #collection.current_page > 1 && #collection.current_page - 1
previous_or_next_page(num, '<span aria-hidden="true">«</span>')
end
def next_page
num = #collection.current_page < total_pages && #collection.current_page + 1
previous_or_next_page(num, '<span aria-hidden="true">»</span>')
end
def previous_or_next_page(page, text)
if page
'<li class="page-item">' + link(text, page, class: 'page-link') + '</li>'
else
'<li class="page-item disabled">' + link(text, page, class: 'page-link') + '</li>'
end
end
end
//app/helpers/application_helper.rb
def will_paginate(coll_or_options = nil, options = {})
if coll_or_options.is_a? Hash
options = coll_or_options
coll_or_options = nil
end
unless options[:renderer]
options = options.merge renderer: BootstrapPaginateRenderer
end
super *[coll_or_options, options].compact
end
For anyone looking for a TailwindUI solution, I'm working on one right now using templates as the renderer.
Gist is here.

Rails 5 Prawn pdf show total table values

In my view I have
<h4><%= number_to_currency #grand_total, precision: 0, unit: "EUR ", separator: "," %></h4>
This shows a correct total for a column. #grand_total is defined in my controller and it's the sum of total defined in model.
My model
class Ticketline < ActiveRecord::Base
belongs_to :ticket, :foreign_key => 'TICKET'
belongs_to :product, :foreign_key => 'PRODUCT'
def discount
(self.AMOUNT - self.TOTAL)
end
def total_amount
( pricesell = self.try(:PRICESELL) || 0
units = self.try(:UNITS) || 0
pricesell * units)
end
def total
(
price = self.try(:PRICE) || 0
units = self.UNITS || 0
price * units)
end
def consignor_cost
cost = product.location.try(:DISCOUNT_CONSIGNOR) || 0
cost ? (self.total * cost) : 0
end
def cost_of_goods_sold
cost = product.PRICEBUY || 0
cost ? (cost * self.TOTALUNITS) : 0
end
def gross_profit
(self.total - self.consignor_cost - self.cost_of_goods_sold)
end
class ProductSale < Ticketline
end
end
My controller
class ProductSalesController < TicketlinesController
def index
params.permit!
#q = Ticketline.joins(:product, :product => :location).group(:PRODUCT, :TICKET, :DISCOUNT_CONSIGNOR).select("PRODUCT, DISCOUNT_CONSIGNOR, UNITS, TICKET, SUM(ticketlines.PRICESELL*UNITS) AS AMOUNT, SUM(PRICE*UNITS) AS TOTAL, PRICE, UNITS, ticketlines.PRICESELL, SUM(UNITS) AS TOTALUNITS").ransack(params[:q])
#product_sales = #q.result.paginate(:page => params[:page], :per_page => 30)
#product_salesnp = #q.result
#amount_total = #q.result.map(&:total_amount).sum
#discount_total = #q.result.map(&:discount).sum
#grand_total = #q.result.map(&:total).sum
#consignor_cost_total = #q.result.map(&:consignor_cost).sum
#cost_of_goods_sold_total = #q.result.map(&:cost_of_goods_sold).sum
#gross_profit_total = #q.result.map(&:gross_profit).sum
respond_to do |format|
format.html
format.pdf do
pdf = SalesByProductPdf.new(#product_salesnp)
pdf.render_file "report.pdf"
send_data pdf.render, filename: 'report.pdf', type: 'application/pdf', disposition: 'inline'
end
end
end
end
On the pdf generated by prawn I want to show the same so I tried to enter on the corresponding pdf.rb file:
class SalesByProductPdf < Prawn::Document
include ActionView::Helpers::NumberHelper
def initialize(product_sales)
super()
#product_sales = product_sales
header
text_content
table_content
footer
end
def header
#something
end
def text_content
#something
end
def table_content
#something
end
def footer
text number_to_currency(grand_total, precision: 0, unit: "EUR ", separator: ",")
end
end
which gives me no error but shows no value.
What is the correct syntax?
You can explicitly include the ActionView::Helpers::NumberHelper module or whatever module you want in your prawn file.
Try passing in the #grand_total instance variable in your pdf file's initialize method:
class SalesByProductPdf < Prawn::Document
include ActionView::Helpers::NumberHelper
def initialize(product_salesnp, grand_total)
super()
#product_salesnp = product_salesnp
#grand_total = grand_total
header
text_content
table_content
footer
end
...
def footer
text number_to_currency(#grand_total, precision: 0, unit: "EUR ", separator: ",")
end
end
And pass in #grand_total in your controller too when you create a new Pdf object:
format.pdf do
pdf = SalesByProductPdf.new(#product_salesnp, #grand_total)
pdf.render_file "report.pdf"
send_data pdf.render, filename: 'report.pdf', type: 'application/pdf', disposition: 'inline'
end
Hopefully that should work..

ActionController::ParameterMissing in LoansController#new

I know there are lots of these errors discussed and I have read many of them with no success so this is not a first step by posting. I am a developer but new to Ruby so any guidance would be appreciated thanks!
I have 2 models Loan and Growthyear and Loan has_many :growthyears
Existing loans are displaying and I can edit them but trying to create a new Loan now gets this error ever since I added the nested association growthyears.
I can post the new.html.erb but I don't believe it is even getting that far.
ActionController::ParameterMissing in LoansController#new
param is missing or the value is empty: loan
Extracted source (around line #249):
value
else
raise ParameterMissing.new(key)
end
end
loan.rb
class Loan < ActiveRecord::Base
has_many :growthyears
accepts_nested_attributes_for :growthyears, reject_if: proc { |attributes| attributes['growth'].blank? }, allow_destroy: true
end
growthyear.rb
class Growthyear < ActiveRecord::Base
belongs_to :loan
validates_presence_of :growth
end
loans_controller.rb
class LoansController < ApplicationController
def show
#loan = Loan.find(params[:id])
end
def home
end
def destroy
Loan.find(params[:id]).destroy
flash[:success] = "Loan (" + params[:id] + ") deleted " + Time.now.localtime("+10:00").to_s
redirect_to loans_url
end
def index
#loans = Loan.paginate(page: params[:page], :per_page => 5)
end
def new
#loan = Loan.new(user_params)
#loan.growthyears.build
end
def create
#loan = Loan.new(user_params) # Not the final implementation!
if #loan.save
flash[:success] = "(" + #loan.id.to_s + ") " + #loan.name + " New Loan Saved! " + Time.now.localtime("+10:00").to_s
render 'edit'
else
render 'new'
end
end
def update
#loan = Loan.find(params[:id])
#loan.yeargrowth = params[:yearlygrowth]
if #loan.update_attributes(user_params)
flash[:success] = "(" + #loan.id.to_s + ") " + #loan.name + " Loan updated " + Time.now.localtime("+10:00").to_s
end
redirect_to edit_loan_path(#loan.id)
end
def edit
#loan = Loan.find(params[:id])
flash[:success] = "(" + #loan.id.to_s + ") " + #loan.name + " Loan retrieved " + Time.now.localtime("+10:00").to_s
end
private
def user_params
params.require(:loan).permit(:name, :CCV, :Loan, :IRV, :ODV, :period, :simLowInt, :simHighInt, :bSimActive, :LoanBook, growthyears_attributes: [:growth, :id, :_destroy] )
end
end
you should declare only Loan.new, because in new you only initliazie this object so instead this
def new
#loan = Loan.new(user_params)
#loan.growthyears.build
end
put this:
def new
#loan = Loan.new
#loan.growthyears.build
end

Prawn generating PDF on localhost but not on server

I am working on an Ruby on Rails application which uses prawn to generate PDF's when a user clicks on a link. The functionality works when running the application on the localhost but when i deploy it to the server and click on the link the file doesnt load with the message "Failed to load PDF document".
I have done some research and seen references in other places regarding other documents generating gems but none specific to Prawn. Any requests for additional information will gladly be met.
I think showing the code might help so here goes. It is financial application where a page lists statements with a view button and a pdf icon to view them pdf format. When the user clicks on the pdf icon prawn then comes into play. Hope this help clarify my problem somewhat.
Controller Code:
require 'json'
InternetBanking.controllers :statements do
get :index do
#today = Time.now
render 'statements/listing'
end
get :listing do
#today = Time.now
render 'statements/listing'
end
get :activity, provides: [:json] do
content_type :json
#customer.accounts.map{|x| {x.name => x.activity}}.to_json
end
get :show, map: "/statements/:id", provides: [:html, :pdf] do
begin
m = params[:id].force_encoding('UTF-8').match(/(\d+)-(\w+)-(\d+)/)
accountno = m[1].to_i
month = Date::MONTHNAMES.index(m[2])
year = m[3].to_i
raise Exception if month.nil? or year.nil?
# Fetch account_id from logged in customer, prevent crafted URLs.
#account = #customer.fetch_account(accountno)
raise Exception if #account.nil?
#statement = #account.statement(year, month)
# TODO: Deal with no transactions
if content_type == :pdf
content_type 'application/pdf'
#statement.to_pdf
else # HTML
render 'statements/show'
end
rescue
flash[:error]= 'You cannot have an empty month field'
end
end
end
Model Code:
Statement.rb
class Statement
attr_accessor :opening_balance, :closing_balance
def initialize(acct, year = nil, month = nil)
#db = Database.instance
#account = acct
#year = year
#month = month
#month_name = Date::MONTHNAMES[#month]
end
def to_s
"#{#account.number}-#{#month_name}-#{#year}"
end
def to_pdf
title = #account.name
subtitle = "Account Statement: #{#month_name} #{#year}"
StatementPDF.new(title, subtitle, transactions).render
end
def transactions
return #transactions if #transactions
start = Date.civil(#year, #month).strftime('%Y-%m-%d')
finish = Date.civil(#year, #month, -1).strftime('%Y-%m-%d')
rows = #db.call('IBStatement2', #account.account_id, start, finish)
#transactions = rows.map {|txn| Transaction.new(txn)}
end
end
Statement_pdf.rb
require 'prawn'
class StatementPDF < Prawn::Document
BOX_MARGIN = 36
# Additional indentation to keep the line measure with a reasonable size
INNER_MARGIN = 30
# Vertical Rhythm settings
RHYTHM = 10
LEADING = 2
# Colors
BLACK = "000000"
LIGHT_GRAY = "F2F2F2"
GRAY = "DDDDDD"
DARK_GRAY = "333333"
BROWN = "A4441C"
ORANGE = "F28157"
LIGHT_GOLD = "FBFBBE"
DARK_GOLD = "EBE389"
BLUE = "0000D0"
GREY = "CCCCCC"
def initialize(title, subtitle, rows)
#rows = rows
#title = title
#subtitle = subtitle
super(page_size: 'A4') do
define_grid(columns: 4, rows: 16, gutter: 10)
header
transactions_table
footer
end
end
private
def header
grid([0,0],[1,0]).bounding_box do
image 'public/images/Statement/logo.png', width: 110
end
grid([0,1],[1,1]).bounding_box do
font_size 10
text 'Yada', font_weight: 'bold'
font_size 9
text 'Yada'
text 'Yada'
text 'Yada'
text 'Yada'
text 'Yada'
text 'Yada'
end
grid([0,2],[0,3]).bounding_box do
font_size(20)
text #title, color: '#0044AA', :align => :right
font_size(14)
text #subtitle, color: '#0044AA', :align => :right, :valign => :bottom
end
font_size(12)
end
def footer
grid([15,0],[15,3]).bounding_box do
image 'public/images/statement-logo.png', width: 100
end
end
def transactions_table
grid([2,0], [13,3]).bounding_box do
data = [%w(Date Description Amount Balance)]
data += #rows.map{|r| [r.value_date, r.description, r.amount, r.balance]}
options = { header: true, width: 520,
column_widths: {0 => 100, 2 => 100},
row_colors: ['EEEEEE', 'FFFFFF']}
table(data, options) do
cells.padding = 5
cells.border_width = 0.5
cells.border_color = GREY
row(0).font_weight = 'bold'
row(0).border_color = BLACK
row(1).border_top_color = BLACK
column(2).align = :right
column(3).align = :right
end
end
end
end
View Code:
- content_for :title, 'Full Statement Listing'
- content_for :toolbar do
%a.back(href='/balances') Back
%table
%thead
%tr
%th.year(rowspan=2) Year
%th.month(rowspan=2) Month
%th.accounts{colspan: #customer.accounts.size} Accounts
%tr
- #customer.accounts.each do |account|
%th= account.name
- #today.year.downto(#today.year - 3) do |year|
%tbody
- x = (#today.year == year) ? #today.month : 12
- x.downto(1) do |month|
%tr
- if month == x
%td{rowspan: x}= year
%td= Date::MONTHNAMES[month]
- #customer.accounts.each do |acct|
- if acct.activity[year] && acct.activity[year][month]
%td{'data-transactions' => acct.activity[year][month]}
- stmt = acct.statement(year, month)
= link_to 'View', url(:statements, :show, id: stmt.to_s)
= link_to image_tag('/icons/document-pdf.png', alt: 'Download PDF'), url(:statements, :show, id: stmt, format: :pdf), class: :pdf
- else
%td

call a class from a collection_action in ActiveAdmin (RoR)

I'm new in ruby on rails. I'm trying to call a class from a collection_action in ActiveAdmin. Here is the code(app/admin/models):
collection_action :status_race, :method => :post do
#Do some import work..
redirect_to :class => :import_route
end
And this is the code of the class I want to call(app/lib/route):
class ImportRoute
def initialize
#seperator = " "
#time_format = "%d-%m-%y"
end
def run(filename)
puts "Running route import file"
raise "File" + filename + "doesn't not exist" unless File.exist(filename)
ri = RouteImporter.find(:name => self.class.name)
if(ri.nil?)
puts "Error, file doesn't exists"
end
CSV.foreach(filename, {:col_sep => #seperator}) do |row|
if row.lenght >5
ri.country_name = row[0] + " " + row[1]
ri.type = row[2]
ri.company = row [3]
else
ri.country_name = row[0]
ri.type = row[1]
ri.company = row[2]
ri.date = row[4].gsub(";", " ")
end
end
end
end
I was using redirect_to to call the class but is not working, and I don't have any clue about how to do it. Any idea? Thanks!
This code is taken from http://activeadmin.info/docs/8-custom-actions.html#collection_actions
ActiveAdmin.register Post do
collection_action :import_csv, :method => :post do
# Do some CSV importing work here...
redirect_to {:action => :index}, :notice => "CSV imported successfully!"
end
end
This collection action will generate a route at
“/admin/posts/import_csv” pointing to the
Admin::PostsController#import_csv controller action.
So it means you have to add a method import_csv in app/controllers/admin/posts_controller.rb. Inside this method, you can instantiate your model:
def import_csv
import_route = ImportRoute.new
# do stuff on this object
end
You can easily adapt this to your code

Resources