Привет! Так что недавно я закончил запускать один раз в день для публичного использования и попросил нескольких моих друзей пойти проверить это. Если вы тоже это проверили, и я вас лично не знаю, я очень благодарен. Пожалуйста, дайте мне знать, что вы предлагаете: tweet: sprakash24oct. Если вы чувствуете, что это круто, и хотите присоединиться ко мне, чтобы сделать когда-нибудь лучше в качестве проекта с открытым исходным кодом, дайте мне знать.

В этом посте я хочу поговорить об алгоритме и планировщике, который раз в день используется для отправки автоматических постов тем, кто зарегистрировался (я уже упоминал, что публиковать, читать и получать классные посты каждый день в свой почтовый ящик можно бесплатно, чтобы вы могли узнать что-то крутое в вашем плотном графике и вам не нужно просматривать миллионы сайтов? Больной, ха! :))

Требовалось разработать алгоритм, чтобы пользователи получали случайно выбранную непрочитанную публикацию в своем почтовом ящике. Это должен быть избранный пост дня для электронных писем, а также главная страница. Если все сообщения были отправлены пользователям, не отправляйте старые.

Давайте начнем.

Как вы понимаете, у меня уже есть модель публикации и модель пользователя (с использованием Devise).

class Post < ApplicationRecord	

end

class User < ApplicationRecord  
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  validates_presence_of :name    

end

Идея заключалась в следующем:
1. Создать другую таблицу базы данных, в которой я могу хранить ранее выбранные идентификаторы сообщений.
2. Затем сравнить, какие сообщения еще не были выбраны.
3. Возьмите все невыбранные сообщения, а затем случайным образом используйте один.
4. Добавьте идентификатор этого сообщения в выбранную таблицу и отправьте ссылку по электронной почте на сообщение всем пользователям, которые выбрали «Да» при получении электронной почты.

Для выбранной таблицы я создал эту миграцию:

rails g migration CreateMailedPost post_id:integer

and ran: rake db:migrate

Я хотел настроить задачу как задачу rake, поэтому в каталоге lib в подкаталоге задач я создал файл rake: send_emails.rake

task :send_email => :environment do
		

end

Помните, что указание: environment do очень важно, поэтому задача rake будет иметь доступ ко всем моделям и их методам.

Идея дизайна, которую я придумал (которая пришла из чтения классных книг, о которых я писал в блоге), заключалась в том, чтобы иметь минимальные зависимости между классами, следовать принципу единой ответственности, а также улучшать возможность повторного использования кода.

Я решил поместить методы, которые используют несколько классов в вспомогательный модуль приложения, который я могу позже включить в задачи rake, и включил некоторые методы классов в соответствующие модели, которые я буду использовать в моей задаче rake.

Чтобы предоставить доступ к классу, укажите маршрут доступа к помощнику в rake-файле:

Чтобы предоставить доступ к классу, укажите маршрут доступа к помощнику в rake-файле:

require "#{Rails.root}/app/helpers/application_helper"

а затем включите все методы в вспомогательный модуль приложения (в Poodr от Sandi Metz есть действительно классная глава 7 об использовании модулей для наследования, прочтите ее, чтобы узнать поток шаблона шаблона и то, как он работает внутри, я резюмирую об этом в скоро еще один блог)

include ApplicationHelper

Я добавил к классам несколько методов класса:

In Post model:

def self.get_all_post_ids
  return Post.pluck(:id)
end

In User model:

def self.emails_of_all_intrested_users
	users = User.where(:receive_email => true).pluck(:email)
    return users
end

In MailedPost Model,

def self.get_all_mailedpost_ids
    return MailedPost.pluck(:post_id)
end

In application helper, the methods added are:


 def send_email(post, subject, message, user)
      from = '[email protected]'          
      content = "<html><head><style type='text/css'>body,html,.body{background:#D3D3D3!important;}</style></head><body><container><spacer size='16'></spacer><row><columns><center><img class='small-float-center' width='500px' height='300px' src=#{Post.find_by_id(post.id).heroimage}></center></columns></row><row><columns large='8'><center><h2>Once A Day</h2></center></columns></row><row><columns large='6'><center><h4>Hey! Go on..just putting your selected post in your inbox for you to read</h4><br><p>#{message}</p><br><p>Follow: https://www.onceaday.today/subjects/#{post.subject_id}/posts/#{post.id} to learn something cool a user posted for today. </p><center></columns><columns large='6'><br><p>If you have any issues or suggestions, send me an email (just be nice!): </p><br><p>Email:[email protected] </p></columns><columns large='4'><img class='small-float-center' width='100px' height='100px' src='//s3-us-west-2.amazonaws.com/wacbacassetsdonttouch/wacbacassets/onceadaylogo.png' alt='once a day'></columns></row><row></row></container><body></html>"
      @notifier = EmailNotifier.new(from, user, subject, content)
      @notifier.send
end

which is the wrapper method to send emails. This method creates a new EmailNotifier objects and uses send method to send emails to the emails in user array.

Вот файл emailnotifier.rb, который я создал в каталоге lib:

require 'sendgrid-ruby'
require 'json'

