thredded 0.14.4 → 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -2
- data/app/assets/javascripts/thredded/components/preview_area.es6 +3 -0
- data/app/assets/javascripts/thredded/components/spoilers.es6 +37 -0
- data/app/assets/stylesheets/thredded/_email.scss +25 -0
- data/app/assets/stylesheets/thredded/_thredded.scss +1 -0
- data/app/assets/stylesheets/thredded/base/_typography.scss +1 -1
- data/app/assets/stylesheets/thredded/base/_variables.scss +2 -3
- data/app/assets/stylesheets/thredded/components/_post.scss +0 -11
- data/app/assets/stylesheets/thredded/components/_spoiler.scss +41 -0
- data/app/commands/thredded/mark_all_read.rb +1 -7
- data/app/controllers/concerns/thredded/new_post_params.rb +1 -1
- data/app/controllers/concerns/thredded/new_private_post_params.rb +1 -1
- data/app/controllers/concerns/thredded/new_private_topic_params.rb +1 -2
- data/app/controllers/concerns/thredded/new_topic_params.rb +0 -1
- data/app/controllers/thredded/application_controller.rb +10 -0
- data/app/controllers/thredded/posts_controller.rb +1 -2
- data/app/controllers/thredded/private_posts_controller.rb +1 -2
- data/app/controllers/thredded/private_topics_controller.rb +10 -12
- data/app/controllers/thredded/topics_controller.rb +14 -17
- data/app/forms/thredded/edit_topic_form.rb +2 -1
- data/app/forms/thredded/post_form.rb +3 -1
- data/app/forms/thredded/private_post_form.rb +3 -1
- data/app/forms/thredded/private_topic_form.rb +1 -0
- data/app/jobs/thredded/activity_updater_job.rb +18 -8
- data/app/jobs/thredded/auto_follow_and_notify_job.rb +2 -1
- data/app/models/concerns/thredded/post_common.rb +3 -4
- data/app/models/concerns/thredded/topic_common.rb +7 -0
- data/app/models/concerns/thredded/user_topic_read_state_common.rb +62 -5
- data/app/models/thredded/null_user_topic_read_state.rb +8 -0
- data/app/policies/thredded/private_post_policy.rb +16 -0
- data/app/view_hooks/thredded/all_view_hooks.rb +3 -0
- data/app/view_models/thredded/base_topic_view.rb +5 -1
- data/app/view_models/thredded/post_view.rb +13 -1
- data/app/view_models/thredded/posts_page_view.rb +1 -1
- data/app/view_models/thredded/topic_posts_page_view.rb +13 -1
- data/app/views/thredded/posts/_post.html.erb +1 -0
- data/app/views/thredded/posts/edit.html.erb +1 -0
- data/app/views/thredded/posts_common/_before_first_unread_post.html.erb +7 -0
- data/app/views/thredded/posts_common/form/_after_content.html.erb +8 -0
- data/app/views/thredded/posts_common/form/_before_content.html.erb +8 -0
- data/app/views/thredded/private_posts/_private_post.html.erb +2 -1
- data/app/views/thredded/private_posts/edit.html.erb +1 -0
- data/app/views/thredded/private_topics/_form.html.erb +1 -0
- data/app/views/thredded/private_topics/edit.html.erb +2 -1
- data/app/views/thredded/shared/_field_errors.html.erb +3 -0
- data/app/views/thredded/shared/_nav.html.erb +1 -1
- data/app/views/thredded/shared/_page.html.erb +1 -1
- data/app/views/thredded/topics/_form.html.erb +1 -0
- data/app/views/thredded/topics/edit.html.erb +2 -1
- data/config/locales/de.yml +2 -0
- data/config/locales/en.yml +2 -0
- data/config/locales/es.yml +2 -0
- data/config/locales/fr.yml +2 -0
- data/config/locales/it.yml +2 -0
- data/config/locales/pl.yml +2 -0
- data/config/locales/pt-BR.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/locales/zh-CN.yml +2 -0
- data/db/migrate/20160329231848_create_thredded.rb +37 -23
- data/db/upgrade_migrations/{20170811090735_upgrade_thredded_v0_13_to_v_014.rb → 20170811090735_upgrade_thredded_v0_13_to_v0_14.rb} +0 -0
- data/db/upgrade_migrations/20180110200009_upgrade_thredded_v0_14_to_v0_15.rb +91 -0
- data/lib/generators/thredded/install/templates/initializer.rb +16 -7
- data/lib/thredded.rb +143 -125
- data/lib/thredded/arel_compat.rb +57 -0
- data/lib/thredded/base_migration.rb +10 -0
- data/lib/thredded/collection_to_strings_with_cache_renderer.rb +35 -9
- data/lib/thredded/content_formatter.rb +27 -18
- data/lib/thredded/database_seeder.rb +218 -64
- data/lib/thredded/email_transformer.rb +5 -2
- data/lib/thredded/email_transformer/spoiler.rb +25 -0
- data/lib/thredded/formatting_demo_content.rb +12 -0
- data/lib/thredded/html_pipeline/onebox_filter.rb +3 -38
- data/lib/thredded/html_pipeline/spoiler_tag_filter.rb +128 -0
- data/lib/thredded/html_pipeline/utils.rb +47 -0
- data/lib/thredded/rails_lt_5_2_arel_case_node.rb +119 -0
- data/lib/thredded/version.rb +1 -1
- metadata +17 -21
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
|
4
|
+
require 'thredded/rails_lt_5_2_arel_case_node.rb'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Thredded
|
8
|
+
module ArelCompat
|
9
|
+
module_function
|
10
|
+
|
11
|
+
# @param [#connection] engine
|
12
|
+
# @param [Arel::Nodes::Node] a integer node
|
13
|
+
# @param [Arel::Nodes::Node] b integer node
|
14
|
+
# @return [Arel::Nodes::Node] a / b
|
15
|
+
def integer_division(engine, a, b)
|
16
|
+
if engine.connection.adapter_name =~ /mysql|mariadb/i
|
17
|
+
Arel::Nodes::InfixOperation.new('DIV', a, b)
|
18
|
+
else
|
19
|
+
Arel::Nodes::Division.new(a, b)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
|
24
|
+
# @param [ActiveRecord::Relation] relation
|
25
|
+
# @return [Arel::Nodes::Node]
|
26
|
+
def relation_to_arel(relation)
|
27
|
+
relation.arel
|
28
|
+
end
|
29
|
+
else
|
30
|
+
def relation_to_arel(relation)
|
31
|
+
Arel.sql("(#{relation.to_sql})")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
if Rails::VERSION::MAJOR >= 5
|
36
|
+
# @param [Arel::Nodes::Node] table
|
37
|
+
# @return [Arel::SelectManager]
|
38
|
+
def new_arel_select_manager(table)
|
39
|
+
Arel::SelectManager.new(table)
|
40
|
+
end
|
41
|
+
else
|
42
|
+
def new_arel_select_manager(table)
|
43
|
+
Arel::SelectManager.new(ActiveRecord::Base, table)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2 || Rails::VERSION::MAJOR > 5
|
48
|
+
def true_value(_engine)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
else
|
52
|
+
def true_value(engine)
|
53
|
+
engine.connection.adapter_name =~ /sqlite|mysql|mariadb/i ? 1 : true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module Thredded
|
4
4
|
class BaseMigration < (Thredded.rails_gte_51? ? ActiveRecord::Migration[5.1] : ActiveRecord::Migration)
|
5
|
+
protected
|
6
|
+
|
5
7
|
def user_id_type
|
6
8
|
Thredded.user_class.columns.find { |c| c.name == Thredded.user_class.primary_key }.sql_type
|
7
9
|
end
|
@@ -10,5 +12,13 @@ module Thredded
|
|
10
12
|
column_name = column_name.to_s
|
11
13
|
columns(table).find { |c| c.name == column_name }.sql_type
|
12
14
|
end
|
15
|
+
|
16
|
+
# @return [Integer, nil] the maximum number of codepoints that can be indexed for a primary key or index.
|
17
|
+
def max_key_length
|
18
|
+
return nil unless connection.adapter_name =~ /mysql|maria/i
|
19
|
+
# Conservatively assume that innodb_large_prefix is **disabled**.
|
20
|
+
# If it were enabled, the maximum key length would instead be 768 utf8mb4 characters.
|
21
|
+
191
|
22
|
+
end
|
13
23
|
end
|
14
24
|
end
|
@@ -1,15 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'action_view/renderer/abstract_renderer'
|
4
|
+
|
4
5
|
module Thredded
|
5
6
|
class CollectionToStringsWithCacheRenderer < ActionView::AbstractRenderer
|
7
|
+
class << self
|
8
|
+
# The default number of threads to use for rendering.
|
9
|
+
attr_accessor :render_threads
|
10
|
+
end
|
11
|
+
|
12
|
+
self.render_threads = 50
|
13
|
+
|
6
14
|
# @param view_context
|
7
|
-
# @param
|
8
|
-
# @param
|
9
|
-
# @param
|
15
|
+
# @param [Array<T>] collection
|
16
|
+
# @param [String] partial
|
17
|
+
# @param [ActiveSupport::Duration] expires_in
|
18
|
+
# @param [Integer] render_threads the number of threads to use for rendering. This is useful even on MRI ruby
|
19
|
+
# for IO-bound operations.
|
20
|
+
# @param [Hash] locals
|
10
21
|
# @return Array<[T, String]>
|
11
22
|
def render_collection_to_strings_with_cache( # rubocop:disable Metrics/ParameterLists
|
12
|
-
view_context, collection:, partial:, expires_in:, locals: {}, **opts
|
23
|
+
view_context, collection:, partial:, expires_in:, render_threads: self.class.render_threads, locals: {}, **opts
|
13
24
|
)
|
14
25
|
template = @lookup_context.find_template(partial, [], true, locals, {})
|
15
26
|
collection = collection.to_a
|
@@ -28,7 +39,9 @@ module Thredded
|
|
28
39
|
|
29
40
|
collection_to_render = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
|
30
41
|
rendered_partials = render_partials(
|
31
|
-
view_context,
|
42
|
+
view_context,
|
43
|
+
collection: collection_to_render, render_threads: render_threads,
|
44
|
+
partial: partial, locals: locals, **opts
|
32
45
|
).each
|
33
46
|
|
34
47
|
keyed_collection.map do |cache_key, item|
|
@@ -52,12 +65,25 @@ module Thredded
|
|
52
65
|
end
|
53
66
|
|
54
67
|
# @return [Array<String>]
|
55
|
-
def render_partials(view_context, collection:, **opts)
|
68
|
+
def render_partials(view_context, collection:, render_threads:, **opts)
|
56
69
|
return [] if collection.empty?
|
57
|
-
|
58
|
-
|
59
|
-
|
70
|
+
num_threads = [render_threads, collection.size].min
|
71
|
+
if num_threads == 1
|
72
|
+
render_partials_serial(view_context, collection, opts)
|
73
|
+
else
|
74
|
+
collection.each_slice(collection.size / num_threads).map do |slice|
|
75
|
+
Thread.start { render_partials_serial(view_context.dup, slice, opts) }
|
76
|
+
end.flat_map(&:value)
|
60
77
|
end
|
61
78
|
end
|
79
|
+
|
80
|
+
# @param [Array<Object>] collection
|
81
|
+
# @param [Hash] opts
|
82
|
+
# @param view_context
|
83
|
+
# @return [Array<String>]
|
84
|
+
def render_partials_serial(view_context, collection, opts)
|
85
|
+
partial_renderer = ActionView::PartialRenderer.new(@lookup_context)
|
86
|
+
collection.map { |object| partial_renderer.render(view_context, opts.merge(object: object), nil) }
|
87
|
+
end
|
62
88
|
end
|
63
89
|
end
|
@@ -3,8 +3,30 @@
|
|
3
3
|
module Thredded
|
4
4
|
# Generates HTML from content source.
|
5
5
|
class ContentFormatter
|
6
|
-
|
7
|
-
|
6
|
+
class << self
|
7
|
+
# Sanitization whitelist options.
|
8
|
+
attr_accessor :whitelist
|
9
|
+
|
10
|
+
# Filters that run before processing the markup.
|
11
|
+
# input: markup, output: markup.
|
12
|
+
attr_accessor :before_markup_filters
|
13
|
+
|
14
|
+
# Markup filters, such as BBCode, Markdown, Autolink, etc.
|
15
|
+
# input: markup, output: html.
|
16
|
+
attr_accessor :markup_filters
|
17
|
+
|
18
|
+
# Filters that run after processing the markup.
|
19
|
+
# input: html, output: html.
|
20
|
+
attr_accessor :after_markup_filters
|
21
|
+
|
22
|
+
# Filters that sanitize the resulting HTML.
|
23
|
+
# input: html, output: sanitized html.
|
24
|
+
attr_accessor :sanitization_filters
|
25
|
+
|
26
|
+
# Filters that run after sanitization
|
27
|
+
# input: sanitized html, output: html
|
28
|
+
attr_accessor :after_sanitization_filters
|
29
|
+
end
|
8
30
|
|
9
31
|
self.whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST.deep_merge(
|
10
32
|
elements: HTML::Pipeline::SanitizationFilter::WHITELIST[:elements] + %w[abbr iframe span figure figcaption],
|
@@ -25,44 +47,31 @@ module Thredded
|
|
25
47
|
'span' => %w[class],
|
26
48
|
'div' => %w[class],
|
27
49
|
:all => HTML::Pipeline::SanitizationFilter::WHITELIST[:attributes][:all] +
|
28
|
-
%w[aria-label aria-labelledby aria-hidden],
|
50
|
+
%w[aria-expanded aria-label aria-labelledby aria-live aria-hidden aria-pressed role],
|
29
51
|
}
|
30
52
|
)
|
31
53
|
|
32
|
-
# Filters that run before processing the markup.
|
33
|
-
# input: markup, output: markup.
|
34
|
-
mattr_accessor :before_markup_filters
|
35
54
|
self.before_markup_filters = [
|
55
|
+
Thredded::HtmlPipeline::SpoilerTagFilter::BeforeMarkup
|
36
56
|
]
|
37
57
|
|
38
|
-
# Markup filters, such as BBCode, Markdown, Autolink, etc.
|
39
|
-
# input: markup, output: html.
|
40
|
-
mattr_accessor :markup_filters
|
41
58
|
self.markup_filters = [
|
42
59
|
Thredded::HtmlPipeline::KramdownFilter,
|
43
60
|
]
|
44
61
|
|
45
|
-
# Filters that run after processing the markup.
|
46
|
-
# input: html, output: html.
|
47
|
-
mattr_accessor :after_markup_filters
|
48
62
|
self.after_markup_filters = [
|
49
63
|
# AutolinkFilter is required because Kramdown does not autolink by default.
|
50
64
|
# https://github.com/gettalong/kramdown/issues/306
|
51
65
|
Thredded::HtmlPipeline::AutolinkFilter,
|
52
66
|
HTML::Pipeline::EmojiFilter,
|
53
67
|
Thredded::HtmlPipeline::AtMentionFilter,
|
68
|
+
Thredded::HtmlPipeline::SpoilerTagFilter::AfterMarkup,
|
54
69
|
]
|
55
70
|
|
56
|
-
# Filters that sanitize the resulting HTML.
|
57
|
-
# input: html, output: sanitized html.
|
58
|
-
mattr_accessor :sanitization_filters
|
59
71
|
self.sanitization_filters = [
|
60
72
|
HTML::Pipeline::SanitizationFilter,
|
61
73
|
]
|
62
74
|
|
63
|
-
# Filters that run after sanitization
|
64
|
-
# input: sanitized html, output: html
|
65
|
-
mattr_accessor :after_sanitization_filters
|
66
75
|
self.after_sanitization_filters = [
|
67
76
|
Thredded::HtmlPipeline::OneboxFilter,
|
68
77
|
Thredded::HtmlPipeline::WrapIframesFilter,
|
@@ -12,40 +12,128 @@ rescue NameError
|
|
12
12
|
end
|
13
13
|
# rubocop:enable HandleExceptions
|
14
14
|
module Thredded
|
15
|
-
class DatabaseSeeder
|
15
|
+
class DatabaseSeeder # rubocop:disable Metrics/ClassLength
|
16
|
+
module LogTime
|
17
|
+
def self.included(base)
|
18
|
+
base.extend ClassMethods
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_time
|
22
|
+
start = Time.now.to_f
|
23
|
+
result = yield
|
24
|
+
print_time_diff start
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_time_diff(from, to = Time.now.to_f)
|
29
|
+
log " [#{format('%.2f', to - from)}s]\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def log_method_time(method_name)
|
34
|
+
prepend(Module.new do
|
35
|
+
define_method method_name do |*args, **kwargs|
|
36
|
+
log_time { super(*args, **kwargs) }
|
37
|
+
end
|
38
|
+
end)
|
39
|
+
method_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
include LogTime
|
45
|
+
|
16
46
|
SKIP_CALLBACKS = [
|
17
|
-
[Thredded::Post, :commit, :after, :
|
18
|
-
[Thredded::
|
47
|
+
[Thredded::Post, :commit, :after, :update_parent_last_user_and_time_from_last_post, on: %i[create destroy]],
|
48
|
+
[Thredded::Post, :commit, :after, :update_parent_last_user_and_time_from_last_post_if_moderation_state_changed,
|
49
|
+
on: :update],
|
50
|
+
[Thredded::Post, :commit, :after, :auto_follow_and_notify, on: %i[create update]],
|
51
|
+
[Thredded::PrivatePost, :commit, :after, :update_parent_last_user_and_timestamp, on: %i[create destroy]],
|
52
|
+
[Thredded::PrivatePost, :commit, :after, :notify_users, on: [:create]],
|
53
|
+
].freeze
|
54
|
+
DISABLE_COUNTER_CACHE = [Thredded::Post, Thredded::PrivatePost].freeze
|
55
|
+
WRITEABLE_READONLY_ATTRIBUTES = [
|
56
|
+
[Thredded::Topic, 'posts_count'],
|
57
|
+
[Thredded::PrivateTopic, 'posts_count'],
|
19
58
|
].freeze
|
20
59
|
|
21
|
-
|
22
|
-
|
60
|
+
# Applies global tweaks required to run seeder methods for the given block.
|
61
|
+
def self.with_seeder_tweaks
|
23
62
|
# Disable callbacks to avoid creating notifications and performing unnecessary updates
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
s.seed(users: users, topics: topics, posts: posts)
|
28
|
-
s.log 'Running after_commit callbacks'
|
63
|
+
DISABLE_COUNTER_CACHE.each do |klass|
|
64
|
+
klass.send(:alias_method, :original_each_counter_cached_associations, :each_counter_cached_associations)
|
65
|
+
klass.send(:define_method, :each_counter_cached_associations) {}
|
29
66
|
end
|
67
|
+
SKIP_CALLBACKS.each { |(klass, *args)| delete_callbacks(klass, *args) }
|
68
|
+
WRITEABLE_READONLY_ATTRIBUTES.each { |(klass, attr)| klass.readonly_attributes.delete(attr) }
|
69
|
+
logger_was = ActiveRecord::Base.logger
|
70
|
+
ActiveRecord::Base.logger = nil
|
71
|
+
yield
|
30
72
|
ensure
|
31
|
-
# Re-enable callbacks
|
32
|
-
|
73
|
+
# Re-enable callbacks and counter cache
|
74
|
+
DISABLE_COUNTER_CACHE.each do |klass|
|
75
|
+
klass.send(:remove_method, :each_counter_cached_associations)
|
76
|
+
klass.send(:alias_method, :each_counter_cached_associations, :original_each_counter_cached_associations)
|
77
|
+
klass.send(:remove_method, :original_each_counter_cached_associations)
|
78
|
+
end
|
79
|
+
SKIP_CALLBACKS.each do |(klass, *args)|
|
80
|
+
args = args.dup
|
81
|
+
klass.send(:set_options_for_callbacks!, args)
|
82
|
+
klass.set_callback(*args)
|
83
|
+
end
|
84
|
+
WRITEABLE_READONLY_ATTRIBUTES.each { |(klass, attr)| klass.readonly_attributes << attr }
|
85
|
+
ActiveRecord::Base.logger = logger_was
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.delete_callbacks(klass, name, *filter_list, &block)
|
89
|
+
type, filters, _options = klass.normalize_callback_params(filter_list, block)
|
90
|
+
klass.__update_callbacks(name) do |target, chain|
|
91
|
+
filters.each do |filter|
|
92
|
+
chain.delete(chain.find { |c| c.matches?(type, filter) })
|
93
|
+
end
|
94
|
+
target.send :set_callbacks, name, chain
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.run(**kwargs)
|
99
|
+
new.run(**kwargs)
|
33
100
|
end
|
34
101
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
102
|
+
def run(users: 200, topics: 70, posts: (1..70))
|
103
|
+
log "Seeding the database...\n"
|
104
|
+
self.class.with_seeder_tweaks do
|
105
|
+
t_txn_0 = nil
|
106
|
+
Messageboard.transaction do
|
107
|
+
initialize_fake_post_contents(topics: topics, posts: posts)
|
108
|
+
users(count: users)
|
109
|
+
first_messageboard
|
110
|
+
topics(count: topics)
|
111
|
+
private_topics(count: topics)
|
112
|
+
posts(count: posts)
|
113
|
+
private_posts(count: posts)
|
114
|
+
create_additional_messageboards
|
115
|
+
follow_some_topics
|
116
|
+
read_some_topics(count: (topics / 4..topics / 3))
|
117
|
+
update_messageboards_data
|
118
|
+
t_txn_0 = Time.now.to_f
|
119
|
+
log 'Committing transaction and running after_commit callbacks'
|
120
|
+
end
|
121
|
+
print_time_diff t_txn_0
|
122
|
+
end
|
45
123
|
end
|
46
124
|
|
47
125
|
def log(message)
|
48
|
-
STDERR.
|
126
|
+
STDERR.write "- #{message}"
|
127
|
+
STDERR.flush
|
128
|
+
end
|
129
|
+
|
130
|
+
log_method_time def initialize_fake_post_contents(topics:, posts:)
|
131
|
+
log 'Initializing fake post contents...'
|
132
|
+
@fake_post_contents = Array.new([topics * (posts.min + posts.max) / 2, 1000].min) { FakeContent.post_content }
|
133
|
+
end
|
134
|
+
|
135
|
+
def fake_post_contents
|
136
|
+
@fake_post_contents ? @fake_post_contents.sample : FakeContent.post_content
|
49
137
|
end
|
50
138
|
|
51
139
|
def first_user
|
@@ -56,6 +144,12 @@ module Thredded
|
|
56
144
|
@users ||= Users.new(self).find_or_create(count: count)
|
57
145
|
end
|
58
146
|
|
147
|
+
def user_details
|
148
|
+
@user_details ||= users.each_with_object({}) do |user, hash|
|
149
|
+
hash[user] = user.thredded_user_detail
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
59
153
|
def first_messageboard
|
60
154
|
@first_messageboard ||= FirstMessageboard.new(self).find_or_create
|
61
155
|
end
|
@@ -68,10 +162,19 @@ module Thredded
|
|
68
162
|
'Need help using the forum? Want to report a bug or make a suggestion? This is the place.', meta_group_id],
|
69
163
|
['Praise', 'Want to tell us how great we are? This is the place.', meta_group_id]
|
70
164
|
]
|
71
|
-
log "Creating #{additional_messageboards.length} additional messageboards
|
165
|
+
log "Creating #{additional_messageboards.length} additional messageboards...\n"
|
72
166
|
additional_messageboards.each do |(name, description, group_id)|
|
73
167
|
messageboard = Messageboard.create!(name: name, description: description, messageboard_group_id: group_id)
|
74
|
-
|
168
|
+
topics = Topics.new(self).create(count: 1 + rand(3), messageboard: messageboard)
|
169
|
+
Posts.new(self).create(count: (1..2), topics: topics)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
log_method_time def update_messageboards_data(**) # `**` for Ruby < 2.5, see https://bugs.ruby-lang.org/issues/10856
|
174
|
+
log 'Updating messageboards data...'
|
175
|
+
Messageboard.all.each do |messageboard|
|
176
|
+
messageboard.update_last_topic!
|
177
|
+
Thredded::Messageboard.reset_counters(messageboard.id, :posts)
|
75
178
|
end
|
76
179
|
end
|
77
180
|
|
@@ -91,10 +194,12 @@ module Thredded
|
|
91
194
|
@private_posts ||= PrivatePosts.new(self).find_or_create(count: count)
|
92
195
|
end
|
93
196
|
|
94
|
-
def follow_some_topics(count: (5..10), count_users: (1..5))
|
197
|
+
log_method_time def follow_some_topics(count: (5..10), count_users: (1..5))
|
95
198
|
log 'Following some topics...'
|
96
199
|
posts.each do |post|
|
97
|
-
|
200
|
+
next unless post.user_id
|
201
|
+
Thredded::UserTopicFollow.create_with(reason: :posted)
|
202
|
+
.find_or_create_by(user_id: post.user_id, topic_id: post.postable_id)
|
98
203
|
end
|
99
204
|
follow_some_topics_by_user(first_user, count: count)
|
100
205
|
users.sample(count_users.min + rand(count_users.max - count_users.min + 2)).each do |user|
|
@@ -104,11 +209,11 @@ module Thredded
|
|
104
209
|
|
105
210
|
def follow_some_topics_by_user(user, count: (1..10))
|
106
211
|
topics.sample(count.min + rand(count.max - count.min + 2)).each do |topic|
|
107
|
-
Thredded::UserTopicFollow.
|
212
|
+
Thredded::UserTopicFollow.create_with(reason: :manual).find_or_create_by(user_id: user.id, topic_id: topic.id)
|
108
213
|
end
|
109
214
|
end
|
110
215
|
|
111
|
-
def read_some_topics(count: (5..10), count_users: (1..5))
|
216
|
+
log_method_time def read_some_topics(count: (5..10), count_users: (1..5))
|
112
217
|
log 'Reading some topics...'
|
113
218
|
topics.each do |topic|
|
114
219
|
read_topic(topic, topic.last_user_id) if topic.last_user_id
|
@@ -126,11 +231,18 @@ module Thredded
|
|
126
231
|
end
|
127
232
|
|
128
233
|
def read_topic(topic, user_id)
|
129
|
-
Thredded::UserTopicReadState.find_or_initialize_by(user_id: user_id, postable_id: topic.id)
|
130
|
-
|
234
|
+
read_state = Thredded::UserTopicReadState.find_or_initialize_by(user_id: user_id, postable_id: topic.id)
|
235
|
+
if rand(2).zero?
|
236
|
+
read_state.update!(read_at: topic.updated_at)
|
237
|
+
else
|
238
|
+
read_state.update!(read_at: topic.posts.order_newest_first.first(2).last.created_at)
|
239
|
+
end
|
131
240
|
end
|
132
241
|
|
133
242
|
class BaseSeedData
|
243
|
+
include LogTime
|
244
|
+
|
245
|
+
# @return [Thredded::DatabaseSeeder]
|
134
246
|
attr_reader :seeder
|
135
247
|
|
136
248
|
def initialize(seed_database = DatabaseSeeder.new)
|
@@ -149,6 +261,11 @@ module Thredded
|
|
149
261
|
@stored = (find || create(*args))
|
150
262
|
end
|
151
263
|
|
264
|
+
def range_of_dates_in_order(up_to: Time.zone.now, count: 1)
|
265
|
+
written = up_to
|
266
|
+
Array.new(count - 1) { written -= random_duration(10.minutes..6.hours) }.reverse + [up_to]
|
267
|
+
end
|
268
|
+
|
152
269
|
protected
|
153
270
|
|
154
271
|
def model_class
|
@@ -164,6 +281,10 @@ module Thredded
|
|
164
281
|
def find
|
165
282
|
fail 'Unimplemented'
|
166
283
|
end
|
284
|
+
|
285
|
+
def random_duration(range)
|
286
|
+
(range.min.to_i + rand(range.max.to_i)).seconds
|
287
|
+
end
|
167
288
|
end
|
168
289
|
|
169
290
|
class FirstSeedData < BaseSeedData
|
@@ -183,7 +304,6 @@ module Thredded
|
|
183
304
|
MODEL_CLASS = User
|
184
305
|
|
185
306
|
def create
|
186
|
-
log 'Creating first user...'
|
187
307
|
FactoryBot.create(:user, :approved, :admin, name: 'Joe', email: 'joe@example.com')
|
188
308
|
end
|
189
309
|
end
|
@@ -192,19 +312,19 @@ module Thredded
|
|
192
312
|
class Users < CollectionSeedData
|
193
313
|
MODEL_CLASS = User
|
194
314
|
|
195
|
-
def create(count: 1)
|
315
|
+
log_method_time def create(count: 1)
|
196
316
|
log "Creating #{count} users..."
|
197
317
|
approved_users_count = (count * 0.97).round
|
198
318
|
[seeder.first_user] +
|
199
|
-
|
200
|
-
|
319
|
+
FactoryBot.create_list(:user, approved_users_count, :approved, :with_user_details) +
|
320
|
+
FactoryBot.create_list(:user, count - approved_users_count, :with_user_details)
|
201
321
|
end
|
202
322
|
end
|
203
323
|
|
204
324
|
class FirstMessageboard < FirstSeedData
|
205
325
|
MODEL_CLASS = Messageboard
|
206
326
|
|
207
|
-
def create
|
327
|
+
log_method_time def create(**) # `**` for Ruby < 2.5, see https://bugs.ruby-lang.org/issues/10856
|
208
328
|
log 'Creating a messageboard...'
|
209
329
|
@first_messageboard = FactoryBot.create(
|
210
330
|
:messageboard,
|
@@ -218,28 +338,39 @@ module Thredded
|
|
218
338
|
class Topics < CollectionSeedData
|
219
339
|
MODEL_CLASS = Topic
|
220
340
|
|
221
|
-
def create(count: 1, messageboard: seeder.first_messageboard)
|
341
|
+
log_method_time def create(count: 1, messageboard: seeder.first_messageboard)
|
222
342
|
log "Creating #{count} topics in #{messageboard.name}..."
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
343
|
+
Array.new(count) do
|
344
|
+
FactoryBot.build(
|
345
|
+
:topic,
|
346
|
+
messageboard: messageboard,
|
347
|
+
user: seeder.users.sample,
|
348
|
+
last_user: seeder.users.sample
|
349
|
+
).tap do |topic|
|
350
|
+
topic.user_detail = seeder.user_details[topic.user]
|
351
|
+
topic.send :set_slug
|
352
|
+
topic.send :set_default_moderation_state
|
353
|
+
topic.save(validate: false)
|
354
|
+
end
|
355
|
+
end
|
229
356
|
end
|
230
357
|
end
|
231
358
|
|
232
359
|
class PrivateTopics < CollectionSeedData
|
233
360
|
MODEL_CLASS = PrivateTopic
|
234
361
|
|
235
|
-
def create(count: 1)
|
362
|
+
log_method_time def create(count: 1)
|
363
|
+
log "Creating #{count} private topics..."
|
236
364
|
Array.new(count) do
|
237
|
-
FactoryBot.
|
365
|
+
FactoryBot.build(
|
238
366
|
:private_topic,
|
239
367
|
user: seeder.users[1..-1].sample,
|
240
368
|
last_user: seeder.users.sample,
|
241
369
|
users: [seeder.first_user, *seeder.users.sample(1 + rand(3))]
|
242
|
-
)
|
370
|
+
).tap do |topic|
|
371
|
+
topic.send :set_slug
|
372
|
+
topic.save(validate: false)
|
373
|
+
end
|
243
374
|
end
|
244
375
|
end
|
245
376
|
end
|
@@ -247,41 +378,64 @@ module Thredded
|
|
247
378
|
class Posts < CollectionSeedData
|
248
379
|
MODEL_CLASS = Post
|
249
380
|
|
250
|
-
def create(count: (1..1))
|
381
|
+
log_method_time def create(count: (1..1), topics: seeder.topics) # rubocop:disable Metrics/MethodLength
|
251
382
|
log "Creating #{count} additional posts in each topic..."
|
252
|
-
|
253
|
-
last_post_at = random_duration(0..
|
383
|
+
topics.flat_map do |topic|
|
384
|
+
last_post_at = random_duration(0..256.hours).ago
|
254
385
|
posts_count = (count.min + rand(count.max + 1))
|
255
386
|
posts = range_of_dates_in_order(up_to: last_post_at, count: posts_count).map.with_index do |written_at, i|
|
256
387
|
author = i.zero? ? topic.user : seeder.users.sample
|
257
|
-
|
258
|
-
|
388
|
+
Post.new(
|
389
|
+
content: seeder.fake_post_contents,
|
390
|
+
messageboard_id: topic.messageboard_id,
|
391
|
+
postable: topic,
|
392
|
+
user: author,
|
393
|
+
user_detail: seeder.user_details[author],
|
394
|
+
created_at: written_at,
|
395
|
+
updated_at: written_at,
|
396
|
+
).tap do |post|
|
397
|
+
post.send :set_default_moderation_state
|
398
|
+
post.save(validate: false)
|
399
|
+
end
|
259
400
|
end
|
260
|
-
topic.
|
401
|
+
topic.update_columns(
|
402
|
+
posts_count: posts_count,
|
403
|
+
last_user_id: posts.last.user.id,
|
404
|
+
updated_at: last_post_at,
|
405
|
+
last_post_at: last_post_at
|
406
|
+
)
|
261
407
|
posts
|
262
408
|
end
|
263
409
|
end
|
264
|
-
|
265
|
-
def range_of_dates_in_order(up_to: Time.zone.now, count: 1)
|
266
|
-
written = up_to
|
267
|
-
Array.new(count - 1) { written -= random_duration(10.minutes..6.hours) }.reverse + [up_to]
|
268
|
-
end
|
269
|
-
|
270
|
-
def random_duration(range)
|
271
|
-
(range.min.to_i + rand(range.max.to_i)).seconds
|
272
|
-
end
|
273
410
|
end
|
274
411
|
|
275
412
|
class PrivatePosts < CollectionSeedData
|
276
413
|
MODEL_CLASS = PrivatePost
|
277
414
|
|
278
|
-
def create(count: (1..1))
|
415
|
+
log_method_time def create(count: (1..1))
|
279
416
|
log "Creating #{count} additional posts in each private topic..."
|
280
417
|
seeder.private_topics.flat_map do |topic|
|
281
|
-
|
418
|
+
last_post_at = random_duration(0..256.hours).ago
|
419
|
+
posts_count = (count.min + rand(count.max + 1))
|
420
|
+
posts = range_of_dates_in_order(up_to: last_post_at, count: posts_count).map.with_index do |written_at, i|
|
282
421
|
author = i.zero? ? topic.user : topic.users.sample
|
283
|
-
|
422
|
+
PrivatePost.new(
|
423
|
+
postable: topic,
|
424
|
+
user: author,
|
425
|
+
created_at: written_at,
|
426
|
+
updated_at: written_at,
|
427
|
+
content: seeder.fake_post_contents,
|
428
|
+
).tap do |post|
|
429
|
+
post.save(validate: false)
|
430
|
+
end
|
284
431
|
end
|
432
|
+
topic.update_columns(
|
433
|
+
posts_count: posts_count,
|
434
|
+
last_user_id: posts.last.user.id,
|
435
|
+
updated_at: last_post_at,
|
436
|
+
last_post_at: last_post_at
|
437
|
+
)
|
438
|
+
posts
|
285
439
|
end
|
286
440
|
end
|
287
441
|
end
|