I'm trying to test a Rails plugin to make sure that the correct action is performed with or without fallbacks turned on. Inside my Dummy application's configuration, I have:
module Dummy
class Application < Rails::Application
config.i18n.fallbacks = true
end
end
How can I flip this back to false for a specific test? So far I have tried:
Dummy::Application.config.i18n.fallbacks = false
Dummy::Application.configure do |app|
app.config.i18n.fallbacks = false
end
Rails.application.config.i18n.fallbacks = false
All to no avail.
This is an interesting question, actually once the app got initialized in the beginning, you couldn't change i18n configuration because it already got memoized with these codes:
# Setup i18n configuration.
def self.initialize_i18n(app)
return if #i18n_inited
...
#i18n_inited = true
end
And I18n.fallbacks was set with this line
I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
When you specify the callbacks as true, I18n.fallbacks has a value of
[1] pry(main)> I18n.fallbacks
=> {}
[2] pry(main)> I18n.fallbacks.class
=> I18n::Locale::Fallbacks
And I18n.fallbacks is used in this way:
locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks
As long as I18n.respond_to? :fallbacks, the app will always have fallbacks feature turned on.
The method is defined here in I18n:
def fallbacks
##fallbacks ||= I18n::Locale::Fallbacks.new
end
To turn off the fallbacks feature, the only way is to make I18n.respond_to? :fallbacks return false.
Here you go:
class << I18n
remove_method :fallbacks
end
Related
I have a problem with my rails application. After an Update from Rails 3 to 4.
When I surf through the pages after starting the server in development mode everything is fine.
But after a single code change (even adding a space) every page request shows the following error.
Unable to autoload constant User, expected
/path/to/my/rails-app/app/models/user.rb to define it
The file lives exactly there and defines the class:
class User < ActiveRecord::Base
…
I tried many things with config.autoload_paths and config.eager_load_paths in application.rb but with no luck.
Deactivating spring did not help either.
Developing an app and having to restart the server after every single change seems so 90s.
$ rails -v
Rails 4.2.4
$ ruby -v
ruby 2.1.7p400 (2015-08-18 revision 51632) [x86_64-linux]
Some relevant configs:
development.rb
MyApp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
config.assets.debug = true
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = {
host: 'localhost',
port: 3000
}
end
application.rb
module Serviceportal
class Application < Rails::Application
# 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'
[… some asset precompile stuff …]
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = 'utf-8'
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += Dir["#{config.root}/app/mailers",
"#{config.root}/app/controllers/concerns",
"#{config.root}/app/models/concerns",
"#{config.root}/app/decorators/concerns",
"#{config.root}/lib",
"#{config.root}/lib/shared"
]
config.eager_load_paths += Dir["#{config.root}/app/mailers",
"#{config.root}/app/controllers/concerns",
"#{config.root}/app/models/concerns",
"#{config.root}/app/decorators/concerns",
"#{config.root}/lib",
"#{config.root}/lib/shared"]
# 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 = 'Berlin'
# 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
[… some SQL and active support stuff …]
config.action_controller.include_all_helpers = false
config.action_controller.action_on_unpermitted_parameters = :raise
# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
end
end
Edit: The error mostly shows up in lib/auth/user_proxy.rb in the following function. Maybe this helps to narrow the range of possible causes.
def self.usertype_allowed?(type)
[ User, TempCustomer ].include? type.classify.safe_constantize rescue false
end
Edit 2: Stringify the class names in Edit 1 helped (thanks #Benjamin Sinclaire). But only leads to the next errors. I could also avoid using classes. But at the following error in app/controllers/concerns/security.rb there is nothing can change?
Unable to autoload constant User, expected
/path/to/my/rails-app/app/models/user.rb to define it
code:
def set_current_user
User.current = current_user
end
with current user saved in the Thread (code from /path/to/my/rails-app/app/models/user.rb
def self.current
Thread.current['current_user']
end
def self.current=(user)
Thread.current['current_user'] = user
end
Just to make it clear again: It works after server restart in development until I change some code somewhere.
1 See if you have any multiple-level class or module declaration done one one line and change them to be declared in several lines.
Instead of
class Parent::Sub::Child
end
Do
module Parent
module Sub
class Child
end
end
end
2 Check your model association definitions, and ensure you are never using constant. Use string instead.
Instead of
belongs_to :manager, class_name: User
Do
belongs_to :manager, class_name: 'User'
3 Just saw your edit. Can you refactor like this?
# I assume `type` is a string or such, so we can compare classes
# names instead of constants, and get rid of `safe_constantize`
def self.usertype_allowed?(type)
['User', 'TempCustomer'].include? type.classify rescue false
end
4 Not a good idea to serialize an active record object in the Thread storage. Change it to store the user id instead, like this:
def set_current_user
User.current = current_user.id
end
def self.current
Thread.current['current_user_id']
end
def self.current=(user_id)
Thread.current['current_user_id'] = user_id
end
You don't need include app/models/concerns and app/controllers/concerns in your autoload/ eagerload paths as they are included by default in Rails 4: https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns
Also make sure that your concerns are defined as modules, extend ActiveSupport::Concern and with the appropriate file name
#taggable.rb
module Taggable
extend ActiveSupport::Concern
end
Another cause of your problem might be that some modules/ classes in app/decorators/concerns, lib, lib/shared are using the User class
which is not loaded yet or some of it's dependencies are not loaded so try adding require_relative path_to_user.rb at the top of those files
-----Edit-------
Try adding at the top of lib/auth/user_proxy.rb
require_dependency 'app/models/user'
This way you'll remove any ambiguity in autoloading the User class and you won't mess around with Rails autoloading see more here: http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#require-dependency , http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#common-gotchas
Same problem but in an engine w/ namespaces. No issues in production or in development until a code-change / autoload.
The solution was to
checking for double definitions (there were none)
checking if the module nesting strictly follows rails conventions in the filesystem.
I've had myns under myengine/app/myns/subns/obj.rb but myns is being ignored as it is at the root of the app folder, so moving the myns folder into a subfolder myengine/app/lib/myns solved the issue.
Note: the rails error message was very explicit about the module nesting (while still pointing to the wrong .rb file in the filesystem) so look closely at the error. The error was 'Unable to autoload constant subns/obj.rb in .../myns/subns/obj.rb'. Rails suggesting the incorrect file-location (which exists) is misleading in this case.
During a Rails/Ruby Update I found time to look into this and finally found the cause.
The user class had an unloadable in it for years. That caused the problems since Rails 4. Now I removed this and found no issues after that.
Since I'm using rails 3 there are a lot of translation missing records in production environment because of the way translation missing strings are handled in rails 3, for example the output of translation missing is now :
<span class="translation_missing" title="translation missing: fr.admin.orders_logs.update.title">Title</span>
In our application we see "Title" so we think the translation is ok but infact the translation is missing, if I"m not wrong the displayed text was "translation missing: fr.admin.orders_logs.update.title" in Rails 2.X
How I can activate this again that he shows me the error message instead of putting it just as title in a span?
This seems to be a feature in Rails. See: https://gist.github.com/rails/rails/issues/6489
A possible fix is to override the culprit by placing something like this to application.rb:
module ActiveModel
module Translation
def human_attribute_name(attribute, options = {})
defaults = []
parts = attribute.to_s.split(".", 2)
attribute = parts.pop
namespace = parts.pop
if namespace
lookup_ancestors.each do |klass|
defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
end
defaults << :"#{self.i18n_scope}.attributes.#{namespace}.#{attribute}"
else
lookup_ancestors.each do |klass|
defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
end
end
defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
defaults << "MISSING TRANSLATION"+defaults.to_yaml # defaults << attribute.humanize
options.reverse_merge! :count => 1, :default => defaults
I18n.translate(defaults.shift, options)
end
end
end
Have you tried setting the i18n.fallbacks config to false?
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = false
I want Rails to raise an exception when an I18n translation is missing in the testing environment (instead of rendering text 'translation missing'). Is there a simple way to achieve this?
As of Rails 4.1.0, there's now a better solution than the 4 year-old answers to this question: add the following line to your config file:
config.action_view.raise_on_missing_translations = true
I like to set this in the test environment only, but you might also want to set it in development. I would strongly advise against setting it to true in production.
To raise exceptions, you can define a class to handle localization errors.
class TestExceptionLocalizationHandler
def call(exception, locale, key, options)
raise exception.to_exception
end
end
Then you attach it to the desired test cases with
I18n.exception_handler = TestExceptionLocalizationHandler.new
This way you get exceptions raised. I don't know how to raise failures (with flunk) to get better results.
Or you can just add those lines to your config/test.rb
config.action_view.raise_on_missing_translations = true
config.i18n.exception_handler = Proc.new { |exception| raise exception.to_exception }
Rails 4.1+
To raise i18n translation missing exceptions you need two things:
1) An initializer config/initializers/i18n_force_exceptions.rb:
module I18n
class ForceMissingTranslationsHandler < ExceptionHandler
def call(exception, locale, key, options)
if Rails.env.test?
raise exception.to_exception
else
super
end
end
end
end
I18n.exception_handler = I18n::ForceMissingTranslationsHandler.new
2) A config setting in config/environments/test.rb (and other environments as needed):
config.action_view.raise_on_missing_translations = true
Note: The config setting is needed in addition to the exception handler because rails wraps calls to I18n.translate in it's view and helpers preventing exceptions from triggering.
I've created this initializer to raise an exception - args are passed so you will know which i18n key is missing!
# only for test
if Rails.env.test?
# raises exception when there is a wrong/no i18n key
module I18n
class JustRaiseExceptionHandler < ExceptionHandler
def call(exception, locale, key, options)
if exception.is_a?(MissingTranslation)
raise exception.to_exception
else
super
end
end
end
end
I18n.exception_handler = I18n::JustRaiseExceptionHandler.new
end
Source
If you're using rails between 4.0.0 to 4.1.0 you should monkey patch this way:
module ActionView::Helpers::TranslationHelper
def t_with_raise(*args)
value = t_without_raise(*args)
if value.to_s.match(/title="translation missing: (.+)"/)
raise "Translation missing: #{$1}"
else
value
end
end
alias_method :translate_with_raise, :t_with_raise
alias_method_chain :t, :raise
alias_method_chain :translate, :raise
end
I'm using Rails 3 with Globalize3 0.2.0.beta4
Ideally I need :fr to fallback to :en and vice versa.
There are cases when only a French translation is available and I need to show it even if the locale is :en.
I tried
config.i18n.fallbacks = { :fr => :en, :en => :fr }
but somewhat unsurprisingly it causes a stack level too deep error.
I'm changing my answer.
To enable fallbacks, add the following to your environment.rb file:
#support for locale fallbacks
require "i18n/backend/fallbacks"
I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
Then, you can enable circular fallbacks like you were trying to, eg:
config.i18n.fallbacks = {'en' => 'fr', 'fr' => 'en'}
In this case, if something is missing in the en locale, it'll check the fr locale, and then the other way around. I don't get any errors running this.
Source: http://batsov.com/articles/2012/09/12/setting-up-fallback-locale-s-in-rails-3/
If you pass an array of locales they will be set as default fallbacks for all locales.
config.i18n.fallbacks = [:en, :fr]
Unfortunately, I haven't found a way to set up just two locales to fall back to each other.
In the end I monkey patched Globalize3. Not great as I have to update the patch whenever the site needs a new locale, but hey, it worked.
module Globalize
class << self
def fallbacks(locale = self.locale)
case locale
when :en then [:en, :fr]
when :fr then [:fr, :en]
end
end
end
end
This seems to have changed to this:
Globalize.fallbacks = {:en => [:en, :fr], :fr => [:fr, :en]}
Got from the official docs:
https://github.com/globalize/globalize#fallback-locales-to-each-other
In latest i18n gem (0.7.0) I have found it necessary to define fallback locales like this (in config/application.rb):
# Custom I18n fallbacks
config.after_initialize do
I18n.fallbacks = I18n::Locale::Fallbacks.new(at: :"de-DE", ch: :"de-DE", gb: :"en-US")
end
You also need to set config.i18n.fallbacks = true in all config/environments/*.rb files.
I have a Rails application that has an action invoked frequently enough to be inconvenient when I am developing, as it results in a lot of extra log output I don't care about. How can I get rails not to log anything (controller, action, parameters, completion time, etc.) for just this one action? I'd like to conditionalize it on RAILS_ENV as well, so logs in production are complete.
Thanks!
You can silence the Rails logger object:
def action
Rails.logger.silence do
# Things within this block will not be logged...
end
end
Use lograge gem.
Gemfile:
gem 'lograge'
config/application.rb:
config.lograge.enabled = true
config.lograge.ignore_actions = ['StatusController#nginx', ...]
The following works with at least Rails 3.1.0:
Make a custom logger that can be silenced:
# selective_logger.rb
class SelectiveLogger < Rails::Rack::Logger
def initialize app, opts = {}
#app = app
#opts = opts
#opts[:silenced] ||= []
end
def call env
if #opts[:silenced].include?(env['PATH_INFO']) || #opts[:silenced].any? {|silencer| silencer.is_a?( Regexp) && silencer.match( env['PATH_INFO']) }
Rails.logger.silence do
#app.call env
end
else
super env
end
end
end
Tell Rails to use it:
# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]
The example above shows silencing asset serving requests, which in the development environment means less ( and sometimes no) scrolling back is required to see the actual request.
The answer turns out to be a lot harder than I expected, since rails really does provide no hook to do this. Instead, you need to wrap some of the guts of ActionController::Base. In the common base class for my controllers, I do
def silent?(action)
false
end
# this knows more than I'd like about the internals of process, but
# the other options require knowing even more. It would have been
# nice to be able to use logger.silence, but there isn't a good
# method to hook that around, due to the way benchmarking logs.
def log_processing_with_silence_logs
if logger && silent?(action_name) then
#old_logger_level, logger.level = logger.level, Logger::ERROR
end
log_processing_without_silence_logs
end
def process_with_silence_logs(request, response, method = :perform_action, *arguments)
ret = process_without_silence_logs(request, response, method, *arguments)
if logger && silent?(action_name) then
logger.level = #old_logger_level
end
ret
end
alias_method_chain :log_processing, :silence_logs
alias_method_chain :process, :silence_logs
then, in the controller with the method I want to suppress logging on:
def silent?(action)
RAILS_ENV == "development" && ['my_noisy_action'].include?(action)
end
You can add the gem to the Gemfile silencer.
gem 'silencer', '>= 1.0.1'
And in your config/initializers/silencer.rb :
require 'silencer/logger'
Rails.application.configure do
config.middleware.swap Rails::Rack::Logger, Silencer::Logger, silence: ['/api/notifications']
end
The following works with Rails 2.3.14:
Make a custom logger that can be silenced:
#selective_logger.rb
require "active_support"
class SelectiveLogger < ActiveSupport::BufferedLogger
attr_accessor :silent
def initialize path_to_log_file
super path_to_log_file
end
def add severity, message = nil, progname = nil, &block
super unless #silent
end
end
Tell Rails to use it:
#environment.rb
config.logger = SelectiveLogger.new config.log_path
Intercept the log output at the beginning of each action and (re)configure the logger depending on whether the action should be silent or not:
#application_controller.rb
# This method is invoked in order to log the lines that begin "Processing..."
# for each new request.
def log_processing
logger.silent = %w"ping time_zone_table".include? params[:action]
super
end
With Rails 5 it gets more complicated request processing is logged in several classes. Firstly we need to override call_app in Logger class, let's call this file lib/logger.rb:
# original class:
# https://github.com/rails/rails/blob/master/railties/lib/rails/rack/logger.rb
require 'rails/rack/logger'
module Rails
module Rack
class Logger < ActiveSupport::LogSubscriber
def call_app(request, env) # :doc:
unless Rails.configuration.logger_exclude.call(request.filtered_path)
instrumenter = ActiveSupport::Notifications.instrumenter
instrumenter.start "request.action_dispatch", request: request
logger.info { started_request_message(request) }
end
status, headers, body = #app.call(env)
body = ::Rack::BodyProxy.new(body) { finish(request) }
[status, headers, body]
rescue Exception
finish(request)
raise
ensure
ActiveSupport::LogSubscriber.flush_all!
end
end
end
end
Then follow with lib/silent_log_subscriber.rb:
require 'active_support/log_subscriber'
require 'action_view/log_subscriber'
require 'action_controller/log_subscriber'
# original class:
# https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/log_subscriber.rb
class SilentLogSubscriber < ActiveSupport::LogSubscriber
def start_processing(event)
return unless logger.info?
payload = event.payload
return if Rails.configuration.logger_exclude.call(payload[:path])
params = payload[:params].except(*ActionController::LogSubscriber::INTERNAL_PARAMS)
format = payload[:format]
format = format.to_s.upcase if format.is_a?(Symbol)
info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
info " Parameters: #{params.inspect}" unless params.empty?
end
def process_action(event)
return if Rails.configuration.logger_exclude.call(event.payload[:path])
info do
payload = event.payload
additions = ActionController::Base.log_process_action(payload)
status = payload[:status]
if status.nil? && payload[:exception].present?
exception_class_name = payload[:exception].first
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
additions << "Allocations: #{event.allocations}" if event.respond_to? :allocations
message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
message << " (#{additions.join(" | ")})" unless additions.empty?
message << "\n\n" if defined?(Rails.env) && Rails.env.development?
message
end
end
def self.setup
# unsubscribe default processors
ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
case subscriber
when ActionView::LogSubscriber
self.unsubscribe(:action_view, subscriber)
when ActionController::LogSubscriber
self.unsubscribe(:action_controller, subscriber)
end
end
end
def self.unsubscribe(component, subscriber)
events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
events.each do |event|
ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
if listener.instance_variable_get('#delegate') == subscriber
ActiveSupport::Notifications.unsubscribe listener
end
end
end
end
end
# subscribe this class
SilentLogSubscriber.attach_to :action_controller
SilentLogSubscriber.setup
Make sure to load modified modules e.g. in config/application.rb after loading rails:
require_relative '../lib/logger'
require_relative '../lib/silent_log_subscriber'
Finally configure excluded paths:
Rails.application.configure do
config.logger_exclude = ->(path) { path == "/health" }
end
As we're modifying core code of Rails it's always good idea to check original classes in Rails version you're using.
If this looks like too many modifications, you can simply use lograge gem which does pretty much the same with few other modifications. Although the Rack::Loggger code has changed since Rails 3, so you might be loosing some functionality.
#neil-stockbridge 's answer not worked for Rails 6.0, I edit some to make it work
# selective_logger.rb
class SelectiveLogger
def initialize app, opts = {}
#app = app
#opts = opts
#opts[:silenced] ||= []
end
def call env
if #opts[:silenced].include?(env['PATH_INFO']) || #opts[:silenced].any? {|silencer| silencer.is_a?( Regexp) && silencer.match( env['PATH_INFO']) }
Rails.logger.silence do
#app.call env
end
else
#app.call env
end
end
end
Test rails app to use it:
# application.rb
config.middleware.swap Rails::Rack::Logger, SelectiveLogger, :silenced => ["/remote/every_minute", %r"^/assets/"]
Sprockets-rails gem starting from version 3.1.0 introduces implementation of quiet assets. Unfortunately it's not flexible at this moment, but can be extended easy enough.
Create config/initializers/custom_quiet_assets.rb file:
class CustomQuietAssets < ::Sprockets::Rails::QuietAssets
def initialize(app)
super
#assets_regex = %r(\A/{0,2}#{quiet_paths})
end
def quiet_paths
[
::Rails.application.config.assets.prefix, # remove if you don't need to quiet assets
'/ping',
].join('|')
end
end
Add it to middleware in config/application.rb:
# NOTE: that config.assets.quiet must be set to false (its default value).
initializer :quiet_assets do |app|
app.middleware.insert_before ::Rails::Rack::Logger, CustomQuietAssets
end
Tested with Rails 4.2
Rails 6. I had to put this in config/application.rb, inside my app's class definition:
require 'silencer/logger'
initializer 'my_app_name.silence_health_check_request_logging' do |app|
app.config.middleware.swap(
Rails::Rack::Logger,
Silencer::Logger,
app.config.log_tags,
silence: %w[/my_health_check_path /my_other_health_check_path],
)
end
That leaves the log_tags config intact and modifies the middleware before it gets frozen. I would like to put it in config/initializers/ somewhere tucked away but haven't figured out how to do that yet.