class EmailNotifier

	include SendGrid

	attr_accessor :from, :to, :subject, :content

	def initialize(from, to, subject, content)
		@from = from
		@to = to
		@subject = subject
		@content = content		
	end


	def personalize		
		users = to

		email = Mail.new
		email.from = Email.new(email: @from)
		email.subject = subject

		personalization = Personalization.new	
		personalization.add_to(Email.new(email: "someemail"))

		users.each do |user|																
  			personalization.add_bcc(Email.new(email: user))					
		end

		email.add_personalization(personalization)
		email.add_content(Content.new(type: 'text/plain', value: "A new action was taken !!"))
		email.add_content( Content.new(type: 'text/html', value: content))
				
		email.reply_to = Email.new(email: 'someemail')
		
		return email
	end


	
	def send 
		use_sendgrid_to_send_email			
	end

	private

	def use_sendgrid_to_send_email
		begin	
				tosend = personalize

				sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])			
				response = sg.client.mail._('send').post(request_body: tosend.to_json)
				puts response.status_code
				puts response.body
				puts response.headers			
				return true
			rescue 			
				puts "Email Failure"			
				return false
			end	
		end
		
end

Помните, что везде, где ваш код взаимодействует с внешним API, заключите его в блок начала повторного использования. Кроме того, лучше обернуть его только одним методом (предпочтительно частным) в классе и позволить общедоступным методам взаимодействовать с ним, чтобы в будущем вы знали, где делать изменения, в то время как открытый интерфейс вообще не изменяется (и, таким образом, не нарушает что угодно)

Чтобы зарегистрироваться в sendgrid, установите gem «sendgrid-ruby»,

Используйте figaro для создания database.yml и поместите туда свои ключи, чтобы вы могли получить к ним доступ с помощью ENV.

Чтобы добавить его в heroku, из командной строки я использую

heroku config:set TRIALAPI=my_api_name
heroku config:set SEND_GRID_API_KEY_ID=my_key_id
heroku config:set SENDGRID_API_KEY=my_key

Алгоритм

Давайте поговорим о том, как выбирать сообщения, которыми еще не поделились.

В ruby, если у вас есть два массива:

a = [1,2,3] and b = [1,2]

и вы хотите найти элемент, который не является обычным среди них, вы можете использовать

a-b , which would give you [3]

Чтобы справиться со случаями неизвестной разницы в длине,

(a-b) | (b - a) will give you the array of elements not in common.

Я использовал эту логику, чтобы получить идентификаторы тех сообщений, которые еще не были добавлены в таблицу mailed_post, а затем случайным образом выбрать одно сообщение, добавить его в таблицу mailed_post и отправить ссылку для публикации всем пользователям:

Финальное задание на рейк приведено ниже:

require "#{Rails.root}/app/helpers/application_helper"

include ApplicationHelper



task :send_email => :environment do
	
	posts = Post.get_all_post_ids
	mailed_posts = MailedPost.get_all_mailedpost_ids

	uncommon_posts_ids_to_randomize = (posts - mailed_posts) | (mailed_posts - posts)
	p uncommon_posts_ids_to_randomize

	if uncommon_posts_ids_to_randomize.size > 0	

		users = User.emails_of_all_intrested_users	

		post_id = uncommon_posts_ids_to_randomize.sample
		post = Post.find_by_id(post_id)

		begin			
			record = MailedPost.find_or_create_by(post_id: post_id)
	        send_email(post, "Go on..Just putting this cool post: #{post.title} in your inbox.", "Hi! When you are free have a look at : #{post.title}", users)
	    rescue
	    	p "email not sent "
	    end

		puts post_id

	end

end

На рисунке ниже вы можете увидеть uncommon_posts_ids_to_randomize, обозначенный как 1, и случайно выбранный post_id как 2.

Также необходимо протестировать вашу рейк-задачу. Для этого вы можете использовать rspec. Прикольный туториал здесь

В случае, если все сообщения уже добавлены в таблицу mailed_post, никакие сообщения не должны выбираться и, следовательно, никакая почта не должна отправляться, как показано цифрами 3 и 4 на изображении ниже.

Последняя задача - настроить задание планировщика для ежедневного выполнения этого задания с граблями. Я начал с каждого gem для cron-заданий, но был разочарован, потому что heroku не поддерживает cron-задания, у него есть собственный шедулер.

Процесс тоже очень прост:

Сначала перейдите в: Heroku Scheduler и добавьте в свое приложение бесплатный планировщик heroku.

Затем перейдите на страницу своего приложения, и если она добавлена, она отобразится на вкладке обзора со ссылкой, как показано ниже:

Ссылка приведет вас к консоли диспетчера планировщика, где вы можете добавить новую задачу, как описано ниже:

Вы можете указать задачу rake, которая будет запускаться в это время, и она сама запустит задачу.

Вот так Once a day присылает вам на электронную почту классные сообщения о том, что другие узнают каждый день.

Дайте мне знать, если у вас есть какие-либо предложения или вопросы или вы хотите внести свой вклад: twitter -› sprakash24oct

Кроме того, воспользуйтесь сайтом www.onceaday.today и начните публиковать интересные вещи, которые вы изучаете сегодня, а также учитесь на том, что изучают другие. Все бесплатно :)