homing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ class EmailsController < ApplicationController
2
+ def unsubscribe
3
+ @email = Email.find_by_unsubscribe_token(params[:unsubscribe_token])
4
+ @email.unsubscribe! if @email && @email.active?
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ class CampaignMailer < ActionMailer::Base
2
+ default :from => Homing::Engine.config.default_email
3
+
4
+ def notification(recipient, campaign)
5
+ recipient.prepare!
6
+ recipient_email = recipient.email
7
+ @edition = campaign.name
8
+ if recipient_email.name.present?
9
+ to = "#{recipient_email.name} <#{recipient_email.address}>"
10
+ @text_message = campaign.text_message.gsub(/RECIPIENT_NAME/,recipient_email.name)
11
+ @html_message = campaign.html_message.gsub(/RECIPIENT_NAME/,recipient_email.name)
12
+ else
13
+ to = recipient_email.address
14
+ @text_message = campaign.text_message.gsub(/RECIPIENT_NAME/,campaign.default_name)
15
+ @html_message = campaign.html_message.gsub(/RECIPIENT_NAME/,campaign.default_name)
16
+ end
17
+ subject, @text_message, @html_message = [campaign.subject, @text_message, @html_message].map {|text|
18
+ text.gsub(/BUSINESS_NAME/,recipient_email.business)}.map {|text|
19
+ text.gsub(/BUSINESS_URL/,recipient_email.business_url)}.map {|text|
20
+ text.gsub(/ASSET_HOST/,Homing::Engine.config.asset_host)}
21
+ @html_message = @html_message.html_safe
22
+ @unsubscribe_token = recipient_email.unsubscribe_token
23
+
24
+ recipient.sending! if mail(:to => to, :subject => subject)
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ class Campaign < ActiveRecord::Base
2
+ has_many :recipients
3
+ attr_accessor :recipients_list
4
+ after_create :save_recipients
5
+ validates_presence_of :name, :subject
6
+
7
+ def save_recipients
8
+ recipients_list.split("\n").each do |row|
9
+ columns = row.split("|")
10
+ email = Email.find_by_address(columns[1]) || Email.create!(:name => columns[0],
11
+ :address => columns[1], :business => columns[2],
12
+ :owner_id => columns[3], :owner_type => columns[4],
13
+ :business_url => columns[5])
14
+ self.recipients.create(:email => email) unless email.unsubscribed?
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ require 'hasher'
2
+
3
+ class Email < ActiveRecord::Base
4
+ include Hasher
5
+ include ActiveRecord::Transitions
6
+
7
+ validates_presence_of :address
8
+ validates_uniqueness_of :address
9
+ validates_presence_of :unsubscribe_token
10
+ validates_uniqueness_of :unsubscribe_token
11
+ before_validation :set_unsubscribe_token
12
+ belongs_to :owner, :polymorphic => true
13
+
14
+ state_machine do
15
+ state :active
16
+ state :unsubscribed
17
+
18
+ event :unsubscribe do
19
+ transitions :to => :unsubscribed, :from => :active, :on_transition => :mark_unsubscribed_at
20
+ end
21
+ end
22
+
23
+ protected
24
+ def set_unsubscribe_token
25
+ self.unsubscribe_token = generate_hash(self.address)
26
+ end
27
+ def mark_unsubscribed_at(unsubscribed_at = Time.now)
28
+ self.unsubscribed_at = unsubscribed_at
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ class Recipient < ActiveRecord::Base
2
+ include ActiveRecord::Transitions
3
+
4
+ belongs_to :email
5
+ validates_uniqueness_of :email_id, :scope => :campaign_id
6
+ belongs_to :campaign
7
+
8
+ state_machine do
9
+ state :pending
10
+ state :ready
11
+ state :sent
12
+
13
+ event :prepare do
14
+ transitions :to => :ready, :from => :pending
15
+ end
16
+ event :sending do
17
+ transitions :to => :sent, :from => :ready
18
+ end
19
+ end
20
+
21
+ state_machine.states.collect(&:name).each do |name|
22
+ scope name, where(:state => name)
23
+ end
24
+
25
+ def state_enum
26
+ Recipient.state_machine.states.collect(&:name).map {|name| name.to_s}
27
+ end
28
+ end
@@ -0,0 +1 @@
1
+ = @email.present? ? "Your email #{@email.address} was unsubscribed." : "No such email found."
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do |map|
2
+ match '/emails/unsubscribe/:unsubscribe_token' => 'emails#unsubscribe', :as => :unsubscribe
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class HomingGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ File.join(File.dirname(__FILE__), 'templates')
9
+ end
10
+
11
+ def self.next_migration_number(dirname)
12
+ current_migration_number(dirname) + 1
13
+ end
14
+
15
+ def create_migration_file
16
+ migration_template "create_campaigns.rb", "db/migrate/create_campaigns.rb"
17
+ migration_template "create_emails.rb", "db/migrate/create_emails.rb"
18
+ migration_template "create_recipients.rb", "db/migrate/create_recipients.rb"
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ class CreateCampaigns < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :campaigns do |t|
4
+ t.string :name
5
+ t.string :default_name
6
+ t.string :subject
7
+ t.text :text_message
8
+ t.text :html_message
9
+ t.boolean :complete
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ drop_table :campaigns
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ class CreateEmails < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :emails do |t|
4
+ t.string :name
5
+ t.string :address
6
+ t.string :business
7
+ t.integer :owner_id
8
+ t.string :owner_type
9
+ t.string :business_url
10
+ t.string :state
11
+ t.string :unsubscribe_token
12
+ t.datetime :unsubscribed_at
13
+ end
14
+
15
+ add_index :emails, :address
16
+ end
17
+
18
+ def self.down
19
+ drop_table :emails
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ class CreateRecipients < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :recipients do |t|
4
+ t.string :state
5
+ t.references :email
6
+ t.references :campaign
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+
12
+ def self.down
13
+ drop_table :recipients
14
+ end
15
+ end
data/lib/hasher.rb ADDED
@@ -0,0 +1,29 @@
1
+ module Hasher
2
+ require 'digest/sha1'
3
+ SECRET = "myhasheriscoolerthanyourhasher"
4
+
5
+ def generate_hash(string)
6
+ encrypt string
7
+ end
8
+
9
+ protected
10
+ def encrypt(string)
11
+ Digest::SHA1.hexdigest(salt + string)
12
+ end
13
+
14
+ def salt
15
+ Digest::SHA1.hexdigest(SECRET + time_stamp + random)
16
+ end
17
+
18
+ def time_stamp(time = Time.now)
19
+ time.strftime("%d%Y%l%M").to_s
20
+ end
21
+
22
+ def random(size = 10)
23
+ (0...size).map { character_set[ rand(character_set.size) ] }.join
24
+ end
25
+
26
+ def character_set
27
+ [('0'..'9'),('A'..'Z')].map { |range| range.to_a }.flatten
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ require "homing"
2
+ require "rails"
3
+ require "transitions"
4
+ require "active_record/transitions"
5
+
6
+ module Homing
7
+ class Engine < Rails::Engine
8
+ config.default_email = "mail@example.com"
9
+ config.asset_host = "http://example.com"
10
+ rake_tasks do
11
+ load "homing/railties/tasks.rake"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,51 @@
1
+ namespace :homing do
2
+ desc "Campaign notifier"
3
+ task :notify, [:campaign_id, :limit] => :environment do |t, args|
4
+ campaign_id = args[:campaign_id]
5
+ limit = (args[:limit] || 200).to_i
6
+ campaign = Campaign.find(campaign_id) rescue nil
7
+ if campaign.present? && campaign.complete.blank?
8
+ recipients = campaign.recipients.pending.limit(limit)
9
+ puts "Sending email to: #{recipients.count} addresses"
10
+ recipients.each_with_index do |recipient, index|
11
+ begin
12
+ puts "#{index}. #{recipient.email.address}"
13
+ CampaignMailer.notification(recipient, campaign).deliver unless recipient.email.unsubscribed?
14
+ rescue Exception => e
15
+ puts "ERROR: #{recipient.email.address}"
16
+ puts e
17
+ end
18
+ end
19
+ campaign.update_attribute(:complete, true) if campaign.recipients.pending.count.zero? && campaign.recipients.ready.count.zero?
20
+ else
21
+ puts campaign.present? ? "Campaign complete!" : "Campaign not found!"
22
+ end
23
+ end
24
+
25
+ desc "Campaign summary"
26
+ task :summary, [:campaign_id] => :environment do |t, args|
27
+ campaign_id = args[:campaign_id]
28
+ campaign = Campaign.find(campaign_id) rescue nil
29
+ if campaign.present?
30
+ puts "Total emails: #{campaign.recipients.count}"
31
+ puts "Emails sent: #{campaign.recipients.sent.count}"
32
+ puts "Emails ready: #{campaign.recipients.ready.count}"
33
+ puts "Emails pending: #{campaign.recipients.pending.count}"
34
+ puts "Campaign complete!" if campaign.complete
35
+ else
36
+ puts "Campaign not found!"
37
+ end
38
+ end
39
+
40
+ desc "Campaign reset"
41
+ task :reset, [:campaign_id] => :environment do |t, args|
42
+ campaign_id = args[:campaign_id]
43
+ campaign = Campaign.find(campaign_id) rescue nil
44
+ if campaign.present?
45
+ campaign.update_attribute(:complete, false)
46
+ campaign.recipients.update_all("state = 'pending'")
47
+ else
48
+ puts "Campaign not found!"
49
+ end
50
+ end
51
+ end
data/lib/homing.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Homing
2
+ require 'homing/engine' if defined?(Rails)
3
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: homing
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Codrea
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-14 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ name: transitions
33
+ version_requirements: *id001
34
+ prerelease: false
35
+ description:
36
+ email:
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - app/controllers/emails_controller.rb
45
+ - app/mailers/campaign_mailer.rb
46
+ - app/models/campaign.rb
47
+ - app/models/email.rb
48
+ - app/models/recipient.rb
49
+ - app/views/emails/unsubscribe.html.haml
50
+ - config/routes.rb
51
+ - lib/generators/homing/homing_generator.rb
52
+ - lib/generators/homing/templates/create_campaigns.rb
53
+ - lib/generators/homing/templates/create_emails.rb
54
+ - lib/generators/homing/templates/create_recipients.rb
55
+ - lib/hasher.rb
56
+ - lib/homing.rb
57
+ - lib/homing/engine.rb
58
+ - lib/homing/railties/tasks.rake
59
+ has_rdoc: true
60
+ homepage:
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.5.2
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Homing
93
+ test_files: []
94
+