Issue including calendar attachment in Mandrill Mailer and Rails - ruby-on-rails

I'm currently using the icalendar gem to create a new ical calendar and then send it via the mandrill_mailer gem as an attachment. I've tried a variety of different methods - so far I believe I've gotten closest with:
Event.rb
require 'base64'
def self.export_events(user)
#event = Event.last
#calendar = Icalendar::Calendar.new
event = Icalendar::Event.new
event.summary = #event.title
event.dtstart = #event.start_time.strftime("%Y%m%dT%H%M%S")
event.dtend = #event.end_time.strftime("%Y%m%dT%H%M%S")
event.description = #event.desc
event.location = #event.location
#calendar.add_event(event)
encoded_cal = Base64.encode64(#calendar.to_ical)
CalendarMailer.send_to_ical(user, encoded_cal).deliver
end
calendar_mailer.rb
class CalendarMailer < MandrillMailer::TemplateMailer
default from: "blah#blah.com"
# iCal
def send_to_ical(user, encoded_cal)
mandrill_mail template: "ical-file",
subject: "Your iCal file",
to: { email: user.email, name: user.name },
inline_css: true,
async: true,
track_clicks: true,
attachments: [
{
type: "text/calendar",
content: encoded_cal,
name: "calendar.ics",
}
]
end
end
I know my mailer stuff is set up correctly since I'm able to send other types of transactional emails successfully. Also, according to this S.O. post I can't send it directly as a .ics file which is why I'm sending the base64 encoded version of it. Here is the error I keep getting regardless of what I do (whether it's the above or creating a tmp file and opening/reading the newly created tmp file in calendar_mailer.rb):
TypeError: no implicit conversion of nil into String
from /usr/local/rvm/rubies/ruby-2.0.0-p481/lib/ruby/2.0.0/base64.rb:38:in pack'
from /usr/local/rvm/rubies/ruby-2.0.0-p481/lib/ruby/2.0.0/base64.rb:38:inencode64'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/mandrill_mailer-0.4.13/lib/mandrill_mailer/core_mailer.rb:263:in block in mandrill_attachment_args'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/mandrill_mailer-0.4.13/lib/mandrill_mailer/core_mailer.rb:258:inmap'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/mandrill_mailer-0.4.13/lib/mandrill_mailer/core_mailer.rb:258:in mandrill_attachment_args'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/mandrill_mailer-0.4.13/lib/mandrill_mailer/template_mailer.rb:191:inmandrill_mail'
from /Users/alansalganik/projects/glyfe/app/mailers/calendar_mailer.rb:8:in send_to_ical'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/mandrill_mailer-0.4.13/lib/mandrill_mailer/core_mailer.rb:283:incall'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/mandrill_mailer-0.4.13/lib/mandrill_mailer/core_mailer.rb:283:in method_missing'
from (irb):763
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/railties-4.1.1/lib/rails/commands/console.rb:90:instart'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/railties-4.1.1/lib/rails/commands/console.rb:9:in start'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:69:inconsole'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/railties-4.1.1/lib/rails/commands/commands_tasks.rb:40:in run_command!'
from /usr/local/rvm/gems/ruby-2.0.0-p481#rails-4.0.2/gems/railties-4.1.1/lib/rails/commands.rb:17:in'
from bin/rails:4:in `require'
Thanks in advance.

Probably not the best code in the world, but an example:
class Outlook
def self.create_cal
#calendar = Icalendar::Calendar.new
event = Icalendar::Event.new
event.summary = "SUMMARY"
event.dtstart = Time.now.strftime("%Y%m%dT%H%M%S")
event.dtend = (Time.now + 1.hour).strftime("%Y%m%dT%H%M%S")
event.description = "DESC"
event.location = "Holborn, London WC1V"
#calendar.add_event(event)
return #calendar.to_ical
end
end
And
ics_file = Outlook.create_cal
mandrill_mail(
(...)
attachments: [
{ content: ics_file, name: 'ical.ics', type: 'text/calendar' }
]
)

Related

Refactoring an API request on Rails

I have a rails worker using redis/sidekiq where I send some data to an API (Active Campaign), so I normally use all the http configurations to send data. I want to have it nice and clean, so it's part of a refactor thing. My worker currently looks like this:
class UpdateLeadIdWorker
include Sidekiq::Worker
BASE_URL = Rails.application.credentials.dig(:active_campaign, :url)
private_constant :BASE_URL
API_KEY = Rails.application.credentials.dig(:active_campaign, :key)
private_constant :API_KEY
def perform(ac_id, current_user_id)
lead = Lead.where(user_id: current_user_id).last
url = URI("#{BASE_URL}/api/3/contacts/#{ac_id}") #<--- need this endpoint
https = bindable_lead_client.assign(url)
pr = post_request.assign(url)
case lead.quote_type
when 'renter'
data = { contact: { fieldValues: [{ field: '5', value: lead.lead_id }] } }
when 'home'
data = { contact: { fieldValues: [{ field: '4', value: lead.lead_id }] } }
when 'auto'
data = { contact: { fieldValues: [{ field: '3', value: lead.lead_id }] } }
else
raise 'Invalid quote type'
end
pr.body = JSON.dump(data)
response = JSON.parse(https.request(pr).read_body).symbolize_keys
if response.code == '200'
Rails.logger.info "Successfully updated contact #{ac_id} with lead id #{lead.lead_id}"
else
raise "Error creating contact: #{response.body}"
end
end
def bindable_lead_client
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http
end
def post_request
post_request_ = Net::HTTP::Put.new(url)
post_request_['Accept'] = 'application/json'
post_request_['Content-Type'] = 'application/json'
post_request_['api-token'] = API_KEY
post_request_
end
end
But whenever I run this I get:
2022-07-28T00:52:08.683Z pid=24178 tid=1s1u WARN: NameError: undefined local variable or method `url' for #<UpdateLeadIdWorker:0x00007fc713442be0 #jid="e2b9ddb6d5f4b8aecffa4d8b">
Did you mean? URI
I don't want everything stuck in one method. How could I achieve to make this cleaner?
Thanks.
Pure ruby wise, The reason you get the error is because your method definition bindable_lead_client is missing the url argument. Hence undefined variable.
So def should look something like:
def bindable_lead_client (url)
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http
end
and call:
bindable_lead_client(url)
As for how to make this code better, falls under question being too subjective under StackOverflow guidelines, which encourage you to ask more specific questions.

