2013-05-31 06:41:29 +08:00
require 'cache'
2017-03-17 14:21:30 +08:00
require 'open3'
2017-12-22 05:29:11 +08:00
require_dependency 'route_format'
2013-08-23 14:21:52 +08:00
require_dependency 'plugin/instance'
2013-10-09 12:10:37 +08:00
require_dependency 'auth/default_current_user_provider'
2015-04-28 01:06:53 +08:00
require_dependency 'version'
2017-09-09 01:38:46 +08:00
require 'digest/sha1'
2013-05-31 06:41:29 +08:00
2015-07-15 02:52:35 +08:00
# Prevents errors with reloading dev with conditional includes
if Rails . env . development?
require_dependency 'file_store/s3_store'
require_dependency 'file_store/local_store'
end
2013-02-06 03:16:51 +08:00
module Discourse
2014-04-17 13:57:17 +08:00
require 'sidekiq/exception_handler'
2014-02-21 11:30:25 +08:00
class SidekiqExceptionHandler
extend Sidekiq :: ExceptionHandler
end
2017-03-17 14:21:30 +08:00
class Utils
def self . execute_command ( * command , failure_message : " " )
stdout , stderr , status = Open3 . capture3 ( * command )
if ! status . success?
failure_message = " #{ failure_message } \n " if ! failure_message . blank?
raise " #{ failure_message } #{ stderr } "
end
stdout
end
def self . pretty_logs ( logs )
logs . join ( " \n " . freeze )
end
end
2014-07-18 04:22:46 +08:00
# Log an exception.
#
2014-07-18 06:07:25 +08:00
# If your code is in a scheduled job, it is recommended to use the
# error_context() method in Jobs::Base to pass the job arguments and any
# other desired context.
2014-07-18 04:22:46 +08:00
# See app/jobs/base.rb for the error_context function.
2015-02-10 04:47:46 +08:00
def self . handle_job_exception ( ex , context = { } , parent_logger = nil )
2014-02-21 11:30:25 +08:00
context || = { }
parent_logger || = SidekiqExceptionHandler
cm = RailsMultisite :: ConnectionManagement
parent_logger . handle_exception ( ex , {
current_db : cm . current_db ,
current_hostname : cm . current_hostname
} . merge ( context ) )
end
2013-06-19 08:31:19 +08:00
# Expected less matches than what we got in a find
2015-03-23 09:16:21 +08:00
class TooManyMatches < StandardError ; end
2013-06-19 08:31:19 +08:00
2013-02-26 00:42:20 +08:00
# When they try to do something they should be logged in for
2015-03-23 09:16:21 +08:00
class NotLoggedIn < StandardError ; end
2013-02-06 03:16:51 +08:00
# When the input is somehow bad
2015-03-23 09:16:21 +08:00
class InvalidParameters < StandardError ; end
2013-02-06 03:16:51 +08:00
# When they don't have permission to do something
2015-09-18 15:14:10 +08:00
class InvalidAccess < StandardError
2018-02-10 08:09:54 +08:00
attr_reader :obj , :custom_message , :opts
2017-09-23 22:39:58 +08:00
def initialize ( msg = nil , obj = nil , opts = nil )
2015-09-18 15:14:10 +08:00
super ( msg )
2017-09-23 22:39:58 +08:00
2018-02-10 08:09:54 +08:00
@opts = opts || { }
@custom_message = opts [ :custom_message ] if @opts [ :custom_message ]
2015-09-18 15:14:10 +08:00
@obj = obj
end
end
2013-02-06 03:16:51 +08:00
# When something they want is not found
2017-09-27 00:58:15 +08:00
class NotFound < StandardError ; end
2013-02-06 03:16:51 +08:00
2013-06-05 06:34:53 +08:00
# When a setting is missing
2015-03-23 09:16:21 +08:00
class SiteSettingMissing < StandardError ; end
2013-06-05 06:34:53 +08:00
2013-11-06 02:04:47 +08:00
# When ImageMagick is missing
2015-03-23 09:16:21 +08:00
class ImageMagickMissing < StandardError ; end
2013-11-06 02:04:47 +08:00
2014-02-13 12:37:28 +08:00
# When read-only mode is enabled
2015-03-23 09:16:21 +08:00
class ReadOnly < StandardError ; end
2014-02-13 12:37:28 +08:00
2013-07-29 13:13:13 +08:00
# Cross site request forgery
2015-03-23 09:16:21 +08:00
class CSRF < StandardError ; end
2013-07-29 13:13:13 +08:00
2017-08-07 09:43:09 +08:00
class Deprecation < StandardError ; end
2013-12-24 07:50:36 +08:00
def self . filters
2015-07-27 14:46:50 +08:00
@filters || = [ :latest , :unread , :new , :read , :posted , :bookmarks ]
2013-12-24 07:50:36 +08:00
end
def self . anonymous_filters
2015-07-27 14:46:50 +08:00
@anonymous_filters || = [ :latest , :top , :categories ]
2013-12-24 07:50:36 +08:00
end
def self . top_menu_items
2014-01-15 01:48:57 +08:00
@top_menu_items || = Discourse . filters + [ :category , :categories , :top ]
2013-12-24 07:50:36 +08:00
end
def self . anonymous_top_menu_items
2014-01-15 01:48:57 +08:00
@anonymous_top_menu_items || = Discourse . anonymous_filters + [ :category , :categories , :top ]
2013-12-24 07:50:36 +08:00
end
2016-04-06 16:57:59 +08:00
PIXEL_RATIOS || = [ 1 , 1 . 5 , 2 , 3 ]
2015-05-29 15:57:54 +08:00
2015-05-25 23:59:00 +08:00
def self . avatar_sizes
2015-05-29 15:57:54 +08:00
# TODO: should cache these when we get a notification system for site settings
set = Set . new
SiteSetting . avatar_sizes . split ( " | " ) . map ( & :to_i ) . each do | size |
PIXEL_RATIOS . each do | pixel_ratio |
set << size * pixel_ratio
end
end
2015-05-26 13:41:50 +08:00
set
2015-05-25 23:59:00 +08:00
end
2013-08-01 13:59:57 +08:00
def self . activate_plugins!
2015-04-28 01:06:53 +08:00
all_plugins = Plugin :: Instance . find_all ( " #{ Rails . root } /plugins " )
2017-09-09 01:38:46 +08:00
if Rails . env . development?
plugin_hash = Digest :: SHA1 . hexdigest ( all_plugins . map { | p | p . path } . sort . join ( '|' ) )
hash_file = " #{ Rails . root } /tmp/plugin-hash "
2018-03-28 16:20:08 +08:00
old_hash = begin
File . read ( hash_file )
rescue Errno :: ENOENT
end
2017-09-09 01:38:46 +08:00
if old_hash && old_hash != plugin_hash
puts " WARNING: It looks like your discourse plugins have recently changed. "
puts " It is highly recommended to remove your `tmp` directory, otherwise "
puts " plugins might not work. "
puts
else
File . write ( hash_file , plugin_hash )
end
end
2015-04-28 01:06:53 +08:00
@plugins = [ ]
all_plugins . each do | p |
v = p . metadata . required_version || Discourse :: VERSION :: STRING
if Discourse . has_needed_version? ( Discourse :: VERSION :: STRING , v )
p . activate!
@plugins << p
else
STDERR . puts " Could not activate #{ p . metadata . name } , discourse does not meet required version ( #{ v } ) "
end
end
2013-08-01 13:59:57 +08:00
end
2015-02-05 05:23:39 +08:00
def self . disabled_plugin_names
2016-06-30 22:55:01 +08:00
plugins . select { | p | ! p . enabled? } . map ( & :name )
2015-02-05 05:23:39 +08:00
end
2013-08-01 13:59:57 +08:00
def self . plugins
2015-02-11 00:18:16 +08:00
@plugins || = [ ]
2013-08-01 13:59:57 +08:00
end
2018-05-08 13:24:58 +08:00
def self . hidden_plugins
@hidden_plugins || = [ ]
end
2018-05-09 07:52:21 +08:00
def self . visible_plugins
2018-05-08 13:24:58 +08:00
self . plugins - self . hidden_plugins
end
2017-01-13 04:43:09 +08:00
def self . plugin_themes
@plugin_themes || = plugins . map ( & :themes ) . flatten
end
2016-11-15 08:42:55 +08:00
def self . official_plugins
2017-07-28 09:20:09 +08:00
plugins . find_all { | p | p . metadata . official? }
2016-11-15 08:42:55 +08:00
end
def self . unofficial_plugins
2017-07-28 09:20:09 +08:00
plugins . find_all { | p | ! p . metadata . official? }
2016-11-15 08:42:55 +08:00
end
2014-01-15 09:07:42 +08:00
def self . assets_digest
@assets_digest || = begin
digest = Digest :: MD5 . hexdigest ( ActionView :: Base . assets_manifest . assets . values . sort . join )
channel = " /global/asset-version "
2015-05-04 10:21:00 +08:00
message = MessageBus . last_message ( channel )
2014-01-15 09:07:42 +08:00
unless message && message . data == digest
2015-05-04 10:21:00 +08:00
MessageBus . publish channel , digest
2014-01-15 09:07:42 +08:00
end
digest
end
end
2013-08-26 09:04:16 +08:00
def self . authenticators
# TODO: perhaps we don't need auth providers and authenticators maybe one object is enough
# NOTE: this bypasses the site settings and gives a list of everything, we need to register every middleware
# for the cases of multisite
# In future we may change it so we don't include them all for cases where we are not a multisite, but we would
# require a restart after site settings change
Users :: OmniauthCallbacksController :: BUILTIN_AUTH + auth_providers . map ( & :authenticator )
end
2013-08-01 13:59:57 +08:00
def self . auth_providers
2013-08-01 14:05:46 +08:00
providers = [ ]
2015-02-11 00:18:16 +08:00
plugins . each do | p |
next unless p . auth_providers
p . auth_providers . each do | prov |
providers << prov
2013-08-01 13:59:57 +08:00
end
end
providers
end
2013-05-31 06:41:29 +08:00
def self . cache
@cache || = Cache . new
end
2013-02-06 03:16:51 +08:00
# Get the current base URL for the current site
def self . current_hostname
2016-06-30 22:55:01 +08:00
SiteSetting . force_hostname . presence || RailsMultisite :: ConnectionManagement . current_hostname
2013-05-31 06:41:29 +08:00
end
2013-11-06 02:04:47 +08:00
def self . base_uri ( default_value = " " )
2016-06-30 22:55:01 +08:00
ActionController :: Base . config . relative_url_root . presence || default_value
2013-03-14 20:01:52 +08:00
end
2016-07-29 01:54:17 +08:00
def self . base_protocol
SiteSetting . force_https? ? " https " : " http "
end
2013-05-31 06:41:29 +08:00
def self . base_url_no_prefix
2016-07-29 01:54:17 +08:00
default_port = SiteSetting . force_https? ? 443 : 80
url = " #{ base_protocol } :// #{ current_hostname } "
2016-06-30 22:55:01 +08:00
url << " : #{ SiteSetting . port } " if SiteSetting . port . to_i > 0 && SiteSetting . port . to_i != default_port
url
2013-04-05 18:38:20 +08:00
end
2013-05-31 06:41:29 +08:00
def self . base_url
2015-09-22 02:28:20 +08:00
base_url_no_prefix + base_uri
2013-05-31 06:41:29 +08:00
end
2017-07-20 03:08:54 +08:00
def self . route_for ( uri )
2018-03-28 16:20:08 +08:00
unless uri . is_a? ( URI )
uri = begin
URI ( uri )
rescue URI :: InvalidURIError
end
end
2017-07-20 03:08:54 +08:00
return unless uri
path = uri . path || " "
2018-02-14 07:39:44 +08:00
if ! uri . host || ( uri . host == Discourse . current_hostname && path . start_with? ( Discourse . base_uri ) )
2017-07-20 03:08:54 +08:00
path . slice! ( Discourse . base_uri )
return Rails . application . routes . recognize_path ( path )
end
2017-07-21 04:01:16 +08:00
nil
rescue ActionController :: RoutingError
2017-07-20 03:08:54 +08:00
nil
end
2017-01-11 18:03:36 +08:00
READONLY_MODE_KEY_TTL || = 60
READONLY_MODE_KEY || = 'readonly_mode' . freeze
PG_READONLY_MODE_KEY || = 'readonly_mode:postgres' . freeze
2016-06-29 14:19:18 +08:00
USER_READONLY_MODE_KEY || = 'readonly_mode:user' . freeze
2017-01-11 18:03:36 +08:00
READONLY_KEYS || = [
2017-01-11 16:38:07 +08:00
READONLY_MODE_KEY ,
PG_READONLY_MODE_KEY ,
USER_READONLY_MODE_KEY
]
def self . enable_readonly_mode ( key = READONLY_MODE_KEY )
if key == USER_READONLY_MODE_KEY
$redis . set ( key , 1 )
2016-06-29 14:19:18 +08:00
else
2017-01-11 16:38:07 +08:00
$redis . setex ( key , READONLY_MODE_KEY_TTL , 1 )
keep_readonly_mode ( key )
2016-06-29 14:19:18 +08:00
end
2016-06-29 13:55:17 +08:00
2015-05-04 10:21:00 +08:00
MessageBus . publish ( readonly_channel , true )
2013-02-06 03:16:51 +08:00
true
end
2017-01-11 16:38:07 +08:00
def self . keep_readonly_mode ( key )
2015-02-12 04:50:17 +08:00
# extend the expiry by 1 minute every 30 seconds
2016-11-10 23:44:51 +08:00
unless Rails . env . test?
2018-06-19 10:15:29 +08:00
@threads || = { }
active_thread = @threads [ key ]
unless active_thread & . alive?
@threads [ key ] = Thread . new do
while readonly_mode? ( key )
$redis . expire ( key , READONLY_MODE_KEY_TTL )
sleep 30 . seconds
end
2016-11-10 23:44:51 +08:00
end
2015-02-12 04:50:17 +08:00
end
end
end
2017-01-11 16:38:07 +08:00
def self . disable_readonly_mode ( key = READONLY_MODE_KEY )
2016-06-29 14:19:18 +08:00
$redis . del ( key )
2015-05-04 10:21:00 +08:00
MessageBus . publish ( readonly_channel , false )
2013-02-06 03:16:51 +08:00
true
end
2018-06-12 00:21:29 +08:00
def self . readonly_mode? ( keys = READONLY_KEYS )
recently_readonly? || $redis . mget ( * keys ) . compact . present?
2017-01-11 16:38:07 +08:00
end
def self . last_read_only
@last_read_only || = { }
end
def self . recently_readonly?
2017-01-11 18:03:36 +08:00
return false unless read_only = last_read_only [ $redis . namespace ]
2017-01-11 16:38:07 +08:00
read_only > 15 . seconds . ago
end
def self . received_readonly!
last_read_only [ $redis . namespace ] = Time . zone . now
end
def self . clear_readonly!
last_read_only [ $redis . namespace ] = nil
2013-02-06 03:16:51 +08:00
end
2017-08-16 10:38:30 +08:00
def self . request_refresh! ( user_ids : nil )
2014-02-21 13:52:11 +08:00
# Causes refresh on next click for all clients
#
2015-05-04 10:21:00 +08:00
# This is better than `MessageBus.publish "/file-change", ["refresh"]` because
2014-02-21 13:52:11 +08:00
# it spreads the refreshes out over a time period
2017-08-16 10:38:30 +08:00
if user_ids
2017-08-16 12:06:47 +08:00
MessageBus . publish ( " /refresh_client " , 'clobber' , user_ids : user_ids )
2017-08-16 10:38:30 +08:00
else
MessageBus . publish ( '/global/asset-version' , 'clobber' )
end
2014-02-21 13:52:11 +08:00
end
2017-10-04 11:22:23 +08:00
def self . ensure_version_file_loaded
unless @version_file_loaded
version_file = " #{ Rails . root } /config/version.rb "
require version_file if File . exists? ( version_file )
@version_file_loaded = true
end
end
2013-08-03 05:25:57 +08:00
2017-10-04 11:22:23 +08:00
def self . git_version
ensure_version_file_loaded
$git_version || =
begin
git_cmd = 'git rev-parse HEAD'
self . try_git ( git_cmd , Discourse :: VERSION :: STRING )
end
2013-02-18 14:39:54 +08:00
end
2014-09-10 05:04:10 +08:00
def self . git_branch
2017-10-04 11:22:23 +08:00
ensure_version_file_loaded
$git_branch || =
begin
git_cmd = 'git rev-parse --abbrev-ref HEAD'
self . try_git ( git_cmd , 'unknown' )
end
2017-08-29 00:24:56 +08:00
end
def self . full_version
2017-10-04 11:22:23 +08:00
ensure_version_file_loaded
$full_version || =
begin
git_cmd = 'git describe --dirty --match "v[0-9]*"'
self . try_git ( git_cmd , 'unknown' )
end
2017-08-29 00:24:56 +08:00
end
2017-10-04 11:22:23 +08:00
def self . try_git ( git_cmd , default_value )
2017-08-29 00:24:56 +08:00
version_value = false
2014-09-10 05:04:10 +08:00
2017-10-04 11:22:23 +08:00
begin
version_value = ` #{ git_cmd } ` . strip
rescue
version_value = default_value
2014-09-10 05:04:10 +08:00
end
2017-08-29 00:24:56 +08:00
if version_value . empty?
version_value = default_value
end
version_value
2014-09-10 05:04:10 +08:00
end
2013-09-06 15:28:37 +08:00
# Either returns the site_contact_username user or the first admin.
def self . site_contact_user
2014-05-06 21:41:59 +08:00
user = User . find_by ( username_lower : SiteSetting . site_contact_username . downcase ) if SiteSetting . site_contact_username . present?
2015-11-25 03:37:33 +08:00
user || = ( system_user || User . admins . real . order ( :id ) . first )
2013-05-31 06:41:29 +08:00
end
2013-02-06 03:16:51 +08:00
2015-05-07 07:00:13 +08:00
SYSTEM_USER_ID || = - 1
2014-06-25 08:45:20 +08:00
2013-09-06 15:28:37 +08:00
def self . system_user
2016-04-26 05:03:17 +08:00
@system_user || = User . find_by ( id : SYSTEM_USER_ID )
2013-09-06 15:28:37 +08:00
end
2013-08-01 05:26:34 +08:00
def self . store
2017-10-06 13:20:01 +08:00
if SiteSetting . Upload . enable_s3_uploads
2013-08-01 05:26:34 +08:00
@s3_store_loaded || = require 'file_store/s3_store'
2013-11-06 02:04:47 +08:00
FileStore :: S3Store . new
2013-08-01 05:26:34 +08:00
else
@local_store_loaded || = require 'file_store/local_store'
2013-11-06 02:04:47 +08:00
FileStore :: LocalStore . new
2013-08-01 05:26:34 +08:00
end
end
2013-10-09 12:10:37 +08:00
def self . current_user_provider
@current_user_provider || Auth :: DefaultCurrentUserProvider
end
def self . current_user_provider = ( val )
@current_user_provider = val
end
2013-11-06 02:04:47 +08:00
def self . asset_host
Rails . configuration . action_controller . asset_host
end
2014-02-13 12:37:28 +08:00
def self . readonly_channel
2014-02-20 01:21:41 +08:00
" /site/read-only "
2013-02-06 03:16:51 +08:00
end
2014-02-13 12:37:28 +08:00
2014-03-28 10:48:14 +08:00
# all forking servers must call this
# after fork, otherwise Discourse will be
# in a bad state
def self . after_fork
2018-06-14 16:22:02 +08:00
# note: some of this reconnecting may no longer be needed per https://github.com/redis/redis-rb/pull/414
2015-05-04 10:21:00 +08:00
MessageBus . after_fork
2014-03-28 10:48:14 +08:00
SiteSetting . after_fork
2018-04-20 13:01:17 +08:00
$redis . _client . reconnect
2014-03-28 10:48:14 +08:00
Rails . cache . reconnect
2014-05-08 06:05:28 +08:00
Logster . store . redis . reconnect
2014-04-23 09:01:17 +08:00
# shuts down all connections in the pool
2017-07-28 09:20:09 +08:00
Sidekiq . redis_pool . shutdown { | c | nil }
2014-04-23 09:01:17 +08:00
# re-establish
Sidekiq . redis = sidekiq_redis_config
2014-08-11 15:51:55 +08:00
start_connection_reaper
2016-07-16 13:11:34 +08:00
# in case v8 was initialized we want to make sure it is nil
PrettyText . reset_context
2016-11-02 10:34:20 +08:00
2016-11-02 13:59:58 +08:00
Tilt :: ES6ModuleTranspilerTemplate . reset_context if defined? Tilt :: ES6ModuleTranspilerTemplate
2016-11-02 10:34:20 +08:00
JsLocaleHelper . reset_context if defined? JsLocaleHelper
2014-05-08 06:05:28 +08:00
nil
2014-04-23 09:01:17 +08:00
end
2017-12-01 13:23:21 +08:00
# report a warning maintaining backtrack for logster
def self . warn_exception ( e , message : " " , env : nil )
if Rails . logger . respond_to? :add_with_opts
2018-01-05 06:54:28 +08:00
env || = { }
env [ :current_db ] || = RailsMultisite :: ConnectionManagement . current_db
2017-12-01 13:23:21 +08:00
# logster
Rails . logger . add_with_opts (
:: Logger :: Severity :: WARN ,
" #{ message } : #{ e } " ,
" discourse-exception " ,
backtrace : e . backtrace . join ( " \n " ) ,
env : env
)
else
# no logster ... fallback
Rails . logger . warn ( " #{ message } #{ e } " )
end
rescue
STDERR . puts " Failed to report exception #{ e } #{ message } "
end
2015-02-17 06:58:23 +08:00
def self . start_connection_reaper
return if GlobalSetting . connection_reaper_age < 1 ||
GlobalSetting . connection_reaper_interval < 1
2014-08-11 15:51:55 +08:00
# this helps keep connection counts in check
Thread . new do
while true
2015-02-17 06:58:23 +08:00
begin
sleep GlobalSetting . connection_reaper_interval
2018-06-14 16:22:02 +08:00
reap_connections ( GlobalSetting . connection_reaper_age )
2015-02-17 06:58:23 +08:00
rescue = > e
2017-12-01 13:23:21 +08:00
Discourse . warn_exception ( e , message : " Error reaping connections " )
2014-08-11 15:51:55 +08:00
end
end
end
end
2018-06-14 16:22:02 +08:00
def self . reap_connections ( idle )
2015-02-17 06:58:23 +08:00
pools = [ ]
2017-07-28 09:20:09 +08:00
ObjectSpace . each_object ( ActiveRecord :: ConnectionAdapters :: ConnectionPool ) { | pool | pools << pool }
2015-02-17 06:58:23 +08:00
pools . each do | pool |
2018-06-14 13:54:48 +08:00
# reap recovers connections that were aborted
# eg a thread died or a dev forgot to check it in
2018-06-14 16:22:02 +08:00
# flush removes idle connections
# after fork we have "deadpools" so ignore them, they have been discarded
# so @connections is set to nil
if pool . connections
pool . reap
2018-06-14 18:53:24 +08:00
pool . flush ( idle )
2018-06-14 16:22:02 +08:00
end
2015-02-17 06:58:23 +08:00
end
end
2016-12-05 11:46:34 +08:00
SIDEKIQ_NAMESPACE || = 'sidekiq' . freeze
2014-04-23 09:01:17 +08:00
def self . sidekiq_redis_config
2015-06-25 14:51:48 +08:00
conf = GlobalSetting . redis_config . dup
2016-12-05 11:46:34 +08:00
conf [ :namespace ] = SIDEKIQ_NAMESPACE
2015-06-25 14:51:48 +08:00
conf
2014-03-28 10:48:14 +08:00
end
2014-07-29 22:40:02 +08:00
def self . static_doc_topic_ids
[ SiteSetting . tos_topic_id , SiteSetting . guidelines_topic_id , SiteSetting . privacy_topic_id ]
end
2017-02-18 01:09:53 +08:00
cattr_accessor :last_ar_cache_reset
def self . reset_active_record_cache_if_needed ( e )
last_cache_reset = Discourse . last_ar_cache_reset
2017-07-28 09:20:09 +08:00
if e && e . message =~ / UndefinedColumn / && ( last_cache_reset . nil? || last_cache_reset < 30 . seconds . ago )
2018-01-19 05:32:15 +08:00
Rails . logger . warn " Clearing Active Record cache, this can happen if schema changed while site is running or in a multisite various databases are running different schemas. Consider running rake multisite:migrate. "
2017-02-18 01:09:53 +08:00
Discourse . last_ar_cache_reset = Time . zone . now
Discourse . reset_active_record_cache
end
end
def self . reset_active_record_cache
ActiveRecord :: Base . connection . query_cache . clear
2017-08-17 18:27:35 +08:00
( ActiveRecord :: Base . connection . tables - %w[ schema_migrations versions ] ) . each do | table |
2017-02-18 01:09:53 +08:00
table . classify . constantize . reset_column_information rescue nil
end
nil
end
2017-11-16 05:39:11 +08:00
def self . running_in_rack?
ENV [ " DISCOURSE_RUNNING_IN_RACK " ] == " 1 "
end
2013-02-06 03:16:51 +08:00
end