Previewing Mailers on non-development tiers - ruby-on-rails

I have several mailer previews under spec/mailer/previews. On development I can view all the previews under /rails/mailers/. However by default this functionality does not exist on other environments.
I wanted to enable it on the staging environment and took a queue from this post here.
I made the following changes -
config/routes.rb
# Add the routes manually
if Rails.env.staging?
get "/rails/mailers" => "rails/mailers#index"
get "/rails/mailers/*path" => "rails/mailers#preview"
end
config/environments/staging.rb
Rails.application.configure do
# Define the mailer preview path
config.action_mailer.preview_path = "spec/mailers/previews"
# Specifically add that path and all files under it to the autoload paths
config.autoload_paths = Dir["#{config.root}/#{config.action_mailer.preview_path}/**"]
end
class ::Rails::MailersController
include Rails.application.routes.url_helpers
# Override the method just for this controller so `MailersController` thinks
# all requests are local.
def local_request?
true
end
end
However on staging, I'm getting the following error when trying to load the /rails/mailers page -
LoadError (No such file to load -- spec/mailers/previews/admin_mailer_preview):
The odd thing is... that file definitely exists. And when I check the autoload paths on staging that file is definitely in the Array/list.
Any thoughts on what might be happening here, or how else I should go about exposing that endpoint?
Thanks!

Having consider_all_requests_local = true or patching local_request? can be security issue. Here is solution we use, it uses authentication to allow only admins to access previews:
# in any enviroment
config.action_mailer.show_previews = true
# in initializers/mailer_previews.rb
# (use your own authentication methods)
# Allow admins to see previews if show_previews enabled.
# It does not affect dev env, as this setting is nil there.
if Rails.application.config.action_mailer.show_previews
Rails::MailersController.prepend_before_action do
authenticate_user!
head :forbidden unless current_user.admin?
end
end
# If you use rspec-rails, it makes rails use spec/mailers/previews
# as previews path. Most likely you don't have rspec-rails on
# staging/production, so need to add this to application.rb:
#
# Make previews available in environments without rspec-rails.
config.action_mailer.preview_path = Rails.root.join('spec', 'mailers', 'previews')
# Bonus: specs. Update with your `sign_in` implementation
# and have `config.action_mailer.show_previews = true` in test.rb
RSpec.describe Rails::MailersController do
shared_examples 'visible to admin only' do
it { should redirect_to new_user_session_path }
context 'for signed in user' do
sign_in { create(:user) }
it { should be_forbidden }
context 'admin' do
let(:current_user) { create(:user, admin: true) }
it { should be_ok }
end
end
end
describe '#index' do
subject { get('/rails/mailers') && response }
include_examples 'visible to admin only'
end
describe '#preview' do
subject { get('/rails/mailers/devise/mailer') && response }
include_examples 'visible to admin only'
end
end

It depends on what Rails version are you running, but if you are on 4.2+ adding these lines to staging.rb should help:
config.action_mailer.show_previews = true
config.consider_all_requests_local = true

Another option would be to use a service like https://mailtrap.io/
and also get some more interesting information about the email such as spam and responsiveness analysis - I find it to be the best option for my staging environment.

Related

ActionMailer fails when testing mailer

