├── VERSION ├── .rspec ├── .coveralls.yml ├── .gitignore ├── example ├── config │ ├── routes.rb │ ├── initializers │ │ └── assets.rb │ ├── boot.rb │ ├── secrets.yml │ ├── environment.rb │ ├── application.rb │ └── environments │ │ ├── production.rb │ │ └── development.rb ├── bin │ └── rails ├── config.ru ├── test │ └── mailers │ │ └── previews │ │ └── example_mailer_preview.rb ├── Gemfile ├── app │ ├── mailers │ │ └── example_mailer.rb │ ├── assets │ │ └── stylesheets │ │ │ └── email.css │ └── views │ │ └── example_mailer │ │ └── test_message.html.erb ├── README.md ├── Rakefile └── .gitignore ├── spec ├── support │ ├── stubs │ │ ├── action_mailer.rb │ │ └── rails.rb │ └── fixtures │ │ ├── html.rb │ │ └── message.rb ├── integration │ ├── hook_registration_spec.rb │ ├── hook_spec.rb │ └── css_helper_spec.rb ├── spec_helper.rb └── unit │ ├── premailer_rails_spec.rb │ ├── css_loaders │ ├── asset_pipeline_loader_spec.rb │ └── network_loader_spec.rb │ └── customized_premailer_spec.rb ├── Rakefile ├── lib └── premailer │ ├── rails │ ├── version.rb │ ├── railtie.rb │ ├── css_loaders.rb │ ├── css_loaders │ │ ├── file_system_loader.rb │ │ ├── cache_loader.rb │ │ ├── asset_pipeline_loader.rb │ │ └── network_loader.rb │ ├── customized_premailer.rb │ ├── css_helper.rb │ └── hook.rb │ └── rails.rb ├── Gemfile ├── .travis.yml ├── LICENSE ├── premailer-rails.gemspec ├── CHANGELOG.md └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 1.9.5 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format doc 2 | --color 3 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | doc/ 3 | Gemfile.lock 4 | coverage/ 5 | -------------------------------------------------------------------------------- /example/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | root to: redirect('rails/mailers/example_mailer/test_message') 3 | end 4 | -------------------------------------------------------------------------------- /spec/support/stubs/action_mailer.rb: -------------------------------------------------------------------------------- 1 | module ActionMailer 2 | class Base 3 | def self.register_interceptor(x); end 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /example/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.assets.version = '1.0' 2 | Rails.application.config.assets.precompile += %w( email.css ) 3 | -------------------------------------------------------------------------------- /example/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | 4 | require 'rspec/core/rake_task' 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | task default: :spec 8 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/test/mailers/previews/example_mailer_preview.rb: -------------------------------------------------------------------------------- 1 | class ExampleMailerPreview < ActionMailer::Preview 2 | def test_message 3 | ExampleMailer.test_message 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/premailer/rails/version.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | VERSION = File.read( 4 | File.expand_path('../../../../VERSION', __FILE__) 5 | ).strip 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /example/config/secrets.yml: -------------------------------------------------------------------------------- 1 | development: 2 | secret_key_base: bc1d05753b1a42a7d983dcb4f998c433532ec8f91ab3842a36ed3d9072d143a2d9c05a6dc43a3d780a2ff3d8e7b75a1011ae2e0d13a022e98dc1f0299da5a5a0 3 | -------------------------------------------------------------------------------- /example/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '~> 5.0.0.beta3' 4 | gem 'premailer-rails', path: '..' 5 | gem 'nokogiri' 6 | 7 | platforms :rbx do 8 | gem 'rubysl' 9 | gem 'racc' 10 | end 11 | -------------------------------------------------------------------------------- /example/app/mailers/example_mailer.rb: -------------------------------------------------------------------------------- 1 | class ExampleMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | 4 | def test_message 5 | mail to: 'to@example.org', subject: 'Test Message' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example Rails App 2 | 3 | To run this app, run: 4 | 5 | ```shell 6 | bundle 7 | bundle exec rails s 8 | ``` 9 | 10 | Then point your browser at [http://localhost:3000/](http://localhost:3000/). 11 | -------------------------------------------------------------------------------- /lib/premailer/rails/railtie.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | class Railtie < ::Rails::Railtie 4 | config.after_initialize do 5 | ::Premailer::Rails.register_interceptors 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /example/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 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /lib/premailer/rails/css_loaders.rb: -------------------------------------------------------------------------------- 1 | require 'uri' 2 | 3 | require 'premailer/rails/css_loaders/cache_loader' 4 | require 'premailer/rails/css_loaders/file_system_loader' 5 | require 'premailer/rails/css_loaders/asset_pipeline_loader' 6 | require 'premailer/rails/css_loaders/network_loader' 7 | -------------------------------------------------------------------------------- /example/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'action_controller/railtie' 4 | require 'action_mailer/railtie' 5 | require 'sprockets/railtie' 6 | 7 | Bundler.require(*Rails.groups) 8 | 9 | module Example 10 | class Application < Rails::Application 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /example/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.cache_classes = true 3 | config.eager_load = true 4 | config.consider_all_requests_local = false 5 | config.action_controller.perform_caching = true 6 | config.assets.compile = false 7 | config.assets.digest = true 8 | config.log_level = :info 9 | end 10 | -------------------------------------------------------------------------------- /example/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.cache_classes = false 3 | config.eager_load = false 4 | config.consider_all_requests_local = true 5 | config.action_controller.perform_caching = false 6 | config.assets.debug = true 7 | config.assets.digest = true 8 | config.assets.raise_runtime_errors = true 9 | end 10 | -------------------------------------------------------------------------------- /lib/premailer/rails/css_loaders/file_system_loader.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | module CSSLoaders 4 | module FileSystemLoader 5 | extend self 6 | 7 | def load(url) 8 | path = URI(url).path 9 | file_path = "public#{path}" 10 | File.read(file_path) if File.file?(file_path) 11 | end 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/integration/hook_registration_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'ActionMailer::Base.register_interceptor' do 4 | it 'registers interceptors' do 5 | expect(ActionMailer::Base).to \ 6 | receive(:register_interceptor).with(Premailer::Rails::Hook) 7 | expect(ActionMailer::Base).to \ 8 | receive(:register_preview_interceptor).with(Premailer::Rails::Hook) 9 | load 'premailer/rails.rb' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | action_mailer_version = ENV.fetch('ACTION_MAILER_VERSION', '4') 6 | 7 | if action_mailer_version == 'master' 8 | git 'git://github.com/rails/rails.git' do 9 | gem 'actionmailer' 10 | end 11 | else 12 | gem 'actionmailer', "~> #{action_mailer_version}" 13 | end 14 | 15 | platforms :rbx do 16 | gem 'rubysl' 17 | gem 'racc' 18 | end 19 | 20 | gem 'tins', '< 1.7' if RUBY_VERSION.split('.').first.to_i < 2 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | cache: bundler 4 | script: bundle exec rspec 5 | rvm: 6 | - 2.3.0 7 | - ruby-head 8 | - jruby 9 | - rbx-2 10 | env: 11 | global: 12 | - JRUBY_OPTS="--2.0" 13 | matrix: 14 | - ACTION_MAILER_VERSION=4 15 | - ACTION_MAILER_VERSION=5.beta 16 | - ACTION_MAILER_VERSION=master 17 | matrix: 18 | fast_finish: true 19 | allow_failures: 20 | - env: ACTION_MAILER_VERSION=master 21 | - env: ACTION_MAILER_VERSION=5.beta 22 | - rvm: ruby-head 23 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if RUBY_ENGINE == 'ruby' 2 | if ENV['CI'] 3 | require 'coveralls' 4 | Coveralls::Output.silent = true 5 | Coveralls.wear! do 6 | add_filter 'spec/' 7 | end 8 | else 9 | require 'simplecov' 10 | SimpleCov.start 11 | end 12 | end 13 | 14 | require 'premailer/rails' 15 | 16 | require 'support/stubs/action_mailer' 17 | require 'support/stubs/rails' 18 | require 'support/fixtures/message' 19 | require 'support/fixtures/html' 20 | 21 | require 'hpricot' unless RUBY_PLATFORM == 'java' 22 | require 'nokogiri' 23 | -------------------------------------------------------------------------------- /spec/unit/premailer_rails_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Premailer::Rails do 4 | describe '#config' do 5 | subject { Premailer::Rails.config } 6 | context 'when set' do 7 | around do |example| 8 | begin 9 | default_config = described_class.config 10 | described_class.config = { foo: :bar } 11 | example.run 12 | ensure 13 | described_class.config = default_config 14 | end 15 | end 16 | it { is_expected.to eq(foo: :bar) } 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /example/app/assets/stylesheets/email.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #efefef; 3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | p { 7 | line-height: 1.4; 8 | } 9 | 10 | .wrap { 11 | max-width: 40em; 12 | margin: 0 auto; 13 | padding: 1em; 14 | background: white; 15 | } 16 | 17 | .greeting { 18 | text-align: center; 19 | font-weight: bold; 20 | font-size: 110%; 21 | } 22 | 23 | .footer { 24 | font-size: 90%; 25 | color: #666; 26 | } 27 | 28 | a { 29 | color: green; 30 | text-decoration: none; 31 | border-bottom: 2px solid green; 32 | } 33 | -------------------------------------------------------------------------------- /lib/premailer/rails/css_loaders/cache_loader.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | module CSSLoaders 4 | module CacheLoader 5 | extend self 6 | 7 | @cache = {} 8 | 9 | def load(url) 10 | @cache[url] unless development_env? 11 | end 12 | 13 | def store(url, content) 14 | @cache[url] ||= content unless development_env? 15 | end 16 | 17 | def clear! 18 | @cache = {} 19 | end 20 | 21 | def development_env? 22 | defined?(::Rails) && 23 | ::Rails.respond_to?(:env) && 24 | ::Rails.env.development? 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/support/stubs/rails.rb: -------------------------------------------------------------------------------- 1 | module Rails 2 | extend self 3 | 4 | module Configuration 5 | extend self 6 | end 7 | 8 | module Env 9 | extend self 10 | 11 | def development? 12 | false 13 | end 14 | end 15 | 16 | module Application 17 | extend self 18 | 19 | module Assets 20 | extend self 21 | end 22 | 23 | def assets 24 | Assets 25 | end 26 | end 27 | 28 | class Railtie 29 | class Configuration 30 | def after_initialize 31 | yield 32 | end 33 | end 34 | 35 | def self.config 36 | Configuration.new 37 | end 38 | end 39 | 40 | def env 41 | Env 42 | end 43 | 44 | def configuration 45 | Configuration 46 | end 47 | 48 | def application 49 | Application 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/premailer/rails.rb: -------------------------------------------------------------------------------- 1 | require 'premailer' 2 | require 'action_mailer' 3 | 4 | require 'premailer/rails/version' 5 | require 'premailer/rails/css_loaders' 6 | require 'premailer/rails/css_helper' 7 | require 'premailer/rails/customized_premailer' 8 | require 'premailer/rails/hook' 9 | 10 | class Premailer 11 | module Rails 12 | @config = { 13 | input_encoding: 'UTF-8', 14 | generate_text_part: true 15 | } 16 | class << self 17 | attr_accessor :config 18 | end 19 | 20 | def self.register_interceptors 21 | ActionMailer::Base.register_interceptor(Premailer::Rails::Hook) 22 | 23 | if ActionMailer::Base.respond_to?(:register_preview_interceptor) 24 | ActionMailer::Base.register_preview_interceptor(Premailer::Rails::Hook) 25 | end 26 | end 27 | end 28 | end 29 | 30 | require 'premailer/rails/railtie' if defined?(Rails) 31 | -------------------------------------------------------------------------------- /example/app/views/example_mailer/test_message.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | <%= stylesheet_link_tag :email %> 6 | 7 | 8 |Hi, John Doe
10 |11 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 12 |
13 | 16 |12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 15 | commodo consequat. 16 |
17 | 18 | 19 | HTML 20 | 21 | LINK = "\n" 22 | 23 | def with_css_links(*files) 24 | opts = files.last.is_a?(Hash) ? files.pop : {} 25 | links = [] 26 | files.each do |file| 27 | attrs = { href: "http://example.com/#{file}" }.merge(opts) 28 | links << LINK % hash_to_attributes(attrs) 29 | end 30 | 31 | TEMPLATE % links.join 32 | end 33 | 34 | def hash_to_attributes(attrs) 35 | attrs.map { |attr, value| "#{attr}='#{value}'" }.join(' ') 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/premailer/rails/css_loaders/asset_pipeline_loader.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | module CSSLoaders 4 | module AssetPipelineLoader 5 | extend self 6 | 7 | def load(url) 8 | if asset_pipeline_present? 9 | file = file_name(url) 10 | asset = ::Rails.application.assets.find_asset(file) 11 | asset.to_s if asset 12 | end 13 | end 14 | 15 | def asset_pipeline_present? 16 | defined?(::Rails) && 17 | ::Rails.respond_to?(:application) && 18 | ::Rails.application.respond_to?(:assets) && 19 | ::Rails.application.assets 20 | end 21 | 22 | def file_name(url) 23 | prefix = [ 24 | ::Rails.configuration.relative_url_root, 25 | ::Rails.configuration.assets.prefix, 26 | '/' 27 | ].join 28 | URI(url).path 29 | .sub(/\A#{prefix}/, '') 30 | .sub(/-(\h{32}|\h{64})\.css\z/, '.css') 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2012 Philipe Fatio (fphilipe) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of 9 | the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/premailer/rails/css_loaders/network_loader.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | module CSSLoaders 4 | module NetworkLoader 5 | extend self 6 | 7 | def load(url) 8 | uri = uri_for_url(url) 9 | Net::HTTP.get(uri) if uri 10 | end 11 | 12 | def uri_for_url(url) 13 | uri = URI(url) 14 | 15 | if uri.host.present? 16 | return uri if uri.scheme.present? 17 | URI("http://#{uri.to_s}") 18 | elsif asset_host_present? 19 | scheme, host = asset_host(url).split(%r{:?//}) 20 | scheme, host = host, scheme if host.nil? 21 | scheme = 'http' if scheme.blank? 22 | path = url 23 | URI(File.join("#{scheme}://#{host}", path)) 24 | end 25 | end 26 | 27 | def asset_host_present? 28 | ::Rails.respond_to?(:configuration) && 29 | ::Rails.configuration.action_controller.asset_host.present? 30 | end 31 | 32 | def asset_host(url) 33 | config = ::Rails.configuration.action_controller.asset_host 34 | config.respond_to?(:call) ? config.call(url) : config 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/premailer/rails/css_helper.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | module CSSHelper 4 | extend self 5 | 6 | FileNotFound = Class.new(StandardError) 7 | 8 | STRATEGIES = [ 9 | CSSLoaders::CacheLoader, 10 | CSSLoaders::FileSystemLoader, 11 | CSSLoaders::AssetPipelineLoader, 12 | CSSLoaders::NetworkLoader 13 | ] 14 | 15 | # Returns all linked CSS files concatenated as string. 16 | def css_for_doc(doc) 17 | css_urls_in_doc(doc).map { |url| css_for_url(url) }.join("\n") 18 | end 19 | 20 | def css_for_url(url) 21 | load_css(url).tap do |content| 22 | CSSLoaders::CacheLoader.store(url, content) 23 | end 24 | end 25 | 26 | private 27 | 28 | def css_urls_in_doc(doc) 29 | doc.search('link[@rel="stylesheet"]:not([@data-premailer="ignore"])').map do |link| 30 | if link.respond_to?(:remove) 31 | link.remove 32 | else 33 | link.parent.children.delete(link) 34 | end 35 | link.attributes['href'].to_s 36 | end 37 | end 38 | 39 | def load_css(url) 40 | STRATEGIES.each do |strategy| 41 | css = strategy.load(url) 42 | return css.force_encoding('UTF-8') if css 43 | end 44 | 45 | raise FileNotFound, %{File with URL "#{url}" could not be loaded by any strategy.} 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /premailer-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "premailer/rails/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "premailer-rails" 7 | s.version = Premailer::Rails::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.license = 'MIT' 10 | s.authors = ["Philipe Fatio"] 11 | s.email = ["philipe.fatio@gmail.com"] 12 | s.homepage = "https://github.com/fphilipe/premailer-rails" 13 | s.summary = %q{Easily create styled HTML emails in Rails.} 14 | s.description = %q{This gem brings you the power of the premailer gem to Rails 15 | without any configuration needs. Create HTML emails, 16 | include a CSS file as you do in a normal HTML document and 17 | premailer will inline the included CSS.} 18 | 19 | s.files = `git ls-files`.split("\n") 20 | s.test_files = `git ls-files -- {example,spec}/*`.split("\n") 21 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 22 | s.require_paths = ["lib"] 23 | 24 | s.add_dependency 'premailer', '~> 1.7', '>= 1.7.9' 25 | s.add_dependency 'actionmailer', '>= 3', '< 6' 26 | 27 | s.add_development_dependency 'rspec', '~> 3.3' 28 | s.add_development_dependency 'nokogiri' 29 | s.add_development_dependency 'hpricot' unless RUBY_PLATFORM == 'java' 30 | s.add_development_dependency 'coveralls' if RUBY_ENGINE == 'ruby' 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/css_loaders/asset_pipeline_loader_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Premailer::Rails::CSSLoaders::AssetPipelineLoader do 4 | before do 5 | allow(Rails.configuration) 6 | .to receive(:assets).and_return(double(prefix: '/assets')) 7 | allow(Rails.configuration).to receive(:relative_url_root).and_return(nil) 8 | end 9 | 10 | describe ".file_name" do 11 | subject do 12 | Premailer::Rails::CSSLoaders::AssetPipelineLoader.file_name(asset) 13 | end 14 | 15 | context "when asset file path contains prefix" do 16 | let(:asset) { '/assets/application.css' } 17 | it { is_expected.to eq('application.css') } 18 | end 19 | 20 | context "when asset file path contains prefix and relative_url_root is set" do 21 | before do 22 | allow(Rails.configuration) 23 | .to receive(:relative_url_root).and_return('/foo') 24 | end 25 | 26 | let(:asset) { '/foo/assets/application.css' } 27 | it { is_expected.to eq('application.css') } 28 | end 29 | 30 | context "when asset file path contains 32 chars fingerprint" do 31 | let(:asset) { 'application-6776f581a4329e299531e1d52aa59832.css' } 32 | it { is_expected.to eq('application.css') } 33 | end 34 | 35 | context "when asset file path contains 64 chars fingerprint" do 36 | let(:asset) { 'application-02275ccb3fd0c11615bbfb11c99ea123ca2287e75045fe7b72cefafb880dad2b.css' } 37 | it { is_expected.to eq('application.css') } 38 | end 39 | 40 | context "when asset file page contains numbers, but not a fingerprint" do 41 | let(:asset) { 'test/20130708152545-foo-bar.css' } 42 | it { is_expected.to eq("test/20130708152545-foo-bar.css") } 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/unit/css_loaders/network_loader_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Premailer::Rails::CSSLoaders::NetworkLoader do 4 | describe '#uri_for_url' do 5 | subject { described_class.uri_for_url(url) } 6 | let(:asset_host) { nil } 7 | 8 | before do 9 | action_controller = double(asset_host: asset_host) 10 | config = double(action_controller: action_controller) 11 | allow(Rails).to receive(:configuration).and_return(config) 12 | end 13 | 14 | context 'with a valid URL' do 15 | let(:url) { 'http://example.com/test.css' } 16 | it { is_expected.to eq(URI(url)) } 17 | end 18 | 19 | context 'with a protocol relative URL' do 20 | let(:url) { '//example.com/test.css' } 21 | it { is_expected.to eq(URI("http://#{url}")) } 22 | end 23 | 24 | context 'with a file path' do 25 | let(:url) { '/assets/foo.css' } 26 | 27 | context 'and a domain as asset host' do 28 | let(:asset_host) { 'example.com' } 29 | it { is_expected.to eq(URI("http://example.com#{url}")) } 30 | end 31 | 32 | context 'and a URL as asset host' do 33 | let(:asset_host) { 'https://example.com' } 34 | it { is_expected.to eq(URI("https://example.com/assets/foo.css")) } 35 | end 36 | 37 | context 'and a protocol relative URL as asset host' do 38 | let(:asset_host) { '//example.com' } 39 | it { is_expected.to eq(URI("http://example.com/assets/foo.css")) } 40 | end 41 | 42 | context 'and a callable object as asset host' do 43 | let(:asset_host) { double } 44 | 45 | it 'calls #call with the asset path as argument' do 46 | expect(asset_host).to receive(:call).with(url).and_return( 47 | 'http://example.com') 48 | expect(subject).to eq(URI('http://example.com/assets/foo.css')) 49 | end 50 | end 51 | 52 | context 'without an asset host' do 53 | let(:asset_host) { nil } 54 | it { is_expected.not_to be } 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/unit/customized_premailer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Premailer::Rails::CustomizedPremailer do 4 | [ :nokogiri, :hpricot ].each do |adapter| 5 | next if adapter == :hpricot and RUBY_PLATFORM == 'java' 6 | 7 | context "when adapter is #{adapter}" do 8 | before { allow(Premailer::Adapter).to receive(:use).and_return(adapter) } 9 | 10 | describe '#to_plain_text' do 11 | it 'includes the text from the HTML part' do 12 | premailer = 13 | Premailer::Rails::CustomizedPremailer 14 | .new(Fixtures::Message::HTML_PART) 15 | expect(premailer.to_plain_text.gsub(/\s/, ' ').strip).to \ 16 | eq(Fixtures::Message::TEXT_PART.gsub(/\s/, ' ').strip) 17 | end 18 | end 19 | 20 | describe '#to_inline_css' do 21 | let(:regex) { %r{} } 22 | 23 | context 'when inline CSS block present' do 24 | it 'returns the HTML with the CSS inlined' do 25 | allow(Premailer::Rails::CSSHelper).to \ 26 | receive(:css_for_doc).and_return('p { color: red; }') 27 | html = Fixtures::Message::HTML_PART 28 | premailer = Premailer::Rails::CustomizedPremailer.new(html) 29 | expect(premailer.to_inline_css).to match(regex) 30 | end 31 | end 32 | 33 | context 'when CSS is loaded externally' do 34 | it 'returns the HTML with the CSS inlined' do 35 | html = Fixtures::Message::HTML_PART_WITH_CSS 36 | premailer = Premailer::Rails::CustomizedPremailer.new(html) 37 | expect(premailer.to_inline_css).to match(regex) 38 | end 39 | end 40 | 41 | context 'when HTML contains unicode' do 42 | it 'does not mess those up' do 43 | html = Fixtures::Message::HTML_PART_WITH_UNICODE 44 | premailer = Premailer::Rails::CustomizedPremailer.new(html) 45 | expect(premailer.to_inline_css).to \ 46 | include(Fixtures::Message::UNICODE_STRING) 47 | end 48 | end 49 | end 50 | end 51 | end 52 | 53 | describe '.new' do 54 | it 'extracts the CSS' do 55 | expect(Premailer::Rails::CSSHelper).to receive(:css_for_doc) 56 | Premailer::Rails::CustomizedPremailer.new('some html') 57 | end 58 | 59 | it 'passes on the configs' do 60 | Premailer::Rails.config.merge!(foo: :bar) 61 | premailer = Premailer::Rails::CustomizedPremailer.new('some html') 62 | expect(premailer.instance_variable_get(:'@options')[:foo]).to eq(:bar) 63 | end 64 | 65 | it 'does not allow to override with_html_string' do 66 | Premailer::Rails.config.merge!(with_html_string: false) 67 | premailer = Premailer::Rails::CustomizedPremailer.new('some html') 68 | options = premailer.instance_variable_get(:'@options') 69 | expect(options[:with_html_string]).to eq(true) 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.9.5 4 | 5 | - Mention license in gemspec 6 | 7 | ## v1.9.4 8 | 9 | - Improve check for Rails module 10 | - Preserve body encoding to prevent garbled mails 11 | 12 | ## v1.9.3 13 | 14 | - Add support for rails' `relative_url_root` config 15 | - Fix link tag removal under Hpricot 16 | - Pass url to `asset_host` if it responds to `call` 17 | - Fixed issue where urls may conflict with folder names. 18 | 19 | ## v1.9.2 20 | 21 | - Update rails dependency to allow rails 5 22 | 23 | ## v1.9.1 24 | 25 | - Respect data-premailer="ignore" on link tags 26 | - Ensure content-transfer-encoding is maintained 27 | 28 | ## v1.9.0 29 | 30 | - Improved CSS loading and caching. 31 | - Fixed incompatibility with newer rails and sprockets versions. 32 | 33 | ## v1.8.2 34 | 35 | - `Premailer::Rails::CSSLoaders::NetworkLoader` is more resilient and works even 36 | if the Rails asset host is set without a URI scheme. (panthomakos) 37 | - Remove stylesheet links from the HTML that have been processed. 38 | 39 | ## v1.8.1 40 | 41 | - Add support for longer fingerprint generated by sprocket 3. 42 | 43 | ## v1.8.0 44 | 45 | - `ActionMailer` interceptors are registered after Rails initialization and no 46 | longer when loading this gem. If you were using this gem outside Rails, you'll 47 | need to call `Premailer::Rails.register_interceptors` manually. 48 | 49 | ## v1.7.0 50 | 51 | - Register preview hook for the new previewing functionality introduced in 52 | rails 4.1.0 53 | 54 | - Add example rails application 55 | 56 | ## v1.6.1 57 | 58 | - Remove Nokogiri unicode fix since it's working properly without it by now 59 | 60 | - Make sure html part comes before text part 61 | 62 | ## v1.6.0 63 | 64 | - Only use asset pipeline if Rails is defined and if compile is true 65 | 66 | - Depend on actionmailer instead of rails 67 | 68 | - Check whether `::Rails` is defined before using it 69 | 70 | - Add ability to skip premailer 71 | 72 | - Test against multiple action mailer versions on travis 73 | 74 | - Ensure CSS strings are always UTF-8 encoded 75 | 76 | - Require premailer version >= 1.7.9 77 | 78 | ## v1.5.1 79 | 80 | - Prefer precompiled assets over asset pipeline 81 | 82 | - Improve construction of file URL when requesting from CDN 83 | 84 | - No longer use open-uri 85 | 86 | - Remove gzip unzipping after requesting file 87 | 88 | ## v1.5.0 89 | 90 | - No longer support ruby 1.8 91 | 92 | - Find linked stylesheets by `rel='stylesheet'` attribute instead of 93 | `type='text/css'` 94 | 95 | - Don't test hpricot on JRuby due to incompatibility 96 | 97 | ## v1.4.0 98 | 99 | - Fix attachments 100 | 101 | ## v1.3.2 102 | 103 | - Rename gem to premailer-rails (drop the 3) 104 | 105 | - Add support for rails 4 106 | 107 | - Refactor code 108 | 109 | - Add support for precompiled assets 110 | 111 | - No longer include default `email.css` 112 | 113 | ## v1.1.0 114 | 115 | - Fixed several bugs 116 | 117 | - Strip asset digest from CSS path 118 | 119 | - Improve nokogiri support 120 | 121 | - Request CSS file if asset is not found locally 122 | 123 | This allows you to host all your assets on a CDN and deploy the 124 | app without the `app/assets` folder. 125 | 126 | Thanks to everyone who contributed! 127 | -------------------------------------------------------------------------------- /spec/support/fixtures/message.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require 'mail' 4 | 5 | module Fixtures 6 | module Message 7 | extend self 8 | 9 | HTML_PART = <<-HTML 10 | 11 |
12 | 13 | 14 |15 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 16 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 17 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 18 | commodo consequat. 19 |
20 | 21 | 22 | HTML 23 | 24 | UNICODE_STRING = '٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).' 25 | 26 | HTML_PART_WITH_UNICODE = <<-HTML 27 | 28 | 29 | 30 | 31 |32 | #{UNICODE_STRING} 33 |
34 | 35 | 36 | HTML 37 | 38 | HTML_PART_WITH_CSS = <<-HTML 39 | 40 | 41 | 44 | 45 | 46 |47 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 48 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 49 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea 50 | commodo consequat. 51 |
52 | 53 | 54 | HTML 55 | 56 | TEXT_PART = <<-TEXT 57 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 58 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 59 | nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 60 | TEXT 61 | 62 | def with_parts(*part_types) 63 | if part_types.count == 1 and [:html, :text].include?(part_types.first) 64 | return with_body(part_types.first) 65 | end 66 | 67 | message = base_message 68 | content_part = message 69 | 70 | if part_types.include?(:html) and part_types.include?(:text) 71 | content_part = Mail::Part.new(content_type: 'multipart/alternative') 72 | message.add_part(content_part) 73 | end 74 | 75 | if part_types.include? :html 76 | html_part = Mail::Part.new do 77 | body HTML_PART_WITH_CSS 78 | content_type 'text/html; charset=UTF-8' 79 | end 80 | content_part.html_part = html_part 81 | end 82 | 83 | if part_types.include? :text 84 | text_part = Mail::Part.new do 85 | body TEXT_PART 86 | content_type 'text/plain; charset=UTF-8' 87 | end 88 | content_part.text_part = text_part 89 | end 90 | 91 | if part_types.include? :attachment 92 | message.add_file(filename: 'foo.png', content: 'foobar') 93 | end 94 | 95 | message.ready_to_send! 96 | 97 | message 98 | end 99 | 100 | def with_body(body_type) 101 | message = base_message 102 | 103 | case body_type 104 | when :html 105 | message.body = HTML_PART_WITH_CSS 106 | message.content_type 'text/html; charset=UTF-8' 107 | when :text 108 | message.body = TEXT_PART 109 | message.content_type 'text/plain; charset=UTF-8' 110 | end 111 | 112 | message.ready_to_send! 113 | 114 | message 115 | end 116 | 117 | def latin_message 118 | base_message.tap do |message| 119 | message.body = HTML_PART 120 | message.content_type 'text/html; charset=UTF-8' 121 | message.ready_to_send! 122 | end 123 | end 124 | 125 | def non_latin_message 126 | base_message.tap do |message| 127 | message.body = HTML_PART_WITH_UNICODE 128 | message.content_type 'text/html; charset=UTF-8' 129 | message.ready_to_send! 130 | end 131 | end 132 | 133 | private 134 | 135 | def base_message 136 | Mail.new do 137 | to 'some@email.com' 138 | subject 'testing premailer-rails' 139 | end 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /lib/premailer/rails/hook.rb: -------------------------------------------------------------------------------- 1 | class Premailer 2 | module Rails 3 | class Hook 4 | attr_reader :message 5 | 6 | class << self 7 | def perform(message) 8 | new(message).perform 9 | message 10 | end 11 | 12 | alias_method :delivering_email, :perform 13 | alias_method :previewing_email, :perform 14 | end 15 | 16 | def initialize(message) 17 | @message = message 18 | end 19 | 20 | def perform 21 | if skip_premailer_header_present? 22 | remove_skip_premailer_header 23 | elsif message_contains_html? 24 | replace_html_part(generate_html_part_replacement) 25 | end 26 | end 27 | 28 | private 29 | 30 | def skip_premailer_header_present? 31 | message.header[:skip_premailer] 32 | end 33 | 34 | def remove_skip_premailer_header 35 | message.header[:skip_premailer] = nil 36 | end 37 | 38 | def message_contains_html? 39 | html_part.present? 40 | end 41 | 42 | # Returns true if the message itself has a content type of text/html, thus 43 | # it does not contain other parts such as alternatives and attachments. 44 | def pure_html_message? 45 | message.content_type && message.content_type.include?('text/html') 46 | end 47 | 48 | def generate_html_part_replacement 49 | if generate_text_part? 50 | generate_alternative_part 51 | else 52 | generate_html_part 53 | end 54 | end 55 | 56 | def generate_text_part? 57 | Rails.config[:generate_text_part] && !message.text_part 58 | end 59 | 60 | def generate_alternative_part 61 | part = Mail::Part.new(content_type: 'multipart/alternative') 62 | part.add_part(generate_text_part) 63 | part.add_part(generate_html_part) 64 | 65 | part 66 | end 67 | 68 | def generate_html_part 69 | # Make sure that the text part is generated first. Otherwise the text 70 | # can end up containing CSS rules. 71 | generate_text_part if generate_text_part? 72 | 73 | part = html_part 74 | html = premailer.to_inline_css 75 | Mail::Part.new do 76 | content_transfer_encoding part.content_transfer_encoding 77 | content_type "text/html; charset=#{part.charset}" 78 | body html 79 | body_encoding part.body.encoding 80 | end 81 | end 82 | 83 | def generate_text_part 84 | @text_part ||= begin 85 | part = html_part 86 | text = premailer.to_plain_text 87 | Mail::Part.new do 88 | content_transfer_encoding part.content_transfer_encoding 89 | content_type "text/plain; charset=#{part.charset}" 90 | body text 91 | body_encoding part.body.encoding 92 | end 93 | end 94 | end 95 | 96 | def premailer 97 | @premailer ||= CustomizedPremailer.new(html_part.decoded) 98 | end 99 | 100 | def html_part 101 | if pure_html_message? 102 | message 103 | else 104 | message.html_part 105 | end 106 | end 107 | 108 | def replace_html_part(new_part) 109 | if pure_html_message? 110 | replace_in_pure_html_message(new_part) 111 | else 112 | replace_part_in_list(message.parts, html_part, new_part) 113 | end 114 | end 115 | 116 | # If the new part is a pure text/html part, the body and its content type 117 | # are used for the message. If the new part is 118 | def replace_in_pure_html_message(new_part) 119 | if new_part.content_type.include?('text/html') 120 | message.body = new_part.decoded 121 | message.content_type = new_part.content_type 122 | else 123 | message.body = nil 124 | message.content_type = new_part.content_type 125 | new_part.parts.each do |part| 126 | message.add_part(part) 127 | end 128 | end 129 | end 130 | 131 | def replace_part_in_list(parts_list, old_part, new_part) 132 | if (index = parts_list.index(old_part)) 133 | parts_list[index] = new_part 134 | else 135 | parts_list.any? do |part| 136 | if part.respond_to?(:parts) 137 | replace_part_in_list(part.parts, old_part, new_part) 138 | end 139 | end 140 | end 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /spec/integration/hook_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Premailer::Rails::Hook do 4 | def run_hook(message) 5 | Premailer::Rails::Hook.perform(message) 6 | end 7 | 8 | def body_content(message) 9 | Nokogiri::HTML(message.html_string).at('body').content 10 | end 11 | 12 | class Mail::Message 13 | def html_string 14 | (html_part || self).body.to_s 15 | end 16 | end 17 | 18 | let(:message) { Fixtures::Message.with_parts(:html) } 19 | let(:processed_message) { run_hook(message) } 20 | 21 | describe '.delivering_email' do 22 | it 'is an alias to .perform' do 23 | method = described_class.method(:delivering_email) 24 | expected_method = described_class.method(:perform) 25 | 26 | expect(method).to eq expected_method 27 | end 28 | end 29 | 30 | describe '.previewing_email' do 31 | it 'is an alias to .perform' do 32 | method = described_class.method(:previewing_email) 33 | expected_method = described_class.method(:perform) 34 | 35 | expect(method).to eq expected_method 36 | end 37 | end 38 | 39 | it 'inlines the CSS' do 40 | expect { run_hook(message) }.to \ 41 | change { message.html_string.include?("'ignore'] } 49 | 50 | it 'ignores links' do 51 | expect(Premailer::Rails::CSSHelper).to_not receive(:css_for_url) 52 | css_for_doc(doc) 53 | end 54 | end 55 | end 56 | 57 | describe '#css_for_url' do 58 | context 'when path is a url' do 59 | it 'loads the CSS at the local path' do 60 | expect_file('public/stylesheets/base.css') 61 | 62 | css_for_url('http://example.com/stylesheets/base.css?test') 63 | end 64 | end 65 | 66 | context 'when path is a relative url' do 67 | it 'loads the CSS at the local path' do 68 | expect_file('public/stylesheets/base.css') 69 | css_for_url('/stylesheets/base.css?test') 70 | end 71 | end 72 | 73 | context 'when file is cached' do 74 | it 'returns the cached value' do 75 | Premailer::Rails::CSSLoaders::CacheLoader.store( 76 | 'http://example.com/stylesheets/base.css', 77 | 'content of base.css' 78 | ) 79 | 80 | expect(css_for_url('http://example.com/stylesheets/base.css')).to \ 81 | eq('content of base.css') 82 | end 83 | end 84 | 85 | context 'when in development mode' do 86 | it 'does not return cached values' do 87 | Premailer::Rails::CSSLoaders::CacheLoader.store( 88 | 'http://example.com/stylesheets/base.css', 89 | 'cached content of base.css' 90 | ) 91 | content = 'new content of base.css' 92 | expect_file('public/stylesheets/base.css', content) 93 | allow(Rails.env).to receive(:development?).and_return(true) 94 | 95 | expect(css_for_url('http://example.com/stylesheets/base.css')).to eq(content) 96 | end 97 | end 98 | 99 | context 'when Rails asset pipeline is used' do 100 | before do 101 | allow(Rails.configuration) 102 | .to receive(:assets).and_return(double(prefix: '/assets')) 103 | allow(Rails.configuration) 104 | .to receive(:relative_url_root).and_return(nil) 105 | end 106 | 107 | context 'and a precompiled file exists' do 108 | it 'returns that file' do 109 | path = '/assets/email-digest.css' 110 | content = 'read from file' 111 | expect_file("public#{path}", content) 112 | expect(css_for_url(path)).to eq(content) 113 | end 114 | end 115 | 116 | it 'returns the content of the file compiled by Rails' do 117 | expect(Rails.application.assets).to \ 118 | receive(:find_asset) 119 | .with('base.css') 120 | .and_return(double(to_s: 'content of base.css')) 121 | 122 | expect(css_for_url('http://example.com/assets/base.css')).to \ 123 | eq('content of base.css') 124 | end 125 | 126 | it 'returns same file when path contains file fingerprint' do 127 | expect(Rails.application.assets).to \ 128 | receive(:find_asset) 129 | .with('base.css') 130 | .and_return(double(to_s: 'content of base.css')) 131 | 132 | expect(css_for_url( 133 | 'http://example.com/assets/base-089e35bd5d84297b8d31ad552e433275.css' 134 | )).to eq('content of base.css') 135 | end 136 | 137 | context 'when asset can not be found' do 138 | let(:response) { 'content of base.css' } 139 | let(:path) { '/assets/base-089e35bd5d84297b8d31ad552e433275.css' } 140 | let(:url) { "http://assets.example.com#{path}" } 141 | let(:asset_host) { 'http://assets.example.com' } 142 | 143 | before do 144 | allow(Rails.application.assets).to \ 145 | receive(:find_asset).and_return(nil) 146 | 147 | config = double(asset_host: asset_host) 148 | allow(Rails.configuration).to \ 149 | receive(:action_controller).and_return(config) 150 | 151 | uri_satisfaction = satisfy { |uri| uri.to_s == url } 152 | allow(Net::HTTP).to \ 153 | receive(:get).with(uri_satisfaction).and_return(response) 154 | end 155 | 156 | it 'requests the file' do 157 | expect(css_for_url(url)).to eq('content of base.css') 158 | end 159 | 160 | context 'when file url does not include the host' do 161 | it 'requests the file using the asset host as host' do 162 | expect(css_for_url(path)).to eq('content of base.css') 163 | end 164 | 165 | context 'and the asset host uses protocol relative scheme' do 166 | let(:asset_host) { '//assets.example.com' } 167 | 168 | it 'requests the file using http as the scheme' do 169 | expect(css_for_url(path)).to eq('content of base.css') 170 | end 171 | end 172 | end 173 | end 174 | end 175 | 176 | context 'when static stylesheets are used' do 177 | it 'returns the content of the static file' do 178 | content = 'content of base.css' 179 | expect_file('public/stylesheets/base.css', content) 180 | loaded_content = css_for_url('http://example.com/stylesheets/base.css') 181 | expect(loaded_content).to eq(content) 182 | end 183 | end 184 | end 185 | end 186 | end 187 | end 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # premailer-rails 2 | 3 | CSS styled emails without the hassle. 4 | 5 | [![Build Status][build-image]][build-link] 6 | [![Gem Version][gem-image]][gem-link] 7 | [![Dependency Status][deps-image]][deps-link] 8 | [![Code Climate][gpa-image]][gpa-link] 9 | [![Coverage Status][cov-image]][cov-link] 10 | 11 | ## Introduction 12 | 13 | This gem is a drop in solution for styling HTML emails with CSS without having 14 | to do the hard work yourself. 15 | 16 | Styling emails is not just a matter of linking to a stylesheet. Most clients, 17 | especially web clients, ignore linked stylesheets or `