Where should I configure rails gems? - ruby-on-rails

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

Related

Previewing Mailers on non-development tiers

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.

Best way to manage set config variables in gem?

Can any one tell me the best practice for initializing config variables and read that variables in gems?
Have tried with the following steps:
This code is written in gem
config = YAML.load_file("#{RAILS_ROOT}/config/config.yml")
#key = config["config"]["key"]
server = config["config"]["server"]
and created yml file in config/config.yml in rails application.
Thanks in advance,
Jagdish
I did it once like following:
module YourGem
class YourClass
#config = { :username => "foo", :password => "bar" } # or #config = SomeHelperClass.default_config if the config is more complex
#valid_config_keys = #config.keys
# Configure through hash
def self.configure(opts = {})
opts.each { |k,v| #config[k.to_sym] = v if #valid_config_keys.include? k.to_sym }
end
# Configure through yaml file
def self.configure_with(path_to_yaml_file)
begin
config = YAML::load(IO.read(path_to_yaml_file))
rescue => e
raise "YAML configuration file couldn't be found: #{e}"
end
configure(config)
end
end
end
And in your Rails Application, where you require your gem, you can add an initializer and configure like following:
config/initializers/your_initializer.rb
YourGem::YourClass.configure_with(path_to_the_yml_config_file)
This solution provides a default config, and the possibility to add a own yaml file to change the default values.
I've found my favourite way to set config variables in rails is using the figaro gem. Figaro basically makes use of the ENV['x'] method that is available throughout rails. It stores all your config variables inside a common application.yml file and makes all the constants accessible via the ENV variable.
The bonus is that this translates 1 to 1 with the way Heroku does things as well.

How to blacklist directory loading in Rails?

I want to disable ActiveAdmin when running the tests.
So I add require: false to the Gemfile and checking if defined?(ActiveAdmin) in routes and initializer.
But Rails still loads the models form app/admin thus I am getting error similar to /app/admin/admin_user.rb:1:in': uninitialized constant ActiveAdmin (NameError)`
What is the best way to "blacklist" the app/admin directory from being loaded?
# config/environments/test.rb
path_rejector = lambda { |s| s.include?("app/admin") }
# Remove the path from being loaded when Rails starts:
config.eager_load_paths = config.eager_load_paths.reject(&path_rejector)
# Remove the path from being lazily loaded
ActiveSupport::Dependencies.autoload_paths.reject!(&path_rejector)
In config/application.rb add:
config.paths.delete("app/admin")
new_eager_load_paths = config.eager_load_paths.dup
new_eager_load_paths.delete("/mnt/hgfs/project/app/admin")
config.eager_load_paths = new_eager_load_paths
You have to replace "/mnt/hgfs/project/eventmust/app/admin" by your path, it's not very clean but it's work.

Refinery CMS + devise_cas_authenticatable

I am trying to figure out how to add CAS authentication to Refinery. The best arrangement I have found so far is to use devise_cas_authenticatable.
I used rake refinery:override model=user and replaced :database_authenticatable:
# /app/models/refinery/user.rb
if self.respond_to?(:devise)
devise :cas_authenticatable, ...
end
But I can't find where to set the CAS configuration values, e.g.:
Devise.setup do |config|
...
config.cas_base_url = "https://cas.myorganization.com"
...
end
Does anyone know if there is an existing initializer where this belongs? Also, any thoughts on the task of making Refinery work with CAS would be helpful. Thanks!
Set the CAS configuration values in a Rails initializer, e.g.
$ cat config/initializers/devise.rb
Devise.setup do |config|
config.cas_base_url = "https://cas.myorganization.com"
# you can override these if you need to, but cas_base_url is usually enough
# config.cas_login_url = "https://cas.myorganization.com/login"
# config.cas_logout_url = "https://cas.myorganization.com/logout"
# config.cas_validate_url = "https://cas.myorganization.com/serviceValidate"
# The CAS specification allows for the passing of a follow URL to be displayed when
# a user logs out on the CAS server. RubyCAS-Server also supports redirecting to a
# URL via the destination param. Set either of these urls and specify either nil,
# 'destination' or 'follow' as the logout_url_param. If the urls are blank but
# logout_url_param is set, a default will be detected for the service.
# config.cas_destination_url = 'https://cas.myorganization.com'
# config.cas_follow_url = 'https://cas.myorganization.com'
# config.cas_logout_url_param = nil
# By default, devise_cas_authenticatable will create users. If you would rather
# require user records to already exist locally before they can authenticate via
# CAS, uncomment the following line.
# config.cas_create_user = false
end

How can I disable logging in Ruby on Rails on a per-action basis?

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.

Resources