unsubscribe 0.1.0 → 1.0.0.alpha.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b90d08ba0979b64c7de3ec38af50fc8e39a5efb80d92218c9c9cac9b409b3cb9
4
- data.tar.gz: ce95af4f4bb3ba2be36a11dce5fa62d960c9eedc0d23093acd6e3fa4c7113868
3
+ metadata.gz: 28f28f3bede2e59008c148b3262c718e621613bff193fcf8f22b0a6ad5302a50
4
+ data.tar.gz: 37b067207a3ed305fda44ca609f78e7d4307d10914ca2c2683f6df87bb978f43
5
5
  SHA512:
6
- metadata.gz: efc5e6c92daacab73a5be06558db5c6fa2fa80b01d069f5eac91349a1fe1da37f2b4b080a35ca1502f7fbf9e7e1710cdc810709cc99f166187b50c28a2d031e3
7
- data.tar.gz: 8eee60eac59948bef5731df07086797992585d4b246386495bf47938b57400d4752d425b8da0ccb42cff5376997c8c18bc656075d7529a2a64dfbbadfba98274
6
+ metadata.gz: b5881a6787fd24d5860a433a93094addf13a143b71b4784a08dfaa1700432ddcbf2e2237351d573cb0030ec472ed49d41242a7fd2d67c73bb309905d4a49c06d
7
+ data.tar.gz: 19bd2ff135befe0b3d5212f9b72b76a8db310df77b1d27eb7abb488443078619160bb6bc60af12a6ade697475855e8db265796676555c578830580170d24578d
data/README.md CHANGED
@@ -1,10 +1,11 @@
1
- # Unsubscribe
2
- Automatically unsubscribe from emails in Rails.
1
+ # 📭 Unsubscribe
3
2
 
4
- ## Usage
5
- TODO
3
+ Automatically unsubscribe from emails in Rails.
4
+
5
+ ![Demo](./docs/demo.gif)
6
+
7
+ ## 🚀 Installation
6
8
 
7
- ## Installation
8
9
  Add this line to your application's Gemfile:
9
10
 
10
11
  ```ruby
@@ -21,8 +22,112 @@ Or install it yourself as:
21
22
  $ gem install unsubscribe
22
23
  ```
23
24
 
24
- ## Contributing
25
- Contribution directions go here.
25
+ Then run the installation commands:
26
+
27
+ ```bash
28
+ rails g unsubscribe:install
29
+ rails unsubscribe:install:migrations
30
+ rails db:migrate
31
+ ```
32
+
33
+ ## 📚 Usage
34
+
35
+ ### Unsubscribe::Owner
36
+
37
+ - Add `include Unsubscribe::Owner` to a `Model`. The `Model` must have an `email` column.
38
+
39
+ ```ruby
40
+ class User < ApplicationRecord
41
+ include Unsubscribe::Owner
42
+ end
43
+ ```
44
+
45
+ #### Available Methods
46
+
47
+ ```ruby
48
+ User.first.subscribed_to_mailer? "MarketingMailer"
49
+ # => true/false
50
+
51
+ User.first.to_sgid_for_mailer_subscription
52
+ # => #<SignedGlobalID:123 ...>
53
+ ```
54
+
55
+ ### Unsubscribe::Mailer
56
+
57
+ - Add `include Unsubscribe::Mailer` to a `Mailer`.
58
+ - Optionally call `unsubscribe_settings` to set a `name` and `description`. This will be used in the unsubscribe page.
59
+ - Set `mail to:` to `@recipient.email`. The `@recipient` is an instance of whatever Class `include Unsubscribe::Owner` was added to.
60
+
61
+ ```ruby
62
+ class MarketingMailer < ApplicationMailer
63
+ include Unsubscribe::Mailer
64
+
65
+ unsubscribe_settings name: "Marketing Emails", description: "Updates on promotions and sales."
66
+
67
+ def promotion
68
+ mail to: @recipient.email
69
+ end
70
+ end
71
+ ```
72
+
73
+ - Call the `Mailer` with a `recipient` parameter.
74
+
75
+ ```ruby
76
+ MarketingMailer.with(
77
+ recipient: User.first
78
+ ).promotion.deliver_now
79
+ ```
80
+
81
+ #### Available Methods
82
+
83
+ ```ruby
84
+ Unsubscribe::MailerSubscription.first.action
85
+ # => "Unsubscribe from"/"Subscribe to"
86
+
87
+ Unsubscribe::MailerSubscription.first.call_to_action
88
+ # => "Unsubscribe from Marketing Emails"/"Subscribe to Marketing Emails"
89
+
90
+ Unsubscribe::MailerSubscription.first.description
91
+ # => "Updates on promotions and sales."
92
+
93
+ Unsubscribe::MailerSubscription.first.name
94
+ # => "Marketing Emails"
95
+ ```
96
+
97
+ ### Unsubscribe Link
98
+
99
+ - Add the `@unsubscribe_url` link to the `Mailer`.
100
+
101
+ ```html+erb
102
+ <%= link_to "Unsubscribe", @unsubscribe_url %>
103
+ ```
104
+
105
+ ## ⚙️ Customize Templates
106
+
107
+ Run `rails g unsubscribe:views` if you want to modify the existing templates.
108
+
109
+ ## 🌐 I18n
110
+
111
+ The language used for `Unsubscribe::MailerSubscription#action` can be translated.
112
+
113
+ ```yml
114
+ # config/locales/en.yml
115
+ en:
116
+ unsubscribe:
117
+ action:
118
+ subscribe: "Subscribe to"
119
+ unsubscribe: "Unsubscribe from"
120
+ ```
121
+
122
+ ## 🙏 Contributing
123
+
124
+ If you'd like to open a PR please make sure the following things pass:
125
+
126
+ ```ruby
127
+ bin/rails test
128
+ bundle exec standardrb
129
+ ```
130
+
131
+ ## 📜 License
26
132
 