Ruby On Rails: Make controller smaller

So, I have this controller, it gets the data from the uploaded file and save it to the right models. I made it work, but it is gigantic. I would like some directions of how to make it smaller. Should I create a class to extract the data and save to the db?
class UploadsController < ApplicationController
require 'csv'
def save_info_to_db
file = params[:file]
purchases_made = []
CSV.open(file.path, "r", { col_sep: "\t", headers: true }).each do |row|
data = Hash.new
data = {
purchaser: { name: row[0] },
item: { description: row[1], price: row[2], quantity: row[3] },
merchant: { name: row[4], address: row[5] }
}
merchant = Merchant.create_with(address: data[:merchant_address]).find_or_create_by(name: data[:merchant][:name])
item = merchant.items.create_with(price: data[:item][:price]).find_or_create_by(description: data[:item][:description])
purchaser = Purchaser.create(name: data[:purchaser][:name])
purchase = purchaser.purchases.create
purchase_items = PurchaseItem.create(item_id: item.id, quantity: data[:item][:quantity])
purchase.add_purchase_items(purchase_items)
purchases_made << purchase.total_price
end
session[:total_gross_income] = Purchase.total_gross_income(purchases_made)
redirect_to display_incomes_path
end
end
Thank you!
Lots of ways to do this, depending on what you prefer, but one idea is to use a Service class. In the service, you could handle the file processing.
Your controller would then end up looking something like:
class UploadsController < ApplicationController
require 'csv'
def save_info_to_db
file_service = FileProcessingService.new(file)
file_service.save_info!
session[:total_gross_income] = Purchase.total_gross_income(file_service.purchases_made)
redirect_to display_incomes_path
rescue
# perhaps some rescue logic in the event processing fails
end
All your file processing logic would go in the Service. You could even split that logic up -- logic for getting the data from the file, then separate logic for saving the data.
Lots of options, but the general idea is you can use a service to handle logic that may span multiple models.

how to configure sensu keepalive to throw an alert to slack