Rails 4.1.4 and Rspec 3
I'm doing a VERY basic email test with Rspec. If I call the mailer from rails console, it works perfectly. If I call it from the mailer spec I get:
wrong number of arguments (0 for 1..2)
The mailer is very basic:
def create_team_invite(org, email)
#organization = org
mail(:to=>email, :subject=>'Test Subject')
end
The Test is pretty basic too:
it 'can send out emails to invite others to create teams' do
UserMailer.create_team_invite(#org, 'test#test.com').deliver
expect(ActionMailer::Base.deliveries.count).to eq 1
mail = ActionMailer::Base.deliveries.first
expect(mail.subject).to eq 'Test Subject'
expect(mail.from).to eq 'test#test.com'
end
Its failing in the "mail(:to..." line in the mailer. Seems like maybe its some configuration issue in my environment, but I have Test setup exactly the same as Dev, using SMTP and sending it to a Mailcatcher port. I caught the exception and looked at the Backtrace, but don't see anything unusual...
Anyone seen this before?
Update: providing additional info that was requested.
My test.rb, minus comments:
Rails.application.configure do
config.cache_classes = true
config.eager_load = false
config.serve_static_assets = true
config.static_cache_control = 'public, max-age=3600'
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_dispatch.show_exceptions = false
config.action_controller.allow_forgery_protection = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.active_support.deprecation = :stderr
config.action_mailer.default_url_options = { :host => 'lvh.me:3000', :only_path=>false }
end
The entire rspec failure is:
UserMailer
Team Joining Email
can send out emails to invite others to create teams (FAILED - 1)
Failures:
1) UserMailer Team Joining Email can send out emails to invite others to create teams
Failure/Error: UserMailer.create_team_invite(#team, 'test#test.com').deliver
ArgumentError:
wrong number of arguments (0 for 1..2)
# ./app/mailers/user_mailer.rb:11:in `create_team_invite'
# ./spec/mailers/user_mailer_spec.rb:36:in `block (3 levels) in <top (required)>'
Finished in 0.25858 seconds (files took 29 minutes 48 seconds to load)
1 example, 1 failure
The way I configure my email is via an initializer that loads an email.yml file from my config, per environment. Exact same process used by both test and dev, with the same settings. (Again, I'm sending to Mailcatcher, instead of just to mail_delivery :test)
Update 2
I have narrowed it down to the Mailer missing the "request" object. If I dig through where the error is occurring (AbstractController rendering.rb, line 109) it tries to reference the request object:
if defined?(request) && request && request.variant.present?
This is calling over to Rack test.rb line 121:
def request(uri, env = {}, &block)
env = env_for(uri, env)
process_request(uri, env, &block)
end
So its like the Rack Test.rb class is being seen as the request method in that abstractcontroller... but I dont know how, or why, or why this is happening in this particular project...
I get this same exact error, and it seems that it's trying to call rack-test request code, when I'm just trying to test a mailer object. So what I did is just not include all the stuff that I put in the spec_helper for the mailer spec. That fixed the problem for me.
# require 'spec_helper' -> I removed this line,
# and manually require the files that's needed to just test the mailer object:
require 'rubygems'
require './config/environment'
require './app/mailers/your_mailer'
Note: I'm just doing a rack project using action_mailer, no rails stuff. So your solution will be different, but you get the idea.
Update:
After doing some more troubleshooting. I found this problem in my spec_helper.rb file
RSpec.configure do |config|
config.include include RackSpecHelper # notice the double include
...
end
# where RackSpecHelper is a custom module
module RackSpecHelper
include Rack::Test::Methods
# a bunch of other helper methods
end
Notice the double include on this line
config.include include RackSpecHelper
At first I tried just removing the line and my mailer test is running just fine. Suspicious with the double include, I remove the include so it's just like this
config.include RackSpecHelper
Now my mailer test runs just fine without having to do manual require like I posted earlier above (and it can run together with the other test that uses rack test stuff).
The double include is basically doing
config.include(include(RackSpecHelper))
which include the RackSpecHelper in the configure block, which loads all the methods in the top level namespace! (very bad thing). It works, but that means all the methods from Rack::Test::Methods are in the global namespace.
So I'm guessing in your case, you might have a line in the spec_helper that include Rack::Test::Methods in the global namespace like this
include Rack::Test::Methods
remove it and instead put it in the RSpec config like this
RSpec.configure do |config|
config.include RackSpecHelper
end

Error on 10.1.3 "User/Micropost associations"

I'm fairly new to Ruby on Rails and making my way through Michael Hartl's Rails Tutorial. I've hit a bug I can't seem to fix on Chapter 10 (specifically, 10.1.3 "User/Micropost associations"). The error I run into is when I execute the following rspec command:
$ bundle exec rspec spec/models
It raises the following error:
Failures:
1) Micropost accessible attributes should not allow access to user_id
←[31mFailure/Error:←[0m ←[31mexpect do←[0m
←[31mexpected ActiveModel::MassAssignmentSecurity::Error but nothing was
raised←[0m
←[36m # ./spec/models/micropost_spec.rb:28:in `block (3 levels) in <top (req
uired)>'←[0m
Finished in 5.79 seconds
←[31m27 examples, 1 failure←[0m
Failed examples:
←[31mrspec ./spec/models/micropost_spec.rb:27←[0m ←[36m# Micropost accessible at
tributes should not allow access to user_id←[0m
This is the code for micropost_spec.rb:
require 'spec_helper'
describe Micropost do
let(:user) { FactoryGirl.create(:user) }
before { #micropost = user.microposts.build(content: "Lorem ipsum") }
subject { #micropost }
it { should respond_to(:content) }
it { should respond_to(:user_id) }
it { should respond_to(:user) }
its(:user) { should == user }
it { should be_valid }
describe "when user_id is not present" do
before { #micropost.user_id = nil }
it { should_not be_valid }
end
describe "accessible attributes" do
it "should not allow access to user_id" do
expect do
Micropost.new(user_id: user.id)
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
end
application.rb:
require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
require "active_resource/railtie"
require "sprockets/railtie"
# require "rails/test_unit/railtie"
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
module SampleApp
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
config.active_record.whitelist_attributes = true
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
end
end
micropost.rb...
class Micropost < ActiveRecord::Base
attr_accessible :content
belongs_to :user
validates :user_id, presence: true
end
Thanks in advance for your help!
Thai
So expected ActiveModel::MassAssignmentSecurity::Error but nothing was
raised when Micropost.new(user_id: user.id) is called.
For it to work correctly:
The Rails application should be configured to explicitly whitelist or blacklist accessible parameters; in rails versions before 3.2.3 this was on by default; in previous versions, it has to be done by the following line in config/application.rb as detailed in Listing 10.6 of RailsTutorial book:
config.active_record.whitelist_attributes = true
There shouldn't be a attr_accessible :user_id in the micropost.rb model file.
Try adding
config.active_record.mass_assignment_sanitizer = :strict
To your application.rb file, what I found out is that if not strict, it would just throw a Warning but not an exception.
Like you, I tried all of the solutions above with no luck. I also notice that Hartl's more recent editions of the book for Rails 4 do not contain those tests, so I suspect this approach to testing accessibility does not work in later versions. Perhaps an expert (someone who isn't running through the Rails Tutorial) can chime in.
But.... I noticed this response in another StackOverflow thread recommending the "shoulda-matchers" Gem to test it, and that solved the problem for me (https://stackoverflow.com/a/11876425/3899955). In investigating that Gem, I find it is a nice way to produce readable tests for a number of common ActiveModel, ActiveRecord, and ActionController tests. I am happy to be adding that to my quiver, perhaps you will be too.
I don't take credit for that answer, just wanted to post this so if anyone else is running in to this frustration doing Chapter 10.5, or completing exercise 6 in Chapter 9.

Where should I configure rails gems?

For example, I use mobylette gem and it's documentation says that I can configure it like this.
mobylette_config do |config|
config[:fallback_chains] = { mobile: [:mobile, :html] }
config[:skip_xhr_requests] = false
config[:mobile_user_agents] = proc { %r{iphone}i }
end
Only problem is that, I don't know where to put that code. I tried creating new file config/initializers/mobylette.rb, but I got no method 'mobylette_config' error when starting rails server.
So where should I put these gem configurations and specifically in this case mobylette configuration?
That would be the conventional place to put it -- config/initializers
You can also check that its being loaded by putting in a logger.debug in the initializer
logger.debug 'LOADED mobylette configuration'
You can quickly test if there's another problem by putting the config in your environment.rb file (which is not where I'd leave it)
Both of those should give you some more info to debug
This had me pulling my hair out too. But digging around in the source on github:
https://github.com/tscolari/mobylette/blob/master/lib/mobylette/respond_to_mobile_requests.rb
I found this in the comments:
# Example Usage:
#
# class ApplicationController...
# include Mobylette::RespondToMobileRequests
# ...
# mobylette_config do |config|
# config[:fall_back] = :html
# config[:skip_xhr_requests] = false
# config[:mobile_user_agents] = proc { %r{iphone|android}i }
# config[:devices] = {cool_phone: %r{cool\s+phone} }
# end
# ...
# end

analytics if on production site, not a local or heroku subdomain

This question is about getting an analytics script to run in one of these three environments.
mysite.heroku.com
mysite-staging.heroku.com
mysite.com - this is the only one I want it to run on.
This is how I plan to lay it out, but any suggestions are welcome.
In my helper
def render_analytics
if local_request? || #on a Heroku subdomain
false
else
true
end
end
In my layout
<%= render 'shared/analytics' if render_analytics %>
render_analytics returns a boolean: true if on mysite.com, false if a local_request? or on a Heroku subdomain (ex: mysite.heroku.com || mysite-staging.heroku.com)
So how can I find out if it is coming from Heroku.
Use hostname:
if local_request? || `hostname` =~ /heroku/i
A cleaner solution is to set a constant in your environment during deployment that allows you to know whether you are on Heroku. As the Heroku deploy process is pretty opaque in terms of letting you dork around with config files, you might have your method memoize the result so you aren't doing a system call each time you render a view.
I just did something similar with a method that checks the database adapter to account for differences between my development environment and Heroku. Here's my lib/adapter.rb:
class Adapter
cattr_reader :adapter
def self.postgres?
##adapter ||= Rails.configuration.database_configuration[Rails.env]['adapter']
adapter == 'postgresql'
end
def self.mysql?
##adapter ||= Rails.configuration.database_configuration[Rails.env]['adapter']
adapter == 'mysql'
end
def self.sqlite?
##adapter ||= Rails.configuration.database_configuration[Rails.env]['adapter']
adapter.include?('sqlite')
end
end
Note that in addition to this, you have to change application.rb such that lib is added to your autoload path:
config.autoload_paths += Dir["#{config.root}/lib/**/"] # include all subdirectories

undefined method `find_link' for #<Cucumber::Rails::World:0x818e02e8> (NoMethodError)

Rspec obviously hates me. I kinda hate him back.
#features/step_definitions/custom_steps.rb
Then /^I should see the link "([^\"]*)"$/ do |linked_text|
find_link(linked_text)
end
#link.feature
Then I should see the link "foo"
From terminal:
undefined method `find_link' for #<Cucumber::Rails::World:0x818e02e8> (NoMethodError)
./features/step_definitions/custom_steps.rb:115:in `/^I should see the link "([^\"]*)"$/'
My env.rb file:
#features/support/env.rb
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
# It is recommended to regenerate this file in the future when you upgrade to a
# newer version of cucumber-rails. Consider adding your own code to a new file
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
# files.
ENV["RAILS_ENV"] ||= "cucumber"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
require 'cucumber/rails/world'
require 'cucumber/rails/active_record'
require 'cucumber/web/tableish'
require 'webrat'
require 'webrat/core/matchers'
require 'spec/stubs/cucumber'
Scenes::load
Webrat.configure do |config|
config.mode = :rails
config.open_error_files = false # Set to true if you want error pages to pop up in the browser
end
# If you set this to false, any error raised from within your app will bubble
# up to your step definition and out to cucumber unless you catch it somewhere
# on the way. You can make Rails rescue errors and render error pages on a
# per-scenario basis by tagging a scenario or feature with the #allow-rescue tag.
#
# If you set this to true, Rails will rescue all errors and render error
# pages, more or less in the same way your application would behave in the
# default production environment. It's not recommended to do this for all
# of your scenarios, as this makes it hard to discover errors in your application.
ActionController::Base.allow_rescue = false
# If you set this to true, each scenario will run in a database transaction.
# You can still turn off transactions on a per-scenario basis, simply tagging
# a feature or scenario with the #no-txn tag. If you are using Capybara,
# tagging with #culerity or #javascript will also turn transactions off.
#
# If you set this to false, transactions will be off for all scenarios,
# regardless of whether you use #no-txn or not.
#
# Beware that turning transactions off will leave data in your database
# after each scenario, which can lead to hard-to-debug failures in
# subsequent scenarios. If you do this, we recommend you create a Before
# block that will explicitly put your database in a known state.
Cucumber::Rails::World.use_transactional_fixtures = true
# How to clean your database when transactions are turned off. See
# http://github.com/bmabey/database_cleaner for more info.
if defined?(ActiveRecord::Base)
begin
require 'database_cleaner'
DatabaseCleaner.strategy = :truncation
rescue LoadError => ignore_if_database_cleaner_not_present
end
end
What is wrong? Thank you.
This error is telling you none of your steps or helpers define this method.
Are you trying to use one of the helpers buried in Webrat? It sounds like you want:
Webrat::Locators.find_link

Resources