Rails 5.1 do not compile asset in test vs asset is not present in the asset pipeline

We don’t want to compile assets during test runs, since that is slow, but we also don’t want the asset pipeline to fail because assets are missing.

This will not work if you plan on doing javascript integration tests, but everything else should work fine.

Rails 5.1 added a flag for this which prints deprecations and will be removed in rails 5.2 so that is not a elegant solution either.

config.assets.unknown_asset_fallback = true

So we are now using this fix to fake assets being available!:

# config/environments/test.rb
# make our tests fast by avoiding asset compilation
# but do not raise when assets are not compiled either
Rails.application.config.assets.compile = false
Sprockets::Rails::Helper.prepend(Module.new do
  def resolve_asset_path(path, *)
    super || path
  end
end)

Ruby on Kubernetes: Newrelic

– Report each environment as separate app
– Report namespace so we can track down where misbehaving pods live

    # config/application.rb (not in initializers)
    if ENV['POD_NAMESPACE'] # on kubernetes
      # report each env into a separate project
      NewRelic::Agent::Configuration::Manager.class_eval do
        undef app_names
        def app_names
          name = ENV.fetch('NEW_RELIC_APP_NAME')
          name += " #{Rails.env}" unless Rails.env.production?
          [name]
        end
      end

      # report namespace so we can track down where a troublesome pod lives
      # newrelic_rpm 3.18.0+ has a NEW_RELIC_PROCESS_HOST_DISPLAY_NAME setting which is supposed to do that, but did not work.
      class << NewRelic::Agent::Hostname
        def get
          "#{ENV.fetch('POD_NAME')}.#{ENV.fetch('POD_NAMESPACE')}"
        end
      end
    end

Making Rails 4 and 3 share signed cookies

Rails 4 by default wants to upgrade all cookies, which makes rails 3 unable to read them. But we want that to work since we let rails 3 and 4 run in parallel to test performance (which is terrible on rails 4 … )

# While we run servers with rails 3 and rails 4 we don't want to encrypt our cookie
# once everything is on rails 4 we can by using the upgrade signed to encrypted strategy
# tested via test/integration/rails_compatibility_test.rb
if RAILS4
  ActionDispatch::Cookies::ChainedCookieJars.class_eval do
    def signed_or_encrypted
      signed
    end
  end

  # do not update ... compare to action_dispatch/middleware/cookies.rb:184
  ActionDispatch::Cookies::UpgradeLegacySignedCookieJar.class_eval do
    def initialize(*args)
      super
      @verifier = @legacy_verifier
    end

    def verify_and_upgrade_legacy_signed_message(name, signed_message)
      deserialize(name, @legacy_verifier.verify(signed_message))
    rescue ActiveSupport::MessageVerifier::InvalidSignature
      nil
    end
  end
end

Render nested Rails object errors

  • No looping
  • Infinitely deep
  • Html safe

nested-errors-wordpress

  # application_helper.rb
  def render_nested_errors(object, seen=Set.new)
    return "" if seen.include?(object)
    seen << object
    return "" if object.errors.empty?

    content_tag :ul do
      lis = object.errors.map do |attribute, message|
        content_tag(:li) do
          content = "".html_safe
          content << object.errors.full_message(attribute, message)
          values = (object.respond_to?(attribute) ? Array.wrap(object.send(attribute)) : [])
          if values.first.is_a?(ActiveRecord::Base)
            values.each do |value|
              content << render_nested_errors(value, seen)
            end
          end
          content
        end
      end
      safe_join lis
    end
  end

  # application_helper_test.rb
  
  describe "#render_nested_errors" do
    # simulate what erb will do so we can see html_safe issues
    def render
      ERB::Util.html_escape(render_nested_errors(stage))
    end

    let(:stage) { stages(:test_staging) }

    it "renders nothing for valid" do
      render.must_equal ""
    end

    it "renders simple errors" do
      stage.errors.add(:base, "Kaboom")
      render.must_equal "<ul><li>Kaboom</li></ul>"
    end

    it "renders nested errors" do
      stage.errors.add(:deploy_groups, "Invalid") # happens on save normally .. not a helpful message for our users
      stage.errors.add(:base, "BASE") # other error to make sure nesting is correct
      stage.deploy_groups.to_a.first.errors.add(:base, "Kaboom")
      render.must_equal "<ul><li>Deploy groups Invalid<ul><li>Kaboom</li></ul></li><li>BASE</li></ul>"
    end

    it "does not loop" do
      stage.errors.add(:project, "Invalid")
      stage.project.stubs(stages: [stage])
      stage.project.errors.add(:stages, "Invalid")
      render.must_equal "<ul><li>Project Invalid<ul><li>Stages Invalid</li></ul></li></ul>"
    end

    it "cannot inject html" do
      stage.errors.add(:deploy_groups, "<foo>")
      stage.errors.add(:base, "<bar>")
      stage.deploy_groups.to_a.first.errors.add(:base, "<baz>")
      render.must_equal "<ul><li>Deploy groups &lt;foo&gt;<ul><li>&lt;baz&gt;</li></ul></li><li>&lt;bar&gt;</li></ul>"
    end
  end

Testing SuckerPunch/Celluloid vs ActiveRecord Transactions

Celluloid runs in a new thread, so it runs on a new transaction. Therefore we cannot test what was done in this transaction.

If you are not using any Celluloid callbacks then a simple unthreaded baseclass can help:

class BackgroundJobTestable
  def perform
    ...
end

class BackgroundJob < BackgroundJobTestable
  include SuckerPunch::Job
  workers 2
end

An alternative way to fix this is to disable AR multithreading support via a mocking library like mocha … this will break any code that truly runs in parallel, but if you only run 1 code path at a time this should work fine.

ActiveRecord::Base.stubs(connection: ActiveRecord::Base.connection)