homing 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+