27
- ## License
28
133
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -10,8 +10,8 @@ require "bundler/gem_tasks"
10
10
  require "rake/testtask"
11
11
 
12
12
  Rake::TestTask.new(:test) do |t|
13
- t.libs << 'test'
14
- t.pattern = 'test/**/*_test.rb'
13
+ t.libs << "test"
14
+ t.pattern = "test/**/*_test.rb"
15
15
  t.verbose = false
16
16
  end
17
17
 
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,66 @@
1
+ require_dependency "unsubscribe/application_controller"
2
+
3
+ module Unsubscribe
4
+ class MailerSubscriptionsController < ApplicationController
5
+ before_action :set_owner, only: [:show, :create, :update]
6
+ before_action :set_mailer, only: [:show]
7
+
8
+ def show
9
+ end
10
+
11
+ def create
12
+ @mailer = Unsubscribe::MailerSubscription.new(mailer_subscription_params)
13
+
14
+ if @owner != @mailer.owner
15
+ redirect_to(
16
+ mailer_subscription_path(@owner.to_sgid_for_mailer_subscription, mailer: params[:mailer_subscription][:mailer]),
17
+ alert: "You are not authorized to perform this action."
18
+ ) and return
19
+ end
20
+
21
+ if @mailer.save
22
+ redirect_to(
23
+ mailer_subscription_path(@owner.to_sgid_for_mailer_subscription, mailer: params[:mailer_subscription][:mailer]),
24
+ notice: "Settings updated."
25
+ )
26
+ else
27
+ redirect_to(
28
+ mailer_subscription_path(@owner.to_sgid_for_mailer_subscription, mailer: params[:mailer_subscription][:mailer]),
29
+ alert: @mailer.errors.full_messages.to_sentence
30
+ )
31
+ end
32
+ end
33
+
34
+ def update
35
+ @mailer = Unsubscribe::MailerSubscription.find(params[:mailer_subscription_id])
36
+
37
+ if @owner != @mailer.owner
38
+ redirect_to(
39
+ mailer_subscription_path(@owner.to_sgid_for_mailer_subscription, mailer: @mailer.mailer),
40
+ alert: "You are not authorized to perform this action."
41
+ ) and return
42
+ end
43
+
44
+ if @mailer.toggle!(:subscribed)
45
+ redirect_to mailer_subscription_path(@owner.to_sgid_for_mailer_subscription, mailer: @mailer.mailer), notice: "Settings updated."
46
+ else
47
+ redirect_to mailer_subscription_path(@owner.to_sgid_for_mailer_subscription, mailer: @mailer.mailer), alert: @mailer.errors.full_messages.to_sentence
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def set_owner
54
+ @owner = GlobalID::Locator.locate_signed(params[:id], for: :mailer_subscription)
55
+ raise ActiveRecord::RecordNotFound if @owner.nil?
56
+ end
57
+
58
+ def set_mailer
59
+ @mailer = @owner.mailer_subscriptions.find_or_initialize_by(mailer: params[:mailer])
60
+ end
61
+
62
+ def mailer_subscription_params
63
+ params.require(:mailer_subscription).permit(:owner_id, :owner_type, :subscribed, :mailer)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,4 @@
1
+ module Unsubscribe
2
+ module MailerSubscriptionsHelper
3
+ end
4
+ end
@@ -1,6 +1,6 @@
1
1
  module Unsubscribe
2
2
  class ApplicationMailer < ActionMailer::Base
3
- default from: 'from@example.com'
4
- layout 'mailer'
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
5
  end
6
6
  end
