Search code examples
herokudeploymentruby-on-rails-5

Shopify/money gem in production: "uninitialized constant ActiveJob::Serializers"


I am attempting to update an older Rails app to run on the heroku-20 stack. I'm running Rails 5.1.1 and shopify-money 1.1.2. It works locally, and everything seemed to deploy correctly to heroku, but when I attempt to start up the application, I get the following error:

gems/shopify-money-1.1.2/lib/money/rails/job_argument_serializer.rb:5:in 'module:Rails': uninitialized constant ActiveJob::Serializers (NameError)

I get a more complete error message when I attempt to reload the database:

heroku run bundle exec rake db:reload -a kiddocloud-staging
Running bundle exec rake db:reload on ⬢ kiddocloud-staging... up, run.3483 (Eco)
W, [2023-05-04T22:55:19.644207 #2]  WARN -- sentry: ** [Raven] You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`
D, [2023-05-04T22:55:19.708017 #2] DEBUG -- : uninitialized constant ActiveJob::Serializers excluded from capture: DSN not set
rake aborted!
NameError: uninitialized constant ActiveJob::Serializers
/app/vendor/bundle/ruby/2.6.0/gems/shopify-money-1.1.2/lib/money/rails/job_argument_serializer.rb:5:in `<module:Rails>'
/app/vendor/bundle/ruby/2.6.0/gems/shopify-money-1.1.2/lib/money/rails/job_argument_serializer.rb:4:in `<class:Money>'
/app/vendor/bundle/ruby/2.6.0/gems/shopify-money-1.1.2/lib/money/rails/job_argument_serializer.rb:3:in `<top (required)>'
/app/vendor/bundle/ruby/2.6.0/gems/shopify-money-1.1.2/lib/money/railtie.rb:7:in `require_relative'
/app/vendor/bundle/ruby/2.6.0/gems/shopify-money-1.1.2/lib/money/railtie.rb:7:in `block (2 levels) in <class:Railtie>'
/app/vendor/bundle/ruby/2.6.0/gems/activesupport-5.1.1/lib/active_support/lazy_load_hooks.rb:45:in `instance_eval'
/app/vendor/bundle/ruby/2.6.0/gems/activesupport-5.1.1/lib/active_support/lazy_load_hooks.rb:45:in `execute_hook'
/app/vendor/bundle/ruby/2.6.0/gems/activesupport-5.1.1/lib/active_support/lazy_load_hooks.rb:35:in `block in on_load'
/app/vendor/bundle/ruby/2.6.0/gems/activesupport-5.1.1/lib/active_support/lazy_load_hooks.rb:34:in `each'
/app/vendor/bundle/ruby/2.6.0/gems/activesupport-5.1.1/lib/active_support/lazy_load_hooks.rb:34:in `on_load'
/app/vendor/bundle/ruby/2.6.0/gems/shopify-money-1.1.2/lib/money/railtie.rb:6:in `block in <class:Railtie>'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/initializable.rb:30:in `instance_exec'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/initializable.rb:30:in `run'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/initializable.rb:59:in `block in run_initializers'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/initializable.rb:58:in `run_initializers'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/application.rb:353:in `initialize!'
/app/config/environment.rb:5:in `<top (required)>'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/application.rb:329:in `require_environment!'
/app/vendor/bundle/ruby/2.6.0/gems/railties-5.1.1/lib/rails/application.rb:445:in `block in run_tasks_blocks'
/app/vendor/bundle/ruby/2.6.0/gems/rake-13.0.6/exe/rake:27:in `<top (required)>'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `load'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:74:in `kernel_load'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/cli/exec.rb:28:in `run'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/cli.rb:463:in `exec'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/cli.rb:27:in `dispatch'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/cli.rb:18:in `start'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/exe/bundle:30:in `block in <top (required)>'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/lib/bundler/friendly_errors.rb:124:in `with_friendly_errors'
/app/vendor/bundle/ruby/2.6.0/gems/bundler-1.17.3/exe/bundle:22:in `<top (required)>'
/app/bin/bundle:3:in `load'
/app/bin/bundle:3:in `<main>'
Tasks: TOP => db:reload => environment
(See full trace by running task with --trace)

It amounts to the same thing. It can't seem to find the ActiveJob::Serializers even though Rails 5.0.0 and above should have that ... Help!

EDIT: Here is the contents of my application.rb:

require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Kidsclub
  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.autoload_paths += %W(#{config.root}/lib)
    config.autoload_paths += Dir["#{config.root}/lib/**/"]
    config.autoload_paths += Dir[Rails.root.join('app', 'models', '**/')]
    config.autoload_paths += Dir[Rails.root.join('app', 'exceptions', '**/')]
    config.autoload_paths += Dir[Rails.root.join('app', 'enums', '**/')]
    config.eager_load_paths << "#{Rails.root}/lib"
    config.assets.paths << "#{Rails.root}/app/assets"

    config.time_zone = "Pacific Time (US & Canada)"

    config.active_job.queue_adapter = :async
  end
end

And here is my production.rb (I didn't see any sensitive info):

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # Code is not reloaded between requests.
  config.cache_classes = true

  # Eager load code on boot. This eager loads most of Rails and
  # your application in memory, allowing both threaded web servers
  # and those relying on copy on write to perform better.
  # Rake tasks automatically ignore this option for performance.
  config.eager_load = true

  # Full error reports are disabled and caching is turned on.
  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true

  # Disable serving static files from the `/public` folder by default since
  # Apache or NGINX already handles this.
  config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

  # Compress JavaScripts and CSS.
  config.assets.js_compressor = :uglifier
  config.assets.css_compressor = :sass

  # Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = false

  config.public_file_server.enabled = true

  # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb

  # Enable serving of images, stylesheets, and JavaScripts from an asset server.
  # config.action_controller.asset_host = 'http://assets.example.com'

  # Specifies the header that your server uses for sending files.
  # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
  # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX

  # Mount Action Cable outside main process or domain
  # config.action_cable.mount_path = nil
  # config.action_cable.url = 'wss://example.com/cable'
  # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]

  # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
  config.force_ssl = true

  # Use the lowest log level to ensure availability of diagnostic information
  # when problems arise.
  config.log_level = :debug

  # Prepend all log lines with the following tags.
  config.log_tags = [ :request_id ]

  # Use a different cache store in production.
  # config.cache_store = :mem_cache_store

  # Use a real queuing backend for Active Job (and separate queues per environment)
  # config.active_job.queue_adapter     = :resque
  # config.active_job.queue_name_prefix = "kidsclub_#{Rails.env}"
  config.action_mailer.perform_caching = false

  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  # config.action_mailer.raise_delivery_errors = false

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation cannot be found).
  config.i18n.fallbacks = true

  # Send deprecation notices to registered listeners.
  config.active_support.deprecation = :notify

  # Use default logging formatter so that PID and timestamp are not suppressed.
  config.log_formatter = ::Logger::Formatter.new

  # Use a different logger for distributed setups.
  # require 'syslog/logger'
  # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')

  if ENV["RAILS_LOG_TO_STDOUT"].present?
    logger           = ActiveSupport::Logger.new(STDOUT)
    logger.formatter = config.log_formatter
    config.logger = ActiveSupport::TaggedLogging.new(logger)
  end

  # Default url options
  config.action_mailer.default_url_options = {:host => 'https://manage.daviskidsklub.com'}
  config.action_controller.default_url_options = {:host => 'https://manage.daviskidsklub.com'}

  #Mailer asset host
  config.action_mailer.asset_host = "https://manage.daviskidsklub.com"

  ActionMailer::Base.smtp_settings = {
      :port =>           '587',
      :address =>        'smtp.sendgrid.net',
      :user_name =>      ENV['SENDGRID_USERNAME'],
      :password =>       ENV['SENDGRID_PASSWORD'],
      :domain =>         'manage.daviskidsklub.com',
      :authentication => :plain,
      :enable_starttls_auto => true
  }
  ActionMailer::Base.delivery_method = :smtp

  # Do not dump schema after migrations.
  config.active_record.dump_schema_after_migration = false

  config.read_encrypted_secrets = true
end

EDIT 2:

Shopify/money initializer file (/config/initializers/shopify_money.rb):

Money.configure do |config|
  config.default_currency = Money::Currency.new("USD")
end

EDIT 3:

Just in case it's some kind of gem problem, here is my Gemfile:

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

ruby "2.6.6"

gem 'devise', '4.4'
gem 'haml-rails', '~> 2.0'
gem 'wicked', '~> 1.3', '>= 1.3.1'
gem 'forgery', '~> 0.6.0'
gem 'seed-fu', '~> 2.3', '>= 2.3.6'
gem 'awesome_print', '~> 1.7'
gem 'flatpickr_rails'
gem 'pundit', '~> 1.1'
gem "font-awesome-rails"
gem "chronic"
gem 'shopify-money', '~> 1.1'
gem "cocoon"
gem "stamp"
gem "stripe"
gem "kaminari"
gem "roo"
gem "classy_enum"
# Error Reporting
gem "sentry-raven"


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '5.1.1'
# Use sqlite3 as the database for Active Record
gem 'pg'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby

# Use jquery as the JavaScript library
gem 'jquery-rails'
gem 'jquery-tablesorter'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
gem "clipboard-rails"
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
gem 'bootstrap-sass', '~> 3.3', '>= 3.3.7'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
  # For easy test records
  gem "factory_girl_rails", "~> 4.8"
  # For generating fake data
  gem "faker", "~> 1.7.3"
  # Test suite
  gem "rspec-rails", "~> 3.6"
end

# Heroku
gem 'rails_12factor', group: [:production, :beta]

group :development do
  # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.0.5'
  # Automatic Ruby code style checking tool
  gem "rubocop", "~> 0.49.1"
  gem "letter_opener"
  gem 'rails_db', '2.0.4'
end

group :test do
  # A gem providing "time travel" and "time freezing" capabilities for tests
  gem "timecop", "~> 0.9.0"
  gem 'rspec-activemodel-mocks'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

FINAL EDIT:

Despite Chiperific's best efforts, nothing seemed to be working. I finally had the brainwave that perhaps the Shopify/Money gem was too updated for the version of Rails I am using. I downgraded it to 0.16.0, and that worked! I still couldn't get production up and running locally for other reasons, but when I deployed to my Heroku beta site, it worked! I'll be upgrading Rails again sometime soon, and revisit the new version of Shopify/Money at that point.


Solution

  • An early swing at this.

    Yes, Rails has ActiveJob::Serializers, I'm guessing it's just not yet initialized when shopify-money is getting loaded.

    I'm guessing it works locally because production.rb has something that development.rb doesn't that is trying to load this gem before ActiveJob.

    To confirm, you could try doing RAILS_ENV=production rails console locally. It should fail the same way.

    As to a fix, your options are to defer loading the gem's JobArgumentSerializer until after you're sure that all of Rails is loaded or do the opposite and make sure to require "active_job/serializers" before any shopify-money code.

    In application.rb, I'd expect to see something like this:

    require "rails" #<- all of Rails, including ActiveJob
    ...
    Bundler.require(*Rails.groups) #<- requires the gems from your Gemfile
    

    This ensures all the gems are loaded after Rails.

    I'm guessing you have something in an initializer or in production.rb that is trying to use the gem before the application.rb file is loaded.


    Update based on comment:

    I made the changes to the development.yml file as you suggested and I got the same error!

    There are two main things that change between environments:

    1. Code in your /environments folder.

    2. Gems that are group'd to a specific environment

    Since nothing stands out to me from your Gemfile and I don't see any red flags in production.rb (though you should still compare it to your development.rb file to be sure)

    AND since you can now test your production environment locally I'd try the easiest thing first:

    # /config/initializers/shopify_money.rb
    
    ## play around with which of these classes are required to avoid the error
    
    # gets you EVERYTHING, avoid if possible:
    require "rails/all"
    
    # all of Rails, try for just ActiveJob:
    require "rails"
    
    # all of ActiveJob, try for just ActiveJob::Serializers:
    require "active_job"
    
    # try only using this one:
    require "active_job/serializers"
    
    # probably not the fix, but worth trying:
    require "money"
    
    Money.configure do |config|
      config.default_currency = Money::Currency.new("USD")
    end