How to delete all events from Google calendar from specified date? - ruby-on-rails

I'm using googlecalendar plugin for working with Google Calendar with ruby.
The only problem I'm facing is with deleting an event from google calendar.
I found an example to delete event in above mentioned github. But we need to pass event id. How can I use the below code to delete all events from google calendar from specified date?
CODE:
require File.dirname(__FILE__) + '/../shared.rb'
g = GData.new
puts 'login'
token = g.login('REPLACE_WITH_YOUR_MAIL#gmail.com', 'REPLACE_WITH_YOUR_PASSWORD')
puts "token: #{token}"
event = {:title=>'title',
:content=>'content',
:author=>'pub.cog',
:email=>'pub.cog#gmail.com',
:where=>'Toulouse,France',
:startTime=>'2009-06-20T15:00:00.000Z',
:endTime=>'2009-06-20T17:00:00.000Z'}
create_response = g.new_event(event)
puts create_response.body
puts 'delete_event'
# TODO GET id from new_event response
id='http://www.google.com/calendar/feeds/default/private/full/pgvgjdnh43g3bo0emnpfg0gnr4'
response = g.delete_event(id)
puts 'done'
URL:
https://github.com/francisoud/googlecalendar/blob/master/googlecalendar/examples/ruby_standalone/gdata_delete_event.rb
Please share your thoughts if we can do this with other plugins.
Thanks in Advance :)

A search on github brought up this google_calendar gem that seems to be the more popular gem (if not the standard). It is also better documented and organized.
This is what I came up with:
require 'google_calendar'
cal = Google::Calendar.new(:username => 'username#gmail.com',
:password => 'password',
:app_name => 'delete_events')
cal.events.each { |event|
event.delete if Time.parse(event.start_time) >= Time.new(2011,10,14)
}
Time.parse puts each event's start time into a format ruby can understand and then compares it to your given date October 14, 2011. If the event is on or after the event, it is deleted. To delete events before do:
if Time.parse(event.start_time) <= Time.new(2011,10,14)
or for all events on a certain date do:
if Time.parse(event.start_time) == Time.new(2011,10,14)

Related

sending mail at user assigned time and updating status in database in rails error

I want to send mail at remind_at time of each stage. i am using whenever gem to schedule task. There are user model that contains email of multiple user with role manager, and each manager has one_to_many association with project and project has one_to_many association with stage.
while sending email to each user i want to update attribute mail_status of stage and mail subject change to stage.name for each user. how can i achieve such goal??
stage.rb
def check_project_activity
current_date = Time.now.strftime("%Y-%m-%d").to_s
#stages = Stage.all
#stages.each do |stage|
ProjectMailer.activity_reminder(stage).deliver and stage.mail_status = true and stage.save! if stage.remind_at.strftime("%Y-%m-%d").to_s == current_date
end
end
schedule.rb
every 1.day, at: '4:30 am' do
runner 'stage.project_activity_check'
end
activity_mailer.rb
def activity_reminder(stage)
#stage = stage
mail(:to => User.joins(projects: :stages).where(role: 'manager', stages: { remind_at: Time.now.strftime("%Y-%m-%d") }).pluck(:email), :subject => "activity reminder " + stage.name)
end
i took reference from this post - Sending emails on user-selected dates
but my implementation is not working.
Here is code that i used but problem is it sends email once to all user but i want dynamic change is email subjet according to user and also template body changes-
activity_reminder.rb
desc 'activity reminder email'
task activity_reminder_email: :environment do
ProjectMailer.activity_reminder(self).deliver!
end
project_mailer.rb
def activity_reminder(stage)
#stage = stage
mail(:to => User.joins(projects: :stages).where(role: 'manager', stages: { remind_at: Time.now.strftime("%Y-%m-%d") }).pluck(:email), :subject => "Project Activity Remainder")
end
schedule.rb
every 1.day, at: '4:30 am' do
rake 'activity_reminder_email'
end
it works fine but i want email subject as stage.name to be according to each user and also it should loop though each task and update stage.email_status for executed task.
I think you can do some changes:
You're formatting the remind_at values as a stringified version of a Y-m-d date, in that case, you could just compare them as dates. to_date is enough.
Instead of selecting everything and filtering then, you can filter the rows within your SQL query; cast the remind_at to date and use your RDBMS function to get the current date, after that the comparison remains the same.
Left it untouched, but you could probably select the exact columns you're using in your query. This is often preferred so you get what you just need, nothing more, nothing less.
Prefer && over and, since they don't have the same precedence (&& being higher), which may led to inconsistences in your code (this isn't Python).
You can use update instead of a two-lines assignment.
def check_project_activity
Stage.where('remind_at::date = CURRENT_DATE').each do |stage|
ProjectMailer.activity_reminder(stage).deliver
stage.update(mail_status: true)
end
end
remind_at::date = CURRENT_DATE is PostgreSQL specific, but it can be easily adapted.