@@ -0,0 +1,45 @@
1
+ module Unsubscribe
2
+ class MailerSubscription < ApplicationRecord
3
+ belongs_to :owner, polymorphic: true
4
+
5
+ validates :subscribed, inclusion: [true, false], allow_nil: true
6
+ validates :mailer, presence: true
7
+ validates :owner_id, uniqueness: {scope: [:mailer, :owner_type]}
8
+ validate :mailer_should_exist
9
+
10
+ def action
11
+ case subscribed
12
+ when nil
13
+ I18n.t("unsubscribe.action.unsubscribe")
14
+ else
15
+ subscribed? ? I18n.t("unsubscribe.action.unsubscribe") : I18n.t("unsubscribe.action.subscribe")
16
+ end
17
+ end
18
+
19
+ def call_to_action
20
+ "#{action} #{name}"
21
+ end
22
+
23
+ def description
24
+ details[:description]
25
+ end
26
+
27
+ def name
28
+ details[:name].present? ? details[:name] : mailer
29
+ end
30
+
31
+ def details
32
+ mailer.constantize.unsubscribe_settings
33
+ rescue NoMethodError
34
+ raise Unsubscribe::Error, "Make sure to include Unsubscribe::Mailer in #{mailer}"
35
+ end
36
+
37
+ private
38
+
39
+ def mailer_should_exist
40
+ errors.add(:mailer, "is not a enbled") unless mailer.constantize.enabled
41
+ rescue NameError
42
+ errors.add(:mailer, "is not a Mailer")
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ <h1><%= @mailer.name %></h1>
2
+ <p><%= @mailer.description %></p>
3
+ <% if @mailer.new_record? %>
4
+ <%= button_to(
5
+ @mailer.call_to_action,
6
+ mailer_subscriptions_path,
7
+ params: {
8
+ id: @owner.to_sgid_for_mailer_subscription,
9
+ mailer_subscription: {
10
+ owner_id: @owner.id,
11
+ owner_type: @owner.class,
12
+ subscribed: false,
13
+ mailer: @mailer.mailer
14
+ }
15
+ }
16
+ ) %>
17
+ <% else %>
18
+ <%= button_to(
19
+ @mailer.call_to_action,
20
+ mailer_subscription_path(@owner.to_sgid_for_mailer_subscription),
21
+ method: :put,
22
+ params: {
23
+ mailer_subscription_id: @mailer.id
24
+ }
25
+ ) %>
26
+ <% end %>
@@ -0,0 +1,5 @@
1
+ en:
2
+ unsubscribe:
3
+ action:
4
+ subscribe: "Subscribe to"
5
+ unsubscribe: "Unsubscribe from"
data/config/routes.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  Unsubscribe::Engine.routes.draw do
2
+ resources :mailer_subscriptions, only: [:create, :update, :show]
2
3
  end
@@ -0,0 +1,15 @@
1
+ class CreateUnsubscribeMailerSubscriptions < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :unsubscribe_mailer_subscriptions do |t|
4
+ # Needs to be polymorphic to be flexible. Owner may not always be a User.
5
+ t.references :owner, polymorphic: true, null: false, index: { name: "unsubscribe_owner_index" }
6
+ t.boolean :subscribed
7
+ t.string :mailer, null: false
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ # An owner should only have one Unsubscribe::MailerSubscription record per Mailer.
13
+ add_index :unsubscribe_mailer_subscriptions, [:owner_id, :owner_type, :mailer], unique: true, name: "unsubscribe_owner_mailer_index"
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ require "rails/generators"
2
+
3
+ module Unsubscribe
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("./../../../../../", __FILE__)
7
+
8
+ def link_manifest_js
9
+ inject_into_file "app/assets/config/manifest.js" do
10
+ <<~EOF
11
+ \n//= link unsubscribe_manifest.js
12
+ EOF
13
+ end
14
+ end
15
+
16
+ def mount_engine
17
+ inject_into_file "config/routes.rb", after: "Rails.application.routes.draw do" do
18
+ <<~EOF
19
+ \n\tmount Unsubscribe::Engine => "/unsubscribe"
20
+ EOF
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ require "rails/generators"
2
+
3
+ module Unsubscribe
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("./../../../../../", __FILE__)
7
+
8
+ def copy_views
9
+ directory "app/views/unsubscribe", "app/views/unsubscribe"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ module Unsubscribe
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,41 @@
1
+ module Unsubscribe
2
+ module Mailer
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_action :set_recipient
7
+ before_action :set_unsubscribe_url, if: :should_unsubscribe?
8
+ before_action :set_headers, if: :should_unsubscribe?
9
+ after_action :prevent_delivery_if_recipient_opted_out, if: :should_unsubscribe?
10
+ end
11
+
12
+ class_methods do
13
+ def unsubscribe_settings(name: nil, description: nil)
14
+ cattr_accessor :unsubscribe_settings, default: {name: name, description: description}
15
+ cattr_accessor :enabled, default: true
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def prevent_delivery_if_recipient_opted_out
22
+ mail.perform_deliveries = @recipient.subscribed_to_mailer? self.class.to_s
23
+ end
24
+
25
+ def set_recipient
26
+ @recipient = params[:recipient]
27
+ end
28
+
29
+ def set_unsubscribe_url
30
+ @unsubscribe_url = unsubscribe.mailer_subscription_url(@recipient.to_sgid_for_mailer_subscription, mailer: self.class)
31
+ end
32
+
33
+ def should_unsubscribe?
34
+ @recipient.present? && @recipient.respond_to?(:subscribed_to_mailer?)
35
+ end
36
+
37
+ def set_headers
38
+ headers["List-Unsubscribe"] = "<#{@unsubscribe_url}>"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ module Unsubscribe
2
+ module Owner
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :mailer_subscriptions, class_name: "Unsubscribe::MailerSubscription", as: :owner, inverse_of: :owner, dependent: :destroy
7
+ end
8
+
9
+ def subscribed_to_mailer?(mailer)
10
+ Unsubscribe::MailerSubscription.find_by(
11
+ owner: self,
12
+ mailer: mailer,
13
+ subscribed: false
14
+ ).nil?
15
+ end
16
+
17
+ def to_sgid_for_mailer_subscription
18
+ to_sgid(for: :mailer_subscription)
19
+ end
20
+ end
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Unsubscribe
2
- VERSION = '0.1.0'
2
+ VERSION = "1.0.0.alpha.1"
3
3
  end
data/lib/unsubscribe.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require "unsubscribe/version"
2
2
  require "unsubscribe/engine"
3
3
 
4
+ require "unsubscribe/error"
5
+ require "unsubscribe/mailer"
6
+ require "unsubscribe/owner"
7
+
4
8
  module Unsubscribe
5
- # Your code goes here...
6
9
  end
metadata CHANGED
@@ -1,35 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unsubscribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0.alpha.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Polito
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-17 00:00:00.000000000 Z
11
+ date: 2021-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 6.1.4
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
- version: 6.1.4.1
19
+ version: 6.0.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: 6.1.4
30
24
  - - ">="
31
25
  - !ruby/object:Gem::Version
32
- version: 6.1.4.1
26
+ version: 6.0.0
33
27
  description: 'Automatically unsubscribe from emails in Rails. '
34
28
  email:
35
29
  - stevepolito@hey.com
@@ -42,16 +36,28 @@ files:
42
36
  - Rakefile
43
37
  - app/assets/config/unsubscribe_manifest.js
44
38
  - app/assets/stylesheets/unsubscribe/application.css
39
+ - app/assets/stylesheets/unsubscribe/mailer_subscriptions.css
45
40
  - app/controllers/unsubscribe/application_controller.rb
41
+ - app/controllers/unsubscribe/mailer_subscriptions_controller.rb
46
42
  - app/helpers/unsubscribe/application_helper.rb
43
+ - app/helpers/unsubscribe/mailer_subscriptions_helper.rb
47
44
  - app/jobs/unsubscribe/application_job.rb
48
45
  - app/mailers/unsubscribe/application_mailer.rb
49
46
  - app/models/unsubscribe/application_record.rb
47
+ - app/models/unsubscribe/mailer_subscription.rb
50
48
  - app/views/layouts/unsubscribe/application.html.erb
49
+ - app/views/unsubscribe/mailer_subscriptions/show.html.erb
50
+ - config/locales/en.yml
51
51
  - config/routes.rb
52
+ - db/migrate/20210918092925_create_unsubscribe_mailer_subscriptions.rb
53
+ - lib/generators/unsubscribe/install/install_generator.rb
54
+ - lib/generators/unsubscribe/views/views_generator.rb
52
55
  - lib/tasks/unsubscribe_tasks.rake
53
56
  - lib/unsubscribe.rb
54
57
  - lib/unsubscribe/engine.rb
58
+ - lib/unsubscribe/error.rb
59
+ - lib/unsubscribe/mailer.rb
60
+ - lib/unsubscribe/owner.rb
55
61
  - lib/unsubscribe/version.rb
56
62
  homepage: https://github.com/stevepolitodesign/unsubscribe
57
63
  licenses:
@@ -71,9 +77,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
71
77
  version: '0'
72
78
  required_rubygems_version: !ruby/object:Gem::Requirement
73
79
  requirements:
74
- - - ">="
80
+ - - ">"
75
81
  - !ruby/object:Gem::Version
76
- version: '0'
82
+ version: 1.3.1
77
83
  requirements: []
78
84
  rubygems_version: 3.1.2
79
85
  signing_key: