├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .rspec
├── Appraisals
├── CHANGELOG.md
├── Gemfile
├── MIT-LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ └── images
│ │ └── maily
│ │ ├── icons
│ │ ├── globe.svg
│ │ └── paperclip.svg
│ │ └── logo.png
├── controllers
│ └── maily
│ │ ├── application_controller.rb
│ │ └── emails_controller.rb
├── helpers
│ └── maily
│ │ ├── application_helper.rb
│ │ └── emails_helper.rb
└── views
│ ├── layouts
│ └── maily
│ │ └── application.html.erb
│ └── maily
│ ├── emails
│ ├── edit.html.erb
│ ├── index.html.erb
│ └── show.html.erb
│ └── shared
│ ├── _flash_messages.html.erb
│ ├── _footer.html.erb
│ ├── _header.html.erb
│ ├── _javascript.html.erb
│ ├── _sidebar.html.erb
│ └── _stylesheet.html.erb
├── config
└── routes.rb
├── gemfiles
├── rails_5.2.gemfile
├── rails_6.0.gemfile
├── rails_6.1.gemfile
├── rails_7.0.gemfile
└── rails_7.1.gemfile
├── lib
├── generators
│ ├── maily
│ │ └── install_generator.rb
│ └── templates
│ │ └── initializer.rb
├── maily.rb
└── maily
│ ├── email.rb
│ ├── engine.rb
│ ├── generator.rb
│ ├── mailer.rb
│ └── version.rb
├── maily.gemspec
├── spec
├── controllers_spec.rb
├── dummy
│ ├── README.md
│ ├── Rakefile
│ ├── app
│ │ ├── controllers
│ │ │ └── application_controller.rb
│ │ ├── mailers
│ │ │ ├── application_mailer.rb
│ │ │ └── notifier.rb
│ │ └── views
│ │ │ ├── notifications
│ │ │ └── custom_template_path.html.erb
│ │ │ └── notifier
│ │ │ ├── custom_template.html.erb
│ │ │ ├── multipart.html.erb
│ │ │ ├── multipart.text.erb
│ │ │ ├── no_arguments.html.erb
│ │ │ ├── only_text.text.erb
│ │ │ ├── version.html.erb
│ │ │ ├── with_arguments.html.erb
│ │ │ ├── with_array_arguments.html.erb
│ │ │ ├── with_description.html.erb
│ │ │ ├── with_inline_attachments.html.erb
│ │ │ ├── with_params.html.erb
│ │ │ └── with_slim_template.html.slim
│ ├── bin
│ │ ├── bundle
│ │ ├── rails
│ │ └── rake
│ ├── config.ru
│ ├── config
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── environment.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ ├── inflections.rb
│ │ │ ├── maily.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ └── routes.rb
│ ├── lib
│ │ └── maily_hooks.rb
│ ├── log
│ │ └── .keep
│ └── public
│ │ ├── 404.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ └── favicon.ico
├── email_spec.rb
├── generator_spec.rb
├── mailer_spec.rb
├── maily_spec.rb
└── spec_helper.rb
└── support
└── images
├── logo.png
└── screenshot.png
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | name: CI
8 | runs-on: ubuntu-latest
9 | env:
10 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | ruby: ["3.0", "3.1", "3.2", "3.3"]
15 | gemfile: [rails_6.1, rails_7.0, rails_7.1]
16 | include:
17 | - ruby: "2.7"
18 | gemfile: rails_5.2
19 | - ruby: "3.0"
20 | gemfile: rails_6.0
21 | - ruby: head
22 | gemfile: rails_7.1
23 | steps:
24 | - uses: actions/checkout@v3
25 | - uses: ruby/setup-ruby@v1
26 | with:
27 | ruby-version: ${{ matrix.ruby }}
28 | bundler-cache: true
29 | - run: bundle exec rspec
30 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | pkg/
3 | Gemfile.lock
4 | *.gemfile.lock
5 | spec/dummy/log/*.log
6 | spec/dummy/tmp/
7 | .byebug_history
8 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | %w(
2 | 7.1
3 | 7.0
4 | 6.1
5 | 6.0
6 | 5.2
7 | ).each do |version|
8 | appraise "rails-#{version}" do
9 | gem "rails", "~> #{version}.0"
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## [2.1.0]
6 |
7 | - Use `ActionMailer::InlinePreviewInterceptor` to facilitate integration with other gems like premailer (#55)
8 | - Move CI from Travis to GitHub Actions
9 |
10 | ## [2.0.2]
11 |
12 | - UI: Hide delivery form if `allow_delivery` is false (#54)
13 |
14 | ## [2.0.1]
15 |
16 | - UI: Don't show empty mailer classes in sidebar (#51)
17 |
18 | ## [2.0.0]
19 |
20 | - Email versions (#49)
21 | - Drop official support for EOL Rails versions: 5.1, 5.0 and 4.2
22 | - Drop official support for EOL Rubies: 2.4
23 |
24 | ## [1.0.0]
25 |
26 | - Avoid the dependency on Sprockets (#48)
27 |
28 | ## [0.12.3]
29 |
30 | - Styles: fix input placeholder color in Firefox
31 |
32 | ## [0.12.2]
33 |
34 | - Refine logo and UI (#45)
35 |
36 | ## [0.12.1]
37 |
38 | - Sprockets v4 fixes (#44)
39 |
40 | ## [0.12.0]
41 |
42 | - Support inline attachments (#30)
43 |
44 | ## [0.11.0]
45 |
46 | - Support arrays in hooks (#42)
47 | - Drop Ruby 2.3 (EOL) from CI
48 |
49 | ## [0.10.1]
50 |
51 | - UI: fix iframe (onload) resize
52 |
53 | ## [0.10.0]
54 |
55 | - Support `ActionMailer::Parameterized` (#39)
56 | - UI tweaks
57 |
58 | ## [0.9.1]
59 |
60 | - Properly display text parts by respecting break lines
61 | - CI: official Rails 6 support
62 | - Reduce gem size by including only necessary files
63 |
64 | ## [0.9.0]
65 |
66 | - Allow to register external mailers, for example, from a gem
67 | - CI Rubies: add 2.6 and drop 2.2 (EOL)
68 |
69 | ## [0.8.2]
70 |
71 | - Fix incompatibility with last gem `mail` (v2.7.1) release (#32)
72 |
73 | ## [0.8.1]
74 |
75 | - Serve fonts from Google Fonts API
76 |
77 | ## [0.8.0]
78 |
79 | - Inherited emails support (#28)
80 | - `Maily.available_locales` defaults to Rails available_locales `config.i18n.available_locales` (#27)
81 |
82 | ## [0.7.2]
83 |
84 | - Definitive fix for newer apps using `webpacker` and without `sass-rails` (#22)
85 | - Fix regression in generator, from v0.7.0 (#24)
86 | - Allow to edit emails with different templates engines: Slim, Haml, ... (#25)
87 |
88 | ## [0.7.1]
89 |
90 | - Fix assets pipeline integration for applications using `webpacker` instead of `sprockets` (#22)
91 |
92 | ## [0.7.0]
93 |
94 | - Allow to hide emails (#11)
95 | - Allow to override `template_name`
96 | - New project logo
97 | - Internal classes refactor
98 | - Front-end cleanup and simplification (remove font icons, update normalize.css, CSS/HTML refactor, better page titles, better flash messages)
99 | - Drop Ruby 2.1 (EOL) from CI
100 | - CI against Rails 5.2
101 |
102 | ## [0.6.3]
103 |
104 | - Add support for `reply_to`
105 |
106 | ## [0.6.2]
107 |
108 | - Rails 5.1 fixes (#19, #20)
109 | - Hooks: improve arguments validation (#20)
110 |
111 | ## [0.6.1]
112 |
113 | - Fix Rails automatic generator (#18)
114 | - UI: display better error messages (#17)
115 | - UI: alphabetically sort mailers and emails in sidebar (#17)
116 |
117 | ## [0.6.0]
118 |
119 | - Lazy hooks (#14)
120 | - Better capture and dispatch internal exceptions (#13)
121 | - Allow to define a description per email (#10)
122 | - Remove HTML5 Shiv (front-end dependency)
123 | - Fix assets pipeline integration
124 | - Lots of front-end tweaks
125 | - Basic dashboard with customizable welcome message
126 | - CI suite: officially supported Rails versions: 4.2, 5.0 and 5.1
127 |
128 | ## [0.5.0]
129 |
130 | - Appraisals integration: Rails 3.2, 4.X and 5.0
131 | - Modernize CI Rubies: add 2.2, 2.3 and 2.4 (remove 1.9.3 and 2.0)
132 | - Fix Ruby warnings (#12)
133 | - Docs typos (#8)
134 |
135 | ## [0.4.0]
136 |
137 | - Allow to setup HTTP basic authentication
138 |
139 | ## [0.3.5]
140 |
141 | - CI: run tests against ruby 2.1
142 | - Properly define dependencies
143 |
144 | ## [0.3.4]
145 |
146 | - Fix syntax error introduced in v0.3.3 :(
147 |
148 | ## [0.3.3]
149 |
150 | - Disallow templates edition in production mode
151 |
152 | ## [0.3.2]
153 |
154 | - Fix #3: email container not displayed properly in Firefox
155 |
156 | ## [0.3.1]
157 |
158 | - Add basic specs
159 | - CI integration
160 |
161 | ## [0.3.0]
162 |
163 | - First real usable release :tada:
164 |
165 | [2.1.0]: https://github.com/markets/maily/compare/v2.0.2...v2.1.0
166 | [2.0.2]: https://github.com/markets/maily/compare/v2.0.1...v2.0.2
167 | [2.0.1]: https://github.com/markets/maily/compare/v2.0.0...v2.0.1
168 | [2.0.0]: https://github.com/markets/maily/compare/v1.0.0...v2.0.0
169 | [1.0.0]: https://github.com/markets/maily/compare/v0.12.3...v1.0.0
170 | [0.12.3]: https://github.com/markets/maily/compare/v0.12.2...v0.12.3
171 | [0.12.2]: https://github.com/markets/maily/compare/v0.12.1...v0.12.2
172 | [0.12.1]: https://github.com/markets/maily/compare/v0.12.0...v0.12.1
173 | [0.12.0]: https://github.com/markets/maily/compare/v0.11.0...v0.12.0
174 | [0.11.0]: https://github.com/markets/maily/compare/v0.10.1...v0.11.0
175 | [0.10.1]: https://github.com/markets/maily/compare/v0.10.0...v0.10.1
176 | [0.10.0]: https://github.com/markets/maily/compare/v0.9.1...v0.10.0
177 | [0.9.1]: https://github.com/markets/maily/compare/v0.9.0...v0.9.1
178 | [0.9.0]: https://github.com/markets/maily/compare/v0.8.2...v0.9.0
179 | [0.8.2]: https://github.com/markets/maily/compare/v0.8.1...v0.8.2
180 | [0.8.1]: https://github.com/markets/maily/compare/v0.8.0...v0.8.1
181 | [0.8.0]: https://github.com/markets/maily/compare/v0.7.2...v0.8.0
182 | [0.7.2]: https://github.com/markets/maily/compare/v0.7.1...v0.7.2
183 | [0.7.1]: https://github.com/markets/maily/compare/v0.7.0...v0.7.1
184 | [0.7.0]: https://github.com/markets/maily/compare/v0.6.3...v0.7.0
185 | [0.6.3]: https://github.com/markets/maily/compare/v0.6.2...v0.6.3
186 | [0.6.2]: https://github.com/markets/maily/compare/v0.6.1...v0.6.2
187 | [0.6.1]: https://github.com/markets/maily/compare/v0.6.0...v0.6.1
188 | [0.6.0]: https://github.com/markets/maily/compare/v0.5.0...v0.6.0
189 | [0.5.0]: https://github.com/markets/maily/compare/v0.4.0...v0.5.0
190 | [0.4.0]: https://github.com/markets/maily/compare/v0.3.5...v0.4.0
191 | [0.3.5]: https://github.com/markets/maily/compare/v0.3.4...v0.3.5
192 | [0.3.4]: https://github.com/markets/maily/compare/v0.3.3...v0.3.4
193 | [0.3.3]: https://github.com/markets/maily/compare/v0.3.2...v0.3.3
194 | [0.3.2]: https://github.com/markets/maily/compare/v0.3.1...v0.3.2
195 | [0.3.1]: https://github.com/markets/maily/compare/v0.3.0...v0.3.1
196 | [0.3.0]: https://github.com/markets/maily/compare/v0.1.0...v0.3.0
197 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem 'byebug', group: [:development, :test]
6 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2024 Marc Anguera Insa
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Maily is a Rails Engine to manage, test and navigate through all your email templates of your app, being able to preview them directly in your browser.
14 |
15 | Maily automatically picks up all your emails and make them accessible from a kind of dashboard.
16 |
17 | ## Features:
18 |
19 | * Mountable engine
20 | * Visual preview in the browser (attachments as well)
21 | * Template edition (only in development)
22 | * Email delivery
23 | * Easy way (aka `Hooks`) to define and customize data for emails
24 | * Email versions
25 | * Flexible authorization system
26 | * Minimalistic and clean interface
27 | * Generator to handle a comfortable installation
28 |
29 | 
30 |
31 | ## Installation
32 |
33 | Add this line to your `Gemfile` and then run `bundle install`:
34 |
35 | ```ruby
36 | gem 'maily'
37 | ```
38 |
39 | Run generator:
40 |
41 | ```
42 | > rails g maily:install
43 | ```
44 |
45 | This generator runs some tasks for you:
46 |
47 | * Mounts the engine (to `/maily` by default) in your routes
48 | * Adds an initializer (into `config/initializers/maily.rb`) to customize some settings
49 | * Adds a file (into `lib/maily_hooks.rb`) to define hooks
50 |
51 | ## Initialization and configuration
52 |
53 | You should use the `setup` method to configure and customize `Maily` settings:
54 |
55 | ```ruby
56 | # config/initializers/maily.rb
57 |
58 | Maily.setup do |config|
59 | # On/off engine
60 | # config.enabled = !Rails.env.production?
61 |
62 | # Allow templates edition
63 | # config.allow_edition = !Rails.env.production?
64 |
65 | # Allow deliveries
66 | # config.allow_delivery = !Rails.env.production?
67 |
68 | # Your application available_locales (or I18n.available_locales) by default
69 | # config.available_locales = [:en, :es, :pt, :fr]
70 |
71 | # Run maily under different controller ('ActionController::Base' by default)
72 | # config.base_controller = '::AdminController'
73 |
74 | # Configure hooks path
75 | # config.hooks_path = 'lib/maily_hooks.rb'
76 |
77 | # Http basic authentication (nil by default)
78 | # config.http_authorization = { username: 'admin', password: 'secret' }
79 |
80 | # Customize welcome message
81 | # config.welcome_message = "Welcome to our email testing platform. If you have any problem, please contact support team at support@example.com."
82 | end
83 | ```
84 |
85 | You can use the following format too:
86 |
87 | ```ruby
88 | Maily.enabled = ENV['MAILY_ENABLED']
89 | Maily.allow_edition = false
90 | ```
91 |
92 | ### Templates edition (`allow_edition` option)
93 |
94 | This feature was designed for the `development` environment. Since it's based on just a file edition, and while running in `production` mode, code is not reloaded between requests, Rails doesn't take into account your changes (without restarting the server). Actually, allowing arbitrary Ruby code evaluation is potentially dangerous, and that's not a good idea in `production`.
95 |
96 | So, template edition is not allowed outside of the `development` environment.
97 |
98 | ## Hooks
99 |
100 | Most of emails need to populate some data to consume it and do interesting things. Hooks are used to define this data via a little DSL. Hooks also accept "callable" objects to *lazy* load variables/data, so each email will evaluate its hooks on demand. Example:
101 |
102 | ```ruby
103 | # lib/maily_hooks.rb
104 |
105 | user = User.new(email: 'user@example.com')
106 | lazy_user = -> { User.with_comments.first } # callable object, lazy evaluation
107 | comment = Struct.new(:body).new('Lorem ipsum') # stub way
108 | service = FactoryGirl.create(:service) # using fixtures with FactoryGirl
109 |
110 | Maily.hooks_for('Notifier') do |mailer|
111 | mailer.register_hook(:welcome, user, template_path: 'users')
112 | mailer.register_hook(:new_comment, lazy_user, comment)
113 | end
114 |
115 | Maily.hooks_for('PaymentNotifier') do |mailer|
116 | mailer.register_hook(:invoice, user, service)
117 | end
118 | ```
119 |
120 | Note that you are able to override the `template_path` and the `template_name` like can be done in Rails. You must pass these options as a hash and last argument:
121 |
122 | ```ruby
123 | Maily.hooks_for('YourMailerClass') do |mailer|
124 | mailer.register_hook(:a_random_email, template_path: 'notifications')
125 | mailer.register_hook(:another_email, template_name: 'email_base')
126 | end
127 | ```
128 |
129 | ### Email versions
130 |
131 | You can add versions for special emails. This is useful in some cases where template content depends on the parameters you provide, for instance, a welcome message for trial accounts and gold accounts.
132 |
133 | ```ruby
134 | free_trial_account = -> { Account.free_trial.first }
135 | gold_account = -> { Account.gold.first }
136 |
137 | Maily.hooks_for('Notifier') do |mailer|
138 | mailer.register_hook(:welcome, free_trial_account, version: 'Free trial account')
139 | mailer.register_hook(:welcome, gold_account, version: 'Gold account')
140 | end
141 | ```
142 |
143 | ### Email description
144 |
145 | You can add a description to any email and it will be displayed along with its preview. This is useful in some cases like: someone from another team, for example, a marketing specialist, visiting Maily to review some texts and images; they can easily understand when this email is sent by the system.
146 |
147 | ```ruby
148 | Maily.hooks_for('BookingNotifier') do |mailer|
149 | mailer.register_hook(:accepted, description: "This email is sent when a reservation has been accepted by the system." )
150 | end
151 | ```
152 |
153 | ### Hide emails
154 |
155 | You are also able to hide emails:
156 |
157 | ```ruby
158 | Maily.hooks_for('Notifier') do |mailer|
159 | mailer.hide_email(:sensible_email, :secret_email)
160 | end
161 | ```
162 |
163 | ### Use `params`
164 |
165 | Support for [`ActionMailer::Parameterized`](https://api.rubyonrails.org/classes/ActionMailer/Parameterized.html) is possible via `with_params` hash:
166 |
167 | ```ruby
168 | message = -> { 'Hello!' }
169 |
170 | Maily.hooks_for('Notifier') do |mailer|
171 | mailer.register_hook(:new_message, with_params: { message: message })
172 | end
173 | ```
174 |
175 | ### External emails
176 |
177 | If you want to register and display in the UI, emails from external sources, like for example a gem, you can do it by adding a hook. Example:
178 |
179 | ```ruby
180 | Maily.hooks_for('Devise::Mailer') do |mailer|
181 | mailer.hide_email(:unlock_instructions)
182 |
183 | mailer.register_hook(:reset_password_instructions, user, 'random_token')
184 | mailer.register_hook(:confirmation_instructions, user, 'random_token')
185 | mailer.register_hook(:password_change, user)
186 | end
187 | ```
188 |
189 | ## Authorization
190 |
191 | Basically, you have 2 ways to restrict access to the `Maily` section. You can even combine both.
192 |
193 | ### Custom base controller
194 |
195 | By default `Maily` runs under `ActionController::Base`, but you are able to customize that parent controller (`Maily.base_controller` option) in order to achieve (using, for example, `before_action` blocks) a kind of access control system. For example, set a different base controller:
196 |
197 | ```ruby
198 | Maily.base_controller = '::SuperAdminController'
199 | ```
200 |
201 | And then write your own authorization rules in this defined controller:
202 |
203 | ```ruby
204 | class SuperAdminController < ActionController::Base
205 | before_action :maily_authorized?
206 |
207 | private
208 |
209 | def maily_authorized?
210 | current_user.admin? || raise("You don't have access to this section!")
211 | end
212 | end
213 | ```
214 |
215 | ### HTTP basic authentication
216 |
217 | You can also authorize yours users via HTTP basic authentication, simply use this option:
218 |
219 | ```ruby
220 | Maily.http_authorization = { username: 'admin', password: 'secret' }
221 | ```
222 |
223 | ## Notes
224 |
225 | Rails 4.1 introduced a built-in mechanism to preview the application emails. It is in fact, a port of [basecamp/mail_view](https://github.com/basecamp/mail_view) gem to the core.
226 |
227 | Alternatively, there are more gems out there to get a similar functionality, but with different approaches and features. Like for example: [ryanb/letter_opener](https://github.com/ryanb/letter_opener), [sj26/mailcatcher](https://github.com/sj26/mailcatcher) or [glebm/rails_email_preview](https://github.com/glebm/rails_email_preview).
228 |
229 | ## Development
230 |
231 | Any kind of feedback, bug report, idea or enhancement are really appreciated :tada:
232 |
233 | To contribute, just fork the repo, hack on it and send a pull request. Don't forget to add tests for behaviour changes and run the test suite:
234 |
235 | ```
236 | > bundle exec rspec
237 | ```
238 |
239 | Run the test suite against all supported versions:
240 |
241 | ```
242 | > bundle exec appraisal install
243 | > bundle exec appraisal rspec
244 | ```
245 |
246 | Run specs against specific version:
247 |
248 | ```
249 | > bundle exec appraisal rails-6.0 rspec
250 | ```
251 |
252 | ### Demo
253 |
254 | Start a sample Rails app ([source code](spec/dummy)) with `Maily` integrated:
255 |
256 | ```
257 | > bundle exec rake web # PORT=4000 (default: 3000)
258 | ```
259 |
260 | ## License
261 |
262 | Copyright (c) Marc Anguera. Maily is released under the [MIT](MIT-LICENSE) License.
263 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | desc 'Start development Rails app'
4 | task :web do
5 | app_path = 'spec/dummy'
6 | port = ENV['PORT'] || 3000
7 |
8 | puts "Starting application in http://localhost:#{port} ... \n"
9 |
10 | Dir.chdir(app_path)
11 | exec("rails s -p #{port}")
12 | end
13 |
--------------------------------------------------------------------------------
/app/assets/images/maily/icons/globe.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Globe
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/assets/images/maily/icons/paperclip.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Paperclip
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/assets/images/maily/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markets/maily/a68c23139055516480758448b5eeaae5e0a0ad50/app/assets/images/maily/logo.png
--------------------------------------------------------------------------------
/app/controllers/maily/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | class ApplicationController < Maily.base_controller.constantize
3 | before_action :maily_enabled?, :http_authorization
4 |
5 | layout 'maily/application'
6 |
7 | def maily_params
8 | _params = {}
9 |
10 | [:mailer, :email, :version, :part, :locale, :version].each do |key|
11 | _params[key] = params[key] if params[key].present?
12 | end
13 |
14 | _params
15 | end
16 | helper_method :maily_params
17 |
18 | private
19 |
20 | def maily_enabled?
21 | Maily.enabled || head(404, message: "Maily disabled")
22 | end
23 |
24 | def http_authorization
25 | if auth_hash = Maily.http_authorization
26 | authenticate_or_request_with_http_basic do |username, password|
27 | username == auth_hash[:username] && password == auth_hash[:password]
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/controllers/maily/emails_controller.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | class EmailsController < Maily::ApplicationController
3 | before_action :allowed_action?, only: [:edit, :update, :deliver]
4 | before_action :load_mailers, only: [:index, :show, :edit]
5 | before_action :load_mailer_and_email, except: [:index]
6 | around_action :perform_with_locale, only: [:show, :raw, :deliver]
7 |
8 | def index
9 | end
10 |
11 | def show
12 | valid, message = @maily_email.validate_arguments
13 |
14 | unless valid
15 | redirect_to(root_path, alert: message)
16 | end
17 | end
18 |
19 | def raw
20 | content = if @email.multipart?
21 | params[:part] == 'text' ? @email.text_part.body : @email.html_part.body
22 | else
23 | @email.body
24 | end
25 |
26 | content = content.raw_source
27 | content = view_context.simple_format(content) if @email.sub_type == 'plain' || params[:part] == 'text'
28 |
29 | render html: content.html_safe, layout: false
30 | end
31 |
32 | def attachment
33 | attachment = @email.attachments.find { |elem| elem.filename == params[:attachment] }
34 |
35 | send_data attachment.body, filename: attachment.filename, type: attachment.content_type
36 | end
37 |
38 | def edit
39 | @template = @maily_email.template(params[:part])
40 | end
41 |
42 | def update
43 | @maily_email.update_template(params[:body], params[:part])
44 |
45 | redirect_to maily_email_path(maily_params), notice: 'Template updated!'
46 | end
47 |
48 | def deliver
49 | @email.to = params[:to]
50 |
51 | @email.deliver
52 |
53 | redirect_to maily_email_path(maily_params), notice: "Email sent to #{params[:to]} !"
54 | end
55 |
56 | private
57 |
58 | def allowed_action?
59 | Maily.allowed_action?(action_name) || redirect_to(root_path, alert: "Action #{action_name} not allowed!")
60 | end
61 |
62 | def load_mailers
63 | @mailers = Maily::Mailer.list
64 | end
65 |
66 | def load_mailer_and_email
67 | mailer = Maily::Mailer.find(params[:mailer])
68 | @maily_email = mailer.find_email(params[:email], params[:version])
69 |
70 | if @maily_email
71 | @email = @maily_email.call
72 | else
73 | redirect_to(root_path, alert: "Email not found!")
74 | end
75 | end
76 |
77 | def perform_with_locale
78 | I18n.with_locale(params[:locale]) do
79 | yield
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/app/helpers/maily/application_helper.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | module ApplicationHelper
3 | def title
4 | _title = 'Maily'
5 |
6 | if params[:mailer] && params[:email]
7 | _title << " - #{params[:mailer].humanize} | #{params[:email].humanize}"
8 | end
9 |
10 | _title
11 | end
12 |
13 | def sidebar_class(mailer, email)
14 | 'selected_mail' if mailer.name == params[:mailer] && email.name == params[:email]
15 | end
16 |
17 | def version_list_class(email)
18 | 'selected_mail' if email.name == params[:email] && email.version == params[:version]
19 | end
20 |
21 | def logo
22 | image_tag(file_to_base64('logo.png', 'image/png'))
23 | end
24 |
25 | def icon(name)
26 | image_tag(file_to_base64("icons/#{name}.svg", 'image/svg+xml'), class: :icon)
27 | end
28 |
29 | private
30 |
31 | def file_to_base64(path, mime_type)
32 | file = Maily::Engine.root.join('app/assets/images/maily').join(path)
33 | base64_contents = Base64.strict_encode64(file.read)
34 |
35 | "data:#{mime_type};base64,#{base64_contents}"
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/helpers/maily/emails_helper.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | module EmailsHelper
3 | def total_emails(mailers)
4 | mailers.map { |mailer| mailer.total_emails }.sum
5 | end
6 |
7 | def email_description(email)
8 | return unless email.description
9 |
10 | tag.div(class: 'mail_description') do
11 | concat tag.strong('Description ')
12 | concat email.description
13 | end
14 | end
15 |
16 | def part_class(part)
17 | 'format_selected' if part == params[:part] || (part == 'html' && !params[:part])
18 | end
19 |
20 | def uniq_emails(email_list)
21 | email_list.inject([]) do |memo, email|
22 | memo << email unless memo.map(&:name).include?(email.name)
23 | memo
24 | end
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/views/layouts/maily/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= title %>
5 | <%= csrf_meta_tags %>
6 | <%= render 'maily/shared/stylesheet' %>
7 | <%= render 'maily/shared/javascript' %>
8 |
9 |
10 | <%= render 'maily/shared/header' %>
11 |
12 |
13 | <%= render 'maily/shared/sidebar' %>
14 |
15 |
16 | <%= render 'maily/shared/flash_messages' %>
17 |
18 | <%= yield %>
19 |
20 |
21 |
22 | <%= render 'maily/shared/footer' %>
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/views/maily/emails/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= email_description(@maily_email) %>
2 |
3 | <%= form_tag(update_maily_email_path(maily_params), method: :put) do %>
4 |
5 |
6 | <%= link_to 'Show', maily_email_path(maily_params) %>
7 |
8 |
9 | |
10 |
11 |
12 | <%= submit_tag 'Update', class: 'button' %>
13 |
14 |
15 |
16 |
17 | <%= text_area_tag :body, @template, class: 'mail_updatearea' %>
18 |
19 | <% end %>
20 |
--------------------------------------------------------------------------------
/app/views/maily/emails/index.html.erb:
--------------------------------------------------------------------------------
1 | <%= total_emails(@mailers) %> emails found
2 | Use the menu on the left hand side of the screen to navigate through the different email templates.
3 |
4 |
5 | <%= Maily.welcome_message.to_s.html_safe %>
6 |
7 |
--------------------------------------------------------------------------------
/app/views/maily/emails/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= email_description(@maily_email) %>
2 |
3 |
4 |
5 | <%= link_to 'Edit', edit_maily_email_path(maily_params) %>
6 |
7 |
8 |
9 | <% if Maily.available_locales.present? %>
10 |
11 |
12 | <%= icon 'globe' %> Languages
13 |
14 | <% Maily.available_locales.each do |locale| %>
15 | <%= link_to locale.upcase, url_for(params.to_unsafe_h.merge(locale: locale)) %>
16 | <% end %>
17 |
18 | <% end %>
19 |
20 |
21 | <% if Maily.allowed_action?(:deliver) %>
22 | |
23 |
24 |
25 | <%= form_tag(deliver_maily_email_path(maily_params), method: :post, class: 'mail_deliver') do %>
26 |
27 | Send to
28 | <%= email_field_tag :to, nil, placeholder: "Enter email", required: true %>
29 | <%= submit_tag 'Send', class: 'button' %>
30 |
31 | <% end %>
32 |
33 | <% end %>
34 |
35 |
36 | <% if @maily_email.has_versions? %>
37 |
38 | Versions
39 |
40 | <% @maily_email.versions.each do |_version_key, version_email| %>
41 | <%= link_to version_email.version.humanize, maily_email_path(maily_params.merge(version: version_email.version)), class: version_list_class(version_email) %>
42 | <% end %>
43 |
44 |
45 | <% end %>
46 |
47 |
48 |
49 | <% if @email.subject %>Subject <%= @email.subject %> <% end %>
50 | <% if @email.from %>From <%= @email.from.join(', ') %> <% end %>
51 | <% if @email.to %>To <%= @email.to.join(', ') %> <% end %>
52 | <% if @email.cc %>Cc <%= @email.cc.join(', ') %> <% end %>
53 | <% if @email.bcc %>Bcc <%= @email.bcc.join(', ') %> <% end %>
54 | <% if @email.reply_to %>Reply to <%= @email.reply_to.join(', ') %> <% end %>
55 |
56 |
57 | <% if @email.html_part && @email.text_part %>
58 |
62 | <% end %>
63 |
64 | <% if @email.html_part || @email.text_part || @email.body.present? %>
65 |
66 | <% end %>
67 |
68 | <% if @email.has_attachments? %>
69 |
70 | Attachments:
71 |
72 |
73 | <% @email.attachments.each do |attachment| %>
74 |
75 | <%= icon 'paperclip' %>
76 | <%= link_to attachment.filename, attachment_maily_email_path(maily_params.merge(attachment: attachment.filename)) %>
77 |
78 | <% end %>
79 |
80 |
81 |
82 | <% end %>
83 |
84 |
--------------------------------------------------------------------------------
/app/views/maily/shared/_flash_messages.html.erb:
--------------------------------------------------------------------------------
1 | <% if flash[:alert].present? %>
2 | <%= flash[:alert].html_safe %>
3 | <% end %>
4 |
5 | <% if flash[:notice].present? %>
6 | <%= flash[:notice].html_safe %>
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/maily/shared/_footer.html.erb:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/views/maily/shared/_header.html.erb:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/app/views/maily/shared/_javascript.html.erb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/maily/shared/_sidebar.html.erb:
--------------------------------------------------------------------------------
1 |
22 |
--------------------------------------------------------------------------------
/app/views/maily/shared/_stylesheet.html.erb:
--------------------------------------------------------------------------------
1 | <%
2 | blue = "#59abc6"
3 | grey = "#cccccc"
4 | dark_blue = "#2f738a"
5 | dark_grey = "#666666"
6 | text_color = "#333333"
7 | link_color = blue
8 | hover_color = dark_blue
9 | border_radius = "3px"
10 | %>
11 |
12 |
284 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Maily::Engine.routes.draw do
2 | get ':mailer/:email' => 'emails#show', as: :maily_email
3 | get ':mailer/:email/raw' => 'emails#raw', as: :raw_maily_email
4 | get ':mailer/:email/edit' => 'emails#edit', as: :edit_maily_email
5 | put ':mailer/:email' => 'emails#update', as: :update_maily_email
6 | post ':mailer/:email/deliver' => 'emails#deliver', as: :deliver_maily_email
7 | get ':mailer/:email/attachment' => 'emails#attachment', as: :attachment_maily_email
8 |
9 | root :to => 'emails#index'
10 | end
11 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "byebug", group: [:development, :test]
6 | gem "rails", "~> 5.2.0"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "byebug", group: [:development, :test]
6 | gem "rails", "~> 6.0.0"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "byebug", group: [:development, :test]
6 | gem "rails", "~> 6.1.0"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_7.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "byebug", group: [:development, :test]
6 | gem "rails", "~> 7.0.0"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_7.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "byebug", group: [:development, :test]
6 | gem "rails", "~> 7.1.0"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/lib/generators/maily/install_generator.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | module Generators
3 | class InstallGenerator < Rails::Generators::Base
4 | desc 'Maily installation: route and initializer'
5 | source_root File.expand_path("../../templates", __FILE__)
6 |
7 | def install
8 | puts "==> Installing Maily components ..."
9 | generate_routing
10 | copy_initializer
11 | build_hooks
12 | puts "Ready! You can now access Maily at /maily"
13 | end
14 |
15 | private
16 |
17 | def generate_routing
18 | route "mount Maily::Engine, at: '/maily'"
19 | end
20 |
21 | def copy_initializer
22 | template 'initializer.rb', 'config/initializers/maily.rb'
23 | end
24 |
25 | def build_hooks
26 | create_file "lib/maily_hooks.rb" do
27 | Maily::Generator.run
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/generators/templates/initializer.rb:
--------------------------------------------------------------------------------
1 | Maily.setup do |config|
2 | # On/off engine
3 | # config.enabled = !Rails.env.production?
4 |
5 | # Allow templates edition
6 | # config.allow_edition = !Rails.env.production?
7 |
8 | # Allow deliveries
9 | # config.allow_delivery = !Rails.env.production?
10 |
11 | # Your application available_locales (or I18n.available_locales) by default
12 | # config.available_locales = [:en, :es, :pt, :fr]
13 |
14 | # Run maily under different controller ('ActionController::Base' by default)
15 | # config.base_controller = '::AdminController'
16 |
17 | # Configure hooks path
18 | # config.hooks_path = 'lib/maily_hooks.rb'
19 |
20 | # Http basic authentication (nil by default)
21 | # config.http_authorization = { username: 'admin', password: 'secret' }
22 |
23 | # Customize welcome message
24 | # config.welcome_message = "Welcome to our email testing platform. If you have any problem, please contact support team at support@example.com."
25 | end
26 |
--------------------------------------------------------------------------------
/lib/maily.rb:
--------------------------------------------------------------------------------
1 | require 'maily/version'
2 | require 'maily/engine'
3 | require 'maily/mailer'
4 | require 'maily/email'
5 | require 'maily/generator'
6 |
7 | module Maily
8 | class << self
9 | attr_accessor :enabled, :allow_edition, :allow_delivery, :available_locales,
10 | :base_controller, :http_authorization, :hooks_path, :welcome_message
11 |
12 | def init!
13 | self.enabled = !Rails.env.production?
14 | self.allow_edition = !Rails.env.production?
15 | self.allow_delivery = !Rails.env.production?
16 | self.available_locales = Rails.application.config.i18n.available_locales || I18n.available_locales
17 | self.base_controller = 'ActionController::Base'
18 | self.http_authorization = nil
19 | self.hooks_path = "lib/maily_hooks.rb"
20 | self.welcome_message = nil
21 | end
22 |
23 | def load_emails_and_hooks
24 | # Load emails from file system
25 | Dir[Rails.root + 'app/mailers/*.rb'].each do |mailer|
26 | klass_name = File.basename(mailer, '.rb')
27 | Maily::Mailer.new(klass_name)
28 | end
29 |
30 | # Load hooks
31 | hooks_file_path = File.join(Rails.root, hooks_path)
32 | require hooks_file_path if File.exist?(hooks_file_path)
33 | end
34 |
35 | def hooks_for(mailer_name)
36 | mailer_name = mailer_name.underscore
37 | mailer = Maily::Mailer.find(mailer_name) || Maily::Mailer.new(mailer_name)
38 |
39 | yield(mailer) if block_given?
40 | end
41 |
42 | def setup
43 | init!
44 | yield(self) if block_given?
45 | end
46 |
47 | def allowed_action?(action)
48 | case action.to_sym
49 | when :edit
50 | allow_edition
51 | when :update
52 | allow_edition && !Rails.env.production?
53 | when :deliver
54 | allow_delivery
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/maily/email.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | class Email
3 | DEFAULT_VERSION = 'default'.freeze
4 |
5 | attr_accessor :name, :mailer, :arguments, :template_path, :template_name, :description, :with_params, :version
6 |
7 | class << self
8 | def name_with_version(name, version = nil)
9 | _version = formatted_version(version)
10 | [name, _version].join(':')
11 | end
12 |
13 | def formatted_version(version)
14 | _version = version.presence || DEFAULT_VERSION
15 | _version&.parameterize&.underscore
16 | end
17 | end
18 |
19 | def initialize(name, mailer)
20 | self.name = name
21 | self.mailer = mailer
22 | self.arguments = nil
23 | self.with_params = nil
24 | self.template_path = mailer.name
25 | self.template_name = name
26 | self.description = nil
27 | end
28 |
29 | def mailer_klass
30 | mailer.klass
31 | end
32 |
33 | def parameterized_mailer_klass
34 | params = with_params && with_params.transform_values { |param| param.respond_to?(:call) ? param.call : param }
35 | mailer_klass.with(params)
36 | end
37 |
38 | def parameters
39 | mailer_klass.instance_method(name).parameters
40 | end
41 |
42 | def require_hook?
43 | parameters.any?
44 | end
45 |
46 | def required_arguments
47 | parameters.select { |param| param.first == :req }.map(&:last)
48 | end
49 |
50 | def optional_arguments
51 | parameters.select { |param| param.first == :opt }.map(&:last)
52 | end
53 |
54 | def validate_arguments
55 | from = required_arguments.size
56 | to = from + optional_arguments.size
57 | passed_by_hook = arguments && arguments.size || 0
58 |
59 | if passed_by_hook < from
60 | [false, "#{name} email requires at least #{from} arguments, passed #{passed_by_hook}"]
61 | elsif passed_by_hook > to
62 | [false, "#{name} email requires at the most #{to} arguments, passed #{passed_by_hook}"]
63 | else
64 | [true, nil]
65 | end
66 | end
67 |
68 | def register_hook(*args)
69 | if args.last.is_a?(Hash)
70 | self.description = args.last.delete(:description)
71 | self.with_params = args.last.delete(:with_params)
72 | self.version = Maily::Email.formatted_version(args.last.delete(:version))
73 |
74 | if tpl_path = args.last.delete(:template_path)
75 | self.template_path = tpl_path
76 | end
77 |
78 | if tpl_name = args.last.delete(:template_name)
79 | self.template_name = tpl_name
80 | end
81 |
82 | args.pop
83 | end
84 |
85 | self.arguments = args
86 | end
87 |
88 | def call
89 | *args = arguments && arguments.map { |arg| arg.respond_to?(:call) ? arg.call : arg }
90 |
91 | message = if args == [nil]
92 | parameterized_mailer_klass.public_send(name)
93 | else
94 | parameterized_mailer_klass.public_send(name, *args)
95 | end
96 |
97 | ActionMailer::Base.preview_interceptors.each do |interceptor|
98 | interceptor.previewing_email(message)
99 | end
100 |
101 | message
102 | end
103 |
104 | def base_path(part)
105 | Dir["#{Rails.root}/app/views/#{template_path}/#{template_name}.#{part}.*"].first
106 | end
107 |
108 | def path(part = nil)
109 | return base_path(part) if part
110 |
111 | html_part = base_path('html')
112 | if html_part && File.exist?(html_part)
113 | html_part
114 | else
115 | base_path('text')
116 | end
117 | end
118 |
119 | def template(part = nil)
120 | File.read(path(part))
121 | end
122 |
123 | def update_template(new_content, part = nil)
124 | File.open(path(part), 'w') do |f|
125 | f.write(new_content)
126 | end
127 | end
128 |
129 | def versions
130 | regexp = Regexp.new("^#{self.name}:")
131 |
132 | mailer.emails.select do |email_key, _email|
133 | email_key.match?(regexp)
134 | end
135 | end
136 |
137 | def has_versions?
138 | versions.count > 1
139 | end
140 | end
141 | end
142 |
--------------------------------------------------------------------------------
/lib/maily/engine.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | class Engine < ::Rails::Engine
3 | isolate_namespace Maily
4 | load_generators
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/maily/generator.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | module Generator
3 | def self.run
4 | Maily.init!
5 |
6 | fixtures = []
7 | hooks = []
8 |
9 | Maily::Mailer.list.each do |mailer|
10 | _hooks = []
11 |
12 | mailer.emails_list.each do |email|
13 | if email.require_hook?
14 | fixtures << email.required_arguments
15 | _hooks << " mailer.register_hook(:#{email.name}, #{email.required_arguments.join(', ')})"
16 | end
17 | end
18 |
19 | if _hooks.present?
20 | hooks << "\nMaily.hooks_for('#{mailer.name.classify}') do |mailer|"
21 | hooks << _hooks
22 | hooks << "end"
23 | end
24 | end
25 |
26 | fixtures = fixtures.flatten.uniq.map do |fixture|
27 | argument = fixture.to_s
28 | value = argument.pluralize == argument ? '[]' : "''"
29 |
30 | [argument, value].join(' = ')
31 | end.join("\n")
32 |
33 | fixtures + "\n" + hooks.join("\n") + "\n"
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/maily/mailer.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | class Mailer
3 | cattr_accessor :collection
4 | attr_accessor :name, :emails, :klass
5 |
6 | def initialize(name)
7 | self.name = name
8 | self.klass = name.camelize.constantize
9 | self.emails = {}
10 |
11 | parse_emails
12 |
13 | self.class.collection ||= {}
14 | self.class.collection[name] = self
15 | end
16 |
17 | def self.all
18 | Maily.load_emails_and_hooks if collection.nil?
19 | collection
20 | end
21 |
22 | def self.list
23 | all.values.sort_by(&:name)
24 | end
25 |
26 | def self.find(mailer_name)
27 | all[mailer_name]
28 | end
29 |
30 | def find_email(email_name, version = nil)
31 | key = Maily::Email.name_with_version(email_name, version)
32 | emails[key]
33 | end
34 |
35 | def emails_list
36 | emails.values.sort_by(&:name)
37 | end
38 |
39 | def total_emails
40 | emails.size
41 | end
42 |
43 | def register_hook(email_name, *args)
44 | version = get_version(*args)
45 | email = find_email(email_name, version) || add_email(email_name, version)
46 | email && email.register_hook(*args)
47 | end
48 |
49 | def hide_email(*email_names)
50 | email_names.each do |email_name|
51 | _email_name = Maily::Email.name_with_version(email_name.to_s)
52 | emails.delete(_email_name)
53 | end
54 | end
55 |
56 | private
57 |
58 | def parse_emails
59 | _emails = klass.send(:public_instance_methods, false)
60 |
61 | _emails.each do |email|
62 | add_email(email)
63 | end
64 | end
65 |
66 | def add_email(email_name, version = nil)
67 | hide_email(email_name) if version
68 | email = Maily::Email.new(email_name.to_s, self)
69 | key = Maily::Email.name_with_version(email_name, version)
70 | emails[key] = email
71 | end
72 |
73 | def get_version(*args)
74 | return unless args.last.is_a?(Hash)
75 |
76 | Maily::Email.formatted_version(args.last[:version])
77 | end
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/lib/maily/version.rb:
--------------------------------------------------------------------------------
1 | module Maily
2 | VERSION = "2.1.0"
3 | end
4 |
--------------------------------------------------------------------------------
/maily.gemspec:
--------------------------------------------------------------------------------
1 | require "./lib/maily/version"
2 |
3 | Gem::Specification.new do |s|
4 | s.name = "maily"
5 | s.version = Maily::VERSION
6 | s.authors = ["markets"]
7 | s.email = ["srmarc.ai@gmail.com"]
8 | s.homepage = "https://github.com/markets/maily"
9 | s.summary = "Rails Engine to preview emails in the browser."
10 | s.description = "Maily is a Rails Engine to manage, test and navigate through all your email templates of your app, being able to preview them directly in your browser."
11 | s.license = "MIT"
12 |
13 | s.files = Dir["{app,config,lib}/**/*"] + %w[README.md CHANGELOG.md MIT-LICENSE]
14 | s.require_paths = ["lib"]
15 |
16 | s.add_dependency "rails", ">= 4.2"
17 |
18 | s.add_development_dependency "rspec-rails"
19 | s.add_development_dependency "appraisal"
20 | end
21 |
--------------------------------------------------------------------------------
/spec/controllers_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Maily::EmailsController, type: :controller do
2 | render_views
3 |
4 | routes { Maily::Engine.routes }
5 |
6 | before(:each) do
7 | Maily.init!
8 | end
9 |
10 | it 'responds ok if enabled' do
11 | expect { get :index }.not_to raise_error
12 | end
13 |
14 | it 'raises 404 if disabled' do
15 | Maily.enabled = false
16 |
17 | get :index
18 |
19 | expect(response.status).to eq(404)
20 | end
21 |
22 | it 'responds with 401 if http authorization fails' do
23 | Maily.http_authorization = { username: 'admin', password: 'admin' }
24 |
25 | get :index
26 |
27 | expect(response.status).to eq(401)
28 | end
29 |
30 | it 'responds ok with valid http authorization' do
31 | Maily.http_authorization = { username: 'admin', password: 'admin' }
32 | request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('admin', 'admin')
33 |
34 | get :index
35 |
36 | expect(response.status).to eq(200)
37 | end
38 |
39 | describe 'non-existent emails' do
40 | it 'email not in the system -> 302' do
41 | get :show, params: { mailer: 'notifier', email: 'non_existent_email' }
42 |
43 | expect(response.status).to eq(302)
44 | expect(flash.alert).to eq("Email not found!")
45 | end
46 |
47 | it 'hidden emails -> 302' do
48 | get :show, params: { mailer: 'notifier', email: 'hidden' }
49 |
50 | expect(response.status).to eq(302)
51 | expect(flash.alert).to eq("Email not found!")
52 | end
53 | end
54 |
55 | describe 'GET #raw' do
56 | it 'renders the template (HTML part)' do
57 | get :raw, params: { mailer: 'notifier', email: 'with_arguments' }
58 |
59 | expect(response.body).to match("With arguments ")
60 | end
61 |
62 | it 'renders the template (TEXT part)' do
63 | get :raw, params: { mailer: 'notifier', email: 'only_text' }
64 |
65 | expect(response.body).to match("Text part\n with break lines
")
66 | end
67 |
68 | it 'renders inline attachments' do
69 | get :raw, params: { mailer: 'notifier', email: 'with_inline_attachments' }
70 |
71 | expect(response.body).to match("data:image/jpeg;base64")
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/spec/dummy/README.md:
--------------------------------------------------------------------------------
1 | # Dummy App
2 |
3 | Dummy Rails application to test the `Maily` engine.
4 |
5 | Run the suite (from `Maily` root path):
6 |
7 | ```
8 | > bundle exec rake
9 | ```
10 |
11 | It's also used as a demo application to show `Maily` in action and for development purposes. You can run the app locally by using the following command:
12 |
13 | ```
14 | > bundle exec rake web
15 | ```
16 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | Dummy::Application.load_tasks
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | # Prevent CSRF attacks by raising an exception.
3 | # For APIs, you may want to use :null_session instead.
4 | protect_from_forgery with: :exception
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'foo@foo.com', to: 'bar@bar.com'
3 |
4 | def from_other_class
5 | mail template_path: 'notifier', template_name: 'custom_template'
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/notifier.rb:
--------------------------------------------------------------------------------
1 | class Notifier < ApplicationMailer
2 | def no_arguments
3 | mail
4 | end
5 |
6 | def missing_arguments(email)
7 | mail
8 | end
9 |
10 | def with_arguments(email, opt_arg = nil)
11 | mail
12 | end
13 |
14 | def with_array_arguments(emails)
15 | mail
16 | end
17 |
18 | def with_description
19 | mail
20 | end
21 |
22 | def with_params
23 | @message = params[:message]
24 | mail
25 | end
26 |
27 | def custom_template_path
28 | mail template_path: 'notifications'
29 | end
30 |
31 | def custom_template_name
32 | mail template_name: 'custom_template'
33 | end
34 |
35 | def hidden
36 | mail
37 | end
38 |
39 | def multipart
40 | mail
41 | end
42 |
43 | def only_text
44 | mail
45 | end
46 |
47 | def with_slim_template
48 | mail
49 | end
50 |
51 | def with_inline_attachments
52 | attachments.inline['image.jpg'] = File.read(Rails.root.join("public/favicon.ico"))
53 | mail
54 | end
55 |
56 | def version(account)
57 | @account = account
58 | mail
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifications/custom_template_path.html.erb:
--------------------------------------------------------------------------------
1 | Custom template path
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/custom_template.html.erb:
--------------------------------------------------------------------------------
1 | Custom template
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/multipart.html.erb:
--------------------------------------------------------------------------------
1 | Multipart HTML
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/multipart.text.erb:
--------------------------------------------------------------------------------
1 | Multipart plain text
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/no_arguments.html.erb:
--------------------------------------------------------------------------------
1 | No arguments
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/only_text.text.erb:
--------------------------------------------------------------------------------
1 | Text part
2 | with break lines
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/version.html.erb:
--------------------------------------------------------------------------------
1 | Welcome to version email <%= @account.name %>
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/with_arguments.html.erb:
--------------------------------------------------------------------------------
1 | With arguments
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/with_array_arguments.html.erb:
--------------------------------------------------------------------------------
1 | With array arguments
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/with_description.html.erb:
--------------------------------------------------------------------------------
1 | This email has a description
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/with_inline_attachments.html.erb:
--------------------------------------------------------------------------------
1 | Inline Attachments
2 |
3 |
4 | <%= image_tag attachments['image.jpg'].url %>
5 |
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/with_params.html.erb:
--------------------------------------------------------------------------------
1 | With params
2 | <%= @message %>
3 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/notifier/with_slim_template.html.slim:
--------------------------------------------------------------------------------
1 | p Hi from a Slim template
2 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'action_controller/railtie'
4 | require 'action_view/railtie'
5 | require 'action_mailer/railtie'
6 |
7 | Bundler.require(*Rails.groups)
8 |
9 | require 'maily'
10 |
11 | module Dummy
12 | class Application < Rails::Application
13 | # Settings in config/environments/* take precedence over those specified here.
14 | # Application configuration should go into files in config/initializers
15 | # -- all .rb files in that directory are automatically loaded.
16 |
17 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
18 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
19 | # config.time_zone = 'Central Time (US & Canada)'
20 |
21 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
22 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
23 | # config.i18n.default_locale = :de
24 | config.i18n.available_locales = [:en, :es, :pt, :fr]
25 | end
26 | end
27 |
28 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | Dummy::Application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations
23 | # config.active_record.migration_error = :page_load
24 | end
25 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both thread web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Specifies the header that your server uses for sending files.
26 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
27 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
28 |
29 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
30 | # config.force_ssl = true
31 |
32 | # Set to :debug to see everything in the log.
33 | config.log_level = :info
34 |
35 | # Prepend all log lines with the following tags.
36 | # config.log_tags = [ :subdomain, :uuid ]
37 |
38 | # Use a different logger for distributed setups.
39 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
40 |
41 | # Use a different cache store in production.
42 | # config.cache_store = :mem_cache_store
43 |
44 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
45 | # config.action_controller.asset_host = "http://assets.example.com"
46 |
47 | # Precompile additional assets.
48 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
49 | # config.assets.precompile += %w( search.js )
50 |
51 | # Ignore bad email addresses and do not raise email delivery errors.
52 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
53 | # config.action_mailer.raise_delivery_errors = false
54 |
55 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
56 | # the I18n.default_locale when a translation can not be found).
57 | config.i18n.fallbacks = true
58 |
59 | # Send deprecation notices to registered listeners.
60 | config.active_support.deprecation = :notify
61 |
62 | # Disable automatic flushing of the log to improve performance.
63 | # config.autoflush_log = false
64 |
65 | # Use default logging formatter so that PID and timestamp are not suppressed.
66 | config.log_formatter = ::Logger::Formatter.new
67 | end
68 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_assets = true
17 | config.static_cache_control = "public, max-age=3600"
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 | end
37 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/maily.rb:
--------------------------------------------------------------------------------
1 | Maily.setup do |config|
2 | config.enabled = true
3 | config.allow_edition = true
4 | config.allow_delivery = true
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure your secret_key_base is kept private
11 | # if you're sharing your code publicly.
12 | Dummy::Application.config.secret_key_base = '2a1193cd2b7a71bb336cc1939bb8e651b330fe43d4792794b0aef2f61a72e3599fa780d0189c303865f8df19785b33b8f3291201299b382766d7dac7953a5983'
13 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.routes.draw do
2 | mount Maily::Engine, at: '/maily'
3 |
4 | root to: redirect('/maily')
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/lib/maily_hooks.rb:
--------------------------------------------------------------------------------
1 | email = -> { 'foo@foo.com' }
2 | emails = ['bar@foo.com']
3 | message = -> { 'Hello!' }
4 | free_trial_account = -> { OpenStruct.new({ name: 'Nicolas Cage' }) }
5 | gold_account = -> { OpenStruct.new({ name: 'John Doe' }) }
6 |
7 | Maily.hooks_for('Notifier') do |mailer|
8 | mailer.register_hook(:with_arguments, email)
9 | mailer.register_hook(:with_array_arguments, emails)
10 | mailer.register_hook(:with_description, description: 'description')
11 | mailer.register_hook(:with_params, with_params: { message: message })
12 | mailer.register_hook(:custom_template_path, template_path: 'notifications')
13 | mailer.register_hook(:custom_template_name, template_name: 'custom_template')
14 | mailer.register_hook(:from_other_class)
15 | mailer.register_hook(:version, free_trial_account, version: 'Free trial account')
16 | mailer.register_hook(:version, gold_account, version: 'Gold account')
17 |
18 | mailer.hide_email(:hidden)
19 | end
20 |
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markets/maily/a68c23139055516480758448b5eeaae5e0a0ad50/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The page you were looking for doesn't exist.
54 |
You may have mistyped the address or the page may have moved.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
The change you wanted was rejected.
54 |
Maybe you tried to change something you didn't have access to.
55 |
56 | If you are the application owner check the logs for more information.
57 |
58 |
59 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
48 |
49 |
50 |
51 |
52 |
53 |
We're sorry, but something went wrong.
54 |
55 | If you are the application owner check the logs for more information.
56 |
57 |
58 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markets/maily/a68c23139055516480758448b5eeaae5e0a0ad50/spec/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/spec/email_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Maily::Email do
2 | let(:mailer) { Maily::Mailer.find('notifier') }
3 |
4 | context "with no arguments" do
5 | let (:email) { mailer.find_email('no_arguments') }
6 |
7 | it "should not require hook" do
8 | expect(email.required_arguments).to be_blank
9 | expect(email.require_hook?).to be false
10 | end
11 |
12 | it ".call" do
13 | expect { email.call }.to_not raise_error
14 | end
15 | end
16 |
17 | context "with arguments" do
18 | let (:email) { mailer.find_email('with_arguments') }
19 |
20 | it "should require hook" do
21 | expect(email.required_arguments).to be_present
22 | expect(email.require_hook?).to be true
23 | end
24 |
25 | it 'should handle lazy arguments successfully' do
26 | expect(email.arguments).to be_present
27 | expect(email.arguments.size).to eq(email.required_arguments.size)
28 | end
29 |
30 | it 'should handle not lazy arguments successfully' do
31 | allow(email).to receive(:email).and_return('foo@foo.com')
32 |
33 | expect(email.arguments).to be_present
34 | expect(email.arguments.size).to eq(email.required_arguments.size)
35 | end
36 |
37 | it 'should handle array type arguments' do
38 | email = mailer.find_email('with_array_arguments')
39 |
40 | expect(email.arguments.first).to be_an(Array)
41 | expect(email.arguments.size).to eq(email.required_arguments.size)
42 | end
43 |
44 | it ".call" do
45 | expect { email.call }.to_not raise_error
46 | end
47 | end
48 |
49 | context "with params" do
50 | let (:email) { mailer.find_email('with_params') }
51 |
52 | it "should not require hook" do
53 | expect(email.required_arguments).to be_blank
54 | expect(email.require_hook?).to be false
55 | end
56 |
57 | it 'should handle lazy params successfully' do
58 | expect(email.with_params).to be_present
59 | expect { email.call }.to_not raise_error
60 | end
61 |
62 | it 'should handle not lazy arguments successfully' do
63 | allow(email).to receive(:message).and_return('Hello!')
64 |
65 | expect(email.with_params).to be_present
66 | expect { email.call }.to_not raise_error
67 | end
68 | end
69 |
70 | it "should handle template_path via hook" do
71 | email = mailer.find_email('custom_template_path')
72 |
73 | expect(email.template_path).to eq('notifications')
74 | end
75 |
76 | it "should handle template_name via hook" do
77 | email = mailer.find_email('custom_template_name')
78 |
79 | expect(email.template_name).to eq('custom_template')
80 | end
81 |
82 | it "should handle description via hook" do
83 | email = mailer.find_email('with_description')
84 |
85 | expect(email.description).to eq('description')
86 | end
87 |
88 | describe '#validate_arguments' do
89 | it 'emails with no arguments required' do
90 | email = mailer.find_email('no_arguments')
91 | expect(email.validate_arguments).to eq [true, nil]
92 |
93 | email.arguments = ["asd"]
94 | expect(email.validate_arguments[1]).to match(/email requires at the most 0 arguments, passed 1/)
95 |
96 | email.arguments = nil # reset arguments for future calls
97 | end
98 |
99 | it 'emails with arguments required' do
100 | email = mailer.find_email('with_arguments')
101 | expect(email.validate_arguments).to eq [true, nil]
102 |
103 | email = mailer.find_email('missing_arguments')
104 | expect(email.validate_arguments[1]).to match(/email requires at least 1 arguments, passed 0/)
105 | end
106 | end
107 |
108 | describe '#path' do
109 | it 'with a multipart email defaults to html' do
110 | email = mailer.find_email('multipart')
111 |
112 | expect(email.path).to include('multipart.html.erb')
113 | expect(email.path('text')).to include('multipart.text.erb')
114 | expect(email.path('foo')).to eq nil
115 | end
116 |
117 | it 'with other template engines (e.g. Slim)' do
118 | email = mailer.find_email('with_slim_template')
119 |
120 | expect(email.path).to include('with_slim_template.html.slim')
121 | end
122 | end
123 |
124 | context 'class methods' do
125 | describe '#name_with_version' do
126 | it 'without version' do
127 | expect(Maily::Email.name_with_version('welcome', nil)).to eq("welcome:#{Maily::Email::DEFAULT_VERSION}")
128 | end
129 |
130 | it 'with version' do
131 | expect(Maily::Email.name_with_version('welcome', 'first_version')).to eq('welcome:first_version')
132 | end
133 | end
134 |
135 | describe '#formatted_version' do
136 | it 'without version' do
137 | expect(Maily::Email.formatted_version(nil)).to eq(Maily::Email.formatted_version(Maily::Email::DEFAULT_VERSION))
138 | end
139 |
140 | it 'with version' do
141 | expect(Maily::Email.formatted_version('First Version')).to eq('first_version')
142 | end
143 | end
144 | end
145 | end
146 |
--------------------------------------------------------------------------------
/spec/generator_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Maily::Generator do
2 | it '.run generates valid fixtures and hooks for current application' do
3 | expect(Maily::Generator.run).to eq <<-HOOKS.strip_heredoc
4 | email = ''
5 | account = ''
6 | emails = []
7 |
8 | Maily.hooks_for('Notifier') do |mailer|
9 | mailer.register_hook(:missing_arguments, email)
10 | mailer.register_hook(:version, account)
11 | mailer.register_hook(:version, account)
12 | mailer.register_hook(:with_arguments, email)
13 | mailer.register_hook(:with_array_arguments, emails)
14 | end
15 | HOOKS
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/mailer_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Maily::Mailer do
2 | let(:mailer) { Maily::Mailer.find('notifier') }
3 |
4 | it "should load mailers" do
5 | expect(Maily::Mailer.all.keys).to include('application_mailer', 'notifier')
6 | end
7 |
8 | it "should build emails" do
9 | expect(mailer.emails.size).to eq(15)
10 | end
11 |
12 | it "should find mailers by name" do
13 | expect(Maily::Mailer.find('notifier').name).to eq('notifier')
14 | end
15 |
16 | it "should find emails by name" do
17 | expect(mailer.find_email('no_arguments').name).to eq('no_arguments')
18 | end
19 |
20 | it "should find emails by name and version" do
21 | email_name = 'version'
22 | version = Maily::Email.formatted_version('Gold account')
23 | email = mailer.find_email(email_name, version)
24 | expect(email.name).to eq(email_name)
25 | expect(email.version).to eq(version)
26 | end
27 |
28 | it "allow to add inherited emails via a hook" do
29 | expect(mailer.find_email('from_other_class').name).to eq('from_other_class')
30 | end
31 |
32 | it "allows to hide email" do
33 | expect(mailer.find_email('hidden')).to be nil
34 | end
35 |
36 | it ".list returns an array with all mailers" do
37 | list = Maily::Mailer.list
38 |
39 | expect(list).to be_an_instance_of(Array)
40 | expect(list.sample).to be_an_instance_of(Maily::Mailer)
41 | end
42 |
43 | it "#emails_list returns an array with all emails" do
44 | expect(mailer.emails_list).to be_an_instance_of(Array)
45 | expect(mailer.emails_list.sample).to be_an_instance_of(Maily::Email)
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/spec/maily_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Maily do
2 | it "#setup should initialize with some defaults if no block is provided" do
3 | Maily.setup
4 |
5 | expect(Maily.enabled).to be true
6 | expect(Maily.allow_edition).to be true
7 | expect(Maily.allow_delivery).to be true
8 | expect(Maily.available_locales).to eq([:en, :es, :pt, :fr])
9 | expect(Maily.base_controller).to eq('ActionController::Base')
10 | expect(Maily.http_authorization).to be nil
11 | expect(Maily.welcome_message).to be nil
12 | end
13 |
14 | describe '#allowed_action?' do
15 | it "should not allow edition if edition is disabled" do
16 | Maily.allow_edition = false
17 |
18 | expect(Maily.allowed_action?(:edit)).to be false
19 | expect(Maily.allowed_action?(:update)).to be false
20 | end
21 |
22 | it "should not allow delivery if delivery is disabled" do
23 | Maily.allow_delivery = false
24 |
25 | expect(Maily.allowed_action?(:deliver)).to be false
26 | end
27 | end
28 |
29 | describe '#hooks_for' do
30 | it "allows to register external hooks" do
31 | class ExternalMailer < ActionMailer::Base
32 | def external_email
33 | end
34 | end
35 |
36 | Maily.hooks_for('ExternalMailer')
37 |
38 | expect(Maily::Mailer.find('external_mailer').emails.count).to eq 1
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] = 'test'
2 |
3 | require File.expand_path("../dummy/config/environment.rb", __FILE__)
4 | require 'rspec/rails'
5 | require 'maily'
6 |
7 | RSpec.configure do |config|
8 | config.mock_with :rspec
9 | config.order = 'random'
10 | config.disable_monkey_patching!
11 | end
12 |
--------------------------------------------------------------------------------
/support/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markets/maily/a68c23139055516480758448b5eeaae5e0a0ad50/support/images/logo.png
--------------------------------------------------------------------------------
/support/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markets/maily/a68c23139055516480758448b5eeaae5e0a0ad50/support/images/screenshot.png
--------------------------------------------------------------------------------