I want to send out an slack alert. when a client fails a keep alive check.
What is the process to do it? can I know how to do it? I am using hiroakis/docker-sensu-server docker image.
On the slack side:
on the slack side you have to create a new incoming webhook to your desired channel.
On the sensu side:
you create a new handler that uses the webhook.
then you have to assign this handler to be used for the checks you desire in their check configuration file.
If you need a proxy to connect to the internet keep in mind to put that one in the handler as well or in a more elegant way pass it on via the config file.
eg. you can use this handler:
#!/usr/bin/env ruby
# Copyright 2014 Dan Shultz and contributors.
#
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#
# In order to use this plugin, you must first configure an incoming webhook
# integration in slack. You can create the required webhook by visiting
# https://{your team}.slack.com/services/new/incoming-webhook
#
# After you configure your webhook, you'll need the webhook URL from the integration.
require 'rubygems' if RUBY_VERSION < '1.9.0'
require 'sensu-handler'
require 'json'
class Slack < Sensu::Handler
option :json_config,
description: 'Configuration name',
short: '-j JSONCONFIG',
long: '--json JSONCONFIG',
default: 'slack'
def slack_webhook_url
get_setting('webhook_url')
end
def slack_channel
get_setting('channel')
end
def slack_proxy_addr
get_setting('proxy_addr')
end
def slack_proxy_port
get_setting('proxy_port')
end
def slack_message_prefix
get_setting('message_prefix')
end
def slack_bot_name
get_setting('bot_name')
end
def slack_surround
get_setting('surround')
end
def markdown_enabled
get_setting('markdown_enabled') || true
end
def incident_key
#event['client']['name'] + '/' + #event['check']['name']
end
def get_setting(name)
settings[config[:json_config]][name]
end
def handle
description = #event['check']['notification'] || build_description
post_data("*Check*\n#{incident_key}\n\n*Description*\n#{description}")
end
def build_description
[
#event['check']['output'].strip,
#event['client']['address'],
#event['client']['subscriptions'].join(',')
].join(' : ')
end
def post_data(notice)
uri = URI(slack_webhook_url)
if (defined?(slack_proxy_addr)).nil?
http = Net::HTTP.new(uri.host, uri.port)
else
http = Net::HTTP::Proxy(slack_proxy_addr, slack_proxy_port).new(uri.host, uri.port)
end
http.use_ssl = true
begin
req = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
text = slack_surround ? slack_surround + notice + slack_surround : notice
req.body = payload(text).to_json
response = http.request(req)
verify_response(response)
rescue Exception => e
puts "An error has ocurred when posting to slack: #{e.message}"
end
end
def verify_response(response)
case response
when Net::HTTPSuccess
true
else
fail response.error!
end
end
def payload(notice)
{
icon_url: 'http://sensuapp.org/img/sensu_logo_large-c92d73db.png',
attachments: [{
text: [slack_message_prefix, notice].compact.join(' '),
color: color
}]
}.tap do |payload|
payload[:channel] = slack_channel if slack_channel
payload[:username] = slack_bot_name if slack_bot_name
payload[:attachments][0][:mrkdwn_in] = %w(text) if markdown_enabled
end
end
def color
color = {
0 => '#36a64f',
1 => '#FFCC00',
2 => '#FF0000',
3 => '#6600CC'
}
color.fetch(check_status.to_i)
end
def check_status
#event['check']['status']
end
end
and then pass a config file like this on to it
{
"handlers": {
"slack": {
"command": "/etc/sensu/handlers/slack.rb",
"type": "pipe",
"filters": [
],
"severities": [
"ok",
"critical"
]
}
}
}
which then would also include which severities to be handled by that handler

dynamically create an ical / ics file from a rails model

Given that I have the following data for an event as a hash in ruby on rails :
event = {
start_at: Time.now(),
end_at: Time.now() + 3600,
summary: 'My meeting',
description: 'A zoom meeting about food',
event_url: 'https://food.example.com',
formatted_address: ''
}
How do I deliver that info to a user a dynamically created an ical/ics file?
The icalendar gem works well. https://github.com/icalendar/icalendar
I use this to generate outlook and ical versions. Works great.
cal = Icalendar::Calendar.new
filename = "Foo at #{foo.name}"
if params[:format] == 'vcs'
cal.prodid = '-//Microsoft Corporation//Outlook MIMEDIR//EN'
cal.version = '1.0'
filename += '.vcs'
else # ical
cal.prodid = '-//Acme Widgets, Inc.//NONSGML ExportToCalendar//EN'
cal.version = '2.0'
filename += '.ics'
end
cal.event do |e|
e.dtstart = Icalendar::Values::DateTime.new(foo.start_at, tzid: foo.time_zone)
e.dtend = Icalendar::Values::DateTime.new(foo.end_at, tzid: foo.course.time_zone)
e.summary = foo.summary
e.description = foo.description
e.url = event_url(foo)
e.location = foo.formatted_address
end
send_data cal.to_ical, type: 'text/calendar', disposition: 'attachment', filename: filename

Sending HTML email using gmail API in ruby

I am creating a ruby script and it should do the above. Over the day I was trying to crack I way to send an HTML email to a selected number of emails addresses. There is no clear documentation on how I should do, So please I will appreciate you helping.
Here is my code, The script is successfully authorizing a user and picking the code to access his/her gmail account. Now I want to send the HTML email on behalf of that user.
require 'rubygems'
require 'google/api_client'
require 'launchy'
CLIENT_ID = 'my_app_Id_on_gmail_developers_console'
CLIENT_SECRET = 'the_secret_key'
OAUTH_SCOPE = 'https://mail.google.com/'
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Create a new API client & load the Google Drive API
client = Google::APIClient.new(:application_name => 'Ruby Gmail sample',
:application_version => '1.0.0')
gmail = client.discovered_api('gmail', "v1")
# Request authorization
client.authorization.client_id = CLIENT_ID
client.authorization.client_secret = CLIENT_SECRET
client.authorization.scope = OAUTH_SCOPE
client.authorization.redirect_uri = REDIRECT_URI
uri = client.authorization.authorization_uri
Launchy.open(uri)
# Exchange authorization code for access token
$stdout.write "Enter authorization code: "
client.authorization.code = gets.chomp
client.authorization.fetch_access_token!
#testing if it is working well by counting the emails.
#emails = client.execute(
api_method: gmail.users.messages.list,
parameters: {
userId: "me"},
headers: {'Content-Type' => 'application/json'}
)
count = #emails.data.messages.count
puts "you have #{count} emails "
# Pretty print the API result
jj #emails.data.messages
how can I do this? is there a way I can an external html file which is the email file to be sent. then I can sent this file using the script?
I partially accept the answer above since you can send an email through STMP pretty easily but with the gmail API it's even easier. According your code it should looks like this:
message = Mail.new
message.date = Time.now
message.subject = 'Supertramp'
message.body = "<p>Hi Alex, how's life?</p>"
message.content_type = 'text/html'
message.from = "Michal Macejko <michal#macejko.sk>"
message.to = 'supetramp#alex.com'
service = client.discovered_api('gmail', 'v1')
result = client.execute(
api_method: service.users.messages.to_h['gmail.users.messages.send'],
body_object: {
raw: Base64.urlsafe_encode64(message.to_s)
},
parameters: {
userId: 'michal#macejko.sk'
},
headers: { 'Content-Type' => 'application/json' }
)
response = JSON.parse(result.body)
For multi-part email with the attachment:
message = Mail.new
message.date = Time.now
message.subject = 'Supertramp'
message.from = "Michal Macejko <michal#macejko.sk>"
message.to = 'supetramp#alex.com'
message.part content_type: 'multipart/alternative' do |part|
part.html_part = Mail::Part.new(body: "<p>Hi Alex, how's life?</p>", content_type: 'text/html; charset=UTF-8')
part.text_part = Mail::Part.new(body: "Hi Alex, how's life?")
end
open('http://google.com/image.jpg') do |file|
message.attachments['image.jpg'] = file.read
end
Just my input. I was able to create a script that emailed html to multiple users in about 100 lines. Without using an api. You need to look into using smtp. It is very simple. You define a server for it to use and then you use it's "send_message" method. Here's a link to a good site! GOOD SITE
I can't post my whole code here for security reasons however this should get you started
class Email_Client
attr_accessor :message_contents, :subject
def initialize(sender_name, receiver_name, sender_email, receiver_email)
#sender_name = sender_name
#receiver_name = receiver_name
#sender_email = sender_email
#receiver_email = receiver_email
end
def send_html
message = <<MESSAGE
From: #{#sender_name} <#{#sender_email}>
To: #{#receiver_name} <#{#receiver_email}>
MIME-Version: 1.0
Content-type: text/html
Subject: #{subject}
#{message_contents}
MESSAGE
Net::SMTP.start('SeRvEr_HeRe') do |smtp|
smtp.send_message message,
#sender_email,
#receiver_email
end
end

Resources