diff --git a/.circleci/config.yml b/.circleci/config.yml index 9fc572d..d03e8bd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,10 +4,10 @@ # version: 2 jobs: - "ruby-2.5": + "ruby-2.6": docker: - - image: hanami/ruby-2.5 - working_directory: ~/hanami-utils + - image: hanami/ruby-2.6 + working_directory: ~/hanami-mailer steps: - checkout # Download and cache dependencies @@ -29,10 +29,10 @@ jobs: name: run tests command: | ./script/ci - "ruby-2.6": + "ruby-2.7": docker: - - image: hanami/ruby-2.6 - working_directory: ~/hanami-utils + - image: hanami/ruby-2.7 + working_directory: ~/hanami-mailer steps: - checkout # Download and cache dependencies @@ -59,5 +59,5 @@ workflows: version: 2 build: jobs: - - "ruby-2.5" - "ruby-2.6" + - "ruby-2.7" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6f74aff..0000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: ruby -sudo: false -cache: bundler -before_script: - - gem update --system -script: ./script/ci -rvm: - - 2.5 - - 2.6 - - ruby-head - -matrix: - allow_failures: - - rvm: ruby-head - -notifications: - webhooks: - urls: - - https://webhooks.gitter.im/e/fde2367248d53de4fe70 - on_success: change # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: never # options: [always|never|change] default: always diff --git a/CHANGELOG.md b/CHANGELOG.md index 6679dc0..dcf303c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,16 @@ # Hanami::Mailer Mail for Ruby applications +<<<<<<< HEAD ## v2.0.0.alpha1 (unreleased) ### Changed - [Luca Guidi] Drop support for Ruby: MRI 2.3, and 2.4. +======= +## v1.3.2 - 2020-02-03 +### Added +- [Luca Guidi] Official support for Ruby: MRI 2.7 +- [glaszig] Added `Hanami::Mailer.return_path` and `#return_path` to specify `MAIL FROM` address +>>>>>>> develop ## v1.3.1 - 2019-01-18 ### Added diff --git a/Gemfile b/Gemfile index ad0a5ae..d8e215b 100644 --- a/Gemfile +++ b/Gemfile @@ -4,9 +4,8 @@ source "https://rubygems.org" gemspec unless ENV["CI"] - gem "byebug", require: false, platforms: :mri - gem "allocation_stats", require: false - gem "benchmark-ips", require: false + gem "byebug", require: false, platforms: :mri + gem "yard", require: false end gem "hanami-utils", "~> 2.0.alpha", require: false, git: "https://github.com/hanami/utils.git", branch: "unstable" diff --git a/README.md b/README.md index 79f47c9..13c7e5b 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,8 @@ Mail for Ruby applications. ## Status [![Gem Version](https://badge.fury.io/rb/hanami-mailer.svg)](https://badge.fury.io/rb/hanami-mailer) -[![TravisCI](https://travis-ci.org/hanami/mailer.svg?branch=master)](https://travis-ci.org/hanami/mailer) -[![CircleCI](https://circleci.com/gh/hanami/mailer/tree/master.svg?style=svg)](https://circleci.com/gh/hanami/mailer/tree/master) -[![Test Coverage](https://codecov.io/gh/hanami/mailer/branch/master/graph/badge.svg)](https://codecov.io/gh/hanami/mailer) +[![CircleCI](https://circleci.com/gh/hanami/mailer/tree/unstable.svg?style=svg)](https://circleci.com/gh/hanami/mailer/tree/unstable) +[![Test Coverage](https://codecov.io/gh/hanami/mailer/branch/unstable/graph/badge.svg)](https://codecov.io/gh/hanami/mailer) [![Depfu](https://badges.depfu.com/badges/739c6e10eaf20d3ba4240d00828284db/overview.svg)](https://depfu.com/github/hanami/mailer?project=Bundler) [![Inline Docs](http://inch-ci.org/github/hanami/mailer.svg)](http://inch-ci.org/github/hanami/mailer) @@ -22,7 +21,7 @@ Mail for Ruby applications. ## Rubies -__Hanami::Mailer__ supports Ruby (MRI) 2.5+ +__Hanami::Mailer__ supports Ruby (MRI) 2.6+ ## Installation @@ -128,6 +127,7 @@ configuration = Hanami::Mailer::Configuration.new do |config| end class WelcomeMailer < Hanami::Mailer + return_path 'bounce@sender.com' from 'noreply@sender.com' to 'noreply@recipient.com' cc 'cc@sender.com' @@ -447,6 +447,6 @@ __Hanami::Mailer__ uses [Semantic Versioning 2.0.0](http://semver.org) ## Copyright -Copyright © 2015-2019 Luca Guidi – Released under MIT License +Copyright © 2015-2020 Luca Guidi – Released under MIT License This project was formerly known as Lotus (`lotus-mailer`). diff --git a/benchmark.rb b/benchmark.rb index b1bce93..26a95ab 100644 --- a/benchmark.rb +++ b/benchmark.rb @@ -40,9 +40,9 @@ total_memsize = stats.allocations.bytes.to_a.inject(&:+) puts "total memsize: #{total_memsize}" detailed_allocations = stats.allocations(alias_paths: true) - .group_by(:sourcefile, :class_plus) - .sort_by_count - .to_text + .group_by(:sourcefile, :class_plus) + .sort_by_count + .to_text puts "allocations by source file and class:" puts detailed_allocations diff --git a/hanami-mailer.gemspec b/hanami-mailer.gemspec index 811b164..7776d9d 100644 --- a/hanami-mailer.gemspec +++ b/hanami-mailer.gemspec @@ -15,17 +15,18 @@ Gem::Specification.new do |spec| spec.homepage = "http://hanamirb.org" spec.license = "MIT" - spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-mailer.gemspec`.split($/) # rubocop:disable Style/SpecialGlobalVars + spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-mailer.gemspec`.split($/) spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.required_ruby_version = ">= 2.5.0" + spec.required_ruby_version = ">= 2.6.0" spec.add_dependency "hanami-utils", "~> 2.0.alpha" spec.add_dependency "tilt", "~> 2.0", ">= 2.0.1" spec.add_dependency "mail", "~> 2.6" spec.add_development_dependency "bundler", ">= 1.6", "< 3" - spec.add_development_dependency "rake", "~> 12" + spec.add_development_dependency "rake", "~> 13" spec.add_development_dependency "rspec", "~> 3.7" + spec.add_development_dependency "rubocop", "0.91" end diff --git a/lib/hanami/mailer.rb b/lib/hanami/mailer.rb index 23f6c0a..f315f82 100644 --- a/lib/hanami/mailer.rb +++ b/lib/hanami/mailer.rb @@ -77,6 +77,7 @@ module Hanami # @since next # @api unstable def self.inherited(base) + super @_subclasses.push(base) base.extend Dsl end @@ -168,15 +169,15 @@ module Hanami # mailer.deliver(invoice: invoice, user: user, charset: 'iso-8859') def deliver(locals) mail(locals).deliver - rescue ArgumentError => e - raise MissingDeliveryDataError if e.message =~ /SMTP (From|To) address/ + rescue ArgumentError => exception + raise MissingDeliveryDataError if exception.message =~ /SMTP (From|To) address/ raise end # @since next # @api unstable - alias call deliver + alias_method :call, :deliver # Render a single template with the specified format. # @@ -208,17 +209,16 @@ module Hanami # @api unstable # @since next # - # rubocop:disable Metrics/AbcSize - # rubocop:disable Metrics/MethodLength - def bind(mail, locals) + def bind(mail, locals) # rubocop:disable Metrics/AbcSize charset = locals.fetch(:charset, configuration.default_charset) - mail.from = __dsl(:from, locals) - mail.to = __dsl(:to, locals) - mail.cc = __dsl(:cc, locals) - mail.bcc = __dsl(:bcc, locals) - mail.reply_to = __dsl(:reply_to, locals) - mail.subject = __dsl(:subject, locals) + mail.return_path = __dsl(:return_path, locals) + mail.from = __dsl(:from, locals) + mail.to = __dsl(:to, locals) + mail.cc = __dsl(:cc, locals) + mail.bcc = __dsl(:bcc, locals) + mail.reply_to = __dsl(:reply_to, locals) + mail.subject = __dsl(:subject, locals) mail.html_part = __part(:html, charset, locals) mail.text_part = __part(:txt, charset, locals) @@ -226,8 +226,6 @@ module Hanami mail.charset = charset mail.delivery_method(*configuration.delivery_method) end - # rubocop:enable Metrics/MethodLength - # rubocop:enable Metrics/AbcSize # @since next # @api unstable diff --git a/lib/hanami/mailer/configuration.rb b/lib/hanami/mailer/configuration.rb index 7120cc1..2a00fe6 100644 --- a/lib/hanami/mailer/configuration.rb +++ b/lib/hanami/mailer/configuration.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "set" require "hanami/utils/kernel" require "hanami/mailer/template_name" require "hanami/mailer/templates_finder" @@ -154,7 +155,7 @@ module Hanami @modules = [] end - alias unload! reset! + alias_method :unload!, :reset! # Copy the configuration for the given mailer # diff --git a/lib/hanami/mailer/dsl.rb b/lib/hanami/mailer/dsl.rb index 2490215..401e18c 100644 --- a/lib/hanami/mailer/dsl.rb +++ b/lib/hanami/mailer/dsl.rb @@ -15,14 +15,15 @@ module Hanami # @api unstable def self.extended(base) base.class_eval do - @from = nil - @to = nil - @cc = nil - @bcc = nil - @reply_to = nil - @subject = nil - @template = nil - @before = ->(*) {} + @from = nil + @to = nil + @cc = nil + @bcc = nil + @reply_to = nil + @return_path = nil + @subject = nil + @template = nil + @before = ->(*) {} end end @@ -210,6 +211,73 @@ module Hanami end end + # Sets the bcc (blind carbon copy) for mail messages + # + # It accepts a hardcoded value as a string or array of strings. + # For dynamic values, you can specify a symbol that represents an instance + # method. + # + # This value is optional. + # + # When a value is given, it specifies the bcc for the email. + # When a value is not given, it returns the bcc of the email. + # + # This is part of a DSL, for this reason when this method is called with + # an argument, it will set the corresponding class variable. When + # called without, it will return the already set value, or the default. + # + # @overload bcc(value) + # Sets the bcc + # @param value [String, Array, Symbol] the hardcoded value or method name + # @return [NilClass] + # + # @overload bcc + # Returns the bcc + # @return [String, Array, Symbol] the recipient + # + # @since 0.3.0 + # + # @example Hardcoded value (String) + # require 'hanami/mailer' + # + # class WelcomeMailer < Hanami::Mailer + # bcc "other.user@example.com" + # end + # + # @example Hardcoded value (Array) + # require 'hanami/mailer' + # + # class WelcomeMailer < Hanami::Mailer + # bcc ["other.user-1@example.com", "other.user-2@example.com"] + # end + # + # @example Lazy value (Proc) + # require 'hanami/mailer' + # + # class WelcomeMailer < Hanami::Mailer + # bcc ->(locals) { locals.fetch(:user).email } + # end + # + # user = User.new(name: 'L') + # WelcomeMailer.new(configuration: configuration).deliver(user: user) + # + # @example Lazy values (Proc) + # require 'hanami/mailer' + # + # class WelcomeMailer < Hanami::Mailer + # bcc ->(locals) { locals.fetch(:users).map(&:email) } + # end + # + # users = [User.new(name: 'L'), User.new(name: 'MG')] + # WelcomeMailer.new(configuration: configuration).deliver(users: users) + def bcc(value = nil) + if value.nil? + @bcc + else + @bcc = value + end + end + # Sets the reply_to for mail messages # # It accepts a hardcoded value as a string or array of strings. @@ -299,70 +367,60 @@ module Hanami end end - # Sets the bcc (blind carbon copy) for mail messages + # Sets the MAIL FROM address for mail messages. + # This lets you specify a "bounce address" different from the sender + # address specified with `from`. # - # It accepts a hardcoded value as a string or array of strings. - # For dynamic values, you can specify a symbol that represents an instance - # method. + # It accepts a hardcoded value as a string, or a symbol that represents + # an instance method for more complex logic. # # This value is optional. # - # When a value is given, it specifies the bcc for the email. - # When a value is not given, it returns the bcc of the email. + # When a value is given, specify the MAIL FROM address of the email + # Otherwise, it returns the MAIL FROM address of the email # # This is part of a DSL, for this reason when this method is called with # an argument, it will set the corresponding class variable. When # called without, it will return the already set value, or the default. # - # @overload bcc(value) - # Sets the bcc - # @param value [String, Array, Symbol] the hardcoded value or method name + # @overload return_path(value) + # Sets the MAIL FROM address + # @param value [String, Symbol] the hardcoded value or method name # @return [NilClass] # - # @overload bcc - # Returns the bcc - # @return [String, Array, Symbol] the recipient + # @overload return_path + # Returns the MAIL FROM address + # @return [String, Symbol] the MAIL FROM address # - # @since 0.3.0 + # @since 1.3.2 # # @example Hardcoded value (String) # require 'hanami/mailer' # - # class WelcomeMailer < Hanami::Mailer - # bcc "other.user@example.com" + # class WelcomeMailer + # include Hanami::Mailer + # + # return_path "bounce@example.com" # end # - # @example Hardcoded value (Array) + # @example Method (Symbol) # require 'hanami/mailer' # - # class WelcomeMailer < Hanami::Mailer - # bcc ["other.user-1@example.com", "other.user-2@example.com"] + # class WelcomeMailer + # include Hanami::Mailer + # return_path :bounce_address + # + # private + # + # def bounce_address + # "bounce@example.com" + # end # end - # - # @example Lazy value (Proc) - # require 'hanami/mailer' - # - # class WelcomeMailer < Hanami::Mailer - # bcc ->(locals) { locals.fetch(:user).email } - # end - # - # user = User.new(name: 'L') - # WelcomeMailer.new(configuration: configuration).deliver(user: user) - # - # @example Lazy values (Proc) - # require 'hanami/mailer' - # - # class WelcomeMailer < Hanami::Mailer - # bcc ->(locals) { locals.fetch(:users).map(&:email) } - # end - # - # users = [User.new(name: 'L'), User.new(name: 'MG')] - # WelcomeMailer.new(configuration: configuration).deliver(users: users) - def bcc(value = nil) + def return_path(value = nil) if value.nil? - @bcc + @return_path else - @bcc = value + @return_path = value end end diff --git a/lib/hanami/mailer/template.rb b/lib/hanami/mailer/template.rb index 6b0be31..de68a69 100644 --- a/lib/hanami/mailer/template.rb +++ b/lib/hanami/mailer/template.rb @@ -11,8 +11,8 @@ module Hanami # # TODO this is identical to Hanami::View, consider to move into Hanami::Utils class Template - def initialize(template) - @_template = Tilt.new(template) + def initialize(template, encoding = Encoding::UTF_8) + @_template = Tilt.new(template, default_encoding: encoding) freeze end diff --git a/lib/hanami/mailer/template_name.rb b/lib/hanami/mailer/template_name.rb index c857c00..76abd97 100644 --- a/lib/hanami/mailer/template_name.rb +++ b/lib/hanami/mailer/template_name.rb @@ -18,7 +18,7 @@ module Hanami class << self # @since next # @api unstable - alias [] call + alias_method :[], :call end end end diff --git a/lib/hanami/mailer/templates_finder.rb b/lib/hanami/mailer/templates_finder.rb index 35d226f..39a56a2 100644 --- a/lib/hanami/mailer/templates_finder.rb +++ b/lib/hanami/mailer/templates_finder.rb @@ -98,7 +98,10 @@ module Hanami # @api unstable # @since 0.1.0 def templates(template_name, lookup = search_path) - Utils::FileList["#{[root, lookup, template_name].join(separator)}#{format_separator}#{format}#{format_separator}#{engines}"] + root_path = [root, lookup, template_name].join(separator) + search_path = "#{format_separator}#{format}#{format_separator}#{engines}" + + Utils::FileList["#{root_path}#{search_path}"] end # @api unstable diff --git a/script/ci b/script/ci index c3946ce..70f1c53 100755 --- a/script/ci +++ b/script/ci @@ -26,11 +26,11 @@ upload_code_coverage() { } main() { - prepare_build && - print_ruby_version && - run_code_quality_checks && - run_unit_tests && - upload_code_coverage + prepare_build + print_ruby_version + run_code_quality_checks + run_unit_tests + upload_code_coverage } main diff --git a/spec/integration/hanami/mailer/delivery_spec.rb b/spec/integration/hanami/mailer/delivery_spec.rb index d690f87..51c37cd 100644 --- a/spec/integration/hanami/mailer/delivery_spec.rb +++ b/spec/integration/hanami/mailer/delivery_spec.rb @@ -120,7 +120,7 @@ RSpec.describe Hanami::Mailer do subject { options.fetch(:deliveries).first } let(:mailer) { WelcomeMailer.new(configuration: configuration) } - let(:options) { { deliveries: [] } } + let(:options) { {deliveries: []} } let(:configuration) do configuration = Hanami::Mailer::Configuration.new do |config| diff --git a/spec/support/fixtures.rb b/spec/support/fixtures.rb index 27b6229..1ea9d76 100644 --- a/spec/support/fixtures.rb +++ b/spec/support/fixtures.rb @@ -62,11 +62,12 @@ class ProcMailer < Hanami::Mailer end class WelcomeMailer < Hanami::Mailer - from "noreply@sender.com" - to ["noreply@recipient.com", "owner@recipient.com"] - cc "cc@recipient.com" - bcc "bcc@recipient.com" - reply_to "reply_to@recipient.com" + from "noreply@sender.com" + to ["noreply@recipient.com", "owner@recipient.com"] + cc "cc@recipient.com" + bcc "bcc@recipient.com" + reply_to "reply_to@recipient.com" + return_path "bounce@sender.com" subject "Welcome" diff --git a/spec/unit/hanami/mailer/configuration_spec.rb b/spec/unit/hanami/mailer/configuration_spec.rb index 99cf5b5..ee973f5 100644 --- a/spec/unit/hanami/mailer/configuration_spec.rb +++ b/spec/unit/hanami/mailer/configuration_spec.rb @@ -63,22 +63,22 @@ RSpec.describe Hanami::Mailer::Configuration do describe "set with a symbol" do before do - subject.delivery_method = :exim, { location: "/path/to/exim" } + subject.delivery_method = :exim, {location: "/path/to/exim"} end it "saves the delivery method in the configuration" do - expect(subject.delivery_method).to eq([:exim, { location: "/path/to/exim" }]) + expect(subject.delivery_method).to eq([:exim, {location: "/path/to/exim"}]) end end describe "set with a class" do before do subject.delivery_method = MandrillDeliveryMethod, - { username: "mandrill-username", password: "mandrill-api-key" } + {username: "mandrill-username", password: "mandrill-api-key"} end it "saves the delivery method in the configuration" do - expect(subject.delivery_method).to eq([MandrillDeliveryMethod, username: "mandrill-username", password: "mandrill-api-key"]) + expect(subject.delivery_method).to eq([MandrillDeliveryMethod, {username: "mandrill-username", password: "mandrill-api-key"}]) end end end diff --git a/spec/unit/hanami/mailer/dsl_spec.rb b/spec/unit/hanami/mailer/dsl_spec.rb index c04a82a..bab68ab 100644 --- a/spec/unit/hanami/mailer/dsl_spec.rb +++ b/spec/unit/hanami/mailer/dsl_spec.rb @@ -76,6 +76,46 @@ RSpec.describe Hanami::Mailer::Dsl do end end + describe ".reply_to" do + it "returns the default value" do + expect(mailer.reply_to).to be(nil) + end + + it "sets a single value" do + email_address = "reply@hanami.test" + mailer.reply_to email_address + + expect(mailer.reply_to).to eq(email_address) + end + + it "sets an array of values" do + email_addresses = ["bcc@hanami.test"] + mailer.reply_to email_addresses + + expect(mailer.reply_to).to eq(email_addresses) + end + end + + describe ".return_path" do + it "returns the default value" do + expect(mailer.return_path).to be(nil) + end + + it "sets a single value" do + email_address = "return@hanami.test" + mailer.return_path email_address + + expect(mailer.return_path).to eq(email_address) + end + + it "sets an array of values" do + email_addresses = ["return@hanami.test"] + mailer.return_path email_addresses + + expect(mailer.return_path).to eq(email_addresses) + end + end + describe ".subject" do it "returns the default value" do expect(mailer.subject).to be(nil) diff --git a/spec/unit/hanami/mailer_spec.rb b/spec/unit/hanami/mailer_spec.rb index b57c107..2c7a70a 100644 --- a/spec/unit/hanami/mailer_spec.rb +++ b/spec/unit/hanami/mailer_spec.rb @@ -151,7 +151,7 @@ RSpec.describe Hanami::Mailer do describe "when locals are parsed in" do let(:mailer) { RenderMailer.new(configuration: configuration) } - let(:locals) { { user: User.new("Luca") } } + let(:locals) { {user: User.new("Luca")} } it "renders template with parsed locals" do expect(mailer.render(:html, locals)).to include(locals.fetch(:user).name) @@ -160,7 +160,7 @@ RSpec.describe Hanami::Mailer do describe "with HAML template engine" do let(:mailer) { TemplateEngineMailer.new(configuration: configuration) } - let(:locals) { { user: User.new("MG") } } + let(:locals) { {user: User.new("MG")} } it "renders template with parsed locals" do expect(mailer.render(:html, locals)).to include(%(

\n#{locals.fetch(:user).name}\n

\n))