Handle time zones in a Rails App with Google Calendar API

I am building an application in which users enter data on a form, including a date and time, which then populates their Google Calendar with events based on that information. I can't seem to figure out how to handle time zones, and I'm not sure whether the issue is with Ruby/Rails or the Google Calendar API, both of which have documentation which is... not great.
If I enter 10:00AM on my form, the Google Calendar invite is created at 6:00 AM. I'm at -0400 from UTC, which accounts for that. Ideally, I'd like for the user to not have to worry about time zones at all, but if I have to have my user choose a time zone from a dropdown it's not the end of the world.
Here's my form (which I am free to modify in any way as long as I have a way for the user to enter a time and date):
<%= date_field(:calendar, "start_date", min: Date.today, :value => Time.now.strftime('%Y-%m-%d'), :class=>'form-control') %>
<input type="time" id="calendar_start_time" name="calendar[start_time]" min="9:00" max="18:00" value="10:00" step="900" />
<%= select("calendar", "time_zone", #timezones, {:class=>'form-control'}) %>
And my controller:
def create
calendar_id = params[:calendar][:id]
start_date = params[:calendar][:start_date]
start_time = params[:calendar][:start_time]
time_zone = params[:calendar][:time_zone]
lesson_hash = get_module_lectures(module_number)
# Time.zone = 'America/New_York'
lesson_hash.each do |days_from_start, lesson_name|
lesson_datetime = Time.parse("#{start_date} #{start_time}").advance(:days => days_from_start)
new_event(calendar_id, lesson_datetime, lesson_name, time_zone)
end
redirect_to calendars_url
end
def new_event(calendar_id, lesson_datetime, lesson_name, time_zone)
client = Signet::OAuth2::Client.new(client_options)
client.update!(session[:authorization])
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = client
event = Google::Apis::CalendarV3::Event.new({
# start: Google::Apis::CalendarV3::EventDateTime.new(datetime: lesson_datetime),
# end: Google::Apis::CalendarV3::EventDateTime.new(datetime: lesson_datetime.advance(:hours => 1)),
start: {
'date_time': lesson_datetime,
# 'time_zone': 'America/New_York'
},
end:{
'date_time': lesson_datetime.advance(:hours => 1),
# 'time_zone': 'America/New_York'
},
summary: lesson_name
})
service.insert_event(calendar_id, event)
end
As you can see, I've tried hard-coding the time zone in a few places for testing but none of them seem to have any effect on the events created--they're always offset by 4 hours. I'm using Rails 5.2
The DateTime must be set up with the TimeZone:
lesson_hash.each do |days_from_start, lesson_name|
lesson_datetime = ActiveSupport::TimeZone["America/New_York"].parse("#{start_date} #{start_time}").advance(:days => days_from_start)
new_event(calendar_id, lesson_datetime, lesson_name, time_zone)
end
The time needs to be formatted correctly. Try this:
event = Google::Apis::CalendarV3::Event.new(
start: Google::Apis::CalendarV3::EventDateTime.new(date_time: lesson_datetime.to_datetime.rfc3339),
end: Google::Apis::CalendarV3::EventDateTime.new(date_time: lesson_datetime.advance(:hours => 1).to_datetime.rfc3339),
summary: lesson_name
)
service.insert_event(calendar_id, event)

How do I take this method I defined in the model and have it executed automatically once per day?

This is only the second rails app I've ever created so I new at doing this. If there is a better way of doing things then I'm open to hearing your suggestions but keep in mind that I'm learning right now.
I have a method in my model called twilio_api_call. It uses an api to send a text message to a specified phone number pulled out of the database. I would like this method to be executed once per day at 12pm without any user interaction. I am working off my localhost and plan to deploy this app to Heroku. How can I achieve this goal? Maybe pull out this code and place it in some external file that can be called automatically somehow?
require 'twilio-ruby'
class User < ActiveRecord::Base
has_many :selections
has_many :movies, through: :selection
has_secure_password
def self.get_notifications
Movie.find_by_sql("SELECT u.phone, m.title FROM users AS u INNER JOIN movies AS m ON u.id = m.user_id WHERE m.release_date::date = current_date")
end
def self.twilio_api_call
# get user data to parse and send to Twilio api
data = get_notifications
# put your own credentials here
account_sid = 'insert sid here'
auth_token = 'token goes here'
# data is array of hashes. Parse data.
# loop through data hash, build Twilio code
data.each { | key |
phone_number = key["phone"]
message = "Message from MovieTextAlert - the movie: '" + key["title"] + "' has been released today in theaters."
# set up a client to talk to the Twilio REST API
#client = Twilio::REST::Client.new account_sid, auth_token
#client.account.messages.create({
:from => '+18563176098',
:to => phone_number,
:body => message,
})
}
end
end
You can use whenever gem for such periodic actions
every 1.day, :at => '12:00 pm' do
runner "User.twilio_api_call"
end
This code will execute your method everyday at 12:00 pm.

How to create a multipart ical email? [Rails]

What I want to achieve is the following:
Send an email with delayed_job containing:
plain-text
html (will be displayed by regular clients which don't understand the inline ical)
"inline" ical which is recognized by Outlook and Thunderbird (with Lightning).
a "regular" ical attachment (for #2)
What works so far/what does'nt:
I am able to send the email via delayed_job with all parts, however:
in Apple's Mail 2 attachments show up (instead of one):
(the html is displayed fine)
in Thunderbird (Lightning) I do get an invitation, just like I want. But the Alarm does not show up.
I have to do some REALLY disgusting gsubs on the rendered iCal in order for the ATTENDEES to show up. (see code snippet)
My thinking:
The first thing to keep in mind is: in order to send an email with attachments from delayed_job
To fix this, remember to add this line to your mailer: content_type "multipart/mixed"
As far as I understand the correct MIME-Type hierarchy would therefore be:
multipart/mixed
multipart/alternative
text/plain
text/html
text/calendar (with: method=REQUEST)
application/ics
Warning! code incoming.
I currently construct this email in the following manner:
Edit: I updated the mailer for Rails 4.2 (attachments must be placed before mail)
in my mailer.rb
def invitation_email(...)
subject = "I suck at email..."
attachments["invite.ics"] = { mime_type: "application/ics",
content: ical_attachment }
email = mail(from: me, to: you, subject: subject)
add_ical_part_to(email)
email
end
def add_ical_part_to(mail)
outlook_body = ical_attachment
mail.add_part(Mail::Part.new do
content_type "text/calendar; method=REQUEST"
body outlook_body
end)
end
and this is how I construct the ical attachments:
def ical_attachment
params_participant = {
"ROLE" => "REQ-PARTICIPANT",
"RSVP" => "FALSE",
"PARTSTAT" => "ACCEPTED"
}
params_invite = {
"CUTYPE" => 'INDIVIDUAL',
"ROLE" => "REQ-PARTICIPANT",
"PARTSTAT" => "NEEDS-ACTION",
"RSVP" => "TRUE"
}
cal = Icalendar::Calendar.new
event = Icalendar::Event.new
event.dtstart #party.from.to_datetime, { "VALUE" => "DATE" }
event.dtend #party.to.to_datetime, { "VALUE" => "DATE" }
event.summary #party.title
event.description #party.description
event.klass "PRIVATE"
event.organizer "cn=#{#user.name} #{#user.surname}:mailto:#{#user.email}"
# THIS DOES NOT WORK
event.alarm.trigger = "-PT5M" # 5 Minutes before...
#party.participations.each do |participation|
str = "cn=#{participation.user.name} #{participation.user.surname}:mailto:#{participation.user.email}"
event.add_attendee(str, params_participant)
end
#party.invitations.each do |invitee|
event.add_attendee("mailto:#{invitee.email}", params_invite)
end
cal.add_event(event)
cal.publish
# I KNOW THIS IS HORRIBLE AND I HATE IT, BUT OTHERWISE THE ATTENDEES DO NOT SHOW UP
cal.to_ical.gsub("ORGANIZER:", "ORGANIZER;").gsub("ACCEPTED:", "ACCEPTED;").gsub("TRUE:", "TRUE;").gsub("PUBLISH", "REQUEST")
end
Any help would be really appreciated!
The email that is being generated: http://pastebin.com/patf05zd
Oh and I'm on:
Rails 3.2.13
The Icalendar gem I'm using
In case someone else happens to come across this, here is what I did:
Instead of the icalendar gem I now use ri_cal. Although I was skeptical because the last commit to that repo was 3 years ago, the google group was a very helpful resource.
Here is how I generate the ical attachment (both inline and normal), which seems to be working fine (although it obviously needs some refactoring :))
def to_ical
# this is horrible
klass = self
cal = RiCal.Calendar do
event = event do
organizer "CN=#{klass.user.name} #{klass.user.surname}:mailto:#{klass.user.email}"
summary klass.party.title
description klass.ical_description
dtstart klass.party.from.utc.to_datetime
dtend klass.party.to.utc.to_datetime
location "See url in description"
security_class klass.security_class
# this is horrible
h = self
klass.party.participations.each do |participation|
h.add_attendee klass.prepare_participant(participation)
end
klass.party.invitations.each do |invitee|
h.add_attendee klass.prepare_invitee(invitee.email)
end
unless klass.party.reminder == 0
alarm do
description "Alarm description"
trigger klass.convert_trigger # -PT1H
action "DISPLAY"
end
end
end
end
# THE HORROR
cal.to_s.gsub("ATTENDEE:", "ATTENDEE")
.gsub("ORGANIZER:", "ORGANIZER;")
.gsub("CALSCALE:GREGORIAN", "CALSCALE:GREGORIAN\nMETHOD:REQUEST\n")
end
The 2 Attachments in Apples Mail still show up, I don't think that can be fixed.
Your second B64 encoded attachment contains a lot of garbage towards the end (attendee field).
That would explain the Thunderbird issue.
Please note that some clients will ignore any alarm you may set on a REQUEST: As an organizer, you should not dictate when each attendee should be reminded of the meeting. That would be a rather rude thing to do.
Regarding the Apple iCal issue, there is not much you can do I'm afraid: Some clients want the ics within, some as an attachment so you have to provide both. Does it show the accept/decline panel on iCal ?

Logging Search Results in a Rails Application

We're interested in logging and computing the number of times an item comes up in search or on a list page. With 50k unique visitors a day, we're expecting we could produce 3-4 million 'impressions' per day, which isn't a terribly high amount, but one we'd like to architect well.
We don't need to read this data in real time, but would like to be able to generate daily totals and analyze trends, etc. Similar to a business analytics tool.
We're planning to do this with an Ajax post after the page is rendered - this will allow us to count results even if those results are cached. We can do this in a single post per page, to send a comma delimited list of ids and their positions on the page.
I am hoping there is some sort of design pattern/gem/blog post about this that would help me avoid the common first-timer mistakes that may come up. I also don't really have much experience logging or reading logs.
My current strategy - make something to write events to a log file, and a background job to tally up the results at the end of the day and put the results back into mysql.
Ok, I have three approaches for you:
1) Queues
In your AJAX Handler, write the simplest method possible (use a Rack Middleware or Rails Metal) to push the query params to a queue. Then, poll the queue and gather the messages.
Queue pushes from a rack middleware are blindingly fast. We use this on a very high traffic site for logging of similar data.
An example rack middleware is below (extracted from our app, can handle request in <2ms or so:
class TrackingMiddleware
CACHE_BUSTER = {"Cache-Control" => "no-cache, no-store, max-age=0, must-revalidate", "Pragma" => "no-cache", "Expires" => "Fri, 29 Aug 1997 02:14:00 EST"}
IMAGE_RESPONSE_HEADERS = CACHE_BUSTER.merge("Content-Type" => "image/gif").freeze
IMAGE_RESPONSE_BODY = [File.open(Rails.root + "public/images/tracker.gif").read].freeze
def initialize(app)
#app = app
end
def call(env)
if env["PATH_INFO"] =~ %r{^/track.gif}
request = Rack::Request.new(env)
YOUR_QUEUE.push([Time.now, request.GET.symbolize_keys])
[200, IMAGE_RESPONSE_BODY, IMAGE_RESPONSE_HEADERS]
else
#app.call(env)
end
end
end
For the queue I'd recommend starling, I've had nothing but good times with it.
On the parsing end, I would use the super-poller toolkit, but I would say that, I wrote it.
2) Logs
Pass all the params along as query params to a static file (/1x1.gif?foo=1&bar=2&baz=3).
This will not hit the rails stack and will be blindingly fast.
When you need the data, just parse the log files!
This is the best scaling home brew approach.
3) Google Analytics
Why handle the load when google will do it for you? You would be surprised at how good google analytics is, before you home brew anything, check it out!
This will scale infinitely, because google buys servers faster than you do.
I could rant on this for ages, but I have to go now. Hope this helps!
Depending no the action required to list items, you might be able to do it in the controller and save yourself a round trip. You can do it with an after_filter, to make the addition unobtrusive.
This only works if all actions that list items you want to log, require parameters. This is because page caching ignores GET requests with parameters.
Assuming you only want to log search data on the search action.
class ItemsController < ApplicationController
after_filter :log_searches, :only => :search
def log_searches
#items.each do |item|
# write to log here
end
end
...
# rest of controller remains unchanged
...
end
Otherwise you're right on track with the AJAX, and an onload remote function.
As for processing the you could use a rake task run by a cron job to collect statistics, and possibly update items for a popularity rating.
Either way you will want to read up on the Ruby Logging class. Learning about cron jobs and rake tasks wouldn't hurt either.
This is what I ultimately did - it was enough for our use for now, and with some simple benchmarking, I feel OK about it. We'll be watching to see how it does in production before we expose the results to our customers.
The components:
class EventsController < ApplicationController
def create
logger = Logger.new("#{RAILS_ROOT}/log/impressions/#{Date.today}.log")
logger.info "#{DateTime.now.strftime} #{params[:ids]}" unless params[:ids].blank?
render :nothing => true
end
end
This is called from an ajax call in the site layout...
<% javascript_tag do %>
var list = '';
$$('div.item').each(function(item) { list += item.id + ','; });
<%= remote_function(:url => { :controller => :events, :action => :create}, :with => "'ids=' + list" ) %>
<% end %>
Then I made a rake task to import these rows of comma delimited ids into the db. This is run the following day:
desc "Calculate impressions"
task :count_impressions => :environment do
date = ENV['DATE'] || (Date.today - 1).to_s # defaults to yesterday (yyyy-mm-dd)
file = File.new("log/impressions/#{date}.log", "r")
item_impressions = {}
while (line = file.gets)
ids_string = line.split(' ')[1]
next unless ids_string
ids = ids_string.split(',')
ids.each {|i| item_impressions[i] ||= 0; item_impressions[i] += 1 }
end
item_impressions.keys.each do |id|
ActiveRecord::Base.connection.execute "insert into item_stats(item_id, impression_count, collected_on) values('#{id}',#{item_impressions[id]},'#{date}')", 'Insert Item Stats'
end
file.close
end
One thing to note - the logger variable is declared in the controller action - not in environment.rb as you would normally do with a logger. I benchmarked this - 10000 writes took about 20 seconds. Averaging about 2 milliseconds a write. With the file name in the envirnment.rb, it took about 14 seconds. We made this trade-off so we could dynamically determine the file name - an easy way to switch files at midnight.
Our main concern at this point - we have no idea how many different items will be counted per day - ie. we don't know how long the tail is. This will determine how many rows are added to the db each day. We expect we'll need to limit how far back we keep daily reports and will role up results even further at that point.

Resources