From e87caf44768341c698189a33464c2be97b97c5ac Mon Sep 17 00:00:00 2001 From: Splines <37160523+Splines@users.noreply.github.com> Date: Tue, 19 Dec 2023 10:52:47 +0100 Subject: [PATCH 1/2] Enforce new RuboCop cops for all Ruby files (#566) * Let RuboCop autocorrect all Ruby files (safe) Only safe autocorrections used. Command: bundle exec rubocop --autocorrect --disable-uncorrectable version: 1.57.2 (using Parser 3.2.2.4, rubocop-ast 1.30.0, running on ruby 3.1.4) [x86_64-linux] See the docs: https://docs.rubocop.org/rubocop/usage/auto_correct.html 704 files inspected, 8138 offenses detected, 7399 offenses corrected, 720 more offenses can be corrected with `rubocop -A`. Note there occurred one error for the file "db/seeds.rb", which will be fixed manually in subsequent commits. * Revert "Let RuboCop autocorrect all Ruby files (safe)" This reverts commit b067b2f7ff3dfeba87e780b4736a383e2ded844c. * Force use of explicit hash literal value (HashSyntax) * Only correct string literals to double quotes (safe) 704 files inspected, 3912 offenses detected, 3912 offenses corrected * Allow use of method ".touch" * Autocorrect all other specified cops (safe) 704 files inspected, 3171 offenses detected, 2432 offenses corrected, 720 more offenses can be corrected with `rubocop -A` Command: bundle exec rubocop --autocorrect --disable-uncorrectable * Fix Layout/IndentationConsistency (manual) * Fix (or disable) Layout/LineLength * Fix all Style/ cops * Fix Lint/ cops (3 still left open) * Rename `get_votes_count` to `votes_count` * Fix other Naming/ cops (1 still left open) * Disable `Rails/HelperInstanceVariable` in `quizzes_helper.rb` This is since we only use the instance variable to provide a *default* value for some params, so the helper methods can still be reused. * Fix all Rails/InverseOf cops * Disable Rails/ cops in existing db migrations The respective comments were added manually, not automatically. * Fix Rails/SkipsModelValidations (1 left open) Replaced `update_all` by `update` and `Time.now` by `Time.current` * Disable Rails/OutputSafety for one line * Fix Rails/I18n related cops * Add custom env variable to rubocop This is to avoid Rails/UnknownEnv error * Disable Performance/CollectionLiteralInLoop in some tests * Merge two duplicates groups in Gemfile together * Automatically autocorrect cops (unsafe) Command used: bundle exec rubocop --autocorrect-all --disable-uncorrectable 704 files inspected, 911 offenses detected, 909 offenses corrected 2 errors occurred: An error occurred while Style/StringConcatenation cop was inspecting mampf/app/models/medium.rb:871:30. An error occurred while Layout/LineLength cop was inspecting mampf/db/seeds.rb. I manually fixed the error in medium.rb. I checked the seeds.rb and dit not find any error there. * Fix missing `Time.zone.now` * Add Style/MethodCallWithArgsParentheses and autofix (safe) Command used: bundle exec rubocop --autocorrect --disable-uncorrectable 704 files inspected, 371 offenses detected, 350 offenses corrected, 13 more offenses can be corrected with `rubocop -A` * Fix Layout/ cops * Fix or disable Style/ cops * Delete weird random character * Fix Security/ cops * Fix Performance/ cops * Fix wrong namespace for a cop * Manually ignore more db/ cop violations * Delete empty test files * Fix line to long * Temporarily disable Rails/LexicallyScopedActionFilter * Fix <= logical bug (registration threshold) * remove duplicated restricted? method * change private methods to non-private methods * remove duplicate method * fix typo * change inverse_of relation * fix existing inverse_of associations * rename duplicate announcements method * undo premature change * add namespace to constant * remove unnecessary logging * remove non existing action from before filter * Rewrite new registrations query with timeframe * Allow touch_all method and replace problematic update calls See this comment: https://github.com/MaMpf-HD/mampf/pull/566#discussion_r1421413461 * Disable Rails/HasManyOrHasOneDependent rule This is because we think it's cleaner not to have to write "dependent: nil". Also removed the respective rubocop:todo comments * Fix unwanted "," character * Get rid of weird "foo" check * remove unused restricted? methods * Replace problematic touch_all by touch * Fix wrong manual Style/ correction of 5081aaa * Fix Rails/OutputSafety * Remove duplicate dates before save see https://github.com/MaMpf-HD/mampf/pull/566#discussion_r1428209744 --------- Co-authored-by: fosterfarrell9 <28628554+fosterfarrell9@users.noreply.github.com> --- .rubocop.yml | 26 + Gemfile | 133 +- Rakefile | 2 +- app/abilities/clicker_ability.rb | 2 +- app/abilities/main_ability.rb | 2 +- app/abilities/medium_ability.rb | 6 +- app/abilities/profile_ability.rb | 2 +- app/abilities/search_ability.rb | 2 +- app/abilities/talk_ability.rb | 2 +- app/abilities/tutorial_ability.rb | 4 +- app/assets/javascripts/media.coffee | 2 +- app/controllers/administration_controller.rb | 4 +- app/controllers/announcements_controller.rb | 30 +- app/controllers/answers_controller.rb | 8 +- app/controllers/application_controller.rb | 42 +- app/controllers/assignments_controller.rb | 18 +- app/controllers/chapters_controller.rb | 50 +- app/controllers/clicker_votes_controller.rb | 4 +- app/controllers/clickers_controller.rb | 61 +- .../commontator/comments_controller.rb | 353 +++--- app/controllers/confirmations_controller.rb | 2 +- app/controllers/courses_controller.rb | 52 +- app/controllers/divisions_controller.rb | 16 +- app/controllers/erdbeere_controller.rb | 62 +- app/controllers/interactions_controller.rb | 18 +- app/controllers/items_controller.rb | 20 +- app/controllers/lectures_controller.rb | 218 ++-- app/controllers/lessons_controller.rb | 26 +- app/controllers/main_controller.rb | 12 +- app/controllers/media_controller.rb | 327 ++--- app/controllers/notifications_controller.rb | 10 +- app/controllers/profile_controller.rb | 60 +- app/controllers/programs_controller.rb | 16 +- app/controllers/questions_controller.rb | 45 +- .../quiz_certificates_controller.rb | 10 +- app/controllers/quizzes_controller.rb | 39 +- app/controllers/readers_controller.rb | 7 +- app/controllers/referrals_controller.rb | 36 +- app/controllers/registrations_controller.rb | 66 +- app/controllers/remarks_controller.rb | 20 +- app/controllers/search_controller.rb | 6 +- app/controllers/sections_controller.rb | 36 +- app/controllers/subjects_controller.rb | 14 +- app/controllers/submissions_controller.rb | 150 +-- app/controllers/tags_controller.rb | 173 ++- app/controllers/talks_controller.rb | 24 +- app/controllers/terms_controller.rb | 36 +- app/controllers/tutorials_controller.rb | 94 +- app/controllers/users_controller.rb | 18 +- app/controllers/vertices_controller.rb | 22 +- .../watchlist_entries_controller.rb | 14 +- app/controllers/watchlists_controller.rb | 76 +- app/helpers/announcements_helper.rb | 14 +- app/helpers/application_helper.rb | 179 ++- app/helpers/assignments_helper.rb | 13 +- app/helpers/chapters_helper.rb | 2 +- app/helpers/clickers_helper.rb | 8 +- app/helpers/courses_helper.rb | 32 +- app/helpers/email_helper.rb | 4 +- app/helpers/items_helper.rb | 8 +- app/helpers/lectures_helper.rb | 88 +- app/helpers/lessons_helper.rb | 2 +- app/helpers/media_helper.rb | 71 +- app/helpers/notifications_helper.rb | 41 +- app/helpers/quizzes_helper.rb | 14 +- app/helpers/referrals_helper.rb | 22 +- app/helpers/search_helper.rb | 4 +- app/helpers/sections_helper.rb | 4 +- app/helpers/submissions_helper.rb | 77 +- app/helpers/talks_helper.rb | 26 +- app/helpers/users_helper.rb | 2 +- app/helpers/vertices_helper.rb | 6 +- app/mailers/application_mailer.rb | 8 +- .../exception_handler/exception_mailer.rb | 14 +- app/mailers/mathi_mailer.rb | 8 +- app/mailers/my_mailer.rb | 8 +- app/mailers/notification_mailer.rb | 49 +- app/models/ability.rb | 3 - app/models/announcement.rb | 4 +- app/models/answer.rb | 14 +- app/models/assignment.rb | 70 +- app/models/chapter.rb | 20 +- app/models/clicker.rb | 12 +- app/models/course.rb | 52 +- app/models/course_self_join.rb | 2 +- app/models/course_tag_join.rb | 2 +- app/models/import.rb | 2 + app/models/interaction.rb | 6 +- app/models/item.rb | 172 ++- app/models/item_self_join.rb | 8 +- app/models/lecture.rb | 257 ++-- app/models/lesson.rb | 86 +- app/models/link.rb | 6 +- app/models/mampf_expression.rb | 4 +- app/models/mampf_matrix.rb | 24 +- app/models/mampf_set.rb | 4 +- app/models/mampf_tuple.rb | 4 +- app/models/manuscript.rb | 217 ++-- app/models/medium.rb | 544 ++++---- app/models/medium_publisher.rb | 64 +- app/models/notification.rb | 36 +- app/models/notion.rb | 6 +- app/models/probe.rb | 6 +- app/models/question.rb | 33 +- app/models/question_sampler.rb | 2 - app/models/quiz.rb | 32 +- app/models/quiz_certificate.rb | 2 +- app/models/quiz_graph.rb | 92 +- app/models/quiz_round.rb | 44 +- app/models/reader.rb | 2 +- app/models/referral.rb | 27 +- app/models/relation.rb | 6 +- app/models/remark.rb | 12 +- app/models/section.rb | 31 +- app/models/solution.rb | 12 +- app/models/speaker_talk_join.rb | 2 +- app/models/submission.rb | 225 ++-- app/models/submission_cleaner.rb | 2 - app/models/tag.rb | 108 +- app/models/talk.rb | 15 +- app/models/teachable_parser.rb | 14 +- app/models/term.rb | 53 +- app/models/time_stamp.rb | 24 +- app/models/tutor_tutorial_join.rb | 2 +- app/models/tutorial.rb | 8 +- app/models/user.rb | 168 +-- app/models/user_cleaner.rb | 58 +- app/models/user_submission_join.rb | 4 +- app/models/watchlist.rb | 6 +- app/models/watchlist_entry.rb | 4 +- app/models/xkcd.rb | 2 +- app/uploaders/correction_uploader.rb | 4 +- app/uploaders/geogebra_uploader.rb | 14 +- app/uploaders/pdf_uploader.rb | 40 +- app/uploaders/profileimage_uploader.rb | 6 +- app/uploaders/screenshot_uploader.rb | 6 +- app/uploaders/submission_uploader.rb | 4 +- app/uploaders/video_uploader.rb | 13 +- app/uploaders/zip_uploader.rb | 6 +- app/validators/http_url_validator.rb | 6 +- app/views/clickers/open.coffee | 2 +- app/views/layouts/devise.html.erb | 2 +- app/views/media/_medium.html.erb | 8 +- app/views/shared/_navbar.html.erb | 2 +- app/views/submissions/_card.html.erb | 4 +- app/workers/cache_cleaner.rb | 4 +- app/workers/interaction_saver.rb | 6 +- app/workers/media_publisher.rb | 2 +- app/workers/probe_saver.rb | 2 + app/workers/user_cleaner_job.rb | 2 +- config/application.rb | 22 +- config/environment.rb | 6 +- config/environments/development.rb | 9 +- config/environments/docker_development.rb | 16 +- config/environments/production.rb | 23 +- config/environments/test.rb | 18 +- config/initializers/active_job.rb | 2 +- config/initializers/assets.rb | 17 +- config/initializers/backtrace_silencers.rb | 8 +- config/initializers/bust_cache.rb | 2 +- config/initializers/commontator.rb | 48 +- config/initializers/core_ext.rb | 2 +- config/initializers/customize_error.rb | 8 +- config/initializers/cypress_on_rails.rb | 3 +- config/initializers/default_setting.rb | 20 +- config/initializers/devise.rb | 16 +- config/initializers/kaminari_config.rb | 1 - config/initializers/mime_types.rb | 6 +- config/initializers/multiple_file_field.rb | 2 +- config/initializers/preload_medium_sti.rb | 6 +- config/initializers/prometheus_exporter.rb | 12 +- config/initializers/scrapers.rb | 2 +- config/initializers/session_store.rb | 6 +- config/initializers/shrine.rb | 25 +- config/initializers/sidekiq.rb | 4 +- config/initializers/spec_files.rb | 2 +- config/initializers/sunspot.rb | 6 +- config/initializers/thredded.rb | 53 +- config/locales/de.yml | 2 +- config/puma.rb | 12 +- config/routes.rb | 1098 ++++++++--------- .../20191209143820_create_interactions.rb | 2 + ...191209165752_add_details_to_interaction.rb | 2 + ...155453_remove_details_from_interactions.rb | 2 + .../20191218131526_create_probes.rb | 2 + db/migrate/20170731143601_null_migration.rb | 11 +- .../20170909120217_devise_create_users.rb | 4 +- .../20170914162323_delete_asset_tag_join.rb | 2 + .../20170919123229_add_admin_to_users.rb | 2 + db/migrate/20171001204632_drop_asset.rb | 2 + .../20171009142032_add_modules_to_lecture.rb | 2 + ...0171011122408_add_confirmable_to_devise.rb | 2 + ...011171726_remove_confirmablefrom_devise.rb | 2 + .../20171016171419_add_kiwi_to_lecture.rb | 2 + .../20171021135749_add_extras_to_medium.rb | 2 + .../20180524065013_add_consents_to_user.rb | 2 + ...180524103749_remove_trackable_from_user.rb | 2 + ...06092317_add_fields_to_course_user_join.rb | 2 + ...0806100940_add_news_to_course_user_join.rb | 2 + ...180806124930_add_edited_profile_to_user.rb | 2 + ...08092444_remove_properties_from_lecture.rb | 2 + ...6_rename_properties_in_course_user_join.rb | 2 +- .../20180816125615_add_teacher_to_user.rb | 2 + ...9151835_create_editable_user_join_table.rb | 10 +- ...80820123437_add_editor_and_name_to_user.rb | 2 + ...remove_teacher_and_teacher_id_from_user.rb | 2 + ...20180821132727_remove_teacher_fragments.rb | 2 + ...0821140606_add_teacher_again_to_lecture.rb | 4 +- .../20180830080855_add_position_to_section.rb | 2 + .../20180830081938_add_position_to_chapter.rb | 2 + ...85749_add_absolute_numbering_to_lecture.rb | 2 + ...80830121324_remove_numbers_from_section.rb | 2 + ...80905085400_add_shrine_columns_to_media.rb | 2 + .../20180905134510_import_files_to_shrine.rb | 25 +- ...130_remove_a_lot_of_columns_from_medium.rb | 2 + ...0908101213_remove_questions_from_medium.rb | 2 + db/migrate/20180909152728_create_referrals.rb | 2 + .../20180911085149_add_medium_self_items.rb | 6 +- .../20180913104612_add_link_to_referral.rb | 2 + ...80913110737_add_medium_link_to_referral.rb | 2 + ...015141212_remove_booleans_from_referral.rb | 2 + .../20181129113233_rename_reste_to_nuesse.rb | 2 +- ...15315_rename_media_sort_reste_to_nuesse.rb | 6 +- ...648_create_activity_notification_tables.rb | 6 +- ...1210173053_remove_activity_notification.rb | 2 + .../20181222160210_create_announcements.rb | 2 +- ...0181222164908_add_index_to_notification.rb | 2 +- ...44_add_notifiable_index_to_notification.rb | 2 +- ...0106121300_add_no_notifications_to_user.rb | 2 + ...20190126161457_create_thredded.thredded.rb | 85 +- ...27120458_change_nil_user_names_to_blank.rb | 4 +- ...03_change_blank_user_names_to_nutzer_in.rb | 4 +- ...121_remove_extras_from_course_user_join.rb | 2 + ...204161127_add_organizational_to_lecture.rb | 2 + .../20190207092000_add_muesli_to_lecture.rb | 2 + .../20190211172104_add_released_to_medium.rb | 4 +- .../20190212143919_add_released_to_lecture.rb | 4 +- .../20190225103346_add_quarantine_to_item.rb | 2 + ...90225142347_add_content_mode_to_lecture.rb | 4 +- ...323_replace_content_mode_media_by_video.rb | 4 +- ...add_start_and_end_destination_to_lesson.rb | 2 + .../20190227130143_add_hidden_to_chapter.rb | 2 + .../20190227142829_add_hidden_to_section.rb | 2 + .../20190227173517_add_hidden_to_item.rb | 2 + ...21741_add_imported_manuscript_to_medium.rb | 2 + ...20190304115617_remove_tags_from_scripts.rb | 4 +- db/migrate/20190323142434_create_answers.rb | 2 + ...5_drop_questions_remarks_quizzes_tables.rb | 2 +- ...9_add_quizzable_data_and_type_to_medium.rb | 2 + ...0190404132603_add_independent_to_medium.rb | 2 + ...10252_add_organizational_data_to_course.rb | 2 + ...081446_rename_keks_question_remark_quiz.rb | 14 +- .../20190424174546_update_tag_orders.rb | 2 + ..._attachments_for_blob_id.active_storage.rb | 6 +- .../20190501135008_remove_news_from_course.rb | 1 - .../20190501135301_remove_number_from_item.rb | 1 - ...0190501135815_remove_colums_from_medium.rb | 3 +- ...0507171538_add_default_locale_to_course.rb | 2 + db/migrate/20190508130822_create_notions.rb | 3 +- .../20190508131506_add_columns_to_notion.rb | 2 + ...0190508140309_add_aliased_tag_to_notion.rb | 1 + ...031012_generate_notions_from_tag_titles.rb | 2 + ...8161033_add_email_notifications_to_user.rb | 2 + ...90522110318_add_mail_properties_to_user.rb | 2 + ...50_remove_email_notifications_from_user.rb | 1 - ...0611081201_remove_title_backup_from_tag.rb | 1 - .../20190808083830_add_open_to_clicker.rb | 2 + ...7163159_add_confirmable_to_devise_again.rb | 4 +- ...124731_add_translation_table_to_program.rb | 9 +- ...124830_add_translation_table_to_subject.rb | 9 +- ...30432_add_translation_table_to_division.rb | 9 +- ...0111827_install_commontator.commontator.rb | 12 +- ...0200412104751_acts_as_votable_migration.rb | 7 +- ...00414170119_add_unread_comments_to_user.rb | 2 + ...11503_remove_fk_on_course_division_join.rb | 6 +- ...510062602_remove_fk_on_course_self_join.rb | 6 +- .../20200510104347_create_item_self_join.rb | 2 + ...120400_add_position_to_section_tag_join.rb | 2 + ...21152416_fill_position_values_for_media.rb | 26 +- ...141619_add_comments_disabled_to_lecture.rb | 2 + .../20200606133607_add_active_to_term.rb | 2 + ...611130229_add_study_participant_to_user.rb | 2 + ...20200613090342_add_text_input_to_medium.rb | 2 + .../20200809123000_add_new_design_to_user.rb | 2 + ...10162008_add_term_independent_to_course.rb | 2 + ...49_add_organizational_on_top_to_lecture.rb | 2 + ..._add_disable_teacher_display_to_lecture.rb | 2 + ...5041_add_invited_user_ids_to_submission.rb | 6 +- ...1435_add_submission_email_flags_to_user.rb | 2 + ...add_submission_max_team_size_to_lecture.rb | 2 +- .../20201002091503_enable_uuid_extension.rb | 2 +- .../20201002092028_add_uuid_to_submissions.rb | 2 +- ...02094500_change_submission_foreign_keys.rb | 10 +- ...5520_set_submission_primary_key_to_uuid.rb | 2 + ...1004141237_add_correction_email_to_user.rb | 4 +- ...201008135825_add_accepted_to_submission.rb | 2 + ..._email_for_submission_decisions_to_user.rb | 2 + .../20201015154231_add_archived_to_user.rb | 2 + ...01017133345_create_tutor_tutorial_joins.rb | 2 +- ...56_add_accepted_file_type_to_assignment.rb | 2 +- ...151550_remove_redundant_columns_for_v13.rb | 2 + .../20201114125010_add_boost_to_medium.rb | 2 +- ...151659_add_on_main_page_to_announcement.rb | 2 + ...3030_create_user_favorite_lecture_joins.rb | 2 +- ...10226094753_add_release_infos_to_medium.rb | 4 +- ...add_submission_deletion_columns_to_term.rb | 2 + db/migrate/20210710122546_create_talks.rb | 2 +- ...710130619_add_legacy_seminar_to_lecture.rb | 2 + ...10152036_add_title_and_position_to_talk.rb | 2 + ...20210827141317_create_watchlist_entries.rb | 2 + ...9153352_add_display_description_to_talk.rb | 2 + ...10923085744_add_protected_to_assignment.rb | 4 +- ...20210923113111_add_public_to_watchlists.rb | 2 + ...162730_add_deletion_date_to_assignments.rb | 5 +- ...ge_deletion_date_default_in_assignemnts.rb | 2 +- ...5_add_devise_trackable_columns_to_users.rb | 4 +- db/seeds.rb | 6 +- lib/collectors/mampf_collector.rb | 51 +- lib/core_ext/array.rb | 4 +- lib/core_ext/enumerable_natural_sort.rb | 4 +- lib/core_ext/integer.rb | 2 +- lib/core_ext/string.rb | 15 +- lib/core_ext/string_to_sort_atoms.rb | 21 +- lib/scrapers/scrapers.rb | 20 +- lib/tasks/db.rake | 139 ++- lib/tasks/mampf_setup.rake | 3 +- spec/controllers/chapters_controller_spec.rb | 6 +- spec/controllers/courses_controller_spec.rb | 6 +- spec/controllers/lessons_controller_spec.rb | 6 +- spec/controllers/main_controller_spec.rb | 6 +- spec/controllers/media_controller_spec.rb | 7 +- spec/controllers/profile_controller_spec.rb | 6 +- spec/controllers/search_controller_spec.rb | 6 +- spec/controllers/sections_controller_spec.rb | 6 +- spec/controllers/tags_controller_spec.rb | 6 +- .../app_commands/activerecord_fixtures.rb | 28 +- spec/cypress/app_commands/clean.rb | 6 +- spec/cypress/app_commands/eval.rb | 2 - spec/cypress/app_commands/factory_bot.rb | 14 +- spec/cypress/app_commands/log_fail.rb | 22 +- spec/cypress/app_commands/scenarios/admin.rb | 6 +- .../app_commands/scenarios/course_created.rb | 0 spec/cypress/app_commands/scenarios/editor.rb | 6 +- .../app_commands/scenarios/non_admin.rb | 6 +- spec/cypress/app_commands/scenarios/setup.rb | 0 .../cypress/app_commands/scenarios/teacher.rb | 6 +- spec/cypress/cypress_helper.rb | 16 +- spec/factories/announcements.rb | 2 - spec/factories/answers.rb | 2 - spec/factories/assignments.rb | 6 +- spec/factories/chapters.rb | 7 +- spec/factories/clicker_votes.rb | 2 - spec/factories/clickers.rb | 2 - spec/factories/consumptions.rb | 12 +- spec/factories/course_self_joins.rb | 2 - spec/factories/course_tag_joins.rb | 2 - spec/factories/courses.rb | 12 +- spec/factories/division_course_joins.rb | 2 - spec/factories/divisions.rb | 2 - spec/factories/editable_user_joins.rb | 2 - spec/factories/imports.rb | 2 - spec/factories/interactions.rb | 2 - spec/factories/item_self_joins.rb | 2 - spec/factories/items.rb | 6 +- spec/factories/lecture_user_joins.rb | 2 - spec/factories/lectures.rb | 12 +- spec/factories/lesson_section_joins.rb | 2 - spec/factories/lesson_tag_joins.rb | 2 - spec/factories/lessons.rb | 4 +- spec/factories/links.rb | 2 - spec/factories/mampf_expressions.rb | 2 - spec/factories/mampf_matrices.rb | 4 +- spec/factories/mampf_sets.rb | 2 - spec/factories/mampf_tuples.rb | 2 - spec/factories/manuscripts.rb | 2 - spec/factories/media.rb | 24 +- spec/factories/medium_publisher.rb | 10 +- spec/factories/medium_tag_joins.rb | 2 - spec/factories/notifications.rb | 4 +- spec/factories/notions.rb | 9 +- spec/factories/probes.rb | 2 - spec/factories/programs.rb | 2 - spec/factories/question_samplers.rb | 4 +- spec/factories/questions.rb | 8 +- spec/factories/quiz_certificates.rb | 2 - spec/factories/quiz_graphs.rb | 6 +- spec/factories/quiz_rounds.rb | 2 - spec/factories/quizzes.rb | 8 +- spec/factories/readers.rb | 2 - spec/factories/referrals.rb | 2 - spec/factories/relations.rb | 2 - spec/factories/remarks.rb | 6 +- spec/factories/section_tag_joins.rb | 2 - spec/factories/sections.rb | 4 +- spec/factories/solutions.rb | 4 +- spec/factories/subjects.rb | 2 - spec/factories/submission_cleaner.rb | 4 +- spec/factories/submissions.rb | 6 +- spec/factories/tags.rb | 6 +- spec/factories/talks.rb | 12 +- spec/factories/teachable_parsers.rb | 4 +- spec/factories/terms.rb | 8 +- spec/factories/time_stamp.rb | 4 +- spec/factories/tutor_tutorial_joins.rb | 2 - spec/factories/tutorials.rb | 4 +- spec/factories/user_favorite_lecture_joins.rb | 2 - spec/factories/user_submission_joins.rb | 2 - spec/factories/users.rb | 4 +- spec/factories/vtt_containers.rb | 6 +- spec/helpers/application_helper_spec.rb | 6 +- spec/mailers/notification_mailer_spec.rb | 6 +- spec/models/announcement_spec.rb | 20 +- spec/models/answer_spec.rb | 18 +- spec/models/assignment_spec.rb | 26 +- spec/models/chapter_spec.rb | 22 +- spec/models/clicker_spec.rb | 36 +- spec/models/clicker_vote_spec.rb | 12 +- spec/models/consumption_spec.rb | 28 +- spec/models/course_self_join_spec.rb | 14 +- spec/models/course_spec.rb | 671 +++++----- spec/models/course_tag_join_spec.rb | 12 +- spec/models/division_course_join_spec.rb | 12 +- spec/models/division_spec.rb | 10 +- spec/models/editable_user_join_spec.rb | 20 +- spec/models/import_spec.rb | 22 +- spec/models/interaction_spec.rb | 16 +- spec/models/item_self_join_spec.rb | 14 +- spec/models/item_spec.rb | 30 +- spec/models/lecture_spec.rb | 58 +- spec/models/lecture_user_join_spec.rb | 12 +- spec/models/lesson_section_join_spec.rb | 12 +- spec/models/lesson_spec.rb | 30 +- spec/models/lesson_tag_join_spec.rb | 12 +- spec/models/link_spec.rb | 22 +- spec/models/mampf_expression_spec.rb | 8 +- spec/models/mampf_matrix_spec.rb | 8 +- spec/models/mampf_set_spec.rb | 8 +- spec/models/mampf_tuple_spec.rb | 8 +- spec/models/manuscript_spec.rb | 8 +- spec/models/medium_publisher_spec.rb | 204 ++- spec/models/medium_spec.rb | 90 +- spec/models/medium_tag_join_spec.rb | 12 +- spec/models/notification_spec.rb | 18 +- spec/models/notion_spec.rb | 24 +- spec/models/probe_spec.rb | 30 +- spec/models/program_spec.rb | 10 +- spec/models/question_sampler_spec.rb | 32 +- spec/models/question_spec.rb | 34 +- spec/models/quiz_certificate_spec.rb | 20 +- spec/models/quiz_graph_spec.rb | 26 +- spec/models/quiz_round_spec.rb | 8 +- spec/models/quiz_spec.rb | 18 +- spec/models/reader_spec.rb | 12 +- spec/models/referral_spec.rb | 24 +- spec/models/relation_spec.rb | 26 +- spec/models/remark_spec.rb | 12 +- spec/models/section_spec.rb | 10 +- spec/models/section_tag_join_spec.rb | 12 +- spec/models/solution_spec.rb | 8 +- spec/models/subject_spec.rb | 8 +- spec/models/submission_cleaner_spec.rb | 39 +- spec/models/submission_spec.rb | 40 +- spec/models/tag_spec.rb | 40 +- spec/models/talk_spec.rb | 193 +-- spec/models/talk_tag_join_spec.rb | 4 +- spec/models/teachable_parser_spec.rb | 28 +- spec/models/term_spec.rb | 38 +- spec/models/time_stamp_spec.rb | 18 +- spec/models/tutor_tutorial_join_spec.rb | 12 +- spec/models/tutorial_spec.rb | 24 +- spec/models/user_cleaner_spec.rb | 12 +- .../models/user_favorite_lecture_join_spec.rb | 12 +- spec/models/user_spec.rb | 36 +- spec/models/user_submission_join_spec.rb | 10 +- spec/models/vtt_container_spec.rb | 16 +- spec/models/watchlist_entry_spec.rb | 16 +- spec/models/watchlist_spec.rb | 24 +- spec/rails_helper.rb | 14 +- spec/requests/media_spec.rb | 91 +- spec/spec_helper.rb | 12 +- spec/support/database_cleaner.rb | 4 +- spec/support/devise_request_helper.rb | 6 +- spec/support/request_parse_helper.rb | 37 +- spec/support/simplecov_helper.rb | 48 +- spec/views/chapters/show.html.erb_spec.rb | 6 +- spec/views/courses/show.html.erb_spec.rb | 6 +- spec/views/lessons/show.html.erb_spec.rb | 6 +- spec/views/main/about.html.erb_spec.rb | 6 +- spec/views/main/home.html.erb_spec.rb | 6 +- spec/views/media/index.html.erb_spec.rb | 6 +- spec/views/media/show.html.erb_spec.rb | 6 +- spec/views/profile/edit.html.erb_spec.rb | 6 +- spec/views/sections/show.html.erb_spec.rb | 6 +- 493 files changed, 5572 insertions(+), 5697 deletions(-) delete mode 100644 spec/cypress/app_commands/scenarios/course_created.rb delete mode 100644 spec/cypress/app_commands/scenarios/setup.rb diff --git a/.rubocop.yml b/.rubocop.yml index f260bf21d..c625d63ec 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -68,6 +68,20 @@ Performance: Performance/FlatMap: Severity: warning # an error in CI/CD + +############################################# +# Rails +############################################# + +Rails/HasManyOrHasOneDependent: + Enabled: false + +Rails/SkipsModelValidations: + AllowedMethods: ["touch", "touch_all"] + +Rails/UnknownEnv: + Environments: ["development", "test", "production", "docker_development"] + ############################################# # Style ############################################# @@ -81,6 +95,18 @@ Style/EmptyMethod: Style/FrozenStringLiteralComment: EnforcedStyle: never +Style/HashSyntax: + EnforcedShorthandSyntax: never + +Style/MethodCallWithArgsParentheses: + Enabled: true + AllowedMethods: ["authorize!", "authorize", "can", "can?", "head", "import", + "include", "not_to", "puts", "render", "require", "to"] + AllowedPatterns: [^redirect_] + # Don't enforce in migrations, as we have methods like `add_column`, + # `change_column` etc. and parentheses would be very annoying there. + Exclude: ["db/**/*"] + Style/RedundantReturn: AllowMultipleReturnValues: true diff --git a/Gemfile b/Gemfile index 371aa4264..aa44445ae 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '3.1.4' +ruby "3.1.4" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem "rails", "~> 7.0.4.3" @@ -32,97 +32,90 @@ gem "jbuilder" # gem 'image_processing', '~> 1.2' # Reduces boot times through caching; required in config/boot.rb +gem "active_model_serializers" gem "bootsnap", ">= 1.4.2", require: false gem "rack" -gem "active_model_serializers" # Use CoffeeScript for .coffee assets and views gem "coffee-rails", "~> 5.0.0" # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 3.0' -gem "shrine" gem "fastimage" -gem "streamio-ffmpeg" -gem "pdf-reader" -gem "mini_magick" gem "image_processing" +gem "mini_magick" +gem "pdf-reader" +gem "shrine" +gem "streamio-ffmpeg" # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' gem "filesize" # Use Capistrano for deployment # gem 'capistrano-rails', group: :development -gem "rgl" -gem "responders" -gem "pg" -gem "devise" -gem "erubis" -gem "cancancan" -gem "jquery-rails" -gem "jquery-ui-rails" -gem "js-routes", '1.4.9' +gem "activerecord-import", + git: "https://github.com/zdennis/activerecord-import.git", + branch: "master" +gem "acts_as_list" +gem "acts_as_tree" +gem "acts_as_votable" +gem "barby" gem "bootstrap", "~>5" gem "bootstrap_form" +gem "cancancan" +gem "clipboard-rails" +gem "commontator" +gem "coveralls", require: false +gem "devise" gem "devise-bootstrap-views" +gem "erubis" +gem "exception_handler", "~> 0.8.0.0" +gem "faraday", "~> 1.8" gem "fuzzy-string-match" -gem "coveralls", require: false +gem "globalize" +gem "globalize-accessors" +gem "jquery-rails" +gem "jquery-ui-rails" +gem "js-routes", "1.4.9" gem "kaminari" -gem "acts_as_list" -gem "acts_as_tree" -gem "activerecord-import", - git: "https://github.com/zdennis/activerecord-import.git", - branch: "master" -gem "thredded" -gem "kramdown-parser-gfm" -gem "thredded-markdown_katex", - git: "https://github.com/thredded/thredded-markdown_katex.git", - branch: "main" -gem "rails-i18n" gem "kaminari-i18n" -gem "trix-rails", require: "trix" -gem "sunspot_rails", - github: 'sunspot/sunspot', - glob: 'sunspot_rails/*.gemspec' -gem "sunspot_solr" +gem "kramdown-parser-gfm" +gem "net-smtp" +gem "pg" +gem "premailer-rails" gem "progress_bar" -gem "barby" +gem "rails-i18n" +gem "responders" +gem "rgl" gem "rqrcode" +gem "rubyzip", "~> 2.3.0" gem "sidekiq" gem "sidekiq-cron", "~> 1.1" -gem "faraday", "~> 1.8" -gem "globalize" -gem "globalize-accessors" -gem "commontator" -gem "acts_as_votable" gem "sprockets-rails", - git: "https://github.com/rails/sprockets-rails", - branch: "master" -gem "premailer-rails" -gem "clipboard-rails" -gem "rubyzip", "~> 2.3.0" -gem "exception_handler", "~> 0.8.0.0" -gem 'webpacker', '~> 5.x' -gem 'net-smtp' - -group :development, :docker_development, :test do - # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem "byebug", platforms: [:mri, :mingw, :x64_mingw] - gem "rspec-rails" - gem "factory_bot_rails" -end + git: "https://github.com/rails/sprockets-rails", + branch: "master" +gem "sunspot_rails", + github: "sunspot/sunspot", + glob: "sunspot_rails/*.gemspec" +gem "sunspot_solr" +gem "thredded" +gem "thredded-markdown_katex", + git: "https://github.com/thredded/thredded-markdown_katex.git", + branch: "main" +gem "trix-rails", require: "trix" +gem "webpacker", "~> 5.x" group :development, :docker_development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. - gem "web-console", ">= 3.3.0" gem "listen", ">= 3.0.5", "< 3.2" gem "rails-erd" + gem "web-console", ">= 3.3.0" # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem "marcel" + gem "pgreset" + gem "rubocop", "~> 1.57", require: false + gem "rubocop-performance", "~> 1.16", require: false + gem "rubocop-rails", "~> 2.22", ">= 2.22.1", require: false gem "spring" gem "spring-watcher-listen", "~> 2.0.0" - gem "rubocop", "~> 1.57", require: false - gem 'rubocop-performance', '~> 1.16', require: false - gem 'rubocop-rails', '~> 2.22', '>= 2.22.1', require: false - gem "pgreset" - gem "marcel" # gem 'bullet' end @@ -130,15 +123,21 @@ group :test do # Adds support for Capybara system testing and selenium driver gem "selenium-webdriver" # Easy installation and use of web drivers to run system tests with browsers - gem 'webdrivers' - gem 'faker' - gem 'database_cleaner' - gem 'launchy' - gem 'simplecov', require: false + gem "database_cleaner" + gem "faker" + gem "launchy" + gem "simplecov", require: false + gem "webdrivers" end group :test, :development, :docker_development do - gem 'cypress-on-rails', '~> 1.0' - gem 'simplecov-cobertura' + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem "byebug", platforms: [:mri, :mingw, :x64_mingw] + gem "factory_bot_rails" + gem "rspec-rails" + + gem "cypress-on-rails", "~> 1.0" + gem "simplecov-cobertura" end -gem 'prometheus_exporter' \ No newline at end of file + +gem "prometheus_exporter" diff --git a/Rakefile b/Rakefile index e85f91391..9a5ea7383 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require_relative 'config/application' +require_relative "config/application" Rails.application.load_tasks diff --git a/app/abilities/clicker_ability.rb b/app/abilities/clicker_ability.rb index 6dfd4d5da..5d048f5c5 100644 --- a/app/abilities/clicker_ability.rb +++ b/app/abilities/clicker_ability.rb @@ -9,7 +9,7 @@ def initialize(user) !user.generic? end - can [:show, :get_votes_count], Clicker + can [:show, :votes_count], Clicker can [:edit, :open, :close, :set_alternatives], Clicker do |clicker, code| (user&.admin? || user == clicker.editor) || code == clicker.code diff --git a/app/abilities/main_ability.rb b/app/abilities/main_ability.rb index 7ced6bc32..157c665d4 100644 --- a/app/abilities/main_ability.rb +++ b/app/abilities/main_ability.rb @@ -1,7 +1,7 @@ class MainAbility include CanCan::Ability - def initialize(user) + def initialize(_user) can :start, :main end end diff --git a/app/abilities/medium_ability.rb b/app/abilities/medium_ability.rb index f173dd07c..01c767b9c 100644 --- a/app/abilities/medium_ability.rb +++ b/app/abilities/medium_ability.rb @@ -9,7 +9,7 @@ def initialize(user) can [:show, :show_comments], Medium do |medium| medium.visible_for_user?(user) && - !(medium.sort.in?(['Question', 'Remark']) && !user.can_edit?(medium)) + !(medium.sort.in?(["Question", "Remark"]) && !user.can_edit?(medium)) end can :inspect, Medium do |medium| @@ -18,7 +18,7 @@ def initialize(user) can [:edit, :update, :enrich, :publish, :destroy, :cancel_publication, :add_item, :add_reference, :add_screenshot, :remove_screenshot, - :import_script_items, :import_manuscript, :get_statistics, + :import_script_items, :import_manuscript, :statistics, :render_medium_tags, :fill_quizzable_area, :fill_reassign_modal], Medium do |medium| user.can_edit?(medium) @@ -46,7 +46,7 @@ def initialize(user) !user.generic? && user.can_edit?(medium) end - can [:register_download], Medium do |medium| + can [:register_download], Medium do |_medium| !user.new_record? end end diff --git a/app/abilities/profile_ability.rb b/app/abilities/profile_ability.rb index 3000ca054..43d263676 100644 --- a/app/abilities/profile_ability.rb +++ b/app/abilities/profile_ability.rb @@ -1,7 +1,7 @@ class ProfileAbility include CanCan::Ability - def initialize(user) + def initialize(_user) clear_aliased_actions can [:edit, :update, :check_for_consent, :add_consent, diff --git a/app/abilities/search_ability.rb b/app/abilities/search_ability.rb index 018392d85..aa4c87890 100644 --- a/app/abilities/search_ability.rb +++ b/app/abilities/search_ability.rb @@ -1,7 +1,7 @@ class SearchAbility include CanCan::Ability - def initialize(user) + def initialize(_user) clear_aliased_actions can :index, :search diff --git a/app/abilities/talk_ability.rb b/app/abilities/talk_ability.rb index fe4e62868..24717b175 100644 --- a/app/abilities/talk_ability.rb +++ b/app/abilities/talk_ability.rb @@ -9,7 +9,7 @@ def initialize(user) end can [:new, :edit, :create, :update, :destroy], Talk do |talk| - (talk.lecture && talk.lecture.edited_by?(user)) || user.admin? + talk.lecture&.edited_by?(user) || user.admin? end can [:assemble, :modify], Talk do |talk| diff --git a/app/abilities/tutorial_ability.rb b/app/abilities/tutorial_ability.rb index f02c683b6..7d7134940 100644 --- a/app/abilities/tutorial_ability.rb +++ b/app/abilities/tutorial_ability.rb @@ -9,11 +9,11 @@ def initialize(user) user.can_update_personell?(tutorial.lecture) end - can :overview, Tutorial do |tutorial, lecture| + can :overview, Tutorial do |_tutorial, lecture| user.editor_or_teacher_in?(lecture) end - can :index, Tutorial do |tutorial, lecture| + can :index, Tutorial do |_tutorial, lecture| user.in?(lecture.tutors) || user.editor_or_teacher_in?(lecture) end diff --git a/app/assets/javascripts/media.coffee b/app/assets/javascripts/media.coffee index 8869ee596..3acd8d046 100644 --- a/app/assets/javascripts/media.coffee +++ b/app/assets/javascripts/media.coffee @@ -378,7 +378,7 @@ $(document).on 'turbolinks:load', -> $(document).on 'click', '#showMediaStatistics', -> mediumId = $(this).data('medium') - $.ajax Routes.get_statistics_path(mediumId), + $.ajax Routes.statistics_path(mediumId), type: 'GET' dataType: 'script' return diff --git a/app/controllers/administration_controller.rb b/app/controllers/administration_controller.rb index 05f39396f..1f5ebe60d 100644 --- a/app/controllers/administration_controller.rb +++ b/app/controllers/administration_controller.rb @@ -5,7 +5,7 @@ class AdministrationController < ApplicationController # tell cancancan there is no model for this controller, but authorize # nevertheless authorize_resource class: false - layout 'administration' + layout "administration" def current_ability @current_ability ||= AdministrationAbility.new(current_user) @@ -26,6 +26,6 @@ def classification end def search - @tags = params[:sort] == 'tag' + @tags = params[:sort] == "tag" end end diff --git a/app/controllers/announcements_controller.rb b/app/controllers/announcements_controller.rb index ba3492fff..8425893a6 100644 --- a/app/controllers/announcements_controller.rb +++ b/app/controllers/announcements_controller.rb @@ -1,6 +1,6 @@ # AnnouncementsController class AnnouncementsController < ApplicationController - layout 'administration' + layout "administration" before_action :set_announcement, except: [:new, :create, :index] authorize_resource except: [:new, :create, :index] @@ -17,7 +17,7 @@ def index end def new - @lecture = Lecture.find_by_id(params[:lecture]) + @lecture = Lecture.find_by(id: params[:lecture]) @announcement = Announcement.new(announcer: current_user, lecture: @lecture) authorize! :new, @announcement end @@ -33,14 +33,14 @@ def create # send notification email send_notification_email # redirection depending from where the announcement was created - unless @announcement.lecture.present? + if @announcement.lecture.blank? redirect_to announcements_path return end redirect_to edit_lecture_path(@announcement.lecture) return end - @errors = @announcement.errors[:details].join(', ') + @errors = @announcement.errors[:details].join(", ") end def propagate @@ -66,12 +66,12 @@ def create_notifications User end notifications = [] - users_to_notify.update_all(updated_at: Time.now) + users_to_notify.touch_all users_to_notify.find_each do |u| notifications << Notification.new(recipient: u, notifiable_id: @announcement.id, - notifiable_type: 'Announcement', - action: 'create') + notifiable_type: "Announcement", + action: "create") end # use activerecord-import gem to use only one SQL instruction Notification.import notifications @@ -86,19 +86,19 @@ def send_notification_email end I18n.available_locales.each do |l| local_recipients = recipients.where(locale: l) - if local_recipients.any? - NotificationMailer.with(recipients: local_recipients.pluck(:id), - locale: l, - announcement: @announcement) - .announcement_email.deliver_later - end + next unless local_recipients.any? + + NotificationMailer.with(recipients: local_recipients.pluck(:id), + locale: l, + announcement: @announcement) + .announcement_email.deliver_later end end def set_announcement - @announcement = Announcement.find_by_id(params[:id]) + @announcement = Announcement.find_by(id: params[:id]) return if @announcement.present? - redirect_to :root, alert: I18n.t('controllers.no_announcement') + redirect_to :root, alert: I18n.t("controllers.no_announcement") end end diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb index 8e0441d4d..937273189 100644 --- a/app/controllers/answers_controller.rb +++ b/app/controllers/answers_controller.rb @@ -8,7 +8,7 @@ def current_ability end def new - question = Question.find_by_id(params[:question_id]) + question = Question.find_by(id: params[:question_id]) @answer = Answer.new(value: true, question: question) authorize! :new, @answer I18n.locale = question&.locale_with_inheritance @@ -36,16 +36,16 @@ def destroy def update_answer_box @answer_id = params[:answer_id].to_i - @value = params[:value] == 'true' + @value = params[:value] == "true" end private def set_answer - @answer = Answer.find_by_id(params[:id]) + @answer = Answer.find_by(id: params[:id]) return if @answer.present? - redirect_to root_path, alert: I18n.t('controllers.no_answer') + redirect_to root_path, alert: I18n.t("controllers.no_answer") end def answer_params diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a19c8553f..0a5a60683 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,12 +9,10 @@ class ApplicationController < ActionController::Base before_action :set_locale after_action :store_interaction, if: :user_signed_in? - etag { current_user.try :id } + etag { current_user.try(:id) } def current_user - unless controller_name == 'administration' && action_name == 'index' - return super - end + return super unless controller_name == "administration" && action_name == "index" @current_user ||= super.tap do |user| ::ActiveRecord::Associations::Preloader.new(records: [user], @@ -22,20 +20,20 @@ def current_user [:lectures, :edited_media, :clickers, - edited_courses: + { edited_courses: [:editors, - lectures: [:term, - :teacher]], - edited_lectures: + { lectures: [:term, + :teacher] }], + edited_lectures: [:course, :term, :teacher], - given_lectures: + given_lectures: [:course, :term, :teacher], - notifications: - [:notifiable]]).call + notifications: + [:notifiable] }]).call end end @@ -46,7 +44,7 @@ def current_user rescue_from ActionController::InvalidAuthenticityToken do redirect_to main_app.root_url, - alert: I18n.t('controllers.session_expired') + alert: I18n.t("controllers.session_expired") end # determine where to send the user after login @@ -96,18 +94,18 @@ def store_user_location! def set_locale I18n.locale = current_user.try(:locale) || locale_param || cookie_locale_param || I18n.default_locale - unless user_signed_in? - cookies[:locale] = I18n.locale - end + return if user_signed_in? + + cookies[:locale] = I18n.locale end def store_interaction - return if controller_name.in?(['sessions', 'administration', 'users', - 'events', 'interactions', 'profile', - 'clickers', 'clicker_votes', 'registrations']) - return if controller_name == 'main' && action_name == 'home' - return if controller_name == 'tags' && action_name.in?(['fill_tag_select', - 'fill_course_tags']) + return if controller_name.in?(["sessions", "administration", "users", + "events", "interactions", "profile", + "clickers", "clicker_votes", "registrations"]) + return if controller_name == "main" && action_name == "home" + return if controller_name == "tags" && action_name.in?(["fill_tag_select", + "fill_course_tags"]) study_participant = current_user.anonymized_id if current_user.study_participant # as of Rack 2.0.8, the session_id is wrapped in a class of its own @@ -115,7 +113,7 @@ def store_interaction # see https://github.com/rack/rack/issues/1433 InteractionSaver.perform_async(request.session_options[:id].public_id, request.original_fullpath, - request.referrer, + request.referer, study_participant) end diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index 9f2b97f09..803c72927 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -10,12 +10,15 @@ def current_ability def new @assignment = Assignment.new - @lecture = Lecture.find_by_id(params[:lecture_id]) + @lecture = Lecture.find_by(id: params[:lecture_id]) @assignment.lecture = @lecture authorize! :new, @assignment set_assignment_locale end + def edit + end + def create @assignment = Assignment.new(assignment_params) authorize! :create, @assignment @@ -25,9 +28,6 @@ def create set_assignment_locale end - def edit - end - def update @assignment.update(assignment_params) @errors = @assignment.errors @@ -44,7 +44,7 @@ def cancel_edit end def cancel_new - @lecture = Lecture.find_by_id(params[:lecture]) + @lecture = Lecture.find_by(id: params[:lecture]) assignment = Assignment.new(lecture: @lecture) authorize! :cancel_new, assignment set_assignment_locale @@ -54,18 +54,18 @@ def cancel_new private def set_assignment - @assignment = Assignment.find_by_id(params[:id]) + @assignment = Assignment.find_by(id: params[:id]) @lecture = @assignment&.lecture set_assignment_locale and return if @assignment - redirect_to :root, alert: I18n.t('controllers.no_assignment') + redirect_to :root, alert: I18n.t("controllers.no_assignment") end def set_lecture - @lecture = Lecture.find_by_id(assignment_params[:lecture_id]) + @lecture = Lecture.find_by(id: assignment_params[:lecture_id]) return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') + redirect_to :root, alert: I18n.t("controllers.no_lecture") end def set_assignment_locale diff --git a/app/controllers/chapters_controller.rb b/app/controllers/chapters_controller.rb index 1b6188a3c..c39c976da 100644 --- a/app/controllers/chapters_controller.rb +++ b/app/controllers/chapters_controller.rb @@ -3,32 +3,22 @@ class ChaptersController < ApplicationController before_action :set_chapter, except: [:new, :create] authorize_resource except: [:new, :create] before_action :set_view_locale, only: [:edit] - layout 'administration' + layout "administration" def current_ability @current_ability ||= ChapterAbility.new(current_user) end - def edit - @section = Section.find_by_id(params[:section_id]) - end - - def update + def new + @lecture = Lecture.find_by(id: params[:lecture_id]) + @chapter = Chapter.new(lecture: @lecture) + authorize! :new, @chapter I18n.locale = @chapter.lecture.locale_with_inheritance || current_user.locale || I18n.default_locale - @chapter.update(chapter_params) - if @chapter.valid? - predecessor = params[:chapter][:predecessor] - # place the chapter in the correct position - if predecessor.present? - position = predecessor.to_i - position -= 1 if position > @chapter.position - @chapter.insert_at(position + 1) - end - redirect_to edit_chapter_path(@chapter) - return - end - @errors = @chapter.errors + end + + def edit + @section = Section.find_by(id: params[:section_id]) end def create @@ -47,12 +37,22 @@ def create @errors = @chapter.errors end - def new - @lecture = Lecture.find_by_id(params[:lecture_id]) - @chapter = Chapter.new(lecture: @lecture) - authorize! :new, @chapter + def update I18n.locale = @chapter.lecture.locale_with_inheritance || current_user.locale || I18n.default_locale + @chapter.update(chapter_params) + if @chapter.valid? + predecessor = params[:chapter][:predecessor] + # place the chapter in the correct position + if predecessor.present? + position = predecessor.to_i + position -= 1 if position > @chapter.position + @chapter.insert_at(position + 1) + end + redirect_to edit_chapter_path(@chapter) + return + end + @errors = @chapter.errors end def destroy @@ -69,10 +69,10 @@ def list_sections private def set_chapter - @chapter = Chapter.find_by_id(params[:id]) + @chapter = Chapter.find_by(id: params[:id]) return if @chapter.present? - redirect_to :root, alert: I18n.t('controllers.no_chapter') + redirect_to :root, alert: I18n.t("controllers.no_chapter") end def chapter_params diff --git a/app/controllers/clicker_votes_controller.rb b/app/controllers/clicker_votes_controller.rb index d2b684e04..5e2ae66d5 100644 --- a/app/controllers/clicker_votes_controller.rb +++ b/app/controllers/clicker_votes_controller.rb @@ -7,9 +7,7 @@ def create @clicker = @vote.clicker if cookies["clicker-#{@clicker.id}"] != @clicker.instance @vote.save - if @vote.valid? - cookies["clicker-#{@vote.clicker_id}"] = @clicker.instance - end + cookies["clicker-#{@vote.clicker_id}"] = @clicker.instance if @vote.valid? end head :ok end diff --git a/app/controllers/clickers_controller.rb b/app/controllers/clickers_controller.rb index 55ced38b8..d15b9098b 100644 --- a/app/controllers/clickers_controller.rb +++ b/app/controllers/clickers_controller.rb @@ -1,19 +1,32 @@ # ClickersController class ClickersController < ApplicationController skip_before_action :authenticate_user!, only: [:show, :edit, :open, :close, - :reset, - :get_votes_count, + :votes_count, :set_alternatives, :render_clickerizable_actions] before_action :set_clicker, except: [:new, :create] authorize_resource except: [:new, :create, :edit, :open, :close, :set_alternatives] - layout 'clicker', except: [:edit] + layout "clicker", except: [:edit] def current_ability @current_ability ||= ClickerAbility.new(current_user) end + def show + if params[:code] == @clicker.code + redirect_to edit_clicker_path(@clicker, + params: { code: @clicker.code }) + return + end + if stale?(etag: @clicker, + last_modified: [@clicker.updated_at, + Time.zone.parse(ENV.fetch("RAILS_CACHE_ID", nil))].max) + render :show + nil + end + end + def new @clicker = Clicker.new authorize! :new, @clicker @@ -23,30 +36,16 @@ def edit authorize! :edit, @clicker, @entered_code @user_path = clicker_url(@clicker, host: DefaultSetting::URL_HOST_SHORT) - .gsub('clickers', 'c') + .gsub("clickers", "c") @editor_path = clicker_url(@clicker, host: DefaultSetting::URL_HOST_SHORT, params: { code: @clicker.code }) - .gsub('clickers', 'c') + .gsub("clickers", "c") if user_signed_in? - render layout: 'administration' - return - end - render layout: 'edit_clicker' - end - - def show - if params[:code] == @clicker.code - redirect_to edit_clicker_path(@clicker, - params: { code: @clicker.code }) - return - end - if stale?(etag: @clicker, - last_modified: [@clicker.updated_at, - Time.parse(ENV['RAILS_CACHE_ID'])].max) - render :show + render layout: "administration" return end + render layout: "edit_clicker" end def create @@ -58,7 +57,7 @@ def create return end @errors = @clicker.errors - render layout: 'administration' + render layout: "administration" end def destroy @@ -69,28 +68,28 @@ def destroy def open authorize! :open, @clicker, @entered_code @clicker.open! - render layout: 'administration' if user_signed_in? + render layout: "administration" if user_signed_in? end def close authorize! :close, @clicker, @entered_code @clicker.close! - render layout: 'administration' if user_signed_in? + render layout: "administration" if user_signed_in? end def set_alternatives authorize! :set_alternatives, @clicker, @entered_code @clicker.update(alternatives: params[:alternatives].to_i) - head :ok, content_type: 'text/html' + head :ok, content_type: "text/html" end - def get_votes_count + def votes_count result = @clicker.votes.count render json: result end def associate_question - question = Question.find_by_id(clicker_params[:question_id]) + question = Question.find_by(id: clicker_params[:question_id]) @clicker.update(question: question, alternatives: question&.answers&.count || 3) redirect_to edit_clicker_path(@clicker) @@ -106,8 +105,8 @@ def remove_question def render_clickerizable_actions I18n.locale = current_user.locale - @medium = Medium.find_by_id(params[:medium_id]) - @question = Question.find_by_id(params[:medium_id]) + @medium = Medium.find_by(id: params[:medium_id]) + @question = Question.find_by(id: params[:medium_id]) end private @@ -122,11 +121,11 @@ def code_params end def set_clicker - @clicker = Clicker.find_by_id(params[:id]) + @clicker = Clicker.find_by(id: params[:id]) @code = user_signed_in? ? nil : @clicker&.code @entered_code = code_params[:code] return if @clicker - redirect_to :root, alert: I18n.t('controllers.no_clicker') + redirect_to :root, alert: I18n.t("controllers.no_clicker") end end diff --git a/app/controllers/commontator/comments_controller.rb b/app/controllers/commontator/comments_controller.rb index 7f670c7e7..31058b3da 100644 --- a/app/controllers/commontator/comments_controller.rb +++ b/app/controllers/commontator/comments_controller.rb @@ -1,222 +1,227 @@ # The CommmentsController is copied from the Commontator gem # Only minor customizations are made -class Commontator::CommentsController < Commontator::ApplicationController - before_action :set_thread, only: [:new, :create] - before_action :set_comment_and_thread, except: [:new, :create] - before_action :commontator_set_thread_variables, - only: [:show, :update, :delete, :undelete] - - # GET /comments/1 - def show - respond_to do |format| - format.html { redirect_to commontable_url } - format.js - end - end - - # GET /threads/1/comments/new - def new - @comment = Commontator::Comment.new(thread: @commontator_thread, - creator: @commontator_user) - parent_id = params.dig(:comment, :parent_id) - unless parent_id.blank? - parent = Commontator::Comment.find parent_id - @comment.parent = parent - @comment.body = "
#{ - Commontator.commontator_name(parent.creator) - }\n#{ - parent.body - }\n
\n" if [:q, - :b].include? @commontator_thread.config.comment_reply_style +module Commontator + class CommentsController < Commontator::ApplicationController + before_action :set_thread, only: [:new, :create] + before_action :set_comment_and_thread, except: [:new, :create] + before_action :commontator_set_thread_variables, + only: [:show, :update, :delete, :undelete] + + # GET /comments/1 + def show + respond_to do |format| + format.html { redirect_to commontable_url } + format.js + end end - security_transgression_unless @comment.can_be_created_by?(@commontator_user) - respond_to do |format| - format.html { redirect_to commontable_url } - format.js - end - end - - # POST /threads/1/comments - def create - @comment = Commontator::Comment.new( - thread: @commontator_thread, creator: @commontator_user, body: params.dig( - :comment, :body - ) - ) - parent_id = params.dig(:comment, :parent_id) - @comment.parent = Commontator::Comment.find(parent_id) unless parent_id.blank? - security_transgression_unless @comment.can_be_created_by?(@commontator_user) - - respond_to do |format| - if params[:cancel].blank? - if @comment.save - sub = @commontator_thread.config.thread_subscription.to_sym - @commontator_thread.subscribe(@commontator_user) if sub == :a || sub == :b - subscribe_mentioned if @commontator_thread.config.mentions_enabled - Commontator::Subscription.comment_created(@comment) - # The next line constitutes a customization of the original controller - update_unread_status - - @commontator_page = @commontator_thread.new_comment_page( - @comment.parent_id, @commontator_show_all - ) - - format.js - else - format.js { render :new } + # GET /threads/1/comments/new + def new + @comment = Commontator::Comment.new(thread: @commontator_thread, + creator: @commontator_user) + parent_id = params.dig(:comment, :parent_id) + if parent_id.present? + parent = Commontator::Comment.find(parent_id) + @comment.parent = parent + if [:q, + :b].include?(@commontator_thread.config.comment_reply_style) + @comment.body = "
#{ + Commontator.commontator_name(parent.creator) + }\n#{ + parent.body + }\n
\n" end - else - format.js { render :cancel } end + security_transgression_unless(@comment.can_be_created_by?(@commontator_user)) - format.html { redirect_to commontable_url } + respond_to do |format| + format.html { redirect_to commontable_url } + format.js + end end - end - # GET /comments/1/edit - def edit - @comment.editor = @commontator_user - security_transgression_unless @comment.can_be_edited_by?(@commontator_user) + # GET /comments/1/edit + def edit + @comment.editor = @commontator_user + security_transgression_unless(@comment.can_be_edited_by?(@commontator_user)) - respond_to do |format| - format.html { redirect_to commontable_url } - format.js + respond_to do |format| + format.html { redirect_to commontable_url } + format.js + end end - end - # PUT /comments/1 - def update - @comment.editor = @commontator_user - @comment.body = params.dig(:comment, :body) - security_transgression_unless @comment.can_be_edited_by?(@commontator_user) + # POST /threads/1/comments + def create + @comment = Commontator::Comment.new( + thread: @commontator_thread, creator: @commontator_user, body: params.dig( + :comment, :body + ) + ) + parent_id = params.dig(:comment, :parent_id) + @comment.parent = Commontator::Comment.find(parent_id) if parent_id.present? + security_transgression_unless(@comment.can_be_created_by?(@commontator_user)) + + respond_to do |format| + if params[:cancel].blank? + if @comment.save + sub = @commontator_thread.config.thread_subscription.to_sym + @commontator_thread.subscribe(@commontator_user) if [:a, :b].include?(sub) + subscribe_mentioned if @commontator_thread.config.mentions_enabled + Commontator::Subscription.comment_created(@comment) + # The next line constitutes a customization of the original controller + update_unread_status + + @commontator_page = @commontator_thread.new_comment_page( + @comment.parent_id, @commontator_show_all + ) + + format.js + else + format.js { render :new } + end + else + format.js { render :cancel } + end - respond_to do |format| - if params[:cancel].blank? - if @comment.save - subscribe_mentioned if @commontator_thread.config.mentions_enabled + format.html { redirect_to commontable_url } + end + end - format.js + # PUT /comments/1 + def update + @comment.editor = @commontator_user + @comment.body = params.dig(:comment, :body) + security_transgression_unless(@comment.can_be_edited_by?(@commontator_user)) + + respond_to do |format| + if params[:cancel].blank? + if @comment.save + subscribe_mentioned if @commontator_thread.config.mentions_enabled + + format.js + else + format.js { render :edit } + end else - format.js { render :edit } + @comment.restore_attributes + + format.js { render :cancel } end - else - @comment.restore_attributes - format.js { render :cancel } + format.html { redirect_to commontable_url } end - - format.html { redirect_to commontable_url } end - end - # PUT /comments/1/delete - def delete - security_transgression_unless @comment.can_be_deleted_by?(@commontator_user) + # PUT /comments/1/delete + def delete + security_transgression_unless(@comment.can_be_deleted_by?(@commontator_user)) - @comment.errors.add(:base, - t('commontator.comment.errors.already_deleted')) \ unless @comment.delete_by(@commontator_user) + @comment.errors.add(:base, + t("commontator.comment.errors.already_deleted")) + end - respond_to do |format| - format.html { redirect_to commontable_url } - format.js { render :delete } + respond_to do |format| + format.html { redirect_to commontable_url } + format.js { render :delete } + end end - end - # PUT /comments/1/undelete - def undelete - security_transgression_unless @comment.can_be_deleted_by?(@commontator_user) + # PUT /comments/1/undelete + def undelete + security_transgression_unless(@comment.can_be_deleted_by?(@commontator_user)) - @comment.errors.add(:base, t('commontator.comment.errors.not_deleted')) \ - unless @comment.undelete_by(@commontator_user) + @comment.errors.add(:base, t("commontator.comment.errors.not_deleted")) \ + unless @comment.undelete_by(@commontator_user) - respond_to do |format| - format.html { redirect_to commontable_url } - format.js { render :delete } + respond_to do |format| + format.html { redirect_to commontable_url } + format.js { render :delete } + end end - end - # PUT /comments/1/upvote - def upvote - security_transgression_unless @comment.can_be_voted_on_by?(@commontator_user) + # PUT /comments/1/upvote + def upvote + security_transgression_unless(@comment.can_be_voted_on_by?(@commontator_user)) - @comment.upvote_from @commontator_user + @comment.upvote_from(@commontator_user) - respond_to do |format| - format.html { redirect_to commontable_url } - format.js { render :vote } + respond_to do |format| + format.html { redirect_to commontable_url } + format.js { render :vote } + end end - end - # PUT /comments/1/downvote - def downvote - security_transgression_unless @comment.can_be_voted_on_by?(@commontator_user) && \ - @comment.thread.config.comment_voting.to_sym == :ld + # PUT /comments/1/downvote + def downvote + security_transgression_unless(@comment.can_be_voted_on_by?(@commontator_user) && \ + @comment.thread.config.comment_voting.to_sym == :ld) - @comment.downvote_from @commontator_user + @comment.downvote_from(@commontator_user) - respond_to do |format| - format.html { redirect_to commontable_url } - format.js { render :vote } + respond_to do |format| + format.html { redirect_to commontable_url } + format.js { render :vote } + end end - end - # PUT /comments/1/unvote - def unvote - security_transgression_unless @comment.can_be_voted_on_by?(@commontator_user) + # PUT /comments/1/unvote + def unvote + security_transgression_unless(@comment.can_be_voted_on_by?(@commontator_user)) - @comment.unvote voter: @commontator_user + @comment.unvote(voter: @commontator_user) - respond_to do |format| - format.html { redirect_to commontable_url } - format.js { render :vote } + respond_to do |format| + format.html { redirect_to commontable_url } + format.js { render :vote } + end end - end - protected + protected - def set_comment_and_thread - @comment = Commontator::Comment.find(params[:id]) - @commontator_thread = @comment.thread - end + def set_comment_and_thread + @comment = Commontator::Comment.find(params[:id]) + @commontator_thread = @comment.thread + end - def subscribe_mentioned - Commontator.commontator_mentions(@commontator_user, @commontator_thread, - '') - .where(id: params[:mentioned_ids]) - .each do |user| - @commontator_thread.subscribe(user) + def subscribe_mentioned + Commontator.commontator_mentions(@commontator_user, @commontator_thread, + "") + .where(id: params[:mentioned_ids]) + .find_each do |user| + @commontator_thread.subscribe(user) + end end - end - # This method ensures that the unread_comments flag is updated - # for users affected by the creation of a newly created comment - # It constitues a customization - def update_unread_status - medium = @commontator_thread.commontable - return unless medium.released.in?(['all', 'users', 'subscribers']) - - relevant_users = medium.teachable.media_scope.users - relevant_users.where.not(id: current_user.id) - .where(unread_comments: false) - .update_all(unread_comments: true) - - # make sure that the thread associated to this comment is marked as read - # by the comment creator (unless some other user posted a comment in it - # that has not yet been read) - @reader = Reader.find_or_create_by(user: current_user, - thread: @commontator_thread) - if unseen_comments? - @update_icon = true - return - end - @reader.update(updated_at: Time.current) - end + # This method ensures that the unread_comments flag is updated + # for users affected by the creation of a newly created comment + # It constitues a customization + def update_unread_status + medium = @commontator_thread.commontable + return unless medium.released.in?(["all", "users", "subscribers"]) + + relevant_users = medium.teachable.media_scope.users + relevant_users.where.not(id: current_user.id) + .where(unread_comments: false) + .update(unread_comments: true) + + # make sure that the thread associated to this comment is marked as read + # by the comment creator (unless some other user posted a comment in it + # that has not yet been read) + @reader = Reader.find_or_create_by(user: current_user, + thread: @commontator_thread) + if unseen_comments? + @update_icon = true + return + end + @reader.touch + end - def unseen_comments? - @commontator_thread.comments.any? do |c| - c.creator != current_user && c.created_at > @reader.updated_at + def unseen_comments? + @commontator_thread.comments.any? do |c| + c.creator != current_user && c.created_at > @reader.updated_at + end end - end + end end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index b0fd5e1ed..66daae203 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -1,7 +1,7 @@ class ConfirmationsController < Devise::ConfirmationsController private - def after_confirmation_path_for(resource_name, resource) + def after_confirmation_path_for(_resource_name, resource) sign_in(resource) # In case you want to sign in the user edit_profile_path end diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 67be24754..bfe9704a9 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -5,7 +5,7 @@ class CoursesController < ApplicationController before_action :check_if_enough_questions, only: [:take_random_quiz] before_action :check_for_consent authorize_resource except: [:create, :search] - layout 'administration' + layout "administration" def current_ability @current_ability ||= CourseAbility.new(current_user) @@ -15,22 +15,6 @@ def edit I18n.locale = @course.locale || I18n.default_locale end - def update - I18n.locale = @course.locale || I18n.default_locale - old_image_data = @course.image_data - @course.update(course_params) - @errors = @course.errors - return unless @errors.empty? - - @course.update(image: nil) if params[:course][:detach_image] == 'true' - changed_image = @course.image_data != old_image_data - if @course.image.present? && changed_image - @course.image_derivatives! - @course.save - end - @errors = @course.errors - end - def create @course = Course.new(course_params) authorize! :create, @course @@ -39,15 +23,31 @@ def create # set organizational_concept to default set_organizational_defaults redirect_to administration_path, - notice: I18n.t('controllers.created_course_success', + notice: I18n.t("controllers.created_course_success", course: @course.title, editors: @course.editors.map(&:name) - .join(', ')) + .join(", ")) return end @errors = @course.errors end + def update + I18n.locale = @course.locale || I18n.default_locale + old_image_data = @course.image_data + @course.update(course_params) + @errors = @course.errors + return unless @errors.empty? + + @course.update(image: nil) if params[:course][:detach_image] == "true" + changed_image = @course.image_data != old_image_data + if @course.image.present? && changed_image + @course.image_derivatives! + @course.save + end + @errors = @course.errors + end + def destroy @course.destroy # destroy all notifications related to this course @@ -80,14 +80,14 @@ def search private def set_course - @course = Course.find_by_id(params[:id]) + @course = Course.find_by(id: params[:id]) return if @course.present? - redirect_to :root, alert: I18n.t('controllers.no_course') + redirect_to :root, alert: I18n.t("controllers.no_course") end def set_course_admin - @course = Course.find_by_id(params[:id]) + @course = Course.find_by(id: params[:id]) return if @course.present? redirect_to administration_path @@ -121,15 +121,15 @@ def random_quiz_params # destroy all notifications related to this course def destroy_notifications - Notification.where(notifiable_id: @course.id, notifiable_type: 'Course') + Notification.where(notifiable_id: @course.id, notifiable_type: "Course") .delete_all end # fill organizational_concept with default view def set_organizational_defaults @course.update(organizational_concept: - render_to_string(partial: 'courses/' \ - 'organizational_default', + render_to_string(partial: "courses/" \ + "organizational_default", formats: :html, layout: false)) end @@ -137,7 +137,7 @@ def set_organizational_defaults def check_if_enough_questions return if @course.enough_questions? - redirect_to :root, alert: I18n.t('controllers.no_test') + redirect_to :root, alert: I18n.t("controllers.no_test") end def check_for_consent diff --git a/app/controllers/divisions_controller.rb b/app/controllers/divisions_controller.rb index 9c8ecdd53..dc2195d75 100644 --- a/app/controllers/divisions_controller.rb +++ b/app/controllers/divisions_controller.rb @@ -7,17 +7,12 @@ def current_ability @current_ability ||= DivisionAbility.new(current_user) end - def edit - end - def new @division = Division.new(program_id: params[:program_id].to_i) authorize! :new, @division end - def update - @division.update(division_params) - redirect_to classification_path + def edit end def create @@ -28,6 +23,11 @@ def create redirect_to classification_path end + def update + @division.update(division_params) + redirect_to classification_path + end + def destroy @division.destroy redirect_to classification_path @@ -36,10 +36,10 @@ def destroy private def set_division - @division = Division.find_by_id(params[:id]) + @division = Division.find_by(id: params[:id]) return if @division.present? - redirect_to root_path, alert: I18n.t('controllers.no_division') + redirect_to root_path, alert: I18n.t("controllers.no_division") end def division_params diff --git a/app/controllers/erdbeere_controller.rb b/app/controllers/erdbeere_controller.rb index d880b9114..23c78601e 100644 --- a/app/controllers/erdbeere_controller.rb +++ b/app/controllers/erdbeere_controller.rb @@ -1,38 +1,38 @@ # ExamplesController class ErdbeereController < ApplicationController authorize_resource class: false - layout 'application' + layout "application" def current_ability @current_ability ||= ErdbeereAbility.new(current_user) end def show_example - response = Faraday.get(ENV['ERDBEERE_API'] + "/examples/#{params[:id]}") + response = Faraday.get(ENV.fetch("ERDBEERE_API", nil) + "/examples/#{params[:id]}") @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] + JSON.parse(response.body)["embedded_html"] else - 'Something went wrong.' + "Something went wrong." end end def show_property - response = Faraday.get(ENV['ERDBEERE_API'] + "/properties/#{params[:id]}") + response = Faraday.get(ENV.fetch("ERDBEERE_API", nil) + "/properties/#{params[:id]}") @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] + JSON.parse(response.body)["embedded_html"] else - 'Something went wrong.' + "Something went wrong." end end def show_structure - id = params[:id] - response = Faraday.get(ENV['ERDBEERE_API'] + "/structures/#{params[:id]}") + params[:id] + response = Faraday.get(ENV.fetch("ERDBEERE_API", nil) + "/structures/#{params[:id]}") @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] + JSON.parse(response.body)["embedded_html"] else - 'Something went wrong.' + "Something went wrong." end end @@ -51,18 +51,18 @@ def cancel_edit_tags def display_info @id = params[:id] @sort = params[:sort] - response = Faraday.get(ENV['ERDBEERE_API'] + + response = Faraday.get(ENV.fetch("ERDBEERE_API", nil) + "/#{@sort.downcase.pluralize}/#{@id}/view_info") @content = JSON.parse(response.body) if response.status != 200 - @info = 'Something went wrong' + @info = "Something went wrong" return end - @info = if @sort == 'Structure' - @content['data']['attributes']['name'] + @info = if @sort == "Structure" + @content["data"]["attributes"]["name"] else - "#{@content['included'][0]['attributes']['name']}:"\ - "#{@content['data']['attributes']['name']}" + "#{@content["included"][0]["attributes"]["name"]}:" \ + "#{@content["data"]["attributes"]["name"]}" end end @@ -79,7 +79,7 @@ def update_tags removed_tags.each do |t| t.update(realizations: t.realizations - [[sort, id]]) end - if sort == 'Structure' + if sort == "Structure" redirect_to erdbeere_structure_path(id) return end @@ -87,28 +87,26 @@ def update_tags end def fill_realizations_select - response = Faraday.get(ENV['ERDBEERE_API'] + '/structures/') - @tag = Tag.find_by_id(params[:id]) + response = Faraday.get("#{ENV.fetch("ERDBEERE_API", nil)}/structures/") + @tag = Tag.find_by(id: params[:id]) hash = JSON.parse(response.body) - @structures = hash['data'].map do |d| - { id: d['id'], - name: d['attributes']['name'], - properties: d['relationships']['original_properties']['data'].map { |x| - x['id'] - } } + @structures = hash["data"].map do |d| + { id: d["id"], + name: d["attributes"]["name"], + properties: d["relationships"]["original_properties"]["data"].pluck("id") } end - @properties = hash['included'].map do |d| - { id: d['id'], - name: d['attributes']['name'] } + @properties = hash["included"].map do |d| + { id: d["id"], + name: d["attributes"]["name"] } end end def find_example - response = Faraday.get(ENV['ERDBEERE_API'] + '/find?' + find_params.to_query) + response = Faraday.get("#{ENV.fetch("ERDBEERE_API", nil)}/find?#{find_params.to_query}") @content = if response.status == 200 - JSON.parse(response.body)['embedded_html'] + JSON.parse(response.body)["embedded_html"] else - 'Something went wrong.' + "Something went wrong." end end diff --git a/app/controllers/interactions_controller.rb b/app/controllers/interactions_controller.rb index 81242996b..20cb9c5fd 100644 --- a/app/controllers/interactions_controller.rb +++ b/app/controllers/interactions_controller.rb @@ -1,7 +1,7 @@ # InteractionsController class InteractionsController < ApplicationController authorize_resource - layout 'administration' + layout "administration" def index end @@ -12,10 +12,10 @@ def export_interactions @interactions = Interaction.created_between(start_date, end_date) respond_to do |format| format.html { head :ok } - format.csv { - send_data @interactions.to_csv, - filename: "interactions-from-#{start_date}-to-#{end_date}-at-#{Time.now}.csv" - } + format.csv do + csv_filename = "interactions-from-#{start_date}-to-#{end_date}-at-#{Time.zone.now}.csv" + send_data(@interactions.to_csv, filename: csv_filename) + end end end @@ -25,10 +25,10 @@ def export_probes @probes = Probe.created_between(start_date, end_date) respond_to do |format| format.html { head :ok } - format.csv { - send_data @probes.to_csv, - filename: "probes-from-#{start_date}-to-#{end_date}-at-#{Time.now}.csv" - } + format.csv do + send_data(@probes.to_csv, + filename: "probes-from-#{start_date}-to-#{end_date}-at-#{Time.zone.now}.csv") + end end end diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb index 56c0bf885..648068202 100644 --- a/app/controllers/items_controller.rb +++ b/app/controllers/items_controller.rb @@ -7,12 +7,6 @@ def current_ability @current_ability ||= ItemAbility.new(current_user) end - def update - I18n.locale = @item.medium.locale_with_inheritance if @item.medium - @item.update(item_params) - @errors = @item.errors unless @item.valid? - end - def edit I18n.locale = @item.medium.locale_with_inheritance if @item.medium end @@ -34,10 +28,16 @@ def create render :update end + def update + I18n.locale = @item.medium.locale_with_inheritance if @item.medium + @item.update(item_params) + @errors = @item.errors unless @item.valid? + end + def destroy @medium = @item.medium @item.destroy - redirect_to edit_medium_path(@medium) if params[:from] == 'quarantine' + redirect_to edit_medium_path(@medium) if params[:from] == "quarantine" end # if an item is selected from within the reference editor in thyme, @@ -55,9 +55,7 @@ def set_item end def set_explanation - if @referral_id.zero? || @item != Referral.find(@referral_id).item - return @item.explanation - end + return @item.explanation if @referral_id.zero? || @item != Referral.find(@referral_id).item Referral.find(@referral_id).explanation end @@ -71,7 +69,7 @@ def item_params if filter[:medium_id].present? filter[:start_time] = TimeStamp.new(time_string: filter[:start_time]) end - filter[:section_id] = nil if filter[:section_id] == '' + filter[:section_id] = nil if filter[:section_id] == "" filter end end diff --git a/app/controllers/lectures_controller.rb b/app/controllers/lectures_controller.rb index b2cf71332..d108fbbc0 100644 --- a/app/controllers/lectures_controller.rb +++ b/app/controllers/lectures_controller.rb @@ -11,58 +11,18 @@ class LecturesController < ApplicationController before_action :set_view_locale, only: [:edit, :show, :subscribe_page, :show_random_quizzes] before_action :check_if_enough_questions, only: [:show_random_quizzes] - layout 'administration' + layout "administration" def current_ability @current_ability ||= LectureAbility.new(current_user) end - def edit - if stale?(etag: @lecture, - last_modified: [current_user.updated_at, @lecture.updated_at, - Time.parse(ENV['RAILS_CACHE_ID'])].max) - eager_load_stuff - end - end - - def update - editor_ids = lecture_params[:editor_ids] - if editor_ids != nil - # removes the empty String "" in the NEW array of editor ids - # and converts it into an array of integers - all_ids = editor_ids.map(&:to_i) - [0] - old_ids = @lecture.editor_ids - new_ids = all_ids - old_ids - - # returns an array of Users that match the given ids - recipients = User.where(id: new_ids) - - recipients.each do |r| - NotificationMailer.with(recipient: r, - locale: r.locale, - lecture: @lecture) - .new_editor_email.deliver_later - end - end - - @lecture.update(lecture_params) - if structure_params.present? - structure_ids = structure_params.select { |_k, v| v.to_i == 1 }.keys - .map(&:to_i) - @lecture.update(structure_ids: structure_ids) - end - @lecture.touch - @lecture.forum&.update(name: @lecture.forum_title) - redirect_to edit_lecture_path(@lecture) if @lecture.valid? - @errors = @lecture.errors - end - def show # deactivate http caching for the moment if stale?(etag: @lecture, last_modified: [current_user.updated_at, @lecture.updated_at, - Time.parse(ENV['RAILS_CACHE_ID']), + Time.zone.parse(ENV.fetch("RAILS_CACHE_ID", nil)), Thredded::UserDetail.find_by(user_id: current_user.id) &.last_seen_at || @lecture.updated_at, @lecture.forum&.updated_at || @lecture.updated_at].max) @@ -72,14 +32,14 @@ def show media: [:teachable, :tags], lessons: [media: [:tags]], chapters: [:lecture, - sections: [lessons: [:tags], - chapter: [:lecture], - tags: [:notions, - :lessons]]]) - .find_by_id(params[:id]) + { sections: [lessons: [:tags], + chapter: [:lecture], + tags: [:notions, + :lessons]] }]) + .find_by(id: params[:id]) @notifications = current_user.active_notifications(@lecture) @new_topics_count = @lecture.unread_forum_topics_count(current_user) || 0 - render layout: 'application' + render layout: "application" end end @@ -87,45 +47,85 @@ def new @lecture = Lecture.new authorize! :new, @lecture @from = params[:from] - return unless @from == 'course' + return unless @from == "course" # if new action was triggered from inside a course view, add the course # info to the lecture - @lecture.course = Course.find_by_id(params[:course]) + @lecture.course = Course.find_by(id: params[:course]) I18n.locale = @lecture.course.locale end + def edit + if stale?(etag: @lecture, + last_modified: [current_user.updated_at, @lecture.updated_at, + Time.zone.parse(ENV.fetch("RAILS_CACHE_ID", nil))].max) + eager_load_stuff + end + end + def create @lecture = Lecture.new(lecture_params) authorize! :create, @lecture @lecture.save if @lecture.valid? - @lecture.update(sort: 'special') if @lecture.course.term_independent + @lecture.update(sort: "special") if @lecture.course.term_independent # set organizational_concept to default set_organizational_defaults # set lenguage to default language set_language # depending on where the create action was trriggered from, return # to admin index view or edit course view - unless params[:lecture][:from] == 'course' + unless params[:lecture][:from] == "course" redirect_to administration_path, - notice: I18n.t('controllers.created_lecture_success', + notice: I18n.t("controllers.created_lecture_success", lecture: @lecture.title_with_teacher) return end redirect_to edit_course_path(@lecture.course), - notice: I18n.t('controllers.created_lecture_success', + notice: I18n.t("controllers.created_lecture_success", lecture: @lecture.title_with_teacher) return end @errors = @lecture.errors end + def update + editor_ids = lecture_params[:editor_ids] + unless editor_ids.nil? + # removes the empty String "" in the NEW array of editor ids + # and converts it into an array of integers + all_ids = editor_ids.map(&:to_i) - [0] + old_ids = @lecture.editor_ids + new_ids = all_ids - old_ids + + # returns an array of Users that match the given ids + recipients = User.where(id: new_ids) + + recipients.each do |r| + NotificationMailer.with(recipient: r, + locale: r.locale, + lecture: @lecture) + .new_editor_email.deliver_later + end + end + + @lecture.update(lecture_params) + if structure_params.present? + structure_ids = structure_params.select { |_k, v| v.to_i == 1 }.keys + .map(&:to_i) + @lecture.update(structure_ids: structure_ids) + end + @lecture.touch + @lecture.forum&.update(name: @lecture.forum_title) + redirect_to edit_lecture_path(@lecture) if @lecture.valid? + @errors = @lecture.errors + end + def publish - @lecture.update(released: 'all') - if params[:medium][:publish_media] == '1' + @lecture.update(released: "all") + if params[:medium][:publish_media] == "1" @lecture.media_with_inheritance - .update_all(released: params[:medium][:released]) + .update(released: params[:medium][:released]) end # create notifications about creation od this lecture and send email create_notifications @@ -177,12 +177,12 @@ def show_announcements @active_notification_count = current_user.active_notifications(@lecture) .size I18n.locale = @lecture.locale_with_inheritance - render layout: 'application' + render layout: "application" end def organizational I18n.locale = @lecture.locale_with_inheritance - render layout: 'application' + render layout: "application" end def import_media @@ -195,9 +195,9 @@ def import_media end def remove_imported_medium - @medium = Medium.find_by_id(params[:medium]) + @medium = Medium.find_by(id: params[:medium]) import = Import.find_by(teachable: @lecture, medium: @medium) - import.destroy if import + import&.destroy @lecture.reload @lecture.touch end @@ -208,24 +208,26 @@ def show_subscribers end def show_structures - render layout: 'application' + render layout: "application" end def edit_structures - render layout: 'application' + render layout: "application" end def search_examples if @lecture.structure_ids.any? - response = Faraday.get(ENV['ERDBEERE_API'] + '/search') - @form = JSON.parse(response.body)['embedded_html'] - @form.gsub!('token_placeholder', - '') + # rubocop:enable Style/StringConcatenation else - @form = I18n.t('erdbeere.no_structures') + @form = I18n.t("erdbeere.no_structures") end - render layout: 'application' + render layout: "application" end def close_comments @@ -246,7 +248,7 @@ def search @total = search.total @lectures = Kaminari.paginate_array(results, total_count: @total) .page(params[:page]).per(search_params[:per]) - @results_as_list = search_params[:results_as_list] == 'true' + @results_as_list = search_params[:results_as_list] == "true" return unless @total.zero? return unless search_params[:fulltext]&.length.to_i > 1 @@ -255,24 +257,24 @@ def search def show_random_quizzes @course = @lecture.course - render layout: 'application' + render layout: "application" end def display_course @course = @lecture.course I18n.locale = @course.locale || @lecture.locale - render layout: 'application' + render layout: "application" end def subscribe_page - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end def import_toc imported_lecture = Lecture - .find_by_id(import_toc_params[:imported_lecture_id]) - import_sections = import_toc_params[:import_sections] == '1' - import_tags = import_toc_params[:import_tags] == '1' + .find_by(id: import_toc_params[:imported_lecture_id]) + import_sections = import_toc_params[:import_sections] == "1" + import_tags = import_toc_params[:import_tags] == "1" @lecture.import_toc!(imported_lecture, import_sections, import_tags) redirect_to edit_lecture_path(@lecture) end @@ -280,10 +282,10 @@ def import_toc private def set_lecture - @lecture = Lecture.find_by_id(params[:id]) + @lecture = Lecture.find_by(id: params[:id]) return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') + redirect_to :root, alert: I18n.t("controllers.no_lecture") end def set_lecture_cookie @@ -300,7 +302,9 @@ def check_for_consent end def check_for_subscribe - redirect_to subscribe_lecture_page_path(@lecture.id) unless @lecture.in?(current_user.lectures) + return if @lecture.in?(current_user.lectures) + + redirect_to subscribe_lecture_page_path(@lecture.id) end def lecture_params @@ -310,12 +314,10 @@ def lecture_params :organizational_on_top, :disable_teacher_display, :content_mode, :passphrase, :sort, :comments_disabled, :submission_max_team_size, :submission_grace_period] - if action_name == 'update' && current_user.can_update_personell?(@lecture) - allowed_params.concat([:teacher_id, editor_ids: []]) - end - if action_name == 'create' - allowed_params.concat([:course_id, :teacher_id, editor_ids: []]) + if action_name == "update" && current_user.can_update_personell?(@lecture) + allowed_params.push(:teacher_id, { editor_ids: [] }) end + allowed_params.push(:course_id, :teacher_id, { editor_ids: [] }) if action_name == "create" params.require(:lecture).permit(allowed_params) end @@ -337,8 +339,8 @@ def create_notifications User.find_each do |u| notifications << Notification.new(recipient: u, notifiable_id: @lecture.id, - notifiable_type: 'Lecture', - action: 'create') + notifiable_type: "Lecture", + action: "create") end Notification.import notifications end @@ -347,25 +349,25 @@ def send_notification_email recipients = User.where(email_for_teachable: true) I18n.available_locales.each do |l| local_recipients = recipients.where(locale: l) - if local_recipients.any? - NotificationMailer.with(recipients: local_recipients.pluck(:id), - locale: l, - lecture: @lecture) - .new_lecture_email.deliver_later - end + next unless local_recipients.any? + + NotificationMailer.with(recipients: local_recipients.pluck(:id), + locale: l, + lecture: @lecture) + .new_lecture_email.deliver_later end end # destroy all notifications related to this lecture def destroy_notifications - Notification.where(notifiable_id: @lecture.id, notifiable_type: 'Lecture') + Notification.where(notifiable_id: @lecture.id, notifiable_type: "Lecture") .delete_all end # fill organizational_concept with default view def set_organizational_defaults - partial_path = 'lectures/organizational/' - partial_path += @lecture.seminar? ? 'seminar' : 'lecture' + partial_path = "lectures/organizational/" + partial_path += @lecture.seminar? ? "seminar" : "lecture" @lecture.update(organizational_concept: render_to_string(partial: partial_path, formats: :html, @@ -384,11 +386,11 @@ def eager_load_stuff media: [:teachable, :tags], lessons: [media: [:tags]], chapters: [:lecture, - sections: [lessons: [:tags], - chapter: [:lecture], - tags: [:notions, - :lessons]]]) - .find_by_id(params[:id]) + { sections: [lessons: [:tags], + chapter: [:lecture], + tags: [:notions, + :lessons]] }]) + .find_by(id: params[:id]) @media = @lecture.media_with_inheritance_uncached_eagerload_stuff lecture_tags = @lecture.tags @course_tags = @lecture.course_tags(lecture_tags: lecture_tags) @@ -400,23 +402,23 @@ def eager_load_stuff def set_erdbeere_data @structure_ids = @lecture.structure_ids - response = Faraday.get(ENV['ERDBEERE_API'] + '/structures') + response = Faraday.get("#{ENV.fetch("ERDBEERE_API", nil)}/structures") response_hash = if response.status == 200 JSON.parse(response.body) else - { 'data' => {}, 'included' => {} } + { "data" => {}, "included" => {} } end - @all_structures = response_hash['data'] + @all_structures = response_hash["data"] @structures = @all_structures.select do |s| - s['id'].to_i.in?(@structure_ids) + s["id"].to_i.in?(@structure_ids) end - @properties = response_hash['included'] + @properties = response_hash["included"] end def search_params types = params[:search][:types] - types = [types] if types && !types.kind_of?(Array) - types -= [''] if types + types = [types] if types && !types.is_a?(Array) + types -= [""] if types types = nil if types == [] params[:search][:types] = types params[:search][:user_id] = current_user.id @@ -432,6 +434,6 @@ def search_params def check_if_enough_questions return if @lecture.course.enough_questions? - redirect_to :root, alert: I18n.t('controllers.no_test') + redirect_to :root, alert: I18n.t("controllers.no_test") end end diff --git a/app/controllers/lessons_controller.rb b/app/controllers/lessons_controller.rb index 2425dce7d..bd8ca7e4f 100644 --- a/app/controllers/lessons_controller.rb +++ b/app/controllers/lessons_controller.rb @@ -2,7 +2,7 @@ class LessonsController < ApplicationController before_action :set_lesson, except: [:new, :create] authorize_resource except: [:new, :create] - layout 'administration' + layout "administration" def current_ability @current_ability ||= LessonAbility.new(current_user) @@ -10,22 +10,22 @@ def current_ability def show I18n.locale = @lesson.locale_with_inheritance - render layout: 'application_no_sidebar' - end - - def edit - I18n.locale = @lesson.locale_with_inheritance + render layout: "application_no_sidebar" end def new - @lecture = Lecture.find_by_id(params[:lecture_id]) + @lecture = Lecture.find_by(id: params[:lecture_id]) I18n.locale = @lecture.locale_with_inheritance if @lecture @lesson = Lesson.new(lecture: @lecture) - section = Section.find_by_id(params[:section_id]) + section = Section.find_by(id: params[:section_id]) @lesson.sections << section if section authorize! :new, @lesson end + def edit + I18n.locale = @lesson.locale_with_inheritance + end + def create @lesson = Lesson.new(lesson_params) authorize! :create, @lesson @@ -34,7 +34,7 @@ def create @lesson.tags = @lesson.sections.map(&:tags).flatten @lesson.save @errors = @lesson.errors - if @lesson.valid? && params[:commit] == t('buttons.save_and_edit') + if @lesson.valid? && params[:commit] == t("buttons.save_and_edit") redirect_to edit_lesson_path(@lesson) return end @@ -45,7 +45,7 @@ def update I18n.locale = @lesson.lecture.locale_with_inheritance @lesson.update(lesson_params) @errors = @lesson.errors - return unless @errors.blank? + return if @errors.present? update_media_order if params[:lesson][:media_order] @tags_without_section = @lesson.tags_without_section @@ -63,7 +63,7 @@ def destroy media.each do |m| m.update(teachable: lecture, description: m.description.presence || - (m.title + ' (' + I18n.t('admin.lesson.destroyed') + ')')) + "#{m.title} (#{I18n.t("admin.lesson.destroyed")})") end @lesson.destroy redirect_to edit_lecture_path(lecture) @@ -72,10 +72,10 @@ def destroy private def set_lesson - @lesson = Lesson.find_by_id(params[:id]) + @lesson = Lesson.find_by(id: params[:id]) return if @lesson.present? - redirect_to :root, alert: I18n.t('controllers.no_lesson') + redirect_to :root, alert: I18n.t("controllers.no_lesson") end def lesson_params diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 490b5103c..8fa45237c 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -2,7 +2,7 @@ class MainController < ApplicationController before_action :check_for_consent authorize_resource class: false, only: :start - layout 'application_no_sidebar' + layout "application_no_sidebar" def current_ability @current_ability ||= MainAbility.new(current_user) @@ -10,11 +10,11 @@ def current_ability def home cookies[:locale] = current_user.locale if user_signed_in? - get_announcements + announcements end def error - redirect_to :root, alert: I18n.t('controllers.no_page') + redirect_to :root, alert: I18n.t("controllers.no_page") end def news @@ -29,7 +29,7 @@ def comments @media_comments = current_user.media_latest_comments @media_comments.select! do |m| (Reader.find_by(user: current_user, thread: m[:thread]) - &.updated_at || (Time.now - 1000.years)) < m[:latest_comment].created_at && + &.updated_at || 1000.years.ago) < m[:latest_comment].created_at && m[:medium].visible_for_user?(current_user) end @media_array = Kaminari.paginate_array(@media_comments) @@ -43,7 +43,7 @@ def start :term) .sort end - get_announcements + announcements @talks = current_user.talks.includes(lecture: :term) .select { |t| t.visible_for_user?(current_user) } .sort_by do |t| @@ -60,7 +60,7 @@ def check_for_consent redirect_to consent_profile_path unless current_user.consents end - def get_announcements + def announcements @announcements = Announcement.where(on_main_page: true, lecture: nil) .pluck(:details) .join('
') diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 1c9633596..cfb78dd6e 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -21,7 +21,7 @@ class MediaController < ApplicationController :fill_medium_preview, :render_medium_actions, :render_import_media, :render_import_vertex, :cancel_import_media, :cancel_import_vertex] - layout 'administration' + layout "administration" def current_ability @current_ability ||= MediumAbility.new(current_user) @@ -30,16 +30,16 @@ def current_ability def index authorize! :index, Medium.new @media = paginated_results - render layout: 'application' + render layout: "application" end def show # destroy the notifications related to the medium - current_user.notifications.where(notifiable_type: 'Medium', - notifiable_id: @medium.id).each(&:destroy) + current_user.notifications.where(notifiable_type: "Medium", + notifiable_id: @medium.id).find_each(&:destroy) I18n.locale = @medium.locale_with_inheritance commontator_thread_show(@medium) - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end def new @@ -48,7 +48,7 @@ def new level: 1, locale: @teachable.locale_with_inheritance) I18n.locale = @teachable.locale_with_inheritance - @medium.sort = params[:sort] ? params[:sort] : 'Kaviar' + @medium.sort = params[:sort] || "Kaviar" end def edit @@ -57,6 +57,46 @@ def edit render layout: current_user.layout end + def create + @medium = Medium.new(medium_params) + @medium.locale = @medium.teachable&.locale + @medium.editors = [current_user] + @medium.tags = @medium.teachable.tags if @medium.teachable.instance_of?(::Lesson) + authorize! :create, @medium + @medium.save + if @medium.valid? + if @medium.sort == "Remark" + @medium.update(type: "Remark", + text: I18n.t("admin.remark.initial_text")) + end + if @medium.sort == "Question" + solution = Solution.new(MampfExpression.trivial_instance) + @medium.update(type: "Question", + text: I18n.t("admin.question.initial_text"), + level: 1, + independent: false, + solution: solution, + question_sort: "mc") + Answer.create(question: @medium.becomes(Question), + text: "0", + value: true) + end + if @medium.sort == "Quiz" + @medium.update(type: "Quiz") + @medium.update(quiz_graph: QuizGraph.new(vertices: {}, + edges: {}, + root: 0, + default_table: {}, + hide_solution: []), + level: 1) + end + redirect_to edit_medium_path(@medium) + return + end + @errors = @medium.errors + render :update + end + def update I18n.locale = @medium.locale_with_inheritance old_manuscript_data = @medium.manuscript_data @@ -71,7 +111,7 @@ def update # update the associated tags), causing trouble for caching) @medium.touch # touch lectures that import this medium - @medium.importing_lectures.update_all(updated_at: Time.now) + @medium.importing_lectures.touch_all @medium.sanitize_type! # detach components if this was chosen by the user detach_components @@ -94,7 +134,7 @@ def update # refreshed_video = @medium.video # @medium.update(video_data: refreshed_video.to_json) end - if @medium.sort == 'Quiz' && params[:medium][:create_quiz_graph] == '1' + if @medium.sort == "Quiz" && params[:medium][:create_quiz_graph] == "1" @medium.becomes(Quiz).update(level: 1, quiz_graph: QuizGraph.new(vertices: {}, edges: {}, @@ -106,7 +146,7 @@ def update # remove items that correspond to named destinations that no longer # exist in the manuscript, but keep those that are referenced # from other places - if @medium.sort == 'Script' && changed_manuscript + if @medium.sort == "Script" && changed_manuscript @medium.update(imported_manuscript: false) @quarantine_added = @medium.update_pdf_destinations! if @quarantine_added.any? @@ -123,53 +163,11 @@ def update end end @tags_without_section = [] - return unless @medium.teachable.class.to_s == 'Lesson' + return unless @medium.teachable.instance_of?(::Lesson) add_tags_in_lesson_and_sections end - def create - @medium = Medium.new(medium_params) - @medium.locale = @medium.teachable&.locale - @medium.editors = [current_user] - if @medium.teachable.class.to_s == 'Lesson' - @medium.tags = @medium.teachable.tags - end - authorize! :create, @medium - @medium.save - if @medium.valid? - if @medium.sort == 'Remark' - @medium.update(type: 'Remark', - text: I18n.t('admin.remark.initial_text')) - end - if @medium.sort == 'Question' - solution = Solution.new(MampfExpression.trivial_instance) - @medium.update(type: 'Question', - text: I18n.t('admin.question.initial_text'), - level: 1, - independent: false, - solution: solution, - question_sort: 'mc') - Answer.create(question: @medium.becomes(Question), - text: '0', - value: true) - end - if @medium.sort == 'Quiz' - @medium.update(type: 'Quiz') - @medium.update(quiz_graph: QuizGraph.new(vertices: {}, - edges: {}, - root: 0, - default_table: {}, - hide_solution: []), - level: 1) - end - redirect_to edit_medium_path(@medium) - return - end - @errors = @medium.errors - render :update - end - def publish publisher = MediumPublisher.parse(@medium, current_user, publish_params) @errors = publisher.errors @@ -189,15 +187,15 @@ def destroy # destroy all notifications related to this medium destroy_notifications @medium.teachable.touch - if @medium.teachable_type == 'Lecture' + if @medium.teachable_type == "Lecture" redirect_to edit_lecture_path(@medium.teachable) return end - if @medium.teachable_type == 'Lesson' + if @medium.teachable_type == "Lesson" redirect_to edit_lesson_path(@medium.teachable) return end - if @medium.teachable_type == 'Talk' + if @medium.teachable_type == "Talk" if current_user.in?(@medium.teachable.speakers) redirect_to assemble_talk_path(@medium.teachable) return @@ -218,10 +216,12 @@ def search # get all media, then set them to only those that are visible to the current user if !current_user.active_teachable_editor? || search_params[:access].blank? filter_media = true - params["search"]["access"] = 'irrelevant' + params["search"]["access"] = "irrelevant" + end + if search_params[:answers_count].blank? + params["search"]["answers_count"] = + "irrelevant" end - params["search"]["answers_count"] = - 'irrelevant' if search_params[:answers_count].blank? search = Medium.search_by(search_params, params[:page]) search.execute @@ -231,14 +231,16 @@ def search # in the case of a search with tag_operator 'or', we # execute two searches and merge the results, where media # with the selected tags are now shown at the front of the list - if search_params[:tag_operator] == "or" and search_params[:all_tags] == "0" and search_params[:fulltext].size >= 2 - params["search"]["all_tags"] = '1' + if (search_params[:tag_operator] == "or") \ + && (search_params[:all_tags] == "0") \ + && (search_params[:fulltext].size >= 2) + params["search"]["all_tags"] = "1" search_no_tags = Medium.search_by(search_params, params[:page]) search_no_tags.execute results_no_tags = search_no_tags.results results = (results + results_no_tags).uniq @total = results.size - params["search"]["all_tags"] = '0' + params["search"]["all_tags"] = "0" end if filter_media @@ -251,40 +253,41 @@ def search @media = Kaminari.paginate_array(results, total_count: @total) .page(params[:page]).per(search_params[:per]) @purpose = search_params[:purpose] - @results_as_list = search_params[:results_as_list] == 'true' - if @purpose.in?(['quiz', 'import']) + @results_as_list = search_params[:results_as_list] == "true" + if @purpose.in?(["quiz", "import"]) render template: "media/catalog/import_preview" return end return unless @total.zero? - return unless search_params[:fulltext]&.length.to_i > 1 + + nil unless search_params[:fulltext]&.length.to_i > 1 end # play the video using thyme player def play if @medium.video.nil? - redirect_to :root, alert: I18n.t('controllers.no_video') + redirect_to :root, alert: I18n.t("controllers.no_video") return end I18n.locale = @medium.locale_with_inheritance @vtt_container = @medium.create_vtt_container! @time = params[:time] - render layout: 'thyme' + render layout: "thyme" end # show the pdf, optionally at specified page or named destination def display if @medium.manuscript.nil? - redirect_to :root, alert: I18n.t('controllers.no_manuscript') + redirect_to :root, alert: I18n.t("controllers.no_manuscript") return end if params[:destination].present? - redirect_to @medium.manuscript_url_with_host + '#' + - params[:destination].to_s, allow_other_host: true + redirect_to "#{@medium.manuscript_url_with_host}##{params[:destination]}", + allow_other_host: true return elsif params[:page].present? - redirect_to @medium.manuscript_url_with_host + '#page=' + - params[:page].to_s, allow_other_host: true + redirect_to "#{@medium.manuscript_url_with_host}#page=#{params[:page]}", + allow_other_host: true return end redirect_to @medium.manuscript_url_with_host, @@ -294,11 +297,11 @@ def display # run the geogebra applet using Geogebra's Javascript API def geogebra if @medium.geogebra.nil? - redirect_to :root, alert: I18n.t('controllers.no_geogebra') + redirect_to :root, alert: I18n.t("controllers.no_geogebra") return end I18n.locale = @medium.locale_with_inheritance - render layout: 'geogebra' + render layout: "geogebra" end # add a toc item for the video @@ -307,8 +310,8 @@ def add_item @time = params[:time].to_f @item = Item.new(medium: @medium, start_time: TimeStamp.new(total_seconds: @time)) - if @medium.sort == 'Kaviar' && - @medium.teachable_type.in?(['Lesson', 'Lecture']) + if @medium.sort == "Kaviar" && + @medium.teachable_type.in?(["Lesson", "Lecture"]) @item.section = @medium.teachable&.sections&.first end end @@ -322,15 +325,13 @@ def add_reference start_time: TimeStamp.new(total_seconds: @time), end_time: TimeStamp.new(total_seconds: @end_time)) @item_selection = @medium.teachable.media_scope.media_items_with_inheritance - @item = Item.new(sort: 'link') + @item = Item.new(sort: "link") end # add a screenshot for the video def add_screenshot - tempfile = Tempfile.new(['screenshot', '.png']) - File.open(tempfile, 'wb') do |f| - f.write params[:image].read - end + tempfile = Tempfile.new(["screenshot", ".png"]) + File.binwrite(tempfile, params[:image].read) @medium.screenshot = File.open(tempfile) @medium.save if @medium.valid? @@ -352,7 +353,7 @@ def remove_screenshot # start the thyme editor def enrich I18n.locale = @medium.locale_with_inheritance - render layout: 'enrich' + render layout: "enrich" end # if the medium is associated to a lesson of a lecture which is in script mode @@ -392,62 +393,70 @@ def fill_media_select end def update_tags - if current_user.admin || @medium.edited_with_inheritance_by?(current_user) - @medium.tags = Tag.where(id: params[:tag_ids]) - @medium.update(updated_at: Time.now) - end + return unless current_user.admin || @medium.edited_with_inheritance_by?(current_user) + + @medium.tags = Tag.where(id: params[:tag_ids]) + @medium.touch end def register_download head :ok end - def get_statistics + def statistics I18n.locale = @medium.locale || I18n.default_locale medium_consumption = Consumption.where(medium_id: @medium.id) if @medium.video.present? - @video_downloads = medium_consumption.where(sort: 'video', - mode: 'download').pluck(:created_at).map(&:to_date).tally.map { |k, t| + @video_downloads = medium_consumption + .where(sort: "video", mode: "download") + .pluck(:created_at) + .map(&:to_date).tally.map do |k, t| { x: k, y: t } - }.to_json - @video_downloads_count = medium_consumption.where(sort: 'video', - mode: 'download').count - @video_thyme = medium_consumption.where(sort: 'video', - mode: 'thyme').pluck(:created_at).map(&:to_date).tally.map { |k, t| + end.to_json + @video_downloads_count = medium_consumption.where(sort: "video", + mode: "download").count + @video_thyme = medium_consumption + .where(sort: "video", mode: "thyme") + .pluck(:created_at) + .map(&:to_date).tally.map do |k, t| { x: k, y: t } - }.to_json - @video_thyme_count = medium_consumption.where(sort: 'video', - mode: 'thyme').count + end.to_json + @video_thyme_count = medium_consumption.where(sort: "video", + mode: "thyme").count end if @medium.manuscript.present? - @manuscript_access = medium_consumption.where(sort: 'manuscript').pluck(:created_at).map(&:to_date).tally.map { |k, t| + @manuscript_access = medium_consumption + .where(sort: "manuscript") + .pluck(:created_at) + .map(&:to_date).tally.map do |k, t| { x: k, y: t } - }.to_json - @manuscript_access_count = medium_consumption.where(sort: 'manuscript').count - end - if @medium.sort == 'Quiz' - - @quiz_plays = medium_consumption.where(sort: 'quiz', - mode: 'browser').pluck(:created_at).map(&:to_date).tally.map { |k, t| - { x: k, y: t } - }.to_json - @quiz_plays_count = medium_consumption.where(sort: 'quiz', - mode: 'browser').count - @quiz_finished_count = Probe.finished_quizzes(@medium) - @global_success = Probe.global_success_in_quiz(@medium.becomes(Quiz)) - @global_success_details = Probe.global_success_details(@medium.becomes(Quiz)) - @question_count = @medium.becomes(Quiz).questions_count - @local_success = Probe.local_success_in_quiz(@medium.becomes(Quiz)) - end + end.to_json + @manuscript_access_count = medium_consumption.where(sort: "manuscript").count + end + return unless @medium.sort == "Quiz" + + @quiz_plays = medium_consumption + .where(sort: "quiz", mode: "browser") + .pluck(:created_at) + .map(&:to_date).tally.map do |k, t| + { x: k, y: t } + end.to_json + @quiz_plays_count = medium_consumption.where(sort: "quiz", + mode: "browser").count + @quiz_finished_count = Probe.finished_quizzes(@medium) + @global_success = Probe.global_success_in_quiz(@medium.becomes(Quiz)) + @global_success_details = Probe.global_success_details(@medium.becomes(Quiz)) + @question_count = @medium.becomes(Quiz).questions_count + @local_success = Probe.local_success_in_quiz(@medium.becomes(Quiz)) end def show_comments commontator_thread_show(@medium) - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end def cancel_publication @@ -457,27 +466,27 @@ def cancel_publication def fill_medium_preview I18n.locale = current_user.locale - @medium = Medium.find_by_id(params[:id])&.becomes(Medium) || Medium.new + @medium = Medium.find_by(id: params[:id])&.becomes(Medium) || Medium.new authorize! :fill_medium_preview, @medium end def render_medium_actions I18n.locale = current_user.locale - @medium = Medium.find_by_id(params[:id])&.becomes(Medium) || Medium.new + @medium = Medium.find_by(id: params[:id])&.becomes(Medium) || Medium.new authorize! :render_medium_actions, @medium end def render_import_media @id = params[:id] - @purpose = 'import' + @purpose = "import" authorize! :render_import_media, Medium.new end def render_import_vertex @id = params[:id] quiz_id = params[:quiz_id] - I18n.locale = Quiz.find_by_id(quiz_id)&.locale_with_inheritance - @purpose = 'quiz' + I18n.locale = Quiz.find_by(id: quiz_id)&.locale_with_inheritance + @purpose = "quiz" authorize! :render_import_vertex, Medium.new render :render_import_media end @@ -492,7 +501,7 @@ def cancel_import_media def cancel_import_vertex authorize! :cancel_import_vertex, Medium.new - I18n.locale = Quiz.find_by_id(params[:quiz_id])&.locale_with_inheritance + I18n.locale = Quiz.find_by(id: params[:quiz_id])&.locale_with_inheritance render :cancel_import_media end @@ -510,9 +519,9 @@ def fill_quizzable_preview def fill_reassign_modal @quizzable = @medium.becomes_quizzable I18n.locale = @quizzable.locale_with_inheritance - @in_quiz = params[:in_quiz] == 'true' + @in_quiz = params[:in_quiz] == "true" @quiz_id = params[:quiz_id].to_i - @no_rights = params[:rights] == 'none' + @no_rights = params[:rights] == "none" end private @@ -539,39 +548,39 @@ def publish_params end def set_medium - @medium = Medium.find_by_id(params[:id])&.becomes(Medium) - return if @medium.present? && @medium.sort != 'RandomQuiz' + @medium = Medium.find_by(id: params[:id])&.becomes(Medium) + return if @medium.present? && @medium.sort != "RandomQuiz" - redirect_to :root, alert: I18n.t('controllers.no_medium') + redirect_to :root, alert: I18n.t("controllers.no_medium") end def set_lecture - @lecture = Lecture.find_by_id(params[:id]) + @lecture = Lecture.find_by(id: params[:id]) # store current lecture in cookie if @lecture cookies[:current_lecture_id] = @lecture.id return end - redirect_to :root, alert: I18n.t('controllers.no_lecture') + redirect_to :root, alert: I18n.t("controllers.no_lecture") end def set_teachable - if params[:teachable_type].in?(['Course', 'Lecture', 'Lesson', 'Talk']) && + if params[:teachable_type].in?(["Course", "Lecture", "Lesson", "Talk"]) && params[:teachable_id].present? @teachable = params[:teachable_type].constantize - .find_by_id(params[:teachable_id]) + .find_by(id: params[:teachable_id]) end end def detach_components - if params[:medium][:detach_video] == 'true' + if params[:medium][:detach_video] == "true" @medium.update(video: nil) @medium.update(screenshot: nil) end - if params[:medium][:detach_geogebra] == 'true' || @medium.sort != 'Sesam' + if params[:medium][:detach_geogebra] == "true" || @medium.sort != "Sesam" @medium.update(geogebra: nil) end - return unless params[:medium][:detach_manuscript] == 'true' + return unless params[:medium][:detach_manuscript] == "true" @medium.update(manuscript: nil) end @@ -580,10 +589,10 @@ def sanitize_params reveal_contradictions sanitize_page! sanitize_per! - params[:all] = (params[:all] == 'true') || (cookies[:all] == 'true') + params[:all] = (params[:all] == "true") || (cookies[:all] == "true") cookies[:all] = params[:all] cookies[:per] = false if cookies[:all] - params[:reverse] = params[:reverse] == 'true' + params[:reverse] = params[:reverse] == "true" end def check_for_consent @@ -613,12 +622,12 @@ def search_results visible_search_results = current_user.filter_visible_media(search_arel) search_results &= visible_search_results total = search_results.size - @lecture = Lecture.find_by_id(params[:id]) + @lecture = Lecture.find_by(id: params[:id]) # filter out stuff from course level for generic users - if params[:visibility] == 'lecture' - search_results.reject! { |m| m.teachable_type == 'Course' } + if params[:visibility] == "lecture" + search_results.reject! { |m| m.teachable_type == "Course" } # yields only lecture media and course media - elsif params[:visibility] == 'all' + elsif params[:visibility] == "all" # yields all lecture media and course media else # this is the default setting: 'thematic' selection of media @@ -627,11 +636,11 @@ def search_results unless current_user.admin || @lecture.edited_by?(current_user) lecture_tags = @lecture.tags_including_media_tags search_results.reject! do |m| - m.teachable_type == 'Course' && (m.tags & lecture_tags).blank? + m.teachable_type == "Course" && !m.tags.intersect?(lecture_tags) end end end - sort = params[:project] == 'keks' ? 'Quiz' : params[:project]&.capitalize + sort = params[:project] == "keks" ? "Quiz" : params[:project]&.capitalize search_results += @lecture.imported_media .where(sort: sort) .locally_visible @@ -643,10 +652,10 @@ def search_results end def reveal_contradictions - return unless params[:lecture_id].present? + return if params[:lecture_id].blank? return if params[:lecture_id].to_i.in?(@course.lecture_ids) - redirect_to :root, alert: I18n.t('controllers.contradiction') + redirect_to :root, alert: I18n.t("controllers.contradiction") end def sanitize_page! @@ -654,9 +663,7 @@ def sanitize_page! end def sanitize_per! - if params[:per] || cookies[:per].to_i.positive? - cookies[:all] = 'false' - end + cookies[:all] = "false" if params[:per] || cookies[:per].to_i.positive? params[:per] = if params[:per].to_i.in?([3, 4, 8, 12, 24, 48]) params[:per].to_i elsif cookies[:per].to_i.positive? @@ -669,8 +676,8 @@ def sanitize_per! def search_params types = params[:search][:types] || [] - types = [types] if types && !types.kind_of?(Array) - types -= [''] if types + types = [types] if types && !types.is_a?(Array) + types -= [""] if types types = nil if types == [] params[:search][:types] = types params[:search][:user_id] = current_user.id @@ -693,29 +700,29 @@ def search_params # destroy all notifications related to this medium def destroy_notifications - Notification.where(notifiable_id: @medium.id, notifiable_type: 'Medium') + Notification.where(notifiable_id: @medium.id, notifiable_type: "Medium") .delete_all end def add_tags_in_lesson_and_sections @tags_outside_lesson = @medium.tags_outside_lesson - if @tags_outside_lesson - @medium.teachable.tags << @tags_outside_lesson - @tags_without_section = @tags_outside_lesson & @medium.teachable.tags_without_section - if @medium.teachable.sections.count == 1 - section = @medium.teachable.sections.first - section.tags << @tags_without_section - end - end + return unless @tags_outside_lesson + + @medium.teachable.tags << @tags_outside_lesson + @tags_without_section = @tags_outside_lesson & @medium.teachable.tags_without_section + return unless @medium.teachable.sections.count == 1 + + section = @medium.teachable.sections.first + section.tags << @tags_without_section end def store_access - mode = action_name == 'play' ? 'thyme' : 'pdf_view' - sort = action_name == 'play' ? 'video' : 'manuscript' + mode = action_name == "play" ? "thyme" : "pdf_view" + sort = action_name == "play" ? "video" : "manuscript" ConsumptionSaver.perform_async(@medium.id, mode, sort) end def store_download - ConsumptionSaver.perform_async(@medium.id, 'download', params[:sort]) + ConsumptionSaver.perform_async(@medium.id, "download", params[:sort]) end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index a43809799..a6d036f60 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -10,7 +10,7 @@ def current_ability def index @notifications = current_user.notifications.order(:created_at) .reverse_order - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end def destroy @@ -27,8 +27,8 @@ def destroy_all # destroy all lecture notifications of current user def destroy_lecture_notifications - lecture = Lecture.find_by_id(params[:lecture_id]) - return unless lecture.present? + lecture = Lecture.find_by(id: params[:lecture_id]) + return if lecture.blank? Notification.delete(current_user.active_notifications(lecture).pluck(:id)) current_user.touch @@ -46,9 +46,9 @@ def destroy_news_notifications private def set_notification - @notification = Notification.find_by_id(params[:id]) + @notification = Notification.find_by(id: params[:id]) return if @notification.present? - redirect_to :root, alert: I18n.t('controllers.no_notification') + redirect_to :root, alert: I18n.t("controllers.no_notification") end end diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb index d74eeb08a..da24afab5 100644 --- a/app/controllers/profile_controller.rb +++ b/app/controllers/profile_controller.rb @@ -16,9 +16,9 @@ def edit return end # destroy the notifications related to new lectures and courses - current_user.notifications.where(notifiable_type: ['Lecture', 'Course']) + current_user.notifications.where(notifiable_type: ["Lecture", "Course"]) .destroy_all - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end def update @@ -38,7 +38,7 @@ def update I18n.locale = @locale cookies[:locale] = @locale @user.touch - redirect_to :start, notice: t('profile.success') + redirect_to :start, notice: t("profile.success") else @errors = @user.errors end @@ -54,25 +54,25 @@ def check_for_consent return unless @user.consents redirect_to edit_profile_path, - notice: t('profile.please_update') + notice: t("profile.please_update") end # DSGVO consent action def add_consent - @user.update(consents: true, consented_at: Time.now) - redirect_to :root, notice: t('profile.consent') + @user.update(consents: true, consented_at: Time.zone.now) + redirect_to :root, notice: t("profile.consent") end def toggle_thread_subscription @thread = Commontator::Thread.find(params[:id]) - if @thread && @thread.can_subscribe?(@user) - if params[:subscribe] == 'true' - @thread.subscribe(@user) - else - @thread.unsubscribe(@user) - end - @result = !!@thread.subscription_for(@user) + return unless @thread&.can_subscribe?(@user) + + if params[:subscribe] == "true" + @thread.subscribe(@user) + else + @thread.unsubscribe(@user) end + @result = !!@thread.subscription_for(@user) end def subscribe_lecture @@ -94,18 +94,16 @@ def subscribe_lecture def unsubscribe_lecture @success = current_user.unsubscribe_lecture!(@lecture) @none_left = case @parent - when 'current_subscribed' then current_user.current_subscribed_lectures + when "current_subscribed" then current_user.current_subscribed_lectures .empty? - when 'inactive' then current_user.inactive_lectures.empty? + when "inactive" then current_user.inactive_lectures.empty? end end def star_lecture return unless @lecture&.in?(current_user.lectures) - if !@lecture.in?(current_user.favorite_lectures) - current_user.favorite_lectures << @lecture - end + current_user.favorite_lectures << @lecture unless @lecture.in?(current_user.favorite_lectures) # as favorite lectures appear in the navbar which is cached e.g. in # the lecture show action, make sure the cache is invalidated by # touching the user @@ -122,22 +120,22 @@ def unstar_lecture def show_accordion @collapse_id = params[:id] - redirect_to :root and return unless @collapse_id.present? + redirect_to :root and return if @collapse_id.blank? @lectures = case @collapse_id - when 'collapseCurrentStuff' then current_user.current_subscribed_lectures - when 'collapseInactiveLectures' then current_user.inactive_lectures + when "collapseCurrentStuff" then current_user.current_subscribed_lectures + when "collapseInactiveLectures" then current_user.inactive_lectures .includes(:course, :term) .sort - when 'collapseAllCurrent' then current_user.current_subscribable_lectures + when "collapseAllCurrent" then current_user.current_subscribable_lectures end - @link = @collapse_id.remove('collapse').camelize(:lower) + 'Link' + @link = "#{@collapse_id.remove("collapse").camelize(:lower)}Link" end def request_data MathiMailer.data_request_email(current_user).deliver_later MathiMailer.data_provide_email(current_user).deliver_later - redirect_to edit_profile_path, notice: t('profile.data_request_sent') + redirect_to edit_profile_path, notice: t("profile.data_request_sent") end private @@ -167,10 +165,10 @@ def email_params end def set_lecture - @lecture = Lecture.find_by_id(lecture_params[:id]) + @lecture = Lecture.find_by(id: lecture_params[:id]) @passphrase = lecture_params[:passphrase] @parent = lecture_params[:parent] - @current = !@parent.in?(['lectureSearch', 'inactive']) + @current = !@parent.in?(["lectureSearch", "inactive"]) redirect_to start_path unless @lecture end @@ -180,7 +178,7 @@ def lecture_params # extracts all lecture ids from user params def lecture_ids - params[:user][:lecture].select { |k, v| v == '1' }.keys.map(&:to_i) + params[:user][:lecture].select { |_k, v| v == "1" }.keys.map(&:to_i) end def clean_up_notifications @@ -197,9 +195,9 @@ def clean_up_notifications # if user unsubscribed the lecture the current lecture cookie refers to, # set the lectures cookie to nil def update_lecture_cookie - unless @current_lecture.in?(@user.lectures) - cookies[:current_lecture_id] = nil - end + return if @current_lecture.in?(@user.lectures) + + cookies[:current_lecture_id] = nil end # stop the update if any of passphrases for newly subscribed @@ -215,7 +213,7 @@ def check_passphrases given_passphrase = params[:user][:pass_lecture][l.id.to_s] unless given_passphrase == l.passphrase @errors[:passphrase] ||= [] - @errors[:passphrase].push l.id + @errors[:passphrase].push(l.id) end end end diff --git a/app/controllers/programs_controller.rb b/app/controllers/programs_controller.rb index babb0eb30..28e9aab55 100644 --- a/app/controllers/programs_controller.rb +++ b/app/controllers/programs_controller.rb @@ -7,17 +7,12 @@ def current_ability @current_ability ||= ProgramAbility.new(current_user) end - def edit - end - def new @program = Program.new(subject_id: params[:subject_id].to_i) authorize! :new, @program end - def update - @program.update(program_params) - redirect_to classification_path + def edit end def create @@ -28,6 +23,11 @@ def create redirect_to classification_path end + def update + @program.update(program_params) + redirect_to classification_path + end + def destroy @program.destroy redirect_to classification_path @@ -36,10 +36,10 @@ def destroy private def set_program - @program = Program.find_by_id(params[:id]) + @program = Program.find_by(id: params[:id]) return if @program.present? - redirect_to root_path, alert: I18n.t('controllers.no_program') + redirect_to root_path, alert: I18n.t("controllers.no_program") end def program_params diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index 1444e3ba1..71ba8a686 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -4,7 +4,7 @@ class QuestionsController < ApplicationController before_action :set_quizzes, only: [:reassign] before_action :check_solution_errors, only: [:update] authorize_resource except: :reassign - layout 'administration' + layout "administration" def current_ability @current_ability ||= QuestionAbility.new(current_user) @@ -18,7 +18,7 @@ def update return if @errors @success = true if @question.update(question_params) - if question_params[:question_sort] == 'free' + if question_params[:question_sort] == "free" answer = @question.answers.first @question.answers.where.not(id: answer.id).destroy_all end @@ -34,33 +34,34 @@ def update end def reassign - question_old = Question.find_by_id(params[:id]) + question_old = Question.find_by(id: params[:id]) authorize! :reassign, question_old I18n.locale = question_old.locale_with_inheritance @question, answer_map = question_old.duplicate @question.editors = [current_user] @quizzes.each do |q| - Quiz.find_by_id(q).replace_reference!(question_old, @question, answer_map) + Quiz.find_by(id: q).replace_reference!(question_old, @question, answer_map) end I18n.locale = @question.locale_with_inheritance - if question_params[:type] == 'edit' + if question_params[:type] == "edit" redirect_to edit_question_path(@question) return end @quizzable = @question - @mode = 'reassigned' - render 'media/fill_quizzable_area' + @mode = "reassigned" + render "media/fill_quizzable_area" end def set_solution_type - content = if params[:type] == 'MampfExpression' - MampfExpression.trivial_instance - elsif params[:type] == 'MampfMatrix' - MampfMatrix.trivial_instance - elsif params[:type] == 'MampfTuple' - MampfTuple.trivial_instance - elsif params[:type] == 'MampfSet' - MampfSet.trivial_instance + content = case params[:type] + when "MampfExpression" + MampfExpression.trivial_instance + when "MampfMatrix" + MampfMatrix.trivial_instance + when "MampfTuple" + MampfTuple.trivial_instance + when "MampfSet" + MampfSet.trivial_instance end @solution = Solution.new(content) end @@ -78,21 +79,21 @@ def render_question_parameters private def set_question - @question = Question.find_by_id(params[:id]) + @question = Question.find_by(id: params[:id]) return if @question.present? - redirect_to :root, alert: I18n.t('controllers.no_question') + redirect_to :root, alert: I18n.t("controllers.no_question") end def set_quizzes - @quizzes = params[:question].select { |k, v| - v == '1' && k.start_with?('quiz-') - } - .keys.map { |k| k.remove('quiz-').to_i } + quizzes = params[:question].select do |k, v| + v == "1" && k.start_with?("quiz-") + end + @quizzes = quizzes.keys.map { |k| k.remove("quiz-").to_i } end def check_solution_errors - return unless params[:question][:solution_error].present? + return if params[:question][:solution_error].blank? @errors = ActiveModel::Errors.new(@question) @errors.add(:base, params[:question][:solution_error]) diff --git a/app/controllers/quiz_certificates_controller.rb b/app/controllers/quiz_certificates_controller.rb index 0f611e18d..d4f0c9cc6 100644 --- a/app/controllers/quiz_certificates_controller.rb +++ b/app/controllers/quiz_certificates_controller.rb @@ -17,23 +17,23 @@ def claim def validate authorize! :validate, QuizCertificate.new code = certificate_params[:code] - @certificate = QuizCertificate.find_by_code(code) + @certificate = QuizCertificate.find_by(code: code) end private def set_certificate - @certificate = QuizCertificate.find_by_id(params[:id]) + @certificate = QuizCertificate.find_by(id: params[:id]) return if @certificate.present? - redirect_to :root, alert: I18n.t('controllers.no_certificate') + redirect_to :root, alert: I18n.t("controllers.no_certificate") end def check_if_claimed return unless @certificate.user redirect_to :root, - alert: I18n.t('controllers.certificate_already_claimed') + alert: I18n.t("controllers.certificate_already_claimed") end def certificate_params @@ -49,7 +49,7 @@ def set_locale_by_quiz end def set_locale_by_lecture - @lecture = Lecture.find_by_id(certificate_params[:lecture_id]) + @lecture = Lecture.find_by(id: certificate_params[:lecture_id]) I18n.locale = @lecture&.locale_with_inheritance || current_user.locale || I18n.default_locale end diff --git a/app/controllers/quizzes_controller.rb b/app/controllers/quizzes_controller.rb index 3cf4ead9c..b363b18bb 100644 --- a/app/controllers/quizzes_controller.rb +++ b/app/controllers/quizzes_controller.rb @@ -10,7 +10,7 @@ class QuizzesController < ApplicationController before_action :init_values, only: [:take, :proceed] after_action :store_access, only: [:take] authorize_resource except: [:new, :update_branching] - layout 'administration' + layout "administration" def current_ability @current_ability ||= QuizAbility.new(current_user) @@ -38,7 +38,7 @@ def destroy def take I18n.locale = @quiz.locale_with_inheritance - render layout: 'quiz' + render layout: "quiz" end def proceed @@ -81,10 +81,10 @@ def delete_edge end def update_branching - quiz = Quiz.find_by_id(params[:quiz_id]) + quiz = Quiz.find_by(id: params[:quiz_id]) authorize! :update_branching, quiz @quizzable = quiz.quizzable(params[:vertex_id].to_i) - @id = params[:id].sub 'select', 'quizzable' + @id = params[:id].sub("select", "quizzable") end def edit_vertex_targets @@ -99,10 +99,10 @@ def render_vertex_quizzable private def set_quiz - @quiz = Quiz.find_by_id(params[:id]) + @quiz = Quiz.find_by(id: params[:id]) return if @quiz.present? - redirect_to :root, alert: I18n.t('controllers.no_quiz') + redirect_to :root, alert: I18n.t("controllers.no_quiz") end def init_values @@ -115,15 +115,14 @@ def init_values if user_signed_in? && current_user.study_participant quiz_round_params[:study_participant] = current_user.anonymized_id end + quiz_round_params[:save_probe] = - if !user_signed_in? - true - elsif current_user.admin? - false - elsif current_user.in?(Quiz.find(params[:id]).editors_with_inheritance) - false + if user_signed_in? + should_omit_probe = current_user.admin? \ + || current_user.in?(Quiz.find(params[:id]).editors_with_inheritance) + !should_omit_probe else - true + true # always save probe for not signed in users end @quiz_round = QuizRound.new(quiz_round_params) end @@ -133,15 +132,15 @@ def quiz_params end def check_accessibility - return if @quiz.sort == 'RandomQuiz' + return if @quiz.sort == "RandomQuiz" return if user_signed_in? && @quiz.visible_for_user?(current_user) return if !user_signed_in? && @quiz.free? - redirect_to :root, alert: I18n.t('controllers.no_quiz_access') + redirect_to :root, alert: I18n.t("controllers.no_quiz_access") end def check_vertex_accessibility - return if @quiz.sort == 'RandomQuiz' + return if @quiz.sort == "RandomQuiz" if user_signed_in? return if current_user.in?(@quiz.editors_with_inheritance) @@ -150,17 +149,17 @@ def check_vertex_accessibility end return if !user_signed_in? && @quiz.quizzables_free? - redirect_to :root, alert: I18n.t('controllers.no_quiz_vertex_access') + redirect_to :root, alert: I18n.t("controllers.no_quiz_vertex_access") end def check_errors - return if @quiz.sort == 'RandomQuiz' + return if @quiz.sort == "RandomQuiz" return unless @quiz.find_errors&.any? - redirect_to :root, alert: I18n.t('controllers.quiz_has_error') + redirect_to :root, alert: I18n.t("controllers.quiz_has_error") end def store_access - ConsumptionSaver.perform_async(@quiz.id, 'browser', 'quiz') + ConsumptionSaver.perform_async(@quiz.id, "browser", "quiz") end end diff --git a/app/controllers/readers_controller.rb b/app/controllers/readers_controller.rb index 362e5cd86..f276f2989 100644 --- a/app/controllers/readers_controller.rb +++ b/app/controllers/readers_controller.rb @@ -3,7 +3,7 @@ class ReadersController < ApplicationController # no authorization for this controller def update - @thread = Commontator::Thread.find_by_id(reader_params[:thread_id]) + @thread = Commontator::Thread.find_by(id: reader_params[:thread_id]) return unless @thread @reader = Reader.find_or_create_by(user: current_user, @@ -11,7 +11,7 @@ def update @reader.touch @anything_left = current_user.media_latest_comments.any? do |m| (Reader.find_by(user: current_user, thread: m[:thread]) - &.updated_at || (Time.now - 1000.years)) < m[:latest_comment].created_at + &.updated_at || 1000.years.ago) < m[:latest_comment].created_at end current_user.update(unread_comments: false) unless @anything_left end @@ -26,8 +26,7 @@ def update_all new_readers << Reader.new(thread_id: t, user: current_user) end Reader.import new_readers - Reader.where(user: current_user, thread: threads) - .update_all(updated_at: Time.now) + Reader.where(user: current_user, thread: threads).touch_all current_user.update(unread_comments: false) end diff --git a/app/controllers/referrals_controller.rb b/app/controllers/referrals_controller.rb index 4d07fa221..3d06fed3e 100644 --- a/app/controllers/referrals_controller.rb +++ b/app/controllers/referrals_controller.rb @@ -8,35 +8,23 @@ def current_ability @current_ability ||= ReferralAbility.new(current_user) end - def update - I18n.locale = @referral.medium.locale_with_inheritance - # if referral's item is a link, it is updated - # this means in particular that *all referrals* that refer to it will - # be affected; links are changed *globally* - update_item if Item.find_by_id(@item_id)&.sort == 'link' - return if @errors.present? - - @referral.update(updated_params) - @errors = @referral.errors unless @referral.valid? - end - def edit I18n.locale = @referral.medium.locale_with_inheritance # if referral's item is a link, load all other links, # otherwise load all items in the referral's item's medium scope # that the user can choose from in the item dropdown menu - @item_selection = if @referral.item.sort == 'link' + @item_selection = if @referral.item.sort == "link" Item.where(medium: nil) .map { |i| [i.description, i.id] } else @referral.item.medium.teachable.media_scope .media_items_with_inheritance end - @item = Item.new(sort: 'link') + @item = Item.new(sort: "link") end def create - update_item if Item.find_by_id(@item_id)&.sort == 'link' + update_item if Item.find_by(id: @item_id)&.sort == "link" if @errors.present? render :update return @@ -48,6 +36,18 @@ def create render :update end + def update + I18n.locale = @referral.medium.locale_with_inheritance + # if referral's item is a link, it is updated + # this means in particular that *all referrals* that refer to it will + # be affected; links are changed *globally* + update_item if Item.find_by(id: @item_id)&.sort == "link" + return if @errors.present? + + @referral.update(updated_params) + @errors = @referral.errors unless @referral.valid? + end + def destroy @medium = @referral.medium @referral.destroy @@ -58,11 +58,11 @@ def destroy # renders it in json as it will be called by ajax def list_items authorize! :list_items, Referral.new - teachable_id = params[:teachable_id].to_s.split('-') - if teachable_id[0] == 'external' + teachable_id = params[:teachable_id].to_s.split("-") + if teachable_id[0] == "external" result = Item.where(medium: nil).pluck(:description, :id) else - @teachable = teachable_id[0].constantize.find_by_id(teachable_id[1]) + @teachable = teachable_id[0].constantize.find_by(id: teachable_id[1]) result = @teachable.media_items_with_inheritance end result ||= Item.none diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 0cd93ae14..bc9152b78 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,20 +1,20 @@ -require 'net/http' -require 'uri' -require 'json' +require "net/http" +require "uri" +require "json" # RegistrationsController class RegistrationsController < Devise::RegistrationsController prepend_before_action :check_registration_limit, only: [:create] def verify_captcha - return true unless ENV['USE_CAPTCHA_SERVICE'] + return true unless ENV["USE_CAPTCHA_SERVICE"] begin - uri = URI.parse(ENV['CAPTCHA_VERIFY_URL']) + uri = URI.parse(ENV.fetch("CAPTCHA_VERIFY_URL", nil)) data = { message: params["frc-captcha-solution"], - application_token: ENV['CAPTCHA_APPLICATION_TOKEN'] } - header = { 'Content-Type': 'text/json' } + application_token: ENV.fetch("CAPTCHA_APPLICATION_TOKEN", nil) } + header = { "Content-Type": "text/json" } http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true if ENV['CAPTCHA_VERIFY_URL'].include?('https') + http.use_ssl = true if ENV["CAPTCHA_VERIFY_URL"].include?("https") request = Net::HTTP::Post.new(uri.request_uri, header) request.body = data.to_json @@ -22,9 +22,9 @@ def verify_captcha response = http.request(request) answer = JSON.parse(response.body) return true if answer["message"] == "verified" - rescue + rescue StandardError # rubocop:todo Lint/SuppressedException end - return false + false end def create @@ -33,50 +33,54 @@ def create else build_resource(devise_parameter_sanitizer.sanitize(:sign_up)) clean_up_passwords(resource) - set_flash_message :alert, :captcha_error + set_flash_message(:alert, :captcha_error) render :new end end def destroy password_correct = resource.valid_password?(deletion_params[:password]) - if !password_correct - set_flash_message :alert, :password_incorrect - respond_with_navigational(resource) { + unless password_correct + set_flash_message(:alert, :password_incorrect) + respond_with_navigational(resource) do redirect_to after_sign_up_path_for(resource_name) - } + end return end success = resource.archive_and_destroy(deletion_params[:archive_name]) - if !success - set_flash_message :alert, :not_destroyed - respond_with_navigational(resource) { + unless success + set_flash_message(:alert, :not_destroyed) + respond_with_navigational(resource) do redirect_to after_sign_up_path_for(resource_name) - } + end return end Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name) - set_flash_message :notice, :destroyed - yield resource if block_given? - respond_with_navigational(resource) { + set_flash_message(:notice, :destroyed) + yield(resource) if block_given? + respond_with_navigational(resource) do redirect_to after_sign_out_path_for(resource_name) - } + end end - def after_sign_up_path_for(resource) + def after_sign_up_path_for(_resource) edit_profile_path end private def check_registration_limit - if User.where("users.confirmed_at is NULL and users.created_at > '#{(DateTime.now() - (ENV['MAMPF_REGISTRATION_TIMEFRAME'] || 15).to_i.minutes)}'").count > (ENV['MAMPF_MAX_REGISTRATION_PER_TIMEFRAME'] || 40).to_i - self.resource = resource_class.new devise_parameter_sanitizer.sanitize(:sign_up) - resource.validate # Look for any other validation errors besides reCAPTCHA - set_flash_message :alert, :too_many_registrations - set_minimum_password_length - respond_with_navigational(resource) { render :new } - end + timeframe = ((ENV["MAMPF_REGISTRATION_TIMEFRAME"] || 15).to_i.minutes.ago..) + num_new_registrations = User.where(confirmed_at: nil, created_at: timeframe).count + max_registrations = (ENV["MAMPF_MAX_REGISTRATION_PER_TIMEFRAME"] || 40).to_i + return if num_new_registrations <= max_registrations + + # Current number of new registrations is too high + self.resource = resource_class.new(devise_parameter_sanitizer.sanitize(:sign_up)) + resource.validate # Look for any other validation errors besides reCAPTCHA + set_flash_message(:alert, :too_many_registrations) + set_minimum_password_length + respond_with_navigational(resource) { render :new } end def deletion_params diff --git a/app/controllers/remarks_controller.rb b/app/controllers/remarks_controller.rb index 9d95e9779..c1ecd275a 100644 --- a/app/controllers/remarks_controller.rb +++ b/app/controllers/remarks_controller.rb @@ -3,7 +3,7 @@ class RemarksController < MediaController before_action :set_remark, except: :reassign before_action :set_quizzes, only: [:reassign] authorize_resource except: :reassign - layout 'administration' + layout "administration" def current_ability @current_ability ||= RemarkAbility.new(current_user) @@ -18,22 +18,22 @@ def update end def reassign - remark_old = Remark.find_by_id(params[:id]) + remark_old = Remark.find_by(id: params[:id]) authorize! :reassign, remark_old I18n.locale = remark_old.locale_with_inheritance @remark = remark_old.duplicate @remark.editors = [current_user] @quizzes.each do |q| - Quiz.find_by_id(q).replace_reference!(remark_old, @remark) + Quiz.find_by(id: q).replace_reference!(remark_old, @remark) end I18n.locale = @remark.locale_with_inheritance - if remark_params[:type] == 'edit' + if remark_params[:type] == "edit" redirect_to edit_remark_path(@remark) return end @quizzable = @remark - @mode = 'reassigned' - render 'media/fill_quizzable_area' + @mode = "reassigned" + render "media/fill_quizzable_area" end def cancel_remark_basics @@ -42,15 +42,15 @@ def cancel_remark_basics private def set_remark - @remark = Remark.find_by_id(params[:id]) + @remark = Remark.find_by(id: params[:id]) return if @remark.present? - redirect_to remarks_path, alert: I18n.t('controllers.no_remark') + redirect_to remarks_path, alert: I18n.t("controllers.no_remark") end def set_quizzes - @quizzes = params[:remark].select { |_k, v| v == '1' }.keys - .map { |k| k.remove('quiz-').to_i } + @quizzes = params[:remark].select { |_k, v| v == "1" }.keys + .map { |k| k.remove("quiz-").to_i } end def remark_params diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index ef9d49636..67b432ea2 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -12,7 +12,7 @@ def current_ability def index search_down = @search_string.downcase # determine tags whose title contains the search string - matches = Notion.all.pluck(:tag_id, :title, :aliased_tag_id) + matches = Notion.pluck(:tag_id, :title, :aliased_tag_id) .select { |x| x.second.downcase.include?(search_down) } .map { |a| a.first || a.third }.uniq @tags = Tag.where(id: matches) @@ -40,13 +40,13 @@ def set_search_string def sanitize_search_string if @search_string.nil? redirect_back fallback_location: root_path, - alert: I18n.t('controllers.no_search_term') + alert: I18n.t("controllers.no_search_term") return end return if @search_string.length > 1 redirect_back fallback_location: root_path, - alert: I18n.t('controllers.search_term_short') + alert: I18n.t("controllers.search_term_short") end def find_similar_tags diff --git a/app/controllers/sections_controller.rb b/app/controllers/sections_controller.rb index f7a87b997..6fa9cae43 100644 --- a/app/controllers/sections_controller.rb +++ b/app/controllers/sections_controller.rb @@ -2,7 +2,7 @@ class SectionsController < ApplicationController before_action :set_section, except: [:new, :create] authorize_resource except: [:new, :create] - layout 'administration' + layout "administration" def current_ability @current_ability ||= SectionAbility.new(current_user) @@ -10,20 +10,20 @@ def current_ability def show I18n.locale = @section.lecture.locale_with_inheritance - render layout: 'application_no_sidebar' - end - - def edit - I18n.locale = @section.lecture.locale_with_inheritance + render layout: "application_no_sidebar" end def new - @chapter = Chapter.find_by_id(params[:chapter_id]) + @chapter = Chapter.find_by(id: params[:chapter_id]) @section = Section.new(chapter: @chapter) authorize! :new, @section I18n.locale = @section.lecture.locale_with_inheritance end + def edit + I18n.locale = @section.lecture.locale_with_inheritance + end + def create @section = Section.new(section_params) authorize! :create, @section @@ -31,12 +31,6 @@ def create @errors = @section.errors end - def destroy - @lecture = @section.lecture - @section.destroy - redirect_to edit_lecture_path(@lecture) - end - def update I18n.locale = @section.lecture.locale_with_inheritance @old_chapter = @section.chapter @@ -50,6 +44,12 @@ def update @errors = @section.errors end + def destroy + @lecture = @section.lecture + @section.destroy + redirect_to edit_lecture_path(@lecture) + end + def display I18n.locale = @section.lecture.locale_with_inheritance end @@ -57,10 +57,10 @@ def display private def set_section - @section = Section.find_by_id(params[:id]) + @section = Section.find_by(id: params[:id]) return if @section.present? - redirect_to :root, alert: I18n.t('controllers.no_section') + redirect_to :root, alert: I18n.t("controllers.no_section") end def section_params @@ -83,12 +83,10 @@ def insert_or_save # updates the position of the section if predecessor is given def update_position predecessor = params[:section][:predecessor] - return unless predecessor.present? + return if predecessor.blank? position = predecessor.to_i - if position > @section.position && @old_chapter == @section.chapter - position -= 1 - end + position -= 1 if position > @section.position && @old_chapter == @section.chapter @section.insert_at(position + 1) end diff --git a/app/controllers/subjects_controller.rb b/app/controllers/subjects_controller.rb index 6cd036d20..29fa26cf4 100644 --- a/app/controllers/subjects_controller.rb +++ b/app/controllers/subjects_controller.rb @@ -15,11 +15,6 @@ def new def edit end - def update - @subject.update(subject_params) - redirect_to classification_path - end - def create @subject = Subject.new(subject_params) authorize! :create, @subject @@ -27,6 +22,11 @@ def create redirect_to classification_path end + def update + @subject.update(subject_params) + redirect_to classification_path + end + def destroy @subject.destroy redirect_to classification_path @@ -35,10 +35,10 @@ def destroy private def set_subject - @subject = Subject.find_by_id(params[:id]) + @subject = Subject.find_by(id: params[:id]) return if @subject.present? - redirect_to root_path, alert: I18n.t('controllers.no_answer') + redirect_to root_path, alert: I18n.t("controllers.no_answer") end def subject_params diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index ad4422df9..870bf22eb 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -16,13 +16,13 @@ def current_ability @current_ability ||= SubmissionAbility.new(current_user) end - # note: authorization for #index is done manually via before_actions + # NOTE: authorization for #index is done manually via before_actions # SubmissionAbility lets anyone pass def index @assignments = @lecture.assignments @current_assignments = @lecture.current_assignments @previous_assignments = @lecture.previous_assignments - @old_assignments = @assignments.expired.order('deadline DESC') - + @old_assignments = @assignments.expired.order("deadline DESC") - @previous_assignments @future_assignments = @assignments.active.order(:deadline) - @current_assignments @@ -37,6 +37,33 @@ def new def edit end + def create + @submission = Submission.new(submission_create_params) + @lecture = @submission&.assignment&.lecture + set_submission_locale + @too_late = @submission.not_updatable? + return if @too_late + + if submission_manuscript_params[:manuscript].present? + @submission.manuscript = submission_manuscript_params[:manuscript] + @errors = @submission.check_file_properties(@submission.manuscript + .metadata, + :manuscript) + return if @errors.present? + end + @submission.user_submission_joins.build(user: current_user) + @submission.save + @assignment = @submission.assignment + @errors = @submission.errors + return unless @submission.valid? + + send_invitation_emails + @submission.update(last_modification_by_users_at: Time.zone.now) + return unless @submission.manuscript + + send_upload_email(User.where(id: current_user.id)) + end + def update return if @too_late @@ -56,45 +83,18 @@ def update @submission.update(submission_update_params) if @submission.valid? @submission.update(accepted: nil) - if params[:submission][:detach_user_manuscript] == 'true' + if params[:submission][:detach_user_manuscript] == "true" @submission.update(manuscript: nil, - last_modification_by_users_at: Time.now) + last_modification_by_users_at: Time.zone.now) send_upload_removal_email(@submission.users) elsif @submission.manuscript_data != old_manuscript_data - @submission.update(last_modification_by_users_at: Time.now) + @submission.update(last_modification_by_users_at: Time.zone.now) send_upload_email(@submission.users) end end @errors = @submission.errors end - def create - @submission = Submission.new(submission_create_params) - @lecture = @submission&.assignment&.lecture - set_submission_locale - @too_late = @submission.not_updatable? - return if @too_late - - if submission_manuscript_params[:manuscript].present? - @submission.manuscript = submission_manuscript_params[:manuscript] - @errors = @submission.check_file_properties(@submission.manuscript - .metadata, - :manuscript) - return if @errors.present? - end - @submission.user_submission_joins.build(user: current_user) - @submission.save - @assignment = @submission.assignment - @errors = @submission.errors - return unless @submission.valid? - - send_invitation_emails - @submission.update(last_modification_by_users_at: Time.now) - return unless @submission.manuscript - - send_upload_email(User.where(id: current_user.id)) - end - def destroy return if @too_late @@ -111,16 +111,16 @@ def redeem_code check_code_and_join unless @error redirect_to lecture_submissions_path(@submission.tutorial.lecture), - notice: t('submission.joined_successfully', + notice: t("submission.joined_successfully", assignment: @submission.assignment.title) return end - redirect_to :start, alert: t('submission.failed_redemption', + redirect_to :start, alert: t("submission.failed_redemption", message: @error) end def join - @assignment = Assignment.find_by_id(join_params[:assignment_id]) + @assignment = Assignment.find_by(id: join_params[:assignment_id]) @lecture = @assignment.lecture set_submission_locale code = join_params[:code] @@ -132,7 +132,7 @@ def leave return if @too_late if @submission.users.count == 1 - @error = I18n.t('submission.no_partners_no_leave') + @error = I18n.t("submission.no_partners_no_leave") return end @submission.users.delete(current_user) @@ -146,28 +146,28 @@ def cancel_new end def show_manuscript - if @submission && @submission.manuscript - send_file @submission.manuscript.to_io, + if @submission&.manuscript + send_file(@submission.manuscript.to_io, type: @submission.manuscript_mime_type, disposition: @disposition, - filename: @submission.manuscript_filename + filename: @submission.manuscript_filename) elsif @submission - redirect_to :start, alert: t('submission.no_manuscript_yet') + redirect_to :start, alert: t("submission.no_manuscript_yet") else - redirect_to :start, alert: t('submission.exists_no_longer') + redirect_to :start, alert: t("submission.exists_no_longer") end end def show_correction - if @submission && @submission.correction - send_file @submission.correction.to_io, + if @submission&.correction + send_file(@submission.correction.to_io, type: @submission.correction_mime_type, disposition: @disposition, - filename: @submission.correction_filename + filename: @submission.correction_filename) elsif @submission - redirect_to :start, alert: t('submission.no_correction_yet') + redirect_to :start, alert: t("submission.no_correction_yet") else - redirect_to :start, alert: t('submission.exists_no_longer') + redirect_to :start, alert: t("submission.exists_no_longer") end end @@ -245,13 +245,13 @@ def reject private def set_submission - @submission = Submission.find_by_id(params[:id]) + @submission = Submission.find_by(id: params[:id]) @assignment = @submission&.assignment @lecture = @assignment&.lecture set_submission_locale return if @submission - flash[:alert] = I18n.t('controllers.no_submission') + flash.now[:alert] = I18n.t("controllers.no_submission") render js: "window.location='#{root_path}'" end @@ -270,21 +270,21 @@ def submission_manuscript_params end def set_assignment - @assignment = Assignment.find_by_id(params[:assignment_id]) + @assignment = Assignment.find_by(id: params[:assignment_id]) @lecture = @assignment&.lecture set_submission_locale return if @assignment - flash[:alert] = I18n.t('controllers.no_assignment') + flash.now[:alert] = I18n.t("controllers.no_assignment") render js: "window.location='#{root_path}'" - return + nil end def set_lecture - @lecture = Lecture.find_by_id(params[:id]) + @lecture = Lecture.find_by(id: params[:id]) set_submission_locale and return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') + redirect_to :root, alert: I18n.t("controllers.no_lecture") end def set_too_late @@ -378,34 +378,34 @@ def send_rejection_email(users) def check_code_validity if !@submission && @assignment - @error = I18n.t('submission.invalid_code_for_assignment', + @error = I18n.t("submission.invalid_code_for_assignment", assignment: @assignment.title) elsif !@submission - @error = I18n.t('submission.invalid_code') + @error = I18n.t("submission.invalid_code") elsif @assignment&.totally_expired? - @error = I18n.t('submission.assignment_expired') + @error = I18n.t("submission.assignment_expired") elsif @submission.correction - @error = I18n.t('submission.already_corrected') + @error = I18n.t("submission.already_corrected") elsif current_user.in?(@submission.users) - @error = I18n.t('submission.already_in') + @error = I18n.t("submission.already_in") elsif !@submission.tutorial.lecture.in?(current_user.lectures) - @error = I18n.t('submission.lecture_not_subscribed') + @error = I18n.t("submission.lecture_not_subscribed") end end def check_code_and_join check_code_validity - unless @error - @join = UserSubmissionJoin.new(user: current_user, - submission: @submission) - @join.save - if @join.valid? - @submission.update(last_modification_by_users_at: Time.now) - send_join_email - remove_invitee_status - else - @error = @join.errors[:base].join(', ') - end + return if @error + + @join = UserSubmissionJoin.new(user: current_user, + submission: @submission) + @join.save + if @join.valid? + @submission.update(last_modification_by_users_at: Time.zone.now) + send_join_email + remove_invitee_status + else + @error = @join.errors[:base].join(", ") end end @@ -438,26 +438,26 @@ def check_student_status return if current_user.proper_student_in?(@lecture) redirect_to :root, - alert: I18n.t('controllers.no_student_status_in_lecture') + alert: I18n.t("controllers.no_student_status_in_lecture") end def check_if_tutorials return if @lecture.tutorials.any? - redirect_to :root, alert: I18n.t('controllers.no_tutorials_in_lecture') + redirect_to :root, alert: I18n.t("controllers.no_tutorials_in_lecture") end def check_if_assignments return if @lecture.assignments.any? - redirect_to :root, alert: I18n.t('controllers.no_assignments_in_lecture') + redirect_to :root, alert: I18n.t("controllers.no_assignments_in_lecture") end def set_disposition - @disposition = params[:download] == 'true' ? 'attachment' : 'inline' + @disposition = params[:download] == "true" ? "attachment" : "inline" accepted = @submission.assignment.accepted_file_type return unless accepted.in?(Assignment.non_inline_file_types) - @disposition = 'attachment' + @disposition = "attachment" end end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 574a42d84..8b5c49d28 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -9,38 +9,43 @@ class TagsController < ApplicationController before_action :check_creation_permission, only: [:create] authorize_resource except: [:new, :modal, :search, :postprocess, :render_tag_title] - layout 'administration' + layout "administration" def current_ability @current_ability ||= TagAbility.new(current_user) end def show - if params[:locale].in?(I18n.available_locales.map(&:to_s)) - I18n.locale = params[:locale] - end + I18n.locale = params[:locale] if params[:locale].in?(I18n.available_locales.map(&:to_s)) set_related_tags_for_user @lectures = current_user.filter_lectures(@tag.lectures) # first, filter the media according to the users subscription type media = current_user.filter_media(@tag.media - .where.not(sort: ['Question', - 'Remark'])) + .where.not(sort: ["Question", + "Remark"])) # then, filter these according to their visibility for the user @media = current_user.filter_visible_media(media) @questions = @tag.visible_questions(current_user) # consider items in manuscripts that are corresponding to tags - manuscripts = current_user.filter_media(Medium.where(sort: 'Script')) + manuscripts = current_user.filter_media(Medium.where(sort: "Script")) @references = Item.where(medium: manuscripts, description: @tag.notions.pluck(:title) + @tag.aliases.pluck(:title)) - .where.not(pdf_destination: [nil, '']) + .where.not(pdf_destination: [nil, ""]) @realizations = @tag.realizations - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end def display_cyto set_related_tags_for_user - render layout: 'cytoscape' + render layout: "cytoscape" + end + + def new + @tag = Tag.new + authorize! :new, @tag + set_notions + @tag.aliases.new(locale: I18n.locale) end def edit @@ -51,11 +56,20 @@ def edit @tag.aliases.new(locale: I18n.locale) end - def new - @tag = Tag.new - authorize! :new, @tag - set_notions - @tag.aliases.new(locale: I18n.locale) + def create + # first, check if errors from creation_permission callback are present + @section = Section.find_by(id: params[:tag][:section_id]) + if @errors.present? + render :update + return + end + @tag.update(tag_params) + if @tag.valid? && !@modal + redirect_to edit_tag_path(@tag) + return + end + @errors = @tag.errors + render :update end def update @@ -74,22 +88,6 @@ def update @errors = @tag.errors end - def create - # first, check if errors from creation_permission callback are present - @section = Section.find_by_id(params[:tag][:section_id]) - if @errors.present? - render :update - return - end - @tag.update(tag_params) - if @tag.valid? && !@modal - redirect_to edit_tag_path(@tag) - return - end - @errors = @tag.errors - render :update - end - def destroy @tag.destroy redirect_to administration_path @@ -110,7 +108,7 @@ def modal end def identify - @identified_tag = Tag.find_by_id(params[:tag][:identified_tag_id]) + @identified_tag = Tag.find_by(id: params[:tag][:identified_tag_id]) @tag.identify_with!(@identified_tag) @identified_tag.destroy @tag.update(tag_params) @@ -118,9 +116,7 @@ def identify end def fill_tag_select - if params[:locale].in?(I18n.available_locales.map(&:to_s)) - I18n.locale = params[:locale] - end + I18n.locale = params[:locale] if params[:locale].in?(I18n.available_locales.map(&:to_s)) if params[:q] result = Tag.select_with_substring(params[:q]) render json: result @@ -131,7 +127,7 @@ def fill_tag_select end def fill_course_tags - course = Course.find_by_id(params[:course_id]) + course = Course.find_by(id: params[:course_id]) result = course&.select_question_tags_by_title render json: result end @@ -141,16 +137,16 @@ def search per_page = search_params[:per] || 10 search = Sunspot.new_search(Tag) search.build do - fulltext search_params[:title] + fulltext(search_params[:title]) end - course_ids = if search_params[:all_courses] == '1' + course_ids = if search_params[:all_courses] == "1" [] - elsif search_params[:course_ids] != [''] + elsif search_params[:course_ids] != [""] search_params[:course_ids] end search.build do with(:course_ids, course_ids) - paginate page: params[:page], per_page: per_page + paginate(page: params[:page], per_page: per_page) end search.execute results = search.results @@ -168,41 +164,39 @@ def postprocess authorize! :postprocess, Tag.new @tags_hash = params[:tags] @tags_hash.each do |t, section_data| - tag = Tag.find_by_id(t) + tag = Tag.find_by(id: t) next unless tag section_data.each do |s, v| - next if v.to_i == 0 + next if v.to_i.zero? section = Section.find(s) next unless section - if !tag.in?(section.tags) - section.tags << tag - end + section.tags << tag unless tag.in?(section.tags) end end - if params['from'] == 'Lesson' - redirect_to edit_lesson_path(Lesson.find_by_id(params[:id])) + if params["from"] == "Lesson" + redirect_to edit_lesson_path(Lesson.find_by(id: params[:id])) return end - redirect_to edit_medium_path(Medium.find_by_id(params[:id])) + redirect_to edit_medium_path(Medium.find_by(id: params[:id])) end def render_tag_title authorize! :render_tag_title, Tag.new - tag = Tag.find_by_id(params[:tag_id]) - @identified_tag = Tag.find_by_id(params[:identified_tag_id]) + tag = Tag.find_by(id: params[:tag_id]) + @identified_tag = Tag.find_by(id: params[:identified_tag_id]) @common_titles = tag.common_titles(@identified_tag) end private def set_tag - @tag = Tag.find_by_id(params[:id]) + @tag = Tag.find_by(id: params[:id]) return if @tag.present? - redirect_to :root, alert: I18n.t('controllers.no_tag') + redirect_to :root, alert: I18n.t("controllers.no_tag") end # set up cytoscape graph data for neighbourhood subgraph of @tag, @@ -215,9 +209,7 @@ def set_related_tags_for_user @depth = depth_param if depth_param.in?([1, 2]) overrule_subscription_type = false selection = params[:selection].to_i - if selection.in?([1, 2, 3]) - overrule_subscription_type = selection - end + overrule_subscription_type = selection if selection.in?([1, 2, 3]) @selection_type = if overrule_subscription_type selection else @@ -246,45 +238,45 @@ def set_related_tags def set_up_tag @tag = Tag.new set_notions - related_tag = Tag.find_by_id(params[:related_tag]) + related_tag = Tag.find_by(id: params[:related_tag]) @tag.related_tags << related_tag if related_tag.present? end def add_course - course = Course.find_by_id(params[:course]) + course = Course.find_by(id: params[:course]) @tag.courses << course if course.present? end def add_section - section = Section.find_by_id(params[:section]) - if section - @tag.sections << section - I18n.locale = section.lecture.locale || current_user.locale - end + section = Section.find_by(id: params[:section]) + return unless section + + @tag.sections << section + I18n.locale = section.lecture.locale || current_user.locale end def add_medium - medium = Medium.find_by_id(params[:medium]) - if medium - I18n.locale = medium.locale_with_inheritance || current_user.locale - @tag.media << medium - end + medium = Medium.find_by(id: params[:medium]) + return unless medium + + I18n.locale = medium.locale_with_inheritance || current_user.locale + @tag.media << medium end def add_lesson - lesson = Lesson.find_by_id(params[:lesson]) - if lesson - @tag.lessons << lesson - I18n.locale = lesson.lecture.locale || current_user.locale - end + lesson = Lesson.find_by(id: params[:lesson]) + return unless lesson + + @tag.lessons << lesson + I18n.locale = lesson.lecture.locale || current_user.locale end def add_talk - talk = Talk.find_by_id(params[:talk]) - if talk - @tag.talks << talk - I18n.locale = talk.lecture.locale || current_user.locale - end + talk = Talk.find_by(id: params[:talk]) + return unless talk + + @tag.talks << talk + I18n.locale = talk.lecture.locale || current_user.locale end def check_for_consent @@ -305,8 +297,8 @@ def tag_params end def realization_params - (params.require(:tag).permit(realizations: [])[:realizations] - ['']) - .map { |r| r.split('-') } + (params.require(:tag).permit(realizations: [])[:realizations] - [""]) + .map { |r| r.split("-") } .map { |x| [x.first, x.second.to_i] } end @@ -322,16 +314,16 @@ def check_permissions def permission_errors errors = [] unless removed_courses.all? { |c| c.removable_by?(current_user) } - errors.push(error_hash['remove_course']) + errors.push(error_hash["remove_course"]) end unless added_courses.all? { |c| c.addable_by?(current_user) } - errors.push(error_hash['add_course']) + errors.push(error_hash["add_course"]) end @errors[:courses] = errors if errors.present? end def check_creation_permission - @modal = (params[:tag][:modal] == 'true') + @modal = (params[:tag][:modal] == "true") @tag = Tag.new check_permissions end @@ -352,19 +344,20 @@ def set_notions end def locale - locale = if params[:from] == 'course' - @tag.courses&.first&.locale - elsif params[:from] == 'medium' - @tag.media&.first&.locale_with_inheritance - elsif params[:from] == 'section' - @tag.sections&.first&.lecture&.locale_with_inheritance + locale = case params[:from] + when "course" + @tag.courses&.first&.locale + when "medium" + @tag.media&.first&.locale_with_inheritance + when "section" + @tag.sections&.first&.lecture&.locale_with_inheritance end locale || current_user.locale end def error_hash - { 'remove_course' => I18n.t('controllers.no_removal_rights'), - 'add_course' => I18n.t('controllers.no_adding_rights') } + { "remove_course" => I18n.t("controllers.no_removal_rights"), + "add_course" => I18n.t("controllers.no_adding_rights") } end def search_params diff --git a/app/controllers/talks_controller.rb b/app/controllers/talks_controller.rb index 475fed70f..01a9a8a5c 100644 --- a/app/controllers/talks_controller.rb +++ b/app/controllers/talks_controller.rb @@ -3,14 +3,18 @@ class TalksController < ApplicationController before_action :set_talk, except: [:new, :create] authorize_resource except: [:new, :create] before_action :set_view_locale, only: [:edit] - layout 'administration' + layout "administration" def current_ability @current_ability ||= TalkAbility.new(current_user) end + def show + render layout: "application_no_sidebar" + end + def new - @lecture = Lecture.find_by_id(params[:lecture_id]) + @lecture = Lecture.find_by(id: params[:lecture_id]) @talk = Talk.new(lecture: @lecture) authorize! :new, @talk I18n.locale = @talk.lecture.locale_with_inheritance || @@ -20,14 +24,10 @@ def new def edit end - def show - render layout: 'application_no_sidebar' - end - def create @talk = Talk.new(talk_params) authorize! :create, @talk - dates = params[:talk][:dates].values.compact - [''] + dates = params[:talk][:dates].values.compact - [""] @talk.dates = dates if dates I18n.locale = @talk&.lecture&.locale_with_inheritance || current_user.locale || I18n.default_locale @@ -45,7 +45,7 @@ def create def update I18n.locale = @talk.lecture.locale_with_inheritance || current_user.locale || I18n.default_locale - dates = params[:talk][:dates]&.values&.compact.to_a - [''] + dates = params[:talk][:dates]&.values&.compact.to_a - [""] @talk.update(talk_params) @talk.update(dates: dates) if dates && @talk.valid? if @talk.valid? @@ -69,7 +69,7 @@ def destroy end def assemble - render layout: 'application_no_sidebar' + render layout: "application_no_sidebar" end # modify is the update action for speakers of the talk @@ -82,15 +82,15 @@ def modify private def set_talk - @talk = Talk.find_by_id(params[:id]) + @talk = Talk.find_by(id: params[:id]) return if @talk.present? - redirect_to :root, alert: I18n.t('controllers.no_talk') + redirect_to :root, alert: I18n.t("controllers.no_talk") end def talk_params attributes = [:title, :lecture_id, :details, :description, - :display_description, speaker_ids: [], tag_ids: []] + :display_description, { speaker_ids: [], tag_ids: [] }] if @talk && !current_user.in?(@talk.speakers) && !@talk.display_description attributes.delete(:display_description) diff --git a/app/controllers/terms_controller.rb b/app/controllers/terms_controller.rb index af473afe0..9727bff09 100644 --- a/app/controllers/terms_controller.rb +++ b/app/controllers/terms_controller.rb @@ -1,9 +1,9 @@ # TermsController class TermsController < ApplicationController before_action :set_term, except: [:index, :new, :create, :cancel, :set_active] - layout 'administration' + layout "administration" authorize_resource except: [:index, :new, :create, :cancel, :set_active] - layout 'administration' + layout "administration" def current_ability @current_ability ||= TermAbility.new(current_user) @@ -11,12 +11,7 @@ def current_ability def index authorize! :index, Term.new - @terms = Term.order(:year, :season).reverse_order.page params[:page] - end - - def destroy - @term.destroy - redirect_to terms_path + @terms = Term.order(:year, :season).reverse_order.page(params[:page]) end def new @@ -24,6 +19,9 @@ def new authorize! :new, @term end + def edit + end + def create @term = Term.new(term_params) authorize! :create, @term @@ -32,28 +30,30 @@ def create redirect_to terms_path return end - @errors = @term.errors[:season].join(', ') + @errors = @term.errors[:season].join(", ") render :update end - def edit - end - def update @term.update(term_params) - @errors = @term.errors[:season].join(', ') unless @term.valid? + @errors = @term.errors[:season].join(", ") unless @term.valid? + end + + def destroy + @term.destroy + redirect_to terms_path end def cancel @id = params[:id] - @term = Term.find_by_id(@id) + @term = Term.find_by(id: @id) authorize! :cancel, @term - @new_action = params[:new] == 'true' + @new_action = params[:new] == "true" end def set_active authorize! :set_active, Term.new - new_active_term = Term.find_by_id(active_term_params[:active_term]) + new_active_term = Term.find_by(id: active_term_params[:active_term]) old_active_term = Term.active if old_active_term && new_active_term && new_active_term != old_active_term old_active_term.update(active: false) @@ -68,10 +68,10 @@ def set_active def set_term @id = params[:id] - @term = Term.find_by_id(@id) + @term = Term.find_by(id: @id) return if @term - redirect_to terms_path, alert: I18n.t('controllers.no_term') + redirect_to terms_path, alert: I18n.t("controllers.no_term") end def term_params diff --git a/app/controllers/tutorials_controller.rb b/app/controllers/tutorials_controller.rb index f849a708a..21287ed79 100644 --- a/app/controllers/tutorials_controller.rb +++ b/app/controllers/tutorials_controller.rb @@ -6,7 +6,7 @@ class TutorialsController < ApplicationController :bulk_upload, :export_teams] before_action :set_assignment, only: [:bulk_download_submissions, - :bulk_download_corrections´, + :bulk_download_corrections, :bulk_upload, :export_teams] before_action :set_lecture, only: [:index, :overview] @@ -15,8 +15,8 @@ class TutorialsController < ApplicationController authorize_resource except: [:index, :overview, :create, :validate_certificate, :new, :cancel_new] - require 'rubygems' - require 'zip' + require "rubygems" + require "zip" def current_ability @current_ability ||= TutorialAbility.new(current_user) @@ -24,35 +24,38 @@ def current_ability def index authorize! :index, Tutorial.new, @lecture - @assignments = @lecture.assignments.order('deadline DESC') - @assignment = Assignment.find_by_id(params[:assignment]) || + @assignments = @lecture.assignments.order("deadline DESC") + @assignment = Assignment.find_by(id: params[:assignment]) || @assignments&.first - if current_user.editor_or_teacher_in?(@lecture) - @tutorials = @lecture.tutorials + @tutorials = if current_user.editor_or_teacher_in?(@lecture) + @lecture.tutorials else - @tutorials = current_user.given_tutorials.where(lecture: @lecture) + current_user.given_tutorials.where(lecture: @lecture) end - @tutorial = Tutorial.find_by_id(params[:tutorial]) || current_user.tutorials(@lecture).first + @tutorial = Tutorial.find_by(id: params[:tutorial]) || current_user.tutorials(@lecture).first @stack = @assignment&.submissions&.where(tutorial: @tutorial)&.proper &.order(:last_modification_by_users_at) end def overview authorize! :overview, Tutorial.new, @lecture - @assignments = @lecture.assignments.order('deadline DESC') - @assignment = Assignment.find_by_id(params[:assignment]) || + @assignments = @lecture.assignments.order("deadline DESC") + @assignment = Assignment.find_by(id: params[:assignment]) || @assignments&.first @tutorials = @lecture.tutorials end def new @tutorial = Tutorial.new - @lecture = Lecture.find_by_id(params[:lecture_id]) + @lecture = Lecture.find_by(id: params[:lecture_id]) set_tutorial_locale @tutorial.lecture = @lecture authorize! :new, @tutorial end + def edit + end + def create @tutorial = Tutorial.new(tutorial_params) authorize! :create, @tutorial @@ -62,13 +65,10 @@ def create @errors = @tutorial.errors end - def edit - end - def update @tutorial.update(tutorial_params) @errors = @tutorial.errors - return if @errors.present? + nil if @errors.present? end def destroy @@ -79,7 +79,7 @@ def cancel_edit end def cancel_new - @lecture = Lecture.find_by_id(params[:lecture]) + @lecture = Lecture.find_by(id: params[:lecture]) authorize! :cancel_new, Tutorial.new(lecture: @lecture) set_tutorial_locale @none_left = @lecture&.tutorials&.none? @@ -92,67 +92,65 @@ def bulk_download_submissions def bulk_download_corrections @zipped_corrections = Submission.zip_corrections!(@tutorial, @assignment) - bulk_download(@zipped_corrections, '-Corrections') + bulk_download(@zipped_corrections, "-Corrections") end def bulk_upload - begin - files = JSON.parse(params[:files]) - @report = Submission.bulk_corrections!(@tutorial, @assignment, files) - @stack = @assignment.submissions.where(tutorial: @tutorial).proper - .order(:last_modification_by_users_at) - send_correction_upload_emails - # in case an empty string for files is sent - rescue JSON::ParserError - flash[:alert] = I18n.t('tutorial.bulk_upload.error') - end + files = JSON.parse(params[:files]) + @report = Submission.bulk_corrections!(@tutorial, @assignment, files) + @stack = @assignment.submissions.where(tutorial: @tutorial).proper + .order(:last_modification_by_users_at) + send_correction_upload_emails + # in case an empty string for files is sent + rescue JSON::ParserError + flash[:alert] = I18n.t("tutorial.bulk_upload.error") end def validate_certificate authorize! :validate_certificate, Tutorial.new - @lecture = Lecture.find_by_id(params[:lecture_id]) + @lecture = Lecture.find_by(id: params[:lecture_id]) set_tutorial_locale end def export_teams respond_to do |format| format.html { head :ok } - format.csv { - send_data @tutorial.teams_to_csv(@assignment), - filename: "#{@tutorial.title}-#{@assignment.title}.csv" - } + format.csv do + send_data(@tutorial.teams_to_csv(@assignment), + filename: "#{@tutorial.title}-#{@assignment.title}.csv") + end end end private def set_tutorial - @tutorial = Tutorial.find_by_id(params[:id]) + @tutorial = Tutorial.find_by(id: params[:id]) @lecture = @tutorial&.lecture set_tutorial_locale and return if @tutorial - redirect_to :root, alert: I18n.t('controllers.no_tutorial') + redirect_to :root, alert: I18n.t("controllers.no_tutorial") end def set_assignment - @assignment = Assignment.find_by_id(params[:ass_id]) + @assignment = Assignment.find_by(id: params[:ass_id]) return if @assignment - redirect_to :root, alert: I18n.t('controllers.no_assignment') + redirect_to :root, alert: I18n.t("controllers.no_assignment") end def set_lecture - @lecture = Lecture.find_by_id(params[:id]) + @lecture = Lecture.find_by(id: params[:id]) set_tutorial_locale and return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') + redirect_to :root, alert: I18n.t("controllers.no_lecture") end def set_lecture_from_form - @lecture = Lecture.find_by_id(tutorial_params[:lecture_id]) + @lecture = Lecture.find_by(id: tutorial_params[:lecture_id]) return if @lecture - redirect_to :root, alert: I18n.t('controllers.no_lecture') + redirect_to :root, alert: I18n.t("controllers.no_lecture") end def set_tutorial_locale @@ -163,7 +161,7 @@ def set_tutorial_locale def can_view_index return if current_user.in?(@lecture.tutors) || current_user.editor_or_teacher_in?(@lecture) - redirect_to :root, alert: I18n.t('controllers.no_tutor_in_this_lecture') + redirect_to :root, alert: I18n.t("controllers.no_tutor_in_this_lecture") end def tutorial_params @@ -174,14 +172,14 @@ def bulk_params params.permit(:package) end - def bulk_download(zipped, end_of_file = '') + def bulk_download(zipped, end_of_file = "") if zipped.is_a?(StringIO) - send_data zipped.read, - filename: @assignment.title + '@' + @tutorial.title + end_of_file + '.zip', - type: 'application/zip', - disposition: 'attachment' + send_data(zipped.read, + filename: "#{@assignment.title}@#{@tutorial.title}#{end_of_file}.zip", + type: "application/zip", + disposition: "attachment") else - flash[:alert] = I18n.t('controllers.tutorials.bulk_download_failed', + flash[:alert] = I18n.t("controllers.tutorials.bulk_download_failed", message: zipped) redirect_to lecture_tutorials_path(@tutorial.lecture, params: diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0f03a0aca..750f5631c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,7 +3,7 @@ class UsersController < ApplicationController before_action :set_elevated_users, only: [:index, :list_generic_users] before_action :set_user, only: [:edit, :update, :destroy] - layout 'administration' + layout "administration" def current_ability @current_ability ||= UserAbility.new(current_user) @@ -24,7 +24,7 @@ def update old_image_data = @user.image_data @user.update(user_params) @errors = @user.errors - @user.update(image: nil) if params[:user][:detach_image] == 'true' + @user.update(image: nil) if params[:user][:detach_image] == "true" changed_image = @user.image_data != old_image_data if @user.image.present? && changed_image @user.image_derivatives! @@ -38,12 +38,12 @@ def elevate authorize! :elevate, User.new @errors = {} @user = User.find(elevate_params[:id]) - admin = elevate_params[:admin] == '1' + admin = elevate_params[:admin] == "1" return unless admin # enforce a name if @user.name.blank? - name = @user.email.split('@')[0] + name = @user.email.split("@")[0] @user.update(admin: true, name: name) else @user.update(admin: true) @@ -64,14 +64,14 @@ def destroy end def teacher - @teacher = User.find_by_id(params[:teacher_id]) + @teacher = User.find_by(id: params[:teacher_id]) authorize! :teacher, @teacher if @teacher.present? && @teacher.teacher? - render layout: 'application' + render layout: "application" return end redirect_to :root, - alert: I18n.t('controllers.no_teacher') + alert: I18n.t("controllers.no_teacher") end def fill_user_select @@ -103,10 +103,10 @@ def user_params end def set_user - @user = User.find_by_id(params[:id]) + @user = User.find_by(id: params[:id]) return unless @user.nil? - redirect_to :root, alert: I18n.t('controllers.no_medium') + redirect_to :root, alert: I18n.t("controllers.no_medium") end def set_elevated_users diff --git a/app/controllers/vertices_controller.rb b/app/controllers/vertices_controller.rb index 3f3e027d9..102a12823 100644 --- a/app/controllers/vertices_controller.rb +++ b/app/controllers/vertices_controller.rb @@ -1,7 +1,7 @@ # VerticesController class VerticesController < ApplicationController before_action :set_values - # note that we do not use cancancan's authorization methods in the actions + # NOTE: that we do not use cancancan's authorization methods in the actions # as we could not get it to work here # it seems not to accept the quiz parameter: # authorize! :new, :vertex, @quiz @@ -52,7 +52,7 @@ def destroy def set_values @quiz_id = params[:quiz_id] - @quiz = Quiz.find_by_id(@quiz_id) + @quiz = Quiz.find_by(id: @quiz_id) @params_v = params[:vertex] end @@ -65,9 +65,9 @@ def set_update_vertex_params def set_create_vertex_params @sort = @params_v[:sort] - if @sort == 'import' + if @sort == "import" @quizzables = Medium.where(id: @params_v[:quizzable_ids], - type: ['Question', 'Remark']) + type: ["Question", "Remark"]) @success = @quizzables.any? else quizzable = @sort.constantize.create_prefilled(@params_v[:label], @@ -80,24 +80,24 @@ def set_create_vertex_params def set_branching_hash @branching = {} - @params_v.keys.select { |k| k.start_with?('branching-') }.each do |k| - next if @params_v[k].to_i == 0 + @params_v.keys.select { |k| k.start_with?("branching-") }.each do |k| + next if @params_v[k].to_i.zero? - @branching[k.remove('branching-').to_h] = + @branching[k.remove("branching-").to_h] = [@vertex_id, @params_v[k].to_i] end end def set_hide_array - @hide = @params_v.keys.select { |k| k.start_with?('hide-') } - .select { |h| @params_v[h] == '1' } - .map { |h| h.remove('hide-').to_h } + @hide = @params_v.keys.select { |k| k.start_with?("hide-") } + .select { |h| @params_v[h] == "1" } + .map { |h| h.remove("hide-").to_h } end def check_permission return if current_user.admin return if current_user.can_edit?(@quiz) - redirect_to :root, alert: I18n.t('controllers.unauthorized') + redirect_to :root, alert: I18n.t("controllers.unauthorized") end end diff --git a/app/controllers/watchlist_entries_controller.rb b/app/controllers/watchlist_entries_controller.rb index 106e8644e..cf1e3f288 100644 --- a/app/controllers/watchlist_entries_controller.rb +++ b/app/controllers/watchlist_entries_controller.rb @@ -6,15 +6,13 @@ def current_ability def create @watchlist_entry = WatchlistEntry.new - @watchlist = Watchlist.find_by_id(params[:watchlist_entry][:watchlist_id]) + @watchlist = Watchlist.find_by(id: params[:watchlist_entry][:watchlist_id]) @watchlist_entry.watchlist = @watchlist - @medium = Medium.find_by_id(params[:watchlist_entry][:medium_id]) + @medium = Medium.find_by(id: params[:watchlist_entry][:medium_id]) @watchlist_entry.medium = @medium authorize! :create, @watchlist_entry @success = @watchlist_entry.save - if @success - flash[:notice] = I18n.t('watchlist_entry.add_success') - end + flash[:notice] = I18n.t("watchlist_entry.add_success") if @success respond_to do |format| format.js end @@ -24,9 +22,9 @@ def destroy @watchlist_entry = WatchlistEntry.find(params[:id]) authorize! :destroy, @watchlist_entry @watchlist_entry.destroy - flash[:notice] = I18n.t('watchlist_entry.deletion') - redirect_to controller: 'watchlists', - action: 'show', + flash[:notice] = I18n.t("watchlist_entry.deletion") + redirect_to controller: "watchlists", + action: "show", id: params[:watchlist], all: params[:all], reverse: params[:reverse], diff --git a/app/controllers/watchlists_controller.rb b/app/controllers/watchlists_controller.rb index fc616dbf9..7e4cbf49c 100644 --- a/app/controllers/watchlists_controller.rb +++ b/app/controllers/watchlists_controller.rb @@ -5,26 +5,47 @@ class WatchlistsController < ApplicationController before_action :sanitize_params, only: [:show, :update_order, :change_visibility] - layout 'application_no_sidebar' + layout "application_no_sidebar" def current_ability @current_ability ||= WatchlistAbility.new(current_user) end + def index + authorize! :index, Watchlist + @watchlists = current_user.watchlists + if @watchlists.present? + redirect_to watchlist_path(@watchlists.first) + return + end + render "show" + end + + def show + authorize! :show, @watchlist + @watchlists = current_user.watchlists + return if @watchlist.watchlist_entries.empty? + + @watchlist_entries = paginated_results + @media = @watchlist_entries.pluck(:medium_id) + end + def new authorize! :new, Watchlist end + def edit + authorize! :edit, @watchlist + end + def create @watchlist = Watchlist.new(name: create_params[:name], user: current_user, description: create_params[:description]) authorize! :create, @watchlist - @medium = Medium.find_by_id(create_params[:medium_id]) + @medium = Medium.find_by(id: create_params[:medium_id]) @success = @watchlist.save - if @medium.blank? && @success - flash[:notice] = I18n.t('watchlist.creation_success') - end + flash[:notice] = I18n.t("watchlist.creation_success") if @medium.blank? && @success respond_to do |format| format.js end @@ -33,63 +54,38 @@ def create def update authorize! :update, @watchlist @success = @watchlist.update(update_params) - if @success - flash[:notice] = I18n.t('watchlist.change_success') - end + flash[:notice] = I18n.t("watchlist.change_success") if @success respond_to do |format| format.js end end - def edit - authorize! :edit, @watchlist - end - def destroy authorize! :destroy, @watchlist @success = @watchlist.destroy if @success - flash[:notice] = I18n.t('watchlist.delete_success') + flash[:notice] = I18n.t("watchlist.delete_success") else - flash[:alert] = I18n.t('watchlist.delete_failed') + flash[:alert] = I18n.t("watchlist.delete_failed") end redirect_to watchlists_path end - def index - authorize! :index, Watchlist - @watchlists = current_user.watchlists - if @watchlists.present? - redirect_to watchlist_path(@watchlists.first) - return - end - render 'show' - end - - def show - authorize! :show, @watchlist - @watchlists = current_user.watchlists - return if @watchlist.watchlist_entries.empty? - - @watchlist_entries = paginated_results - @media = @watchlist_entries.pluck(:medium_id) - end - def add_medium authorize! :add_medium, Watchlist @watchlists = current_user.watchlists - @medium = Medium.find_by_id(params[:medium_id]) + @medium = Medium.find_by(id: params[:medium_id]) end def update_order - entries = params[:order].map { |id| WatchlistEntry.find_by_id(id) } + entries = params[:order].map { |id| WatchlistEntry.find_by(id: id) } authorize! :update_order, @watchlist, entries page = params[:page].to_i per = params[:per].to_i if params[:reverse] entries.reverse! - shift = @watchlist.watchlist_entries.size - page * per unless page == 0 + shift = @watchlist.watchlist_entries.size - (page * per) unless page.zero? else shift = page * per end @@ -106,15 +102,15 @@ def change_visibility private def set_watchlist - @watchlist = Watchlist.find_by_id(params[:id]) + @watchlist = Watchlist.find_by(id: params[:id]) return if @watchlist.present? - redirect_to :root, alert: I18n.t('controllers.no_watchlist') + redirect_to :root, alert: I18n.t("controllers.no_watchlist") end def sanitize_params - params[:reverse] = params[:reverse] == 'true' - params[:public] = params[:public] == 'true' + params[:reverse] = params[:reverse] == "true" + params[:public] = params[:public] == "true" end def paginated_results diff --git a/app/helpers/announcements_helper.rb b/app/helpers/announcements_helper.rb index 9994c564b..13d4cc709 100644 --- a/app/helpers/announcements_helper.rb +++ b/app/helpers/announcements_helper.rb @@ -3,26 +3,24 @@ module AnnouncementsHelper # create text for notification about new announcement in notification dropdown # menu def announcement_notification_item_header(announcement) - unless announcement.lecture.present? - return t('notifications.mampf_announcement') - end + return t("notifications.mampf_announcement") if announcement.lecture.blank? - t('notifications.lecture_announcement', + t("notifications.lecture_announcement", title: announcement.lecture.title_for_viewers) end # make announcements cards colored if the announcement is active def news_card_color(announcement) - return '' unless user_signed_in? - return 'bg-post-it-blue' if announcement.active?(current_user) + return "" unless user_signed_in? + return "bg-post-it-blue" if announcement.active?(current_user) - '' + "" end # create text for lecture announcement in notification card header def announcement_notification_card_header(announcement) link_to(announcement.lecture.title_for_viewers, announcement.lecture.path(current_user), - class: 'text-dark') + class: "text-dark") end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 24a7684a9..50f6b333b 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -14,7 +14,13 @@ def current_lecture # Returns the complete url for the media upload folder if in production def host - Rails.env.production? ? ENV['MEDIA_SERVER'] + '/' + ENV['INSTANCE_NAME'] : '' + if Rails.env.production? + # rubocop:disable Style/StringConcatenation + ENV.fetch("MEDIA_SERVER", nil) + "/" + ENV.fetch("INSTANCE_NAME", nil) + # rubocop:enable Style/StringConcatenation + else + "" + end end # The HTML download attribute only works for files within the domain of @@ -23,15 +29,15 @@ def host # the actual media server. # This is used for the download buttons for videos and manuscripts. def download_host - Rails.env.production? ? ENV['DOWNLOAD_LOCATION'] : '' + Rails.env.production? ? ENV.fetch("DOWNLOAD_LOCATION", nil) : "" end # Returns the full title on a per-page basis. - def full_title(page_title = '') - return page_title if action_name == 'play' && controller_name == 'media' - return 'Quiz' if action_name == 'take' && controller_name == 'quizzes' + def full_title(page_title = "") + return page_title if action_name == "play" && controller_name == "media" + return "Quiz" if action_name == "take" && controller_name == "quizzes" - base_title = 'MaMpf' + base_title = "MaMpf" if user_signed_in? && current_user.notifications.any? base_title += " (#{current_user.notifications.size})" end @@ -40,92 +46,92 @@ def full_title(page_title = '') # next methods are service methods for the display status of HTML elmements def hide(value) - value ? 'none;' : 'block;' + value ? "none;" : "block;" end def show(value) - value ? 'block;' : 'none;' + value ? "block;" : "none;" end def show_inline(value) - value ? 'inline;' : 'none;' + value ? "inline;" : "none;" end def show_no_block(value) - value ? '' : 'none;' + value ? "" : "none;" end # active attribute for navs def active(value) - value ? 'active' : '' + value ? "active" : "" end # show/collapse attributes for collapses and accordions def show_collapse(value) - value ? 'show collapse' : 'collapse' + value ? "show collapse" : "collapse" end def show_tab(value) - value ? 'show active' : '' + value ? "show active" : "" end def text_dark(value) - value ? '' : 'text-dark' + value ? "" : "text-dark" end def text_dark_link(value) - value ? 'text-primary' : 'text-dark' + value ? "text-primary" : "text-dark" end # media_sort -> database fields def media_types - { 'kaviar' => ['Kaviar'], 'sesam' => ['Sesam'], - 'keks' => ['Quiz'], - 'kiwi' => ['Kiwi'], - 'erdbeere' => ['Erdbeere'], 'nuesse' => ['Nuesse'], - 'script' => ['Script'], 'questions' => ['Question'], - 'remarks' => ['Remark'], 'reste' => ['Reste'] } + { "kaviar" => ["Kaviar"], "sesam" => ["Sesam"], + "keks" => ["Quiz"], + "kiwi" => ["Kiwi"], + "erdbeere" => ["Erdbeere"], "nuesse" => ["Nuesse"], + "script" => ["Script"], "questions" => ["Question"], + "remarks" => ["Remark"], "reste" => ["Reste"] } end # media_sorts def media_sorts - %w[kaviar sesam keks kiwi erdbeere nuesse script - questions remarks reste] + ["kaviar", "sesam", "keks", "kiwi", "erdbeere", "nuesse", "script", "questions", "remarks", + "reste"] end # media_sort -> acronym def media_names - { 'kaviar' => t('categories.kaviar.plural'), - 'sesam' => t('categories.sesam.plural'), - 'keks' => t('categories.quiz.plural'), - 'kiwi' => t('categories.kiwi.singular'), - 'erdbeere' => t('categories.erdbeere.singular'), - 'nuesse' => t('categories.exercises.plural'), - 'script' => t('categories.script.singular'), - 'questions' => t('categories.question.plural'), - 'remarks' => t('categories.remark.plural'), - 'reste' => t('categories.reste.singular') } + { "kaviar" => t("categories.kaviar.plural"), + "sesam" => t("categories.sesam.plural"), + "keks" => t("categories.quiz.plural"), + "kiwi" => t("categories.kiwi.singular"), + "erdbeere" => t("categories.erdbeere.singular"), + "nuesse" => t("categories.exercises.plural"), + "script" => t("categories.script.singular"), + "questions" => t("categories.question.plural"), + "remarks" => t("categories.remark.plural"), + "reste" => t("categories.reste.singular") } end # Selects all media associated to lectures and lessons from a given list # of media def lecture_media(media) - media.where(teachable_type: %w[Lecture Lesson]) + media.where(teachable_type: ["Lecture", "Lesson"]) end # Selects all media associated to courses from a given list of media def course_media(media) - media.where(teachable_type: 'Course') + media.where(teachable_type: "Course") end # For a given list of media, returns the array of courses and lectures # the given media are associated to. def lecture_course_teachables(media) teachables = media.pluck(:teachable_type, :teachable_id).uniq - course_ids = teachables.select { |t| t.first == 'Course' }.map(&:second) - lecture_ids = teachables.select { |t| t.first == 'Lecture' }.map(&:second) - lesson_ids = teachables.select { |t| t.first == 'Lesson' }.map(&:second) - talk_ids = teachables.select { |t| t.first == 'Talk' }.map(&:second) + course_ids = teachables.select { |t| t.first == "Course" }.map(&:second) + lecture_ids = teachables.select { |t| t.first == "Lecture" }.map(&:second) + lesson_ids = teachables.select { |t| t.first == "Lesson" }.map(&:second) + talk_ids = teachables.select { |t| t.first == "Talk" }.map(&:second) lecture_ids += Lesson.where(id: lesson_ids).pluck(:lecture_id).uniq lecture_ids += Talk.where(id: talk_ids).pluck(:lecture_id).uniq Course.where(id: course_ids) + Lecture.where(id: lecture_ids.uniq) @@ -137,7 +143,6 @@ def lecture_course_teachables(media) # (b) associated to the given lecture or a lesson associated to the given # lecture def relevant_media(teachable, media, limit) - result = [] if teachable.instance_of?(Course) return media.where(teachable: teachable).order(:created_at) .reverse_order @@ -149,7 +154,7 @@ def relevant_media(teachable, media, limit) # splits an array into smaller parts def split_list(list, pieces = 4) - group_size = (list.count / pieces) != 0 ? list.count / pieces : 1 + group_size = (list.count / pieces).zero? ? 1 : list.count / pieces groups = list.in_groups_of(group_size) diff = groups.count - pieces return groups if diff <= 0 @@ -161,7 +166,7 @@ def split_list(list, pieces = 4) # returns true for 'media#enrich' action def enrich?(controller, action) - return true if controller == 'media' && action == 'enrich' + return true if controller == "media" && action == "enrich" false end @@ -169,38 +174,38 @@ def enrich?(controller, action) # cuts off a given string so that a given number of letters is not exceeded # string is given ... as ending if it is too long def shorten(title, max_letters) - return '' if title.blank? + return "" if title.blank? return title unless title.length > max_letters - title[0, max_letters - 3] + '...' + "#{title[0, max_letters - 3]}..." end # Returns the grouped list of all courses/lectures/references together # with their ids. Is used in grouped_options_for_select in form helpers. def grouped_teachable_list list = [] - Course.all.each do |c| - lectures = [[c.short_title + ' (' + t('basics.all') + ')', - 'Course-' + c.id.to_s]] - c.lectures.includes(:term).each do |l| - lectures.push [l.short_title_release, 'Lecture-' + l.id.to_s] + Course.find_each do |c| + lectures = [["#{c.short_title} (#{t("basics.all")})", + "Course-#{c.id}"]] + c.lectures.includes(:term).find_each do |l| + lectures.push([l.short_title_release, "Lecture-#{l.id}"]) end - list.push [c.title, lectures] + list.push([c.title, lectures]) end - list.push [t('admin.referral.external_references'), - [[t('admin.referral.external_all'), 'external-0']]] + list.push([t("admin.referral.external_references"), + [[t("admin.referral.external_all"), "external-0"]]]) end # Returns the grouped list of all courses/lectures together with their ids. # Is used in grouped_options_for_select in form helpers def grouped_teachable_list_alternative list = [] - Course.all.each do |c| - lectures = [[c.short_title + ' Modul', 'Course-' + c.id.to_s]] - c.lectures.includes(:term).each do |l| - lectures.push [l.short_title, 'Lecture-' + l.id.to_s] + Course.find_each do |c| + lectures = [["#{c.short_title} Modul", "Course-#{c.id}"]] + c.lectures.includes(:term).find_each do |l| + lectures.push([l.short_title, "Lecture-#{l.id}"]) end - list.push [c.title, lectures] + list.push([c.title, lectures]) end list end @@ -231,57 +236,53 @@ def edit_or_inspect_medium_path(medium) # anything older than today or yesterday gets reduced to the day.month.year # yesterday's/today's dates are return as 'gestern/heute' plus hour:mins def human_readable_date(date) - if date.to_date == Date.today - return t('today') + ', ' + date.strftime('%H:%M') - end - if date.to_date == Date.yesterday - return t('yesterday') + ', ' + date.strftime('%H:%M') - end + return "#{t("today")}, #{date.strftime("%H:%M")}" if date.to_date == Time.zone.today + return "#{t("yesterday")}, #{date.strftime("%H:%M")}" if date.to_date == Date.yesterday - I18n.localize date, format: :concise + I18n.l(date, format: :concise) end # prepend a select prompt to selection for options_for_select def add_prompt(selection) - [[t('basics.select'), '']] + selection + [[t("basics.select"), ""]] + selection end def quizzable_color(type) - 'bg-' + type.downcase + "bg-#{type.downcase}" end def questioncolor(value) - value ? 'bg-question' : '' + value ? "bg-question" : "" end def vertex_label(quiz, vertex_id) - vertex_id.to_s + ' ' + quiz.quizzable(vertex_id)&.label.to_s + "#{vertex_id} #{quiz.quizzable(vertex_id)&.label}" end def ballot_box(correctness) - raw(correctness ? '☒' : '☐') + raw(correctness ? "☒" : "☐") # rubocop:disable Rails/OutputSafety end def boxcolor(correctness) - correctness ? 'correct' : 'incorrect' + correctness ? "correct" : "incorrect" end def bgcolor(correctness) - correctness ? 'bg-correct' : 'bg-incorrect' + correctness ? "bg-correct" : "bg-incorrect" end def hide_as_class(value) - value ? 'no_display' : '' + value ? "no_display" : "" end - def helpdesk(text, html, title = t('info')) - tag.i class: 'far fa-question-circle helpdesk ms-2', + def helpdesk(text, html, title = t("info")) + tag.i(class: "far fa-question-circle helpdesk ms-2", tabindex: -1, - 'data-bs-toggle': 'popover', - 'data-bs-trigger': 'focus', - 'data-bs-content': text, - 'data-bs-html': html, - title: title + "data-bs-toggle": "popover", + "data-bs-trigger": "focus", + "data-bs-content": text, + "data-bs-html": html, + title: title) end def realization_path(realization) @@ -291,10 +292,10 @@ def realization_path(realization) def first_course_independent? current_user.administrated_courses .natural_sort_by(&:title) - &.first&.term_independent + &.first&.term_independent end - def get_announcements + def main_page_announcements megaphone_icon_str = '' separator_str = "
#{megaphone_icon_str}" Announcement.active_on_main @@ -305,29 +306,25 @@ def get_announcements # Navbar items styling based on which page we are on # https://gist.github.com/mynameispj/5692162 - $active_css_class = 'active-item' + ACTIVE_CSS_CLASS = "active-item".freeze def get_class_for_project(project) - request.params['project'] == project ? $active_css_class : '' + request.params["project"] == project ? ACTIVE_CSS_CLASS : "" end def get_class_for_path(path) - request.path == path ? $active_css_class : '' + request.path == path ? ACTIVE_CSS_CLASS : "" end def get_class_for_path_startswith(path) - request.path.starts_with?(path) ? $active_css_class : '' + request.path.starts_with?(path) ? ACTIVE_CSS_CLASS : "" end def get_class_for_any_path(paths) - paths.include?(request.path) ? $active_css_class : '' + paths.include?(request.path) ? ACTIVE_CSS_CLASS : "" end def get_class_for_any_path_startswith(paths) - if paths.any? { |path| request.path.starts_with?(path) } - return $active_css_class - end - - '' + paths.any? { |path| request.path.starts_with?(path) } ? ACTIVE_CSS_CLASS : "" end end diff --git a/app/helpers/assignments_helper.rb b/app/helpers/assignments_helper.rb index ce0369e2f..6f6a01ad5 100644 --- a/app/helpers/assignments_helper.rb +++ b/app/helpers/assignments_helper.rb @@ -6,18 +6,9 @@ def cancel_editing_assignment_path(assignment) cancel_new_assignment_path(params: { lecture: assignment.lecture }) end - def has_documents?(assignment) - return false unless assignment.medium - - assignment.medium.video || assignment.medium.manuscript || - assignment.medium.geogebra || - assignment.medium.external_reference_link.present? || - (assignment.medium.sort == 'Quiz' && assignment.medium.quiz_graph) - end - def file_button_text(assignment) - return I18n.t('basics.file') unless assignment.accepted_file_type == '.pdf' + return I18n.t("basics.file") unless assignment.accepted_file_type == ".pdf" - I18n.t('basics.files') + I18n.t("basics.files") end end diff --git a/app/helpers/chapters_helper.rb b/app/helpers/chapters_helper.rb index 8d806f67b..bf4b454a1 100644 --- a/app/helpers/chapters_helper.rb +++ b/app/helpers/chapters_helper.rb @@ -1,7 +1,7 @@ # Chapters Helper module ChaptersHelper def chapter_positions_for_select(chapter) - [[t('basics.at_the_beginning'), 0]] + chapter.lecture.select_chapters - + [[t("basics.at_the_beginning"), 0]] + chapter.lecture.select_chapters - [[chapter.to_label, chapter.position]] end end diff --git a/app/helpers/clickers_helper.rb b/app/helpers/clickers_helper.rb index cde787a1e..d2477f2de 100644 --- a/app/helpers/clickers_helper.rb +++ b/app/helpers/clickers_helper.rb @@ -1,10 +1,10 @@ # Clickers Helper module ClickersHelper def generate_qr(text) - require 'barby' - require 'barby/barcode' - require 'barby/barcode/qr_code' - require 'barby/outputter/png_outputter' + require "barby" + require "barby/barcode" + require "barby/barcode/qr_code" + require "barby/outputter/png_outputter" qrcode = Barby::QrCode.new(text, level: :h, size: 8) base64_output = Base64.encode64(qrcode.to_png({ xdim: 8 })) diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb index d205eb5e0..749726aec 100644 --- a/app/helpers/courses_helper.rb +++ b/app/helpers/courses_helper.rb @@ -2,43 +2,41 @@ module CoursesHelper # create text for notification about new course in notification dropdown menu def course_notification_item_header(course) - t('notifications.new_course', title: course.title) + t("notifications.new_course", title: course.title) end # create text for notification card - def course_notification_item_details(course) - t('notifications.subscribe_course') + def course_notification_item_details(_course) + t("notifications.subscribe_course") end # create text for notification about new course in notification card def course_notification_card_text(course) - t('notifications.new_course_created_html', title: course.title) + t("notifications.new_course_created_html", title: course.title) end # create link for notification about new lecture in notification card def course_notification_card_link - t('notifications.subscribe_course_html', - profile: link_to(t('notifications.profile'), + t("notifications.subscribe_course_html", + profile: link_to(t("notifications.profile"), edit_profile_path, - class: 'darkblue')) + class: "darkblue")) end def course_link_or_text(course, user) - unless user.admin || user.in?(course.editors) - return course.title - end + return course.title unless user.admin || user.in?(course.editors) link_to(course.title, edit_course_path(course)) end def course_edit_icon(course) - link_to edit_course_path(course), - class: 'text-dark me-2', - style: 'text-decoration: none;', - data: { toggle: 'tooltip', - placement: 'bottom' }, - title: t('buttons.edit') do - tag.i class: 'far fa-edit' + link_to(edit_course_path(course), + class: "text-dark me-2", + style: "text-decoration: none;", + data: { toggle: "tooltip", + placement: "bottom" }, + title: t("buttons.edit")) do + tag.i(class: "far fa-edit") end end end diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb index 17ea7a4e3..1031b37fc 100644 --- a/app/helpers/email_helper.rb +++ b/app/helpers/email_helper.rb @@ -1,6 +1,6 @@ module EmailHelper def email_image_tag(image, **options) - attachments.inline[image] = File.read(Rails.root.join("public/#{image}")) - image_tag attachments[image].url, **options + attachments.inline[image] = Rails.root.join("public/#{image}").read + image_tag(attachments[image].url, **options) end end diff --git a/app/helpers/items_helper.rb b/app/helpers/items_helper.rb index 217a5c6cc..a3de12bee 100644 --- a/app/helpers/items_helper.rb +++ b/app/helpers/items_helper.rb @@ -3,7 +3,7 @@ module ItemsHelper # returns the list of sections for the given item, # as is used in options_for_select def select_sections(item) - [[I18n.t('admin.item.no_section'), '']] + + [[I18n.t("admin.item.no_section"), ""]] + item.medium.teachable&.section_selection end @@ -16,12 +16,12 @@ def select_script_items(lecture) end def check_unless_hidden(item_id) - return 'checked' unless Item.find_by_id(item_id)&.hidden + return "checked" unless Item.find_by(id: item_id)&.hidden - '' + "" end def check_status(content) - content['hidden'] ? '' : 'checked ' + content["hidden"] ? "" : "checked " end end diff --git a/app/helpers/lectures_helper.rb b/app/helpers/lectures_helper.rb index c1dda9fd3..b7d7f739e 100644 --- a/app/helpers/lectures_helper.rb +++ b/app/helpers/lectures_helper.rb @@ -10,17 +10,17 @@ def lecture_deletable?(lecture) # create text for notification about new lecture in notification dropdown menu def lecture_notification_item_header(lecture) - t('notifications.new_lecture', title: lecture.title_for_viewers) + t("notifications.new_lecture", title: lecture.title_for_viewers) end # create text for notification card - def lecture_notification_item_details(lecture) - t('notifications.subscribe_lecture') + def lecture_notification_item_details(_lecture) + t("notifications.subscribe_lecture") end # create text for notification about new course in notification card def lecture_notification_card_text(lecture) - t('notifications.new_lecture_created_html', + t("notifications.new_lecture_created_html", title: lecture.course.title, term: lecture.term_to_label, teacher: lecture.teacher.name) @@ -28,80 +28,78 @@ def lecture_notification_card_text(lecture) # create link for notification about new course in notification card def lecture_notification_card_link - t('notifications.subscribe_lecture_html', - profile: link_to(t('notifications.profile'), + t("notifications.subscribe_lecture_html", + profile: link_to(t("notifications.profile"), edit_profile_path, - class: 'darkblue')) + class: "darkblue")) end def days_short - ['Mo', 'Di', 'Mi', 'Do', 'Fr'] + ["Mo", "Di", "Mi", "Do", "Fr"] end # unpublished lecture get a different link color def lectures_color(lecture) - return '' if lecture.published? + return "" if lecture.published? - 'unpublished' + "unpublished" end # hidden chapters get a different color def chapter_card_color(chapter) - return 'bg-mdb-color-lighten-5' unless chapter.hidden + return "bg-mdb-color-lighten-5" unless chapter.hidden - 'greyed_out bg-grey' + "greyed_out bg-grey" end # hidden chapters get a different header color def chapter_header_color(chapter) - return 'bg-mdb-color-lighten-2' unless chapter.hidden + return "bg-mdb-color-lighten-2" unless chapter.hidden - '' + "" end # hidden sections get a different color def section_color(section) - return '' unless section.hidden + return "" unless section.hidden - 'greyed_out' + "greyed_out" end # hidden sections get a different background color def section_background_color(section) - unless !section.chapter.hidden && section.hidden - return 'bg-mdb-color-lighten-6' - end + return "bg-mdb-color-lighten-6" unless !section.chapter.hidden && section.hidden - 'bg-grey' + "bg-grey" end def news_color(news_count) - return '' unless news_count.positive? + return "" unless news_count.positive? - 'text-primary' + "text-primary" end def lecture_header_color(subscribed, lecture) - return '' unless subscribed + return "" unless subscribed - result = 'text-light ' - result += if lecture.term - 'bg-mdb-color-lighten-1' + result = "text-light " + result + if lecture.term + "bg-mdb-color-lighten-1" else - 'bg-info' + "bg-info" end end def circle_icon(subscribed) - return 'fas fa-check-circle' if subscribed + return "fas fa-check-circle" if subscribed - 'far fa-circle' + "far fa-circle" end def lecture_border(lecture) - return '' if lecture.published? + return "" if lecture.published? - 'border-danger' + "border-danger" end def lecture_access_icon(lecture) @@ -111,24 +109,24 @@ def lecture_access_icon(lecture) end def lecture_edit_icon(lecture) - link_to edit_lecture_path(lecture), - class: 'text-dark me-2', - style: 'text-decoration: none;', - data: { toggle: 'tooltip', - placement: 'bottom' }, - title: t('buttons.edit') do - tag.i class: 'far fa-edit' + link_to(edit_lecture_path(lecture), + class: "text-dark me-2", + style: "text-decoration: none;", + data: { toggle: "tooltip", + placement: "bottom" }, + title: t("buttons.edit")) do + tag.i(class: "far fa-edit") end end def lecture_view_icon(lecture) - link_to lecture_path(lecture), - class: 'text-dark me-2', - style: 'text-decoration: none;', - data: { toggle: 'tooltip', - placement: 'bottom' }, - title: t('buttons.view') do - tag.i class: 'fas fa-eye' + link_to(lecture_path(lecture), + class: "text-dark me-2", + style: "text-decoration: none;", + data: { toggle: "tooltip", + placement: "bottom" }, + title: t("buttons.view")) do + tag.i(class: "fas fa-eye") end end end diff --git a/app/helpers/lessons_helper.rb b/app/helpers/lessons_helper.rb index 0653e9207..da138045d 100644 --- a/app/helpers/lessons_helper.rb +++ b/app/helpers/lessons_helper.rb @@ -4,7 +4,7 @@ module LessonsHelper # tags, all given by label, together with their ids. # Is used in options_for_select in form helpers. def lesson_tag_selection(lesson) - lesson.section_tags.map { |t| t.extended_title_id_hash } + lesson.section_tags.map(&:extended_title_id_hash) .map { |t| [t[:title], t[:id]] } end diff --git a/app/helpers/media_helper.rb b/app/helpers/media_helper.rb index 711a57dfa..6cc9c5140 100644 --- a/app/helpers/media_helper.rb +++ b/app/helpers/media_helper.rb @@ -14,15 +14,15 @@ def medium_editor?(medium) end def video_download_file(medium) - medium.title + '.mp4' + "#{medium.title}.mp4" end def manuscript_download_file(medium) - medium.title + '.pdf' + "#{medium.title}.pdf" end def geogebra_download_file(medium) - medium.title + '.ggb' + "#{medium.title}.ggb" end def inspect_or_edit_medium_path(medium, inspection) @@ -33,7 +33,7 @@ def inspect_or_edit_medium_path(medium, inspection) def medium_notification_item_header(medium) return unless medium.proper? - t('notifications.new_medium_in') + medium.scoped_teachable_title + t("notifications.new_medium_in") + medium.scoped_teachable_title end def medium_notification_item_details(medium) @@ -43,20 +43,18 @@ def medium_notification_item_details(medium) # create text for notification about new medium in notification card def medium_notification_card_header(medium) teachable = medium.teachable - if teachable.media_scope.class.to_s == 'Course' - return teachable.media_scope.title_for_viewers - end + return teachable.media_scope.title_for_viewers if teachable.media_scope.instance_of?(::Course) link_to(teachable.media_scope.title_for_viewers, medium.teachable.media_scope.path(current_user), - class: 'text-dark') + class: "text-dark") end # create link to medium in notification card def medium_notification_card_link(medium) link_to(medium.local_title_for_viewers, medium.becomes(Medium), - class: 'darkblue') + class: "darkblue") end def section_selection(medium) @@ -64,62 +62,60 @@ def section_selection(medium) end def preselected_sections(medium) - return [] unless medium.teachable.class.to_s == 'Lesson' + return [] unless medium.teachable.instance_of?(::Lesson) medium.teachable.sections.map(&:id) end def textcolor(medium) - return '' if medium.visible? - return 'locked' if medium.locked? - return 'scheduled_release' if medium.publisher.present? + return "" if medium.visible? + return "locked" if medium.locked? + return "scheduled_release" if medium.publisher.present? - 'unpublished' + "unpublished" end def infotainment(medium) - return 'nichts' unless medium.video || medium.manuscript - return 'ein Video' unless medium.manuscript - return 'ein Manuskript' unless medium.video + return "nichts" unless medium.video || medium.manuscript + return "ein Video" unless medium.manuscript + return "ein Manuskript" unless medium.video - 'ein Video und ein Manuskript' + "ein Video und ein Manuskript" end def level_to_word(medium) - return t('basics.not_set') unless medium.level.present? - return t('basics.level_easy') if medium.level == 0 - return t('basics.level_medium') if medium.level == 1 + return t("basics.not_set") if medium.level.blank? + return t("basics.level_easy") if medium.level.zero? + return t("basics.level_medium") if medium.level == 1 - t('basics.level_hard') + t("basics.level_hard") end def independent_to_word(medium) - return t('basics.no_lc') unless medium.independent + return t("basics.no_lc") unless medium.independent - t('basics.yes_lc') + t("basics.yes_lc") end def medium_border(medium) return if medium.published? && !medium.locked? - 'border-danger' + "border-danger" end def media_sorts_select(purpose) - return add_prompt(Medium.select_quizzables) if purpose == 'quiz' - return Medium.select_question if purpose == 'clicker' - return add_prompt(Medium.select_importables) if purpose == 'import' - unless current_user.admin_or_editor? - return add_prompt(Medium.select_generic) - end + return add_prompt(Medium.select_quizzables) if purpose == "quiz" + return Medium.select_question if purpose == "clicker" + return add_prompt(Medium.select_importables) if purpose == "import" + return add_prompt(Medium.select_generic) unless current_user.admin_or_editor? add_prompt(Medium.select_sorts) end def sort_preselect(purpose) - return '' unless purpose == 'quiz' + return "" unless purpose == "quiz" - 'Question' + "Question" end def related_media_hash(references, media) @@ -128,15 +124,15 @@ def related_media_hash(references, media) hash = {} Medium.sort_enum.each do |s| media_in_s = media_list.select { |m| m.first.sort == s } - hash[s] = media_in_s unless media_in_s.blank? + hash[s] = media_in_s if media_in_s.present? end hash end def release_date_info(medium) - return unless medium.publisher.present? + return if medium.publisher.blank? - t('admin.medium.scheduled_for_release_short', + t("admin.medium.scheduled_for_release_short", release_date: I18n.l(medium.publisher&.release_date, format: :long, locale: I18n.locale)) @@ -151,7 +147,6 @@ def edit_or_show_medium_path(medium) def external_link_description_not_empty(medium) # Uses link display name if not empty, otherwise falls back to the # link url itself. - return medium.external_link_description.blank?\ - ? medium.external_reference_link : medium.external_link_description + (medium.external_link_description.presence || medium.external_reference_link) end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 74ff9883c..be7160ec2 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -3,7 +3,7 @@ module NotificationsHelper # create text for notification in notification dropdown menu def notification_menu_item_header(notification) notifiable = notification.notifiable - return '' unless notifiable + return "" unless notifiable return medium_notification_item_header(notifiable) if notification.medium? return course_notification_item_header(notifiable) if notification.course? return lecture_notification_item_header(notifiable) if notification.lecture? @@ -16,58 +16,54 @@ def notification_menu_item_details(notification) notifiable = notification.notifiable return medium_notification_item_details(notifiable) if notification.medium? return course_notification_item_details(notifiable) if notification.course? - if notification.lecture? - return lecture_notification_item_details(notifiable) - end + return lecture_notification_item_details(notifiable) if notification.lecture? - '' + "" end # determine the color of a notification card def notification_color(notification) - return 'bg-post-it-blue' if notification.generic_announcement? - return 'bg-post-it-red' if notification.announcement? - return 'bg-post-it-orange' if notification.course? || notification.lecture? + return "bg-post-it-blue" if notification.generic_announcement? + return "bg-post-it-red" if notification.announcement? + return "bg-post-it-orange" if notification.course? || notification.lecture? - 'bg-post-it-yellow' + "bg-post-it-yellow" end # provide text or link for header of notification card def notification_header(notification) notifiable = notification.notifiable - text = if notification.medium? + if notification.medium? medium_notification_card_header(notifiable) elsif notification.course? || notification.lecture? - t('notifications.course_selection') + t("notifications.course_selection") elsif notification.lecture_announcement? announcement_notification_card_header(notifiable) else - link_to t('mampf_news.title'), news_path, class: 'text-dark' + link_to(t("mampf_news.title"), news_path, class: "text-dark") end - text.html_safe end # provide text for body of notification card def notification_text(notification) notifiable = notification.notifiable - text = if notification.medium? - t('notifications.new_medium') + if notification.medium? + t("notifications.new_medium") elsif notification.course? course_notification_card_text(notifiable) elsif notification.lecture? lecture_notification_card_text(notifiable) else - t('notifications.new_announcement') + t("notifications.new_announcement") end - text.html_safe end # provide link for body of notification card def notification_link(notification) notifiable = notification.notifiable - return '' unless notifiable + return "" unless notifiable - text = if notification.medium? + if notification.medium? medium_notification_card_link(notifiable) elsif notification.course? course_notification_card_link @@ -76,13 +72,12 @@ def notification_link(notification) else notifiable.details end - text.html_safe end def items_card_size(small, comments_below) - return '30vh' if comments_below - return '60vh' if small + return "30vh" if comments_below + return "60vh" if small - '70vh' + "70vh" end end diff --git a/app/helpers/quizzes_helper.rb b/app/helpers/quizzes_helper.rb index 99c3ed98d..6a47eca0b 100644 --- a/app/helpers/quizzes_helper.rb +++ b/app/helpers/quizzes_helper.rb @@ -1,27 +1,29 @@ # Quizzes Helper module QuizzesHelper + # rubocop:disable Rails/HelperInstanceVariable def answer_id(a_id, progress = @quiz_round.progress, vertex = @quiz_round.vertex) - 'r' + progress.to_s + 'q' + vertex[:id].to_s + 'a' + a_id.to_s + "r#{progress}q#{vertex[:id]}a#{a_id}" end def quiz_id(q_id = @quiz_round.quiz.id) - 'quiz' + q_id.to_s + "quiz#{q_id}" end def result_id(a_id, progress = @quiz_round.progress, vertex = @quiz_round.vertex) - 'result' + progress.to_s + 'q' + vertex[:id].to_s + 'a' + a_id.to_s + "result#{progress}q#{vertex[:id]}a#{a_id}" end def cross_id(a_id, progress = @quiz_round.progress, vertex = @quiz_round.vertex) - 'cross' + progress.to_s + 'q' + vertex[:id].to_s + 'a' + a_id.to_s + "cross#{progress}q#{vertex[:id]}a#{a_id}" end + # rubocop:enable Rails/HelperInstanceVariable def vertices_labels_no_end(quiz) - special = [[I18n.t('admin.quiz.undefined'), 0]] + special = [[I18n.t("admin.quiz.undefined"), 0]] list = quiz.vertices.keys.collect { |k| [vertex_label(quiz, k), k] } - special.concat list + special.concat(list) end end diff --git a/app/helpers/referrals_helper.rb b/app/helpers/referrals_helper.rb index fe8eef939..b003d8112 100644 --- a/app/helpers/referrals_helper.rb +++ b/app/helpers/referrals_helper.rb @@ -4,17 +4,15 @@ module ReferralsHelper # in the form required by the teachable selector in the referral form, # e.g. as 'Lecture-42', 'Course-5' etc. def teachable_selector(referral) - return '' unless referral.medium.present? - unless referral.item.present? - return referral.medium.teachable&.media_scope&.selector_value - end - return 'external-0' if referral.item.sort == 'link' + return "" if referral.medium.blank? + return referral.medium.teachable&.media_scope&.selector_value if referral.item.blank? + return "external-0" if referral.item.sort == "link" referral.item.medium.teachable&.media_scope&.selector_value end def show_link(referral) - return true if referral.item.present? && referral.item.sort == 'link' + return true if referral.item.present? && referral.item.sort == "link" false end @@ -29,19 +27,19 @@ def show_explanation(referral) # (pink if the item belongs to a unpublished or locked medium, white otherwise) def item_status_color(referral) - return '' if referral.item.sort == 'link' + return "" if referral.item.sort == "link" if !referral.item_published? || referral.item_locked? || referral.item.quarantine - return 'bg-post-it-pink' + return "bg-post-it-pink" end - '' + "" end def item_status_color_value(referral) - return 'white' if referral.item.sort == 'link' - return '#fad1df' if !referral.item_published? || referral.item_locked? + return "white" if referral.item.sort == "link" + return "#fad1df" if !referral.item_published? || referral.item_locked? - 'white' + "white" end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index e1e5d90c4..c0fe05188 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,11 +1,11 @@ # Search Helper -require 'fuzzystringmatch' +require "fuzzystringmatch" # if more than one matching tag was omitted letter, # add letter 'n' to 'wurde' module SearchHelper def plural_n(tags, filtered_tags) - (tags.count - filtered_tags.count) > 1 ? 'n' : '' + (tags.count - filtered_tags.count) > 1 ? "n" : "" end def hits_per_page(results_as_list) diff --git a/app/helpers/sections_helper.rb b/app/helpers/sections_helper.rb index 13b8d4eb3..58d089bea 100644 --- a/app/helpers/sections_helper.rb +++ b/app/helpers/sections_helper.rb @@ -8,12 +8,12 @@ def lecture_chapters_for_select(section) end def section_positions_for_select(section) - [[t('basics.at_the_beginning'), 0]] + section.chapter.select_sections - + [[t("basics.at_the_beginning"), 0]] + section.chapter.select_sections - [[section.to_label, section.position]] end def new_section_position_for_select(chapter) - [[t('basics.at_beginning_of_chapter'), 0]] + chapter.select_sections + [[t("basics.at_beginning_of_chapter"), 0]] + chapter.select_sections end def section_lessons_for_select(section) diff --git a/app/helpers/submissions_helper.rb b/app/helpers/submissions_helper.rb index b44505b31..480418fd3 100644 --- a/app/helpers/submissions_helper.rb +++ b/app/helpers/submissions_helper.rb @@ -14,7 +14,7 @@ def partner_preselection(user, lecture) user.recent_submission_partners(lecture).map(&:id) end - def admissible_invitee_selection(user, submission, lecture) + def admissible_invitee_selection(user, submission, _lecture) submission.admissible_invitees(user).map { |u| [u.tutorial_name, u.id] } end @@ -33,70 +33,67 @@ def invitations_possible?(submission, user) def submission_color(submission, assignment) if assignment.active? - return 'bg-submission-green' if submission&.manuscript - return 'bg-submission-yellow' if submission + return "bg-submission-green" if submission&.manuscript + return "bg-submission-yellow" if submission - return 'bg-submission-red' else - return 'bg-submission-darker-green' if submission&.correction + return "bg-submission-darker-green" if submission&.correction - if submission&.manuscript && submission.too_late? - return 'bg-submission-orange' if submission.accepted.nil? - return 'bg-submission-green' if submission.accepted + if submission&.manuscript && submission&.too_late? + return "bg-submission-orange" if submission.accepted.nil? + return "bg-submission-green" if submission.accepted - return 'bg-submission-red' + return "bg-submission-red" end - return 'bg-submission-green' if submission&.manuscript + return "bg-submission-green" if submission&.manuscript - return 'bg-submission-red' end + "bg-submission-red" end def submission_status_icon(submission, assignment) if assignment.active? - return 'far fa-smile' if submission&.manuscript + return "far fa-smile" if submission&.manuscript - return 'fas fa-exclamation-triangle' else - return 'far fa-smile' if submission&.correction + return "far fa-smile" if submission&.correction - if submission&.manuscript && submission.too_late? - return 'fas fa-hourglass-start' if submission.accepted + if submission&.manuscript && submission&.too_late? + return "fas fa-hourglass-start" if submission.accepted - return 'fas fa-exclamation-triangle' + return "fas fa-exclamation-triangle" end - return 'fas fa-hourglass-start' if submission&.manuscript + return "fas fa-hourglass-start" if submission&.manuscript - return 'fas fa-exclamation-triangle' end + "fas fa-exclamation-triangle" end def submission_status_text(submission, assignment) if assignment.active? - return t('submission.okay') if submission&.manuscript - return t('submission.no_file') if submission + return t("submission.okay") if submission&.manuscript - return t('submission.nothing') else - return t('submission.with_correction') if submission&.correction + return t("submission.with_correction") if submission&.correction - if submission&.manuscript && submission.too_late? - return t('submission.too_late') if submission.accepted.nil? - return t('submission.too_late_accepted') if submission.accepted + if submission&.manuscript && submission&.too_late? + return t("submission.too_late") if submission.accepted.nil? + return t("submission.too_late_accepted") if submission.accepted - return t('submission.too_late_rejected') + return t("submission.too_late_rejected") end - return t('submission.under_review') if submission&.manuscript - return t('submission.no_file') if submission + return t("submission.under_review") if submission&.manuscript - return t('submission.nothing') end + return t("submission.no_file") if submission + + t("submission.nothing") end def submission_status(submission, assignment) - tag.i class: [submission_status_icon(submission, assignment), 'fa-lg'], - data: { toggle: 'tooltip' }, - title: submission_status_text(submission, assignment) + tag.i(class: [submission_status_icon(submission, assignment), "fa-lg"], + data: { toggle: "tooltip" }, + title: submission_status_text(submission, assignment)) end def show_submission_footer?(submission, assignment) @@ -108,24 +105,24 @@ def show_submission_footer?(submission, assignment) end def submission_late_color(submission) - return '' unless submission.too_late? - return '' unless submission.accepted.nil? + return "" unless submission.too_late? + return "" unless submission.accepted.nil? - 'bg-submission-orange' + "bg-submission-orange" end def late_submission_info(submission, tutorial) - text = t('submission.late') + text = t("submission.late") return text unless submission.accepted.nil? && current_user.in?(tutorial.tutors) - "#{text} (#{t('tutorial.late_submission_decision')})" + "#{text} (#{t("tutorial.late_submission_decision")})" end def correction_display_mode(submission) accepted = submission.assignment.accepted_file_type non_inline = Assignment.non_inline_file_types - return t('buttons.show') unless accepted.in?(non_inline) + return t("buttons.show") unless accepted.in?(non_inline) - t('buttons.download') + t("buttons.download") end end diff --git a/app/helpers/talks_helper.rb b/app/helpers/talks_helper.rb index 73a2fcc22..8f17c847b 100644 --- a/app/helpers/talks_helper.rb +++ b/app/helpers/talks_helper.rb @@ -1,45 +1,45 @@ # Talks Helper module TalksHelper def talk_positions_for_select(talk) - [[t('basics.at_the_beginning'), 0]] + talk.lecture.select_talks - + [[t("basics.at_the_beginning"), 0]] + talk.lecture.select_talks - [[talk.to_label, talk.position]] end def talk_card_color(talk, user) - return 'bg-mdb-color-lighten-2' unless user.in?(talk.speakers) + return "bg-mdb-color-lighten-2" unless user.in?(talk.speakers) - 'bg-info' + "bg-info" end def speaker_list(talk) - return t('basics.tba') unless talk.speakers.present? + return t("basics.tba") if talk.speakers.blank? - talk.speakers.map(&:tutorial_name).join(', ') + talk.speakers.map(&:tutorial_name).join(", ") end def speaker_icon_class(talk) - return 'fas fa-user' unless talk.speakers.count > 1 + return "fas fa-user" unless talk.speakers.count > 1 - 'fas fa-users' + "fas fa-users" end def speaker_icon(talk) content_tag(:i, - '', + "", class: "#{speaker_icon_class(talk)} me-2", - data: { toggle: 'tooltip' }, - title: t('admin.talk.speakers')).html_safe + data: { toggle: "tooltip" }, + title: t("admin.talk.speakers")).html_safe end def speaker_list_with_icon(talk) - (speaker_icon(talk) + speaker_list(talk)).html_safe + speaker_icon(talk) + speaker_list(talk) end def date_list(talk) - talk.dates.map { |d| I18n.l(d) }.join(', ') + talk.dates.map { |d| I18n.l(d) }.join(", ") end def cospeaker_list(talk, user) - (talk.speakers.to_a - [user]).map(&:tutorial_name).join(', ') + (talk.speakers.to_a - [user]).map(&:tutorial_name).join(", ") end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 1918ef519..0e92f4b15 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -6,7 +6,7 @@ def users_for_select(users) def select_proper_teaching_related_lectures(user) user.proper_teaching_related_lectures - .sort_by { |l| [l.begin_date.to_time.to_i * (-1), l.title] } + .sort_by { |l| [l.begin_date.to_time.to_i * -1, l.title] } .map { |l| [l.title, l.id] } end end diff --git a/app/helpers/vertices_helper.rb b/app/helpers/vertices_helper.rb index db0401c48..a02c7cf89 100644 --- a/app/helpers/vertices_helper.rb +++ b/app/helpers/vertices_helper.rb @@ -1,12 +1,12 @@ # Vertices Helper module VerticesHelper def vertices_labels(quiz, vertex_id, undefined) - special = [[undefined ? I18n.t('admin.quiz.undefined') : I18n.t('admin.quiz.default'), 0], - [I18n.t('admin.quiz.end'), -1]] + special = [[undefined ? I18n.t("admin.quiz.undefined") : I18n.t("admin.quiz.default"), 0], + [I18n.t("admin.quiz.end"), -1]] list = (quiz.vertices.keys - [vertex_id]).collect do |k| [quiz.quizzable(k).label, k] end - special.concat list + special.concat(list) end def crosses_id(crosses) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index d8255248e..61fa90cb9 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,8 +1,10 @@ class ApplicationMailer < ActionMailer::Base helper EmailHelper default from: DefaultSetting::PROJECT_EMAIL - default "Message-ID" => -> { - "<#{rand.to_s.split('.')[1]}.#{Time.now.to_i}@#{ENV['MAILID_DOMAIN']}>" + default "Message-ID" => lambda { + "<#{rand.to_s.split(".")[1]}.#{Time.now.to_i}@#{ENV.fetch( + "MAILID_DOMAIN", nil + )}>" } - layout 'mailer' + layout "mailer" end diff --git a/app/mailers/exception_handler/exception_mailer.rb b/app/mailers/exception_handler/exception_mailer.rb index aa1913e52..84db08e96 100644 --- a/app/mailers/exception_handler/exception_mailer.rb +++ b/app/mailers/exception_handler/exception_mailer.rb @@ -1,19 +1,19 @@ module ExceptionHandler - class ExceptionMailer < ActionMailer::Base + class ExceptionMailer < ApplicationMailer # Layout layout "exception_mailer" # Defaults - default subject: I18n.t('exception.exception', - host: ENV['URL_HOST']) + default subject: I18n.t("exception.exception", + host: ENV.fetch("URL_HOST", nil)) default from: ExceptionHandler.config.email default template_path: "exception_handler/mailers" # => http://stackoverflow.com/a/18579046/1143732 - def new_exception e - @exception = e - mail to: ExceptionHandler.config.email - Rails.logger.info "Exception Sent To → #{ExceptionHandler.config.email}" + def new_exception(err) + @exception = err + mail(to: ExceptionHandler.config.email) + Rails.logger.info("Exception Sent To → #{ExceptionHandler.config.email}") end end end diff --git a/app/mailers/mathi_mailer.rb b/app/mailers/mathi_mailer.rb index 438fbe30e..de9a71172 100644 --- a/app/mailers/mathi_mailer.rb +++ b/app/mailers/mathi_mailer.rb @@ -7,20 +7,20 @@ def ghost_email(user) @name = user.name @hash = user.ghost_hash - mail(to: user.email, subject: t('mailer.hash_mail_subject')) + mail(to: user.email, subject: t("mailer.hash_mail_subject")) end def data_request_email(user) @mail = user.email @id = user.id - mail(to: DefaultSetting::PROJECT_EMAIL, subject: 'Data request') + mail(to: DefaultSetting::PROJECT_EMAIL, subject: t("mailer.data_provide_mail_subject")) end def data_provide_email(user) @user = user mail(to: user.email, - subject: t('mailer.data_provide_mail_subject')) do |format| - format.html { render layout: 'mailer' } + subject: t("mailer.data_provide_mail_subject")) do |format| + format.html { render layout: "mailer" } end end end diff --git a/app/mailers/my_mailer.rb b/app/mailers/my_mailer.rb index c57e0eb04..5d7e1ced2 100644 --- a/app/mailers/my_mailer.rb +++ b/app/mailers/my_mailer.rb @@ -2,10 +2,12 @@ class MyMailer < Devise::Mailer helper :application # gives access to all helpers defined within `application_helper`. include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url` layout "devise_mailer" - default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views + default template_path: "devise/mailer" # to make sure that your mailer uses the devise views default from: DefaultSetting::PROJECT_EMAIL - default "Message-ID" => -> { - "<#{rand.to_s.split('.')[1]}.#{Time.now.to_i}@#{ENV['MAILID_DOMAIN']}>" + default "Message-ID" => lambda { + "<#{rand.to_s.split(".")[1]}.#{Time.now.to_i}@#{ENV.fetch( + "MAILID_DOMAIN", nil + )}>" } helper EmailHelper end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index 03a4419b0..ae6a281c1 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -24,31 +24,30 @@ class NotificationMailer < ApplicationMailer def medium_email @medium = params[:medium] + subject = t("mailer.medium_subject") + viewer_title = @medium.teachable.media_scope.title_for_viewers mail(from: @sender, bcc: @recipients.pluck(:email), - subject: t('mailer.medium_subject') + ' ' + t('in') + ' ' + - @medium.teachable.media_scope.title_for_viewers) + subject: "#{subject} #{t("in")} #{viewer_title}") end def announcement_email @announcement = params[:announcement] @announcement_details = if @announcement.lecture.present? - t('in') + ' ' + - @announcement.lecture.title_for_viewers + "#{t("in")} #{@announcement.lecture.title_for_viewers}" else - t('mailer.mampf_news') + t("mailer.mampf_news") end mail(from: @sender, bcc: @recipients.pluck(:email), - subject: t('mailer.announcement_subject') + ' ' + - @announcement_details) + subject: "#{t("mailer.announcement_subject")} #{@announcement_details}") end def new_lecture_email @lecture = params[:lecture] mail(from: @sender, bcc: @recipients.pluck(:email), - subject: t('mailer.new_lecture_subject', + subject: t("mailer.new_lecture_subject", title: @lecture.title_for_viewers)) end @@ -59,7 +58,7 @@ def new_editor_email mail(from: @sender, to: @recipient.email, - subject: t('mailer.new_editor_subject', + subject: t("mailer.new_editor_subject", title: @lecture.title_for_viewers)) end @@ -70,7 +69,7 @@ def submission_invitation_email @issuer = params[:issuer] mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_invitation_subject', + subject: t("mailer.submission_invitation_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title)) end @@ -79,7 +78,7 @@ def submission_upload_email @uploader = params[:uploader] mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_upload_subject', + subject: t("mailer.submission_upload_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title)) end @@ -88,7 +87,7 @@ def submission_upload_removal_email @remover = params[:remover] mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_upload_removal_subject', + subject: t("mailer.submission_upload_removal_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title)) end @@ -96,7 +95,7 @@ def submission_upload_removal_email def submission_join_email mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_join_subject', + subject: t("mailer.submission_join_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title, user: @user.tutorial_name)) @@ -105,7 +104,7 @@ def submission_join_email def submission_leave_email mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_leave_subject', + subject: t("mailer.submission_leave_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title, user: @user.tutorial_name)) @@ -115,7 +114,7 @@ def correction_upload_email @tutor = params[:tutor] mail(from: @sender, to: @recipient.email, - subject: t('mailer.correction_upload_subject', + subject: t("mailer.correction_upload_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title)) end @@ -123,7 +122,7 @@ def correction_upload_email def submission_acceptance_email mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_acceptance_subject', + subject: t("mailer.submission_acceptance_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title)) end @@ -131,7 +130,7 @@ def submission_acceptance_email def submission_rejection_email mail(from: @sender, to: @recipient.email, - subject: t('mailer.submission_rejection_subject', + subject: t("mailer.submission_rejection_subject", assignment: @assignment.title, lecture: @assignment.lecture.short_title)) end @@ -139,8 +138,8 @@ def submission_rejection_email def submission_deletion_email @deletion_date = params[:deletion_date] @lectures = params[:lectures] - subject = params[:reminder] ? t('basics.reminder') + ': ' : '' - subject += t('mailer.submission_deletion_subject') + subject = params[:reminder] ? "#{t("basics.reminder")}: " : "" + subject += t("mailer.submission_deletion_subject") mail(from: @sender, bcc: @recipients.pluck(:email), subject: subject) @@ -149,8 +148,8 @@ def submission_deletion_email def submission_deletion_lecture_email @lecture = params[:lecture] @deletion_date = params[:deletion_date] - subject = params[:reminder] ? t('basics.reminder') + ': ' : '' - subject += t('mailer.submission_deletion_lecture_subject', + subject = params[:reminder] ? "#{t("basics.reminder")}: " : "" + subject += t("mailer.submission_deletion_lecture_subject", lecture: @lecture.title) mail(from: @sender, bcc: @recipients.pluck(:email), @@ -161,8 +160,8 @@ def submission_destruction_email @deletion_date = params[:deletion_date] mail(from: @sender, bcc: @recipients.pluck(:email), - subject: t('mailer.submission_destruction_subject', - deletion_date: @deletion_date.strftime(I18n.t('date.formats.concise')))) + subject: t("mailer.submission_destruction_subject", + deletion_date: @deletion_date.strftime(I18n.t("date.formats.concise")))) end def submission_destruction_lecture_email @@ -170,14 +169,14 @@ def submission_destruction_lecture_email @deletion_date = params[:deletion_date] mail(from: @sender, bcc: @recipients.pluck(:email), - subject: t('mailer.submission_destruction_lecture_subject', + subject: t("mailer.submission_destruction_lecture_subject", lecture: @lecture.title)) end private def set_sender_and_locale - @sender = "#{t('mailer.notification')} <#{DefaultSetting::PROJECT_NOTIFICATION_EMAIL}>" + @sender = "#{t("mailer.notification")} <#{DefaultSetting::PROJECT_NOTIFICATION_EMAIL}>" I18n.locale = params[:locale] end diff --git a/app/models/ability.rb b/app/models/ability.rb index fbf76c958..26dd1134b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -5,7 +5,4 @@ class Ability # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities include CanCan::Ability - - def initialize(user) - end end diff --git a/app/models/announcement.rb b/app/models/announcement.rb index 04d8eae44..4504b0ff7 100644 --- a/app/models/announcement.rb +++ b/app/models/announcement.rb @@ -2,7 +2,7 @@ class Announcement < ApplicationRecord # changing an announcement needs to make the lecture cache key expire belongs_to :lecture, optional: true, touch: true - belongs_to :announcer, class_name: 'User' + belongs_to :announcer, class_name: "User" validates :details, presence: true @@ -13,6 +13,6 @@ class Announcement < ApplicationRecord # does there (still) exist a notification for the announcement for # the given user def active?(user) - user.notifications.where(notifiable: self).exists? + user.notifications.exists?(notifiable: self) end end diff --git a/app/models/answer.rb b/app/models/answer.rb index 7bc2403a1..ea407d192 100644 --- a/app/models/answer.rb +++ b/app/models/answer.rb @@ -1,19 +1,15 @@ class Answer < ApplicationRecord belongs_to :question, touch: true + after_create :update_quizzes before_destroy :question_not_orphaned? after_destroy :update_quizzes - after_create :update_quizzes after_save :touch_medium def conditional_explanation(correct) - unless /\(korrekt:.*\):\(inkorrekt:.*\)/.match?(explanation) - return explanation - end - unless correct - return explanation.string_between_markers(':(inkorrekt:', ')') - end + return explanation unless /\(korrekt:.*\):\(inkorrekt:.*\)/.match?(explanation) + return explanation.string_between_markers(":(inkorrekt:", ")") unless correct - explanation.string_between_markers('(korrekt:', '):') + explanation.string_between_markers("(korrekt:", "):") end def duplicate(new_question) @@ -45,6 +41,6 @@ def update_quizzes end def touch_medium - question.becomes(Medium).update(updated_at: Time.now) + question.becomes(Medium).touch end end diff --git a/app/models/assignment.rb b/app/models/assignment.rb index 0da21a6ad..40f143d54 100644 --- a/app/models/assignment.rb +++ b/app/models/assignment.rb @@ -5,7 +5,9 @@ class Assignment < ApplicationRecord before_destroy :check_destructibility, prepend: true + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :title, uniqueness: { scope: [:lecture_id] }, presence: true + # rubocop:enable Rails/UniqueValidationWithoutIndex validates :deadline, presence: true validates :deletion_date, presence: true validate :deletion_date_cannot_be_in_the_past @@ -13,17 +15,17 @@ class Assignment < ApplicationRecord def deletion_date_cannot_be_in_the_past return unless deletion_date.present? && deletion_date < Time.zone.now.to_date - errors.add(:deletion_date, I18n.t('activerecord.errors.models.' \ - 'assignment.attributes.deletion_date.' \ - 'in_past')) + errors.add(:deletion_date, I18n.t("activerecord.errors.models." \ + "assignment.attributes.deletion_date." \ + "in_past")) end - scope :active, -> { where('deadline >= ?', Time.now) } + scope :active, -> { where("deadline >= ?", Time.zone.now) } - scope :expired, -> { where('deadline < ?', Time.now) } + scope :expired, -> { where("deadline < ?", Time.zone.now) } def self.accepted_file_types - ['.pdf', '.tar.gz', '.cc', '.hh', '.m', '.mlx', '.zip'] + [".pdf", ".tar.gz", ".cc", ".hh", ".m", ".mlx", ".zip"] end validates :accepted_file_type, @@ -32,7 +34,7 @@ def self.accepted_file_types def submission(user) UserSubmissionJoin.where(submission: Submission.where(assignment: self), user: user) - &.first&.submission + &.first&.submission end def submitter_ids @@ -44,11 +46,11 @@ def submitters end def active? - Time.now <= deadline + Time.zone.now <= deadline end def semiactive? - Time.now <= friendly_deadline + Time.zone.now <= friendly_deadline end def expired? @@ -70,11 +72,11 @@ def friendly_deadline end def current? - self.in?(lecture.current_assignments) + in?(lecture.current_assignments) end def previous? - self.in?(lecture.previous_assignments) + in?(lecture.previous_assignments) end def previous @@ -105,36 +107,36 @@ def check_destructibility true end - def has_documents? + def documents? return false unless medium medium.video || medium.manuscript || medium.geogebra || medium.external_reference_link.present? || - (medium.sort == 'Quiz' && medium.quiz_graph) + (medium.sort == "Quiz" && medium.quiz_graph) end def self.accepted_mime_types - { '.pdf' => ['application/pdf'], - '.tar.gz' => ['application/gzip', 'application/x-gzip', - 'application/x-gunzip', 'application/gzipped', - 'application/gzip-compressed', 'application/x-compressed', - 'application/x-compress', 'gzip/document', - 'application/octet-stream'], - '.cc' => ['text/*'], - '.hh' => ['text/*'], - '.m' => ['text/*'], - '.mlx' => ['application/zip', 'application/x-zip', - 'application/x-zip-compressed', 'application/octet-stream', - 'application/x-compress', 'application/x-compressed', - 'multipart/x-zip'], - '.zip' => ['application/zip', 'application/x-zip', - 'application/x-zip-compressed', 'application/octet-stream', - 'application/x-compress', 'application/x-compressed', - 'multipart/x-zip'] } + { ".pdf" => ["application/pdf"], + ".tar.gz" => ["application/gzip", "application/x-gzip", + "application/x-gunzip", "application/gzipped", + "application/gzip-compressed", "application/x-compressed", + "application/x-compress", "gzip/document", + "application/octet-stream"], + ".cc" => ["text/*"], + ".hh" => ["text/*"], + ".m" => ["text/*"], + ".mlx" => ["application/zip", "application/x-zip", + "application/x-zip-compressed", "application/octet-stream", + "application/x-compress", "application/x-compressed", + "multipart/x-zip"], + ".zip" => ["application/zip", "application/x-zip", + "application/x-zip-compressed", "application/octet-stream", + "application/x-compress", "application/x-compressed", + "multipart/x-zip"] } end def self.non_inline_file_types - ['.tar.gz', '.zip', '.mlx'] + [".tar.gz", ".zip", ".mlx"] end def accepted_mime_types @@ -145,12 +147,12 @@ def accepted_mime_types # is set to .tar.gz # see e.g. https://bugs.chromium.org/p/chromium/issues/detail?id=521781 def accepted_for_file_input - return accepted_file_type unless accepted_file_type == '.tar.gz' + return accepted_file_type unless accepted_file_type == ".tar.gz" - '.gz' + ".gz" end def localized_deletion_date - deletion_date.strftime(I18n.t('date.formats.concise')) + deletion_date.strftime(I18n.t("date.formats.concise")) end end diff --git a/app/models/chapter.rb b/app/models/chapter.rb index cad5d882c..c94c344cf 100644 --- a/app/models/chapter.rb +++ b/app/models/chapter.rb @@ -3,12 +3,14 @@ class Chapter < ApplicationRecord belongs_to :lecture, touch: true # the chapters of a lecture form an ordered list acts_as_list scope: :lecture - has_many :sections, -> { order(position: :asc) }, dependent: :destroy + has_many :sections, -> { order(position: :asc) }, + dependent: :destroy, + inverse_of: :chapter validates :title, presence: true - after_save :touch_sections - after_save :touch_chapters before_destroy :touch_sections before_destroy :touch_chapters + after_save :touch_sections + after_save :touch_chapters def to_label unless hidden @@ -23,7 +25,7 @@ def to_label # Returns the number of the chapter. Unless the user explicitly specified # a display number, this number is calculated def displayed_number - return calculated_number unless display_number.present? + return calculated_number if display_number.blank? display_number end @@ -36,7 +38,7 @@ def reference # Returns the chapter number based on the position in the chapters list. def calculated_number - return position.to_s unless lecture.start_chapter.present? + return position.to_s if lecture.start_chapter.blank? (lecture.start_chapter + position - 1).to_s end @@ -65,18 +67,18 @@ def select_sections end def cache_key - super + '-' + I18n.locale.to_s + "#{super}-#{I18n.locale}" end def touch_chapters - lecture.chapters.update_all(updated_at: Time.now) + lecture.chapters.touch_all end def touch_sections unless lecture.absolute_numbering - sections.update_all(updated_at: Time.now) + sections.touch_all return end - Section.where(chapter: lecture.chapters).update_all(updated_at: Time.now) + Section.where(chapter: lecture.chapters).touch_all end end diff --git a/app/models/clicker.rb b/app/models/clicker.rb index 075918d04..5ba6dcaec 100644 --- a/app/models/clicker.rb +++ b/app/models/clicker.rb @@ -1,23 +1,25 @@ # Clicker class class Clicker < ApplicationRecord - belongs_to :editor, class_name: 'User' + belongs_to :editor, class_name: "User" belongs_to :question, optional: true before_create :set_basics + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :title, uniqueness: { scope: [:editor_id] } + # rubocop:enable Rails/UniqueValidationWithoutIndex validates :title, presence: true - has_many :votes, dependent: :destroy, class_name: 'ClickerVote' + has_many :votes, dependent: :destroy, class_name: "ClickerVote" def user_link - clicker_url(self, host: 'localhost').gsub('clickers', 'c') + clicker_url(self, host: "localhost").gsub("clickers", "c") end def editor_link clicker_url(self, - host: 'localhost', - params: { code: code }).gsub('clickers', 'c') + host: "localhost", + params: { code: code }).gsub("clickers", "c") end def closed? diff --git a/app/models/course.rb b/app/models/course.rb index 4389c3325..5edf63bfa 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # Course class class Course < ApplicationRecord include ApplicationHelper @@ -14,7 +12,9 @@ class Course < ApplicationRecord after_remove: :touch_tag, after_add: :touch_tag - has_many :media, -> { order(position: :asc) }, as: :teachable + has_many :media, -> { order(position: :asc) }, + as: :teachable, + inverse_of: :teachable # in a course, you can import other media has_many :imports, as: :teachable, dependent: :destroy @@ -35,8 +35,12 @@ class Course < ApplicationRecord has_many :division_course_joins, dependent: :destroy has_many :divisions, through: :division_course_joins + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :title, presence: true, uniqueness: true + # rubocop:enable Rails/UniqueValidationWithoutIndex + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :short_title, presence: true, uniqueness: true + # rubocop:enable Rails/UniqueValidationWithoutIndex # some information about media and lectures are cached # to find out whether the cache is out of date, always touch'em after saving @@ -79,7 +83,7 @@ def talk end def selector_value - 'Course-' + id.to_s + "Course-#{id}" end def to_label @@ -123,12 +127,8 @@ def subscribable_lectures(user) return lectures.published unless user.edited_lectures.any? || user.teacher? lectures.left_outer_joins(:editable_user_joins) - .where('released IS NOT NULL OR editable_user_joins.user_id = ?'\ - ' OR teacher_id = ?', user.id, user.id).distinct - end - - def restricted? - false + .where("released IS NOT NULL OR editable_user_joins.user_id = ? " \ + "OR teacher_id = ?", user.id, user.id).distinct end def lectures_by_date @@ -279,19 +279,19 @@ def normalized_image_url_with_host def image_filename return unless image - image.metadata['filename'] + image.metadata["filename"] end def image_size return unless image - image.metadata['size'] + image.metadata["size"] end def image_resolution return unless image - "#{image.metadata['width']}x#{image.metadata['height']}" + "#{image.metadata["width"]}x#{image.metadata["height"]}" end # returns all titles of courses whose title is close to the given search @@ -306,17 +306,17 @@ def self.similar_courses(search_string) def self.search_by(search_params, page) editor_ids = search_params[:editor_ids] - editor_ids = [] if search_params[:all_editors] == '1' + editor_ids = [] if search_params[:all_editors] == "1" program_ids = search_params[:program_ids] || [] - program_ids = [] if search_params[:all_programs] == '1' + program_ids = [] if search_params[:all_programs] == "1" search = Sunspot.new_search(Course) search.build do with(:editor_ids, editor_ids) with(:program_ids, program_ids) unless program_ids.empty? - with(:term_independent, true) if search_params[:term_independent] == '1' - fulltext search_params[:fulltext] if search_params[:fulltext].present? + with(:term_independent, true) if search_params[:term_independent] == "1" + fulltext(search_params[:fulltext]) if search_params[:fulltext].present? order_by(:sort_title, :asc) - paginate page: page, per_page: search_params[:per] + paginate(page: page, per_page: search_params[:per]) end search end @@ -324,26 +324,26 @@ def self.search_by(search_params, page) private def touch_media - media_with_inheritance.update_all(updated_at: Time.now) + media_with_inheritance.touch_all end def touch_tag(tag) tag.touch - Sunspot.index! tag + Sunspot.index!(tag) end def touch_lectures_and_lessons - lectures.update_all(updated_at: Time.now) - Lesson.where(lecture: lectures).update_all(updated_at: Time.now) + lectures.touch_all + Lesson.where(lecture: lectures).touch_all end def create_quiz_by_questions!(question_ids) quiz_graph = QuizGraph.build_from_questions(question_ids) - Quiz.create(description: "#{I18n.t('categories.randomquiz.singular')} "\ - "#{course.title} #{Time.now}", + Quiz.create(description: "#{I18n.t("categories.randomquiz.singular")} " \ + "#{course.title} #{Time.current}", level: 1, quiz_graph: quiz_graph, - sort: 'RandomQuiz', + sort: "RandomQuiz", locale: locale) end @@ -351,7 +351,7 @@ def question_ids_for_quiz(tags, count) return questions_w_inheritance.pluck(:id).sample(count) unless tags.any? tagged_questions = questions(tags) - question_ids = if tagged_questions.count > count + if tagged_questions.count > count QuestionSampler.new(tagged_questions, tags, count).sample! else tagged_questions.map(&:id).shuffle diff --git a/app/models/course_self_join.rb b/app/models/course_self_join.rb index 7311f85eb..689df24ac 100644 --- a/app/models/course_self_join.rb +++ b/app/models/course_self_join.rb @@ -4,7 +4,7 @@ # is built upon class CourseSelfJoin < ApplicationRecord belongs_to :course - belongs_to :preceding_course, class_name: 'Course' + belongs_to :preceding_course, class_name: "Course" validates :preceding_course, uniqueness: { scope: :course } # we do not allow a course to be preceding itself diff --git a/app/models/course_tag_join.rb b/app/models/course_tag_join.rb index 92a30f4e2..45a05093a 100644 --- a/app/models/course_tag_join.rb +++ b/app/models/course_tag_join.rb @@ -4,11 +4,11 @@ class CourseTagJoin < ApplicationRecord belongs_to :course belongs_to :tag + before_destroy :touch_tag # tags are cached in several situations # in order to see when changes have been made, # touches are triggered after_save :touch_tag - before_destroy :touch_tag private diff --git a/app/models/import.rb b/app/models/import.rb index 7455657a1..466c6e9c3 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -2,5 +2,7 @@ class Import < ApplicationRecord belongs_to :medium belongs_to :teachable, polymorphic: true + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :medium, uniqueness: { scope: [:teachable] } + # rubocop:enable Rails/UniqueValidationWithoutIndex end diff --git a/app/models/interaction.rb b/app/models/interaction.rb index 774e8e794..87e8331a2 100644 --- a/app/models/interaction.rb +++ b/app/models/interaction.rb @@ -2,11 +2,11 @@ class Interaction < InteractionsRecord scope :created_between, lambda { |start_date, end_date| where(created_at: start_date.beginning_of_day..end_date.end_of_day) } - require 'csv' + require "csv" def self.to_csv - attributes = %w{id session_id created_at full_path referrer_url - study_participant} + attributes = ["id", "session_id", "created_at", "full_path", "referrer_url", + "study_participant"] CSV.generate(headers: true) do |csv| csv << attributes diff --git a/app/models/item.rb b/app/models/item.rb index bef489836..c4778ca63 100644 --- a/app/models/item.rb +++ b/app/models/item.rb @@ -34,13 +34,13 @@ class Item < ApplicationRecord # (these can be generated using the \hypertarget command of # the hyperref package of LaTex) # self - corresponds to items that are just wrappers around a medium - validates :sort, inclusion: { in: ['remark', 'theorem', 'lemma', 'definition', - 'annotation', 'example', 'section', - 'algorithm', 'label', 'corollary', - 'link', 'pdf_destination', 'self', - 'proposition', 'Lemma', 'Theorem', - 'subsection', 'Corollary', 'figure', - 'chapter', 'exercise', 'equation'] } + validates :sort, inclusion: { in: ["remark", "theorem", "lemma", "definition", + "annotation", "example", "section", + "algorithm", "label", "corollary", + "link", "pdf_destination", "self", + "proposition", "Lemma", "Theorem", + "subsection", "Corollary", "figure", + "chapter", "exercise", "equation"] } validates :link, http_url: true, if: :proper_link? validates :description, presence: true, if: :link? validate :valid_start_time @@ -48,10 +48,10 @@ class Item < ApplicationRecord validate :no_duplicate_start_time validate :nonempty_link_or_explanation + before_destroy :touch_medium # media are cached in several places # items are touched in order to find out whether cache is out of date after_save :touch_medium - before_destroy :touch_medium scope :unquarantined, -> { where(quarantine: [nil, false]) } scope :content, -> { where(sort: Item.content_sorts) } @@ -70,7 +70,7 @@ def end_time # result might look like this: # "01:14:40.500 --> 01:19:42.249\n" def vtt_time_span - start_time.vtt_string + ' --> ' + end_time.vtt_string + "\n" + "#{start_time.vtt_string} --> #{end_time.vtt_string}\n" end # returns the description of the toc entry corresponding to this item @@ -78,8 +78,8 @@ def vtt_time_span # result might look like this: # "zu freien Moduln" def vtt_text - return '' if sort == 'pdf_destination' - return description if sort == 'link' + return "" if sort == "pdf_destination" + return description if sort == "link" short_description end @@ -89,9 +89,9 @@ def vtt_text # result might look like this: # "Bem. 29.13: zu freien Moduln\n\n" def vtt_reference - return short_description + "\n\n" unless short_reference.present? + return "#{short_description}\n\n" if short_reference.blank? - short_reference + ': ' + short_description + "\n\n" + "#{short_reference}: #{short_description}\n\n" end # returns a reference to the item as it is used in .vtt files, @@ -99,10 +99,10 @@ def vtt_reference # result might look like this: # "Verweis auf LA 2 SS 17, Bem. 29.13:" def vtt_meta_reference(referring_medium) - return I18n.t('item.external_reference') if sort == 'link' + return I18n.t("item.external_reference") if sort == "link" ref = local?(referring_medium) ? short_reference : long_reference - I18n.t('item.internal_reference', ref: ref) + I18n.t("item.internal_reference", ref: ref) end # creates a reference as it would look like form *within* the given context @@ -118,18 +118,18 @@ def short_reference # result might look like this: # "LA 2 SS 17, Bem. 29.13" def long_reference - return short_reference if sort.in?(['self', 'link']) + return short_reference if sort.in?(["self", "link"]) return short_ref_with_teachable if section.present? - return medium.title_for_viewers unless short_reference.present? + return medium.title_for_viewers if short_reference.blank? - medium.title_for_viewers + ', ' + short_reference + "#{medium.title_for_viewers}, #{short_reference}" end # returns just the description, unless sort is section or self # result might look like this: "zu freien Moduln" def short_description - return section.title if sort == 'section' && section.present? - return medium.title_for_viewers if sort == 'self' + return section.title if sort == "section" && section.present? + return medium.title_for_viewers if sort == "self" description.to_s end @@ -141,10 +141,10 @@ def short_description # "KaViaR, Sitzung 27 vom 17.8.2017" (self) # "extern Spiegel" (link) def local_reference - unless sort.in?(['self', 'link', 'pdf_destination']) - return short_ref_with_description unless medium&.sort == 'Script' + unless sort.in?(["self", "link", "pdf_destination"]) + return short_ref_with_description unless medium&.sort == "Script" - return 'Skript, ' + short_ref_with_description + return "Skript, #{short_ref_with_description}" end local_non_math_reference end @@ -153,11 +153,11 @@ def local_reference # Result might look like this: # "SS 17, Bem. 29.13 zu freien Moduln" def title_within_course - return '' unless medium.present? && medium.proper? - return local_reference if medium.teachable_type == 'Course' + return "" unless medium.present? && medium.proper? + return local_reference if medium.teachable_type == "Course" return local_reference unless medium.teachable.media_scope.term - medium.teachable.media_scope.term.to_label_short + ', ' + local_reference + "#{medium.teachable.media_scope.term.to_label_short}, #{local_reference}" end # returns the title of the item *within* a given lecture @@ -173,28 +173,28 @@ def title_within_lecture # this is true if the item belongs to a section of the lecture or the # lesson's lecture def local?(referring_medium) - return false unless section.present? + return false if section.blank? in?(referring_medium.teachable.lecture&.items.to_a) end # background color of different item sorts within thyme editor def background - return '#70db70;' if ['remark', 'theorem', 'lemma', 'corollary', - 'algorithm', 'Theorem', 'Corollary', 'Lemma', - 'proposition'].include?(sort) - return '#75d7f0;' if ['definition', 'annotation', 'example', - 'figure', 'exercise', 'equation'].include?(sort) - return 'lightgray;' if sort == 'link' || sort == 'self' + return "#70db70;" if ["remark", "theorem", "lemma", "corollary", + "algorithm", "Theorem", "Corollary", "Lemma", + "proposition"].include?(sort) + return "#75d7f0;" if ["definition", "annotation", "example", + "figure", "exercise", "equation"].include?(sort) + return "lightgray;" if sort == "link" || sort == "self" - '' + "" end # special background for sections def section_background - return 'beige;' if sort == 'section' + return "beige;" if sort == "section" - 'aliceblue;' + "aliceblue;" end # if the associated medium contains a video, returns a link to the play @@ -202,9 +202,9 @@ def section_background # result might look like this: # "/media/22/play?time=4480.5" def video_link - return if sort == 'pdf_destination' + return if sort == "pdf_destination" return unless video? - return video_link_untimed if sort == 'self' + return video_link_untimed if sort == "self" video_link_timed end @@ -226,7 +226,7 @@ def manuscript_link def quiz_link return unless quiz? - return quiz_link_generic + quiz_link_generic end # if the associated medium contains an external link, it is returned @@ -237,10 +237,10 @@ def medium_link end def self.available_sorts - ['definition', 'remark', 'lemma', 'theorem', 'example', 'annotation', - 'algorithm', 'corollary', 'section', 'label', 'subsection', 'Theorem', - 'proposition', 'Lemma', 'Corollary', 'figure', 'chapter', 'exercise', - 'equation'] + ["definition", "remark", "lemma", "theorem", "example", "annotation", + "algorithm", "corollary", "section", "label", "subsection", "Theorem", + "proposition", "Lemma", "Corollary", "figure", "chapter", "exercise", + "equation"] end def self.localized_sorts @@ -252,17 +252,17 @@ def self.inverted_sorts end def self.content_sorts - ['remark', 'theorem', 'lemma', 'definition', 'annotation', 'example', - 'algorithm', 'label', 'corollary', 'proposition', 'Lemma', 'Theorem', - 'subsection', 'Corollary', 'equation', 'exercise', 'figure', 'self'] + ["remark", "theorem", "lemma", "definition", "annotation", "example", + "algorithm", "label", "corollary", "proposition", "Lemma", "Theorem", + "subsection", "Corollary", "equation", "exercise", "figure", "self"] end def self.toc_sorts - ['chapter', 'section'] + ["chapter", "section"] end def self.external_sorts - ['link', 'pdf_destination'] + ["link", "pdf_destination"] end def self.internal_sort(sort) @@ -278,7 +278,7 @@ def manuscript? end def quiz? - medium.present? && medium.type == 'Quiz' && medium.quiz_graph.present? + medium.present? && medium.type == "Quiz" && medium.quiz_graph.present? end def medium_link? @@ -286,7 +286,7 @@ def medium_link? end def link? - sort == 'link' + sort == "link" end def referencing_media @@ -294,24 +294,24 @@ def referencing_media end def related_items_visible? - !!related_items&.first&.medium&.published? && + !related_items&.first&.medium&.published?.nil? && !related_items&.first&.medium&.locked? end private def math_items - ['remark', 'theorem', 'lemma', 'definition', 'annotation', 'example', - 'corollary', 'algorithm', 'Theorem', 'proposition', 'Lemma', 'Corollary', - 'figure', 'subsection', 'exercise', 'equation'] + ["remark", "theorem", "lemma", "definition", "annotation", "example", + "corollary", "algorithm", "Theorem", "proposition", "Lemma", "Corollary", + "figure", "subsection", "exercise", "equation"] end def other_items - ['section', 'self', 'link', 'label', 'pdf_destination', 'chapter'] + ["section", "self", "link", "label", "pdf_destination", "chapter"] end def proper_link? - sort == 'link' && link.present? + sort == "link" && link.present? end def next_item @@ -331,25 +331,25 @@ def math_item_number end def math_reference - sort_long + ' ' + math_item_number + "#{sort_long} #{math_item_number}" end def special_reference - return 'Medium' if sort == 'self' - return '' if sort == 'pdf_destination' + return "Medium" if sort == "self" + return "" if sort == "pdf_destination" - 'extern' + "extern" end def section_reference return section.displayed_number.to_s if section.present? - return '§' + ref_number if ref_number.present? + return "§#{ref_number}" if ref_number.present? - '' + "" end def chapter_reference - chapter_short = I18n.t('admin.item.chapter_short', + chapter_short = I18n.t("admin.item.chapter_short", locale: locale) return "#{chapter_short} #{ref_number}" if ref_number.present? @@ -357,52 +357,46 @@ def chapter_reference end def toc_reference - return section_reference if sort == 'section' - return chapter_reference if sort == 'chapter' + return section_reference if sort == "section" + return chapter_reference if sort == "chapter" - if sort == 'label' - return '' if description.present? + if sort == "label" + return "" if description.present? - return 'destination: ' + pdf_destination.to_s + return "destination: #{pdf_destination}" end special_reference end def non_math_reference - return medium.title_for_viewers if sort == 'self' - if sort == 'pdf_destination' - return medium.title_for_viewers + ' (pdf) # ' + description - end + return medium.title_for_viewers if sort == "self" + return "#{medium.title_for_viewers} (pdf) # #{description}" if sort == "pdf_destination" - 'extern ' + description.to_s if sort == 'link' + "extern #{description}" if sort == "link" end def local_non_math_reference - return medium.local_title_for_viewers if sort == 'self' - if sort == 'pdf_destination' - return medium.local_title_for_viewers + ' (pdf) # ' + description - end + return medium.local_title_for_viewers if sort == "self" + return "#{medium.local_title_for_viewers} (pdf) # #{description}" if sort == "pdf_destination" - 'extern ' + description.to_s if sort == 'link' + "extern #{description}" if sort == "link" end def short_ref_with_teachable - unless short_reference.present? - return medium.teachable.lecture.title_for_viewers - end + return medium.teachable.lecture.title_for_viewers if short_reference.blank? - medium.teachable.lecture.title_for_viewers + ', ' + short_reference + "#{medium.teachable.lecture.title_for_viewers}, #{short_reference}" end def short_ref_with_description - return short_reference + ' ' + description.to_s unless sort == 'section' + return "#{short_reference} #{description}" unless sort == "section" short_ref_for_sections end def short_ref_for_sections - return short_reference + ' ' + description if description.present? - return short_reference + ' ' + section.title if section.present? + return "#{short_reference} #{description}" if description.present? + return "#{short_reference} #{section.title}" if section.present? short_reference end @@ -447,13 +441,13 @@ def valid_start_time end def start_time_not_required - medium.nil? || medium.sort == 'Script' || sort == 'self' || - sort == 'pdf_destination' || !start_time&.valid? || !medium.video + medium.nil? || medium.sort == "Script" || sort == "self" || + sort == "pdf_destination" || !start_time&.valid? || !medium.video end def start_time_not_too_late return true if start_time_not_required - return true if start_time.total_seconds <= medium.video.metadata['duration'] + return true if start_time.total_seconds <= medium.video.metadata["duration"] errors.add(:start_time, :too_late) false @@ -477,7 +471,7 @@ def no_duplicate_start_time end def nonempty_link_or_explanation - return true if sort != 'link' + return true if sort != "link" return true if link.present? return true if explanation.present? diff --git a/app/models/item_self_join.rb b/app/models/item_self_join.rb index 8f03219d2..d3ca149e4 100644 --- a/app/models/item_self_join.rb +++ b/app/models/item_self_join.rb @@ -5,14 +5,16 @@ # in a lesson medium) class ItemSelfJoin < ApplicationRecord belongs_to :item - belongs_to :related_item, class_name: 'Item' + belongs_to :related_item, class_name: "Item" + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :related_item, uniqueness: { scope: :item } + before_destroy :touch_item + after_destroy :destroy_inverses, if: :inverse? + # rubocop:enable Rails/UniqueValidationWithoutIndex after_save :create_inverse, unless: :inverse? after_save :destroy, if: :self_inverse? after_save :touch_item - before_destroy :touch_item - after_destroy :destroy_inverses, if: :inverse? private diff --git a/app/models/lecture.rb b/app/models/lecture.rb index a81bda896..8d5c1d353 100644 --- a/app/models/lecture.rb +++ b/app/models/lecture.rb @@ -5,26 +5,33 @@ class Lecture < ApplicationRecord belongs_to :course # teacher is the user that gives the lecture - belongs_to :teacher, class_name: 'User', foreign_key: 'teacher_id' + belongs_to :teacher, class_name: "User" # a lecture takes place in a certain term, except those where the course # is marked as term_independent belongs_to :term, optional: true # a lecture has many chapters, who have positions - has_many :chapters, -> { order(position: :asc) }, dependent: :destroy + has_many :chapters, -> { order(position: :asc) }, + dependent: :destroy, + inverse_of: :lecture # during the term, a lot of lessons take place for this lecture has_many :lessons, -> { order(date: :asc, id: :asc) }, dependent: :destroy, after_add: :touch_siblings, - after_remove: :touch_siblings + after_remove: :touch_siblings, + inverse_of: :lecture # a lecture has many talks, which have positions - has_many :talks, -> { order(position: :asc) }, dependent: :destroy + has_many :talks, -> { order(position: :asc) }, + dependent: :destroy, + inverse_of: :lecture # being a teachable (course/lecture/lesson), a lecture has associated media - has_many :media, -> { order(position: :asc) }, as: :teachable + has_many :media, -> { order(position: :asc) }, + as: :teachable, + inverse_of: :teachable # in a lecture, you can import other media has_many :imports, as: :teachable, dependent: :destroy @@ -50,7 +57,8 @@ class Lecture < ApplicationRecord has_many :announcements, dependent: :destroy # a lecture has many tutorials - has_many :tutorials, -> { order(:title) } + has_many :tutorials, -> { order(:title) }, + inverse_of: :lecture # a lecture has many assignments (e.g. exercises with deadlines) has_many :assignments @@ -61,14 +69,16 @@ class Lecture < ApplicationRecord # we do not allow that a teacher gives a certain lecture in a given term # of the same sort twice + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :course, uniqueness: { scope: [:teacher_id, :term_id, :sort] } + # rubocop:enable Rails/UniqueValidationWithoutIndex - validates :content_mode, inclusion: { in: ['video', 'manuscript'] } + validates :content_mode, inclusion: { in: ["video", "manuscript"] } - validates :sort, inclusion: { in: ['lecture', 'seminar', 'oberseminar', - 'proseminar', 'special'] } + validates :sort, inclusion: { in: ["lecture", "seminar", "oberseminar", + "proseminar", "special"] } - validates_presence_of :term, unless: :term_independent? + validates :term, presence: { unless: :term_independent? } validate :absence_of_term, if: :term_independent? @@ -84,6 +94,9 @@ class Lecture < ApplicationRecord greater_than: -1 }, allow_nil: true + # if the lecture is destroyed, its forum (if existent) should be destroyed + # as well + before_destroy :destroy_forum # as a teacher has editing rights by definition, we do not need him in the # list of editors after_save :remove_teacher_as_editor @@ -95,18 +108,14 @@ class Lecture < ApplicationRecord after_save :touch_chapters after_save :touch_sections - # if the lecture is destroyed, its forum (if existent) should be destroyed - # as well - before_destroy :destroy_forum - # scopes scope :published, -> { where.not(released: nil) } scope :no_term, -> { where(term: nil) } - scope :restricted, -> { where.not(passphrase: ['', nil]) } + scope :restricted, -> { where.not(passphrase: ["", nil]) } - scope :seminar, -> { where(sort: ['seminar', 'oberseminar', 'proseminar']) } + scope :seminar, -> { where(sort: ["seminar", "oberseminar", "proseminar"]) } searchable do integer :term_id do @@ -155,7 +164,7 @@ def media_scope end def selector_value - 'Lecture-' + id.to_s + "Lecture-#{id}" end def title @@ -209,7 +218,7 @@ def card_header_path(user) end def cache_key - super + '-' + I18n.locale.to_s + "#{super}-#{I18n.locale}" end def restricted? @@ -220,7 +229,7 @@ def visible_for_user?(user) return true if user.admin return true if edited_by?(user) return false unless published? - return false if restricted? && !self.in?(user.lectures) + return false if restricted? && !in?(user.lectures) true end @@ -260,10 +269,10 @@ def tags_including_media_tags (tags + lessons.includes(media: :tags) .map(&:media).flatten.uniq - .select { |m| m.released.in?(['all', 'users', 'subscribers']) } + .select { |m| m.released.in?(["all", "users", "subscribers"]) } .map(&:tags).flatten + media.includes(:tags) - .select { |m| m.released.in?(['all', 'users', 'subscribers']) } + .select { |m| m.released.in?(["all", "users", "subscribers"]) } .map(&:tags).flatten).uniq end @@ -285,7 +294,7 @@ def script_items_by_position hidden_sections = Section.where(hidden: true) .or(Section.where(chapter: hidden_chapters)) Item.where(medium: lecture.manuscript) - .where.not(sort: 'self') + .where.not(sort: "self") .content .unquarantined .unhidden @@ -294,22 +303,22 @@ def script_items_by_position end def manuscript - Medium.where(sort: 'Script', teachable: lecture)&.first + Medium.where(sort: "Script", teachable: lecture)&.first end # returns the ARel of all media whose teachable's lecture is the given lecture def media_with_inheritance_uncached Medium.proper.where(teachable: self) - .or(Medium.proper.where(teachable: self.lessons)) - .or(Medium.proper.where(teachable: self.talks)) + .or(Medium.proper.where(teachable: lessons)) + .or(Medium.proper.where(teachable: talks)) end def media_with_inheritance_uncached_eagerload_stuff Medium.includes(:tags, teachable: [lecture: [:lessons, :talks]]) .proper.where(teachable: self) .or(Medium.includes(:tags, teachable: [lecture: [:lessons, :talks]]) - .proper.where(teachable: self.lessons + self.talks)) + .proper.where(teachable: lessons + talks)) end def media_with_inheritance @@ -336,35 +345,35 @@ def published? # These methods make use of caching. def kaviar?(user) - project?('kaviar', user) || imported_any?('kaviar') + project?("kaviar", user) || imported_any?("kaviar") end def sesam?(user) - project?('sesam', user) || imported_any?('sesam') + project?("sesam", user) || imported_any?("sesam") end def keks?(user) - project?('keks', user) || imported_any?('keks') + project?("keks", user) || imported_any?("keks") end def erdbeere?(user) - project?('erdbeere', user) || imported_any?('erdbeere') + project?("erdbeere", user) || imported_any?("erdbeere") end def kiwi?(user) - project?('kiwi', user) || imported_any?('kiwi') + project?("kiwi", user) || imported_any?("kiwi") end def nuesse?(user) - project?('nuesse', user) || imported_any?('nuesse') + project?("nuesse", user) || imported_any?("nuesse") end def script?(user) - project?('script', user) || imported_any?('nuesse') + project?("script", user) || imported_any?("nuesse") end def reste?(user) - project?('reste', user) || imported_any?('reste') + project?("reste", user) || imported_any?("reste") end # the next methods put together some information on the lecture (teacher, @@ -379,7 +388,7 @@ def short_title def short_title_release return short_title if published? - "#{short_title} (#{I18n.t('access.unpublished')})" + "#{short_title} (#{I18n.t("access.unpublished")})" end def short_title_brackets @@ -401,8 +410,8 @@ def title_with_teacher_no_type end def term_teacher_info - return term_to_label unless teacher.present? - return term_to_label unless teacher.name.present? + return term_to_label if teacher.blank? + return term_to_label if teacher.name.blank? return "#{course.title}, #{teacher.name}" unless term "(#{sort_localized_short}) #{term_to_label}, #{teacher.name}" @@ -411,7 +420,7 @@ def term_teacher_info def term_teacher_published_info return term_teacher_info if published? - "#{term_teacher_info} (#{I18n.t('access.unpublished')})" + "#{term_teacher_info} (#{I18n.t("access.unpublished")})" end def title_term_info @@ -496,11 +505,11 @@ def select_editors def self.editable_selection(user) if user.admin? return Lecture.sort_by_date(Lecture.includes(:term).all) - .map { |l| [l.title_for_viewers, 'Lecture-' + l.id.to_s] } + .map { |l| [l.title_for_viewers, "Lecture-#{l.id}"] } end Lecture.sort_by_date(Lecture.includes(:course, :editors).all) .select { |l| l.edited_by?(user) } - .map { |l| [l.title_for_viewers, 'Lecture-' + l.id.to_s] } + .map { |l| [l.title_for_viewers, "Lecture-#{l.id}"] } end # the next methods provide infos on editors and teacher @@ -561,7 +570,7 @@ def lecture_lesson_results(filtered_media) end def order_factor - return -1 unless lecture.term.present? + return -1 if lecture.term.blank? return -1 if lecture.term.active 1 @@ -569,7 +578,7 @@ def order_factor def begin_date Rails.cache.fetch("#{cache_key_with_version}/begin_date") do - term&.begin_date || Term.active.begin_date || Date.today + term&.begin_date || Term.active.begin_date || Time.zone.today end end @@ -588,7 +597,7 @@ def forum? end def forum - Thredded::Messageboard.find_by_id(forum_id) + Thredded::Messageboard.find_by(id: forum_id) end # extract how many posts in the lecture's forum have not been read @@ -612,11 +621,11 @@ def lecture_path end def self.sorts - ['lecture', 'seminar', 'proseminar', 'oberseminar'] + ["lecture", "seminar", "proseminar", "oberseminar"] end def self.sort_localized - Lecture.sorts.map { |s| [s, I18n.t("admin.lecture.#{s}")] }.to_h + Lecture.sorts.index_with { |s| I18n.t("admin.lecture.#{s}") } end def self.select_sorts @@ -624,19 +633,19 @@ def self.select_sorts end def seminar? - return true if sort.in?(['seminar', 'proseminar', 'oberseminar']) + return true if sort.in?(["seminar", "proseminar", "oberseminar"]) false end def chapter_name - return 'chapter' unless seminar? + return "chapter" unless seminar? - 'talk' + "talk" end def comments_closed? - media_with_inheritance.map(&:commontator_thread).map(&:is_closed?).all? + media_with_inheritance.map { |media| media.commontator_thread.is_closed? }.all? end def close_comments!(user) @@ -645,7 +654,7 @@ def close_comments!(user) end end - def open_comments!(user) + def open_comments!(_user) media_with_inheritance.select { |m| m.commontator_thread.is_closed? } .each { |m| m.commontator_thread.reopen } end @@ -656,12 +665,12 @@ def self.in_current_term def <=>(other) return 0 if self == other - return 1 if self.begin_date < other.begin_date - return 1 if self.term == other.term && - ActiveSupport::Inflector.transliterate(self.course.title) > + return 1 if begin_date < other.begin_date + return 1 if term == other.term && + ActiveSupport::Inflector.transliterate(course.title) > ActiveSupport::Inflector.transliterate(other.course.title) - return 1 if self.term == other.term && self.course == other.course && - self.sort_localized < other.sort_localized + return 1 if term == other.term && course == other.course && + sort_localized < other.sort_localized -1 end @@ -671,29 +680,43 @@ def subscribed_by?(user) end def self.search_by(search_params, page) - search_params[:types] = - [] if search_params[:all_types] == '1' || search_params[:types].nil? - search_params[:term_ids] = - [] if search_params[:all_terms] == '1' || search_params[:term_ids].nil? - search_params[:teacher_ids] = - [] if search_params[:all_teachers] == '1' || search_params[:teacher_ids].nil? - search_params[:program_ids] = - [] if search_params[:all_programs] == '1' || search_params[:program_ids].nil? + if search_params[:all_types] == "1" || search_params[:types].nil? + search_params[:types] = + [] + end + if search_params[:all_terms] == "1" || search_params[:term_ids].nil? + search_params[:term_ids] = + [] + end + if search_params[:all_teachers] == "1" || search_params[:teacher_ids].nil? + search_params[:teacher_ids] = + [] + end + if search_params[:all_programs] == "1" || search_params[:program_ids].nil? + search_params[:program_ids] = + [] + end search = Sunspot.new_search(Lecture) # add lectures without term to current term if Term.active.try(:id).to_i.to_s.in?(search_params[:term_ids]) - search_params[:term_ids].push('0') + search_params[:term_ids].push("0") end search.build do with(:sort, search_params[:types]) unless search_params[:types].empty? - with(:teacher_id, - search_params[:teacher_ids]) unless search_params[:teacher_ids].empty? - with(:program_ids, - search_params[:program_ids]) unless search_params[:program_ids].empty? - with(:term_id, - search_params[:term_ids]) unless search_params[:term_ids].empty? - end - admin = User.find_by_id(search_params[:user_id])&.admin + unless search_params[:teacher_ids].empty? + with(:teacher_id, + search_params[:teacher_ids]) + end + unless search_params[:program_ids].empty? + with(:program_ids, + search_params[:program_ids]) + end + unless search_params[:term_ids].empty? + with(:term_id, + search_params[:term_ids]) + end + end + admin = User.find_by(id: search_params[:user_id])&.admin unless admin search.build do any_of do @@ -705,13 +728,13 @@ def self.search_by(search_params, page) end if search_params[:fulltext].present? search.build do - fulltext search_params[:fulltext] + fulltext(search_params[:fulltext]) end end search.build do order_by(:sort_date, :desc) order_by(:sort_title, :asc) - paginate page: page, per_page: search_params[:per] + paginate(page: page, per_page: search_params[:per]) end search end @@ -719,13 +742,13 @@ def self.search_by(search_params, page) def term_to_label return term.to_label if term - '' + "" end def term_to_label_short return term.to_label_short if term - '' + "" end def tutors @@ -735,7 +758,7 @@ def tutors def submission_deletion_date Rails.cache.fetch("#{cache_key_with_version}/submission_deletion_date") do - (term&.end_date || Term.active&.end_date || (Date.today + 180.days)) + + (term&.end_date || Term.active&.end_date || (Time.zone.today + 180.days)) + 15.days end end @@ -745,21 +768,21 @@ def assignments_by_deadline end def current_assignments - assignments_by_deadline.select { |x| x.first >= Time.now }.first&.second + assignments_by_deadline.find { |x| x.first >= Time.zone.now }&.second .to_a end def previous_assignments - assignments_by_deadline.select { |x| x.first < Time.now }.last&.second.to_a + assignments_by_deadline.reverse.find { |x| x.first < Time.zone.now }&.second.to_a end def scheduled_assignments? - media.where(sort: 'Nuesse').where.not(publisher: nil) + media.where(sort: "Nuesse").where.not(publisher: nil) .any? { |m| m.publisher.create_assignment } end def scheduled_assignments - media.where(sort: 'Nuesse').where.not(publisher: nil) + media.where(sort: "Nuesse").where.not(publisher: nil) .select { |m| m.publisher.create_assignment } .map { |m| m.publisher.assignment } end @@ -803,11 +826,13 @@ def import_toc!(imported_lecture, import_sections, import_tags) def speakers return User.none unless seminar? - User.where(id: SpeakerTalkJoin.where(talk: talks).pluck(:speaker_id)) + + User.where(id: SpeakerTalkJoin.where(talk: talks).select(:speaker_id)) end def older_than?(timespan) return true unless term + term.begin_date <= Term.active.begin_date - timespan end @@ -826,25 +851,25 @@ def remove_teacher_as_editor # to this lecture and a given project (kaviar, sesam etc.) def project_as_user?(project) Rails.cache.fetch("#{cache_key_with_version}/#{project}") do - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: self).exists? || - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: lessons).exists? || - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: talks).exists? || - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: course).exists? + Medium.exists?(sort: medium_sort[project], + released: ["all", "users", "subscribers"], + teachable: self) || + Medium.exists?(sort: medium_sort[project], + released: ["all", "users", "subscribers"], + teachable: lessons) || + Medium.exists?(sort: medium_sort[project], + released: ["all", "users", "subscribers"], + teachable: talks) || + Medium.exists?(sort: medium_sort[project], + released: ["all", "users", "subscribers"], + teachable: course) end end def imported_any?(project) Rails.cache.fetch("#{cache_key_with_version}/imported_#{project}") do imported_media.exists?(sort: medium_sort[project], - released: ['all', 'users']) + released: ["all", "users"]) end end @@ -852,47 +877,47 @@ def project?(project, user) return project_as_user?(project) unless edited_by?(user) || user.admin course_media = if user.in?(course.editors) || user.admin - Medium.where(sort: medium_sort[project], - teachable: course).exists? + Medium.exists?(sort: medium_sort[project], + teachable: course) else - Medium.where(sort: medium_sort[project], - released: ['all', 'users', 'subscribers'], - teachable: course).exists? + Medium.exists?(sort: medium_sort[project], + released: ["all", "users", "subscribers"], + teachable: course) end - lecture_media = Medium.where(sort: medium_sort[project], - teachable: self).exists? - lesson_media = Medium.where(sort: medium_sort[project], - teachable: lessons).exists? - talk_media = Medium.where(sort: medium_sort[project], - teachable: talks).exists? + lecture_media = Medium.exists?(sort: medium_sort[project], + teachable: self) + lesson_media = Medium.exists?(sort: medium_sort[project], + teachable: lessons) + talk_media = Medium.exists?(sort: medium_sort[project], + teachable: talks) course_media || lecture_media || lesson_media || talk_media end def medium_sort - { 'kaviar' => ['Kaviar'], 'sesam' => ['Sesam'], 'kiwi' => ['Kiwi'], - 'keks' => ['Quiz'], 'nuesse' => ['Nuesse'], - 'erdbeere' => ['Erdbeere'], 'script' => ['Script'], 'reste' => ['Reste'] } + { "kaviar" => ["Kaviar"], "sesam" => ["Sesam"], "kiwi" => ["Kiwi"], + "keks" => ["Quiz"], "nuesse" => ["Nuesse"], + "erdbeere" => ["Erdbeere"], "script" => ["Script"], "reste" => ["Reste"] } end def touch_media - media_with_inheritance.update_all(updated_at: Time.now) + media_with_inheritance.touch_all end def touch_lessons - lessons.update_all(updated_at: Time.now) + lessons.touch_all end - def touch_siblings(lesson) - lessons.update_all(updated_at: Time.now) - Medium.where(teachable: lessons).update_all(updated_at: Time.now) + def touch_siblings(_lesson) + lessons.touch_all + Medium.where(teachable: lessons).touch_all end def touch_chapters - chapters.update_all(updated_at: Time.now) + chapters.touch_all end def touch_sections - Section.where(chapter: chapters).update_all(updated_at: Time.now) + Section.where(chapter: chapters).touch_all end def destroy_forum diff --git a/app/models/lesson.rb b/app/models/lesson.rb index 852e1dfff..e2dad90be 100644 --- a/app/models/lesson.rb +++ b/app/models/lesson.rb @@ -16,11 +16,15 @@ class Lesson < ApplicationRecord # being a teachable (course/lecture/lesson), a lesson has associated media has_many :media, -> { order(position: :asc) }, - as: :teachable + as: :teachable, + inverse_of: :teachable validates :date, presence: true validates :sections, presence: true + before_destroy :touch_media + before_destroy :touch_siblings + before_destroy :touch_sections, prepend: true # media are cached in several places # media are touched in order to find out whether cache is out of date after_save :touch_media @@ -29,9 +33,6 @@ class Lesson < ApplicationRecord after_save :touch_siblings after_save :touch_self after_save :touch_tags - before_destroy :touch_media - before_destroy :touch_siblings - before_destroy :touch_sections, prepend: true delegate :editors_with_inheritance, to: :lecture, allow_nil: true @@ -39,7 +40,7 @@ class Lesson < ApplicationRecord # Therefore, they can be called on any *teachable* def course - return unless lecture.present? + return if lecture.blank? lecture.course end @@ -57,46 +58,45 @@ def media_scope end def selector_value - 'Lesson-' + id.to_s + "Lesson-#{id}" end def title - I18n.t('lesson') + ' ' + number.to_s + ', ' + date_localized.to_s + "#{I18n.t("lesson")} #{number}, #{date_localized}" end def to_label - 'Nr. ' + number.to_s + ', ' + date_localized.to_s + "Nr. #{number}, #{date_localized}" end def compact_title - lecture.compact_title + '.E' + number.to_s + "#{lecture.compact_title}.E#{number}" end def cache_key - super + '-' + I18n.locale.to_s + "#{super}-#{I18n.locale}" end def title_for_viewers Rails.cache.fetch("#{cache_key_with_version}/title_for_viewers") do - lecture.title_for_viewers + ', ' + I18n.t('lesson') + ' ' + number.to_s + - ' ' + I18n.t('from') + ' ' + date_localized + lesson_str = "#{I18n.t("lesson")} #{number}" + date_str = "#{I18n.t("from")} #{date_localized}" + "#{lecture.title_for_viewers}, #{lesson_str} #{date_str}" end end def long_title - lecture.title + ', ' + title + "#{lecture.title}, #{title}" end - def locale_with_inheritance - lecture.locale_with_inheritance - end + delegate :locale_with_inheritance, to: :lecture def locale locale_with_inheritance end def card_header - lecture.short_title_brackets + ', ' + date_localized + "#{lecture.short_title_brackets}, #{date_localized}" end def card_header_path(user) @@ -105,36 +105,30 @@ def card_header_path(user) lesson_path end - def published? - lecture.published? - end + delegate :published?, to: :lecture # some more methods dealing with the title def short_title_with_lecture - lecture.short_title + ', S.' + number.to_s + "#{lecture.short_title}, S.#{number}" end def short_title_with_lecture_date - lecture.short_title + ', ' + date_localized + "#{lecture.short_title}, #{date_localized}" end def short_title - lecture.short_title + '_E' + number.to_s + "#{lecture.short_title}_E#{number}" end def local_title_for_viewers - "#{I18n.t('lesson')} #{number} #{I18n.t('from')} #{date_localized}" - end - - def restricted? - lecture.restricted? + "#{I18n.t("lesson")} #{number} #{I18n.t("from")} #{date_localized}" end # more infos that can be extracted def term - return unless lecture.present? + return if lecture.blank? lecture.term end @@ -158,9 +152,7 @@ def visible_media_for_user(user) media.select { |m| m.visible_for_user?(user) } end - def visible_for_user?(user) - lecture.visible_for_user?(user) - end + delegate :visible_for_user?, to: :lecture # the number of a lesson is calculated by its date relative to the other # lessons @@ -169,17 +161,15 @@ def number end def date_localized - I18n.localize date, format: :concise + I18n.l(date, format: :concise) end def section_titles - sections.map(&:title).join(', ') + sections.map(&:title).join(", ") end # a lesson can be edited by any user who can edit its lecture - def edited_by?(user) - lecture.edited_by?(user) - end + delegate :edited_by?, to: :lecture def section_tags sections.collect(&:tags).flatten @@ -201,13 +191,13 @@ def visible_items end def content_items - return visible_items if lecture.content_mode == 'video' + return visible_items if lecture.content_mode == "video" script_items end def content - ([details] + media.potentially_visible.map(&:content)).compact - [''] + ([details] + media.potentially_visible.map(&:content)).compact - [""] end def singular_medium @@ -228,7 +218,7 @@ def script_items return [] unless start_item && end_item range = (start_item.position..end_item.position).to_a - return [] unless range.present? + return [] if range.blank? hidden_chapters = Chapter.where(hidden: true) hidden_sections = Section.where(hidden: true) @@ -258,12 +248,12 @@ def self.editable_selection(user) if user.admin? return Lesson.order_reverse .map do |l| - [l.title_for_viewers, 'Lesson-' + l.id.to_s] + [l.title_for_viewers, "Lesson-#{l.id}"] end end Lesson.includes(:lecture).order_reverse .select { |l| l.edited_by?(user) } - .map { |l| [l.title_for_viewers, 'Lesson-' + l.id.to_s] } + .map { |l| [l.title_for_viewers, "Lesson-#{l.id}"] } end def guess_start_destination @@ -288,7 +278,7 @@ def probable_start_destination position = end_item.position return unless position - successor = lecture.script_items_by_position.where('position > ?', position) + successor = lecture.script_items_by_position.where("position > ?", position) .order(:position)&.first&.pdf_destination return successor if successor @@ -308,16 +298,16 @@ def lesson_path # used for after save callback def touch_media - lecture.media_with_inheritance.update_all(updated_at: Time.now) + lecture.media_with_inheritance.touch_all end def touch_siblings - lecture.lessons.update_all(updated_at: Time.now) + lecture.lessons.touch_all end def touch_sections - sections.update_all(updated_at: Time.now) - chapters = sections.map(&:chapter) + sections.touch_all + sections.map(&:chapter) sections.map(&:chapter).each(&:touch) lecture.touch end @@ -327,7 +317,7 @@ def touch_self end def touch_tags - tags.update_all(updated_at: Time.now) + tags.touch_all end def touch_section(section) diff --git a/app/models/link.rb b/app/models/link.rb index 285eef6c0..2591caa8d 100644 --- a/app/models/link.rb +++ b/app/models/link.rb @@ -3,17 +3,17 @@ # describes which media are related to a given medium class Link < ApplicationRecord belongs_to :medium - belongs_to :linked_medium, class_name: 'Medium' + belongs_to :linked_medium, class_name: "Medium" # we do not want duplicate entries validates :linked_medium, uniqueness: { scope: :medium } + # after a link is destroyed, destroy the link in the other direction as well + after_destroy :destroy_inverses, if: :inverse? # after saving, we symmetrize the relation after_save :create_inverse, unless: :inverse? # we do not want a medium to be in relation to itself after_save :destroy, if: :self_inverse? - # after a link is destroyed, destroy the link in the other direction as well - after_destroy :destroy_inverses, if: :inverse? private diff --git a/app/models/mampf_expression.rb b/app/models/mampf_expression.rb index b0c09afc1..06fb38bb5 100644 --- a/app/models/mampf_expression.rb +++ b/app/models/mampf_expression.rb @@ -10,10 +10,10 @@ def initialize(value, tex, nerd) end def self.trivial_instance - MampfExpression.new('0', '0', '0') + MampfExpression.new("0", "0", "0") end def self.from_hash(content) - MampfExpression.new(content['0'], content['tex'], content['nerd']) + MampfExpression.new(content["0"], content["tex"], content["nerd"]) end end diff --git a/app/models/mampf_matrix.rb b/app/models/mampf_matrix.rb index fbe5c1562..65f4ad269 100644 --- a/app/models/mampf_matrix.rb +++ b/app/models/mampf_matrix.rb @@ -12,25 +12,23 @@ def initialize(row_count, column_count, coefficients, tex, nerd) end def self.trivial_instance - self.new(2, 2, - ['0', '0', '0', '0'], - '\begin{pmatrix} 0 & 0 \cr 0 & 0 \end{pmatrix}', - 'matrix([0,0],[0,0]') + new(2, 2, + ["0", "0", "0", "0"], + '\begin{pmatrix} 0 & 0 \cr 0 & 0 \end{pmatrix}', + "matrix([0,0],[0,0]") end - def entry(i, j) - if i > @row_count || j > @column_count - return '0' - end + def entry(i, j) # rubocop:disable Naming/MethodParameterName + return "0" if i > @row_count || j > @column_count - @coefficients[(i - 1) * @column_count + (j - 1)] + @coefficients[((i - 1) * @column_count) + (j - 1)] end def self.from_hash(content) - row_count = content['row_count'].to_i - column_count = content['column_count'].to_i - tex = content['tex'] - nerd = content['nerd'] + row_count = content["row_count"].to_i + column_count = content["column_count"].to_i + tex = content["tex"] + nerd = content["nerd"] coefficients = [] (1..row_count).each do |i| (1..column_count).each do |j| diff --git a/app/models/mampf_set.rb b/app/models/mampf_set.rb index 333e8d08a..3b00fff2d 100644 --- a/app/models/mampf_set.rb +++ b/app/models/mampf_set.rb @@ -10,10 +10,10 @@ def initialize(value, tex, nerd) end def self.trivial_instance - self.new('0,1', '\{0,1\}', 'vector(0,1)') + new("0,1", '\{0,1\}', "vector(0,1)") end def self.from_hash(content) - MampfSet.new(content['0'], content['tex'], content['nerd']) + MampfSet.new(content["0"], content["tex"], content["nerd"]) end end diff --git a/app/models/mampf_tuple.rb b/app/models/mampf_tuple.rb index dd997f92a..ac61b8016 100644 --- a/app/models/mampf_tuple.rb +++ b/app/models/mampf_tuple.rb @@ -10,10 +10,10 @@ def initialize(value, tex, nerd) end def self.trivial_instance - self.new('0,1', '(0,1)', 'vector(0,1)') + new("0,1", "(0,1)", "vector(0,1)") end def self.from_hash(content) - MampfTuple.new(content['0'], content['tex'], content['nerd']) + MampfTuple.new(content["0"], content["tex"], content["nerd"]) end end diff --git a/app/models/manuscript.rb b/app/models/manuscript.rb index f11e245e7..3316d5fb7 100644 --- a/app/models/manuscript.rb +++ b/app/models/manuscript.rb @@ -8,8 +8,8 @@ class Manuscript :content_descriptions, :version def initialize(medium) - unless medium && medium.sort == 'Script' && - medium&.teachable_type == 'Lecture' && + unless medium && medium.sort == "Script" && + medium&.teachable_type == "Lecture" && medium.manuscript return end @@ -17,17 +17,17 @@ def initialize(medium) @medium = medium @lecture = medium.teachable.lecture @locale = @lecture.locale_with_inheritance || I18n.default_locale - @chapter_marker = I18n.t('manuscript.chapter', locale: @locale) - @section_marker = I18n.t('manuscript.section', locale: @locale) - @version = medium.manuscript.metadata['version'] - bookmarks = medium.manuscript.metadata['bookmarks'] || [] + @chapter_marker = I18n.t("manuscript.chapter", locale: @locale) + @section_marker = I18n.t("manuscript.section", locale: @locale) + @version = medium.manuscript.metadata["version"] + bookmarks = medium.manuscript.metadata["bookmarks"] || [] @chapters = get_chapters(bookmarks) match_mampf_chapters @sections = get_sections(bookmarks) match_mampf_sections @content = get_content(bookmarks) check_content - @content_descriptions = @content.map { |c| c['description'] } - [''] + @content_descriptions = @content.pluck("description") - [""] add_info_on_tag_ids add_info_on_item_ids_and_hidden_status @contradictions = determine_contradictions @@ -40,49 +40,49 @@ def empty? end def sections_in_chapter(chapter) - @sections.select { |s| s['chapter'] == chapter['chapter'] } - .sort_by { |s| s['counter'] } + @sections.select { |s| s["chapter"] == chapter["chapter"] } + .sort_by { |s| s["counter"] } end def content_in_section(section) - @content.select { |c| c['section'] == section['section'] } - .sort_by { |c| c['counter'] } + @content.select { |c| c["section"] == section["section"] } + .sort_by { |c| c["counter"] } end # returns those content bookmarks who have a chapter or section counter # that corresponds to a chapter or section without a bookmark def content_in_unbookmarked_locations - @content.select { |c| c['contradiction'] } + @content.select { |c| c["contradiction"] } end def content_in_unbookmarked_locations? - @content.any? { |c| c['contradiction'] } + @content.any? { |c| c["contradiction"] } end def sections_in_unbookmarked_chapters - @sections.select { |s| s['contradiction'] == :missing_chapter } + @sections.select { |s| s["contradiction"] == :missing_chapter } end def sections_in_unbookmarked_chapters? - @sections.any? { |s| s['contradiction'] == :missing_chapter } + @sections.any? { |s| s["contradiction"] == :missing_chapter } end # returns the matching chapter in mampf for the given manuscript chapter # (matching is done by label) def chapter_in_mampf(chapter) @lecture&.chapters - &.find { |chap| chap.reference == chapter['label'] } + &.find { |chap| chap.reference == chapter["label"] } end def section_in_mampf(section) @lecture&.sections_cached - &.find { |sec| sec.reference == section['label'] } + &.find { |sec| sec.reference == section["label"] } end # returns if the mampf chapter for the corresponding chapter has a different # title def manuscript_chapter_contradicts?(chapter) - chapter_in_mampf(chapter)&.title != chapter['description'] + chapter_in_mampf(chapter)&.title != chapter["description"] end # export manuscript to database: @@ -95,7 +95,7 @@ def export_to_db!(filter_boxes) create_new_chapters! @chapters.each do |c| create_new_sections!(c) - c['mampf_chapter'] = c['mampf_chapter'].reload + c["mampf_chapter"] = c["mampf_chapter"].reload end create_or_update_chapter_items! create_or_update_section_items! @@ -105,12 +105,12 @@ def export_to_db!(filter_boxes) # chapters in mampf that are not represented in the manuscript def unmatched_mampf_chapters - chapters_in_mampf = @chapters.map { |c| c['mampf_chapter'] }.compact + chapters_in_mampf = @chapters.filter_map { |c| c["mampf_chapter"] } @lecture.chapters - chapters_in_mampf end def unmatched_mampf_sections - sections_in_mampf = @sections.map { |s| s['mampf_section'] }.compact + sections_in_mampf = @sections.filter_map { |s| s["mampf_section"] } @lecture.sections - sections_in_mampf end @@ -119,45 +119,45 @@ def create_new_chapters! new_chapters.each do |c| chap = Chapter.new(lecture_id: @lecture.id, title: c.second) chap.insert_at(c.first) - corresponding = @chapters.find { |d| d['counter'] == c.third } - corresponding['mampf_chapter'] = chap + corresponding = @chapters.find { |d| d["counter"] == c.third } + corresponding["mampf_chapter"] = chap end @lecture = @lecture.reload end # create sections in mampf for those manuscript sections not yet in mampf def create_new_sections!(chapter) - return if chapter['mampf_chapter'].nil? + return if chapter["mampf_chapter"].nil? - mampf_chapter = chapter['mampf_chapter'] + mampf_chapter = chapter["mampf_chapter"] new_sections_in_chapter(chapter).each do |s| sect = Section.new(chapter_id: mampf_chapter.id, title: s.second) sect.insert_at(s.first) - corresponding = @sections.find { |d| d['counter'] == s.third } - corresponding['mampf_section'] = sect + corresponding = @sections.find { |d| d["counter"] == s.third } + corresponding["mampf_section"] = sect end end def create_or_update_chapter_items! - destinations = @chapters.map { |c| c['destination'] } - [''] + destinations = @chapters.pluck("destination") - [""] items = Item.where(medium: @medium, pdf_destination: destinations, - sort: 'chapter') + sort: "chapter") item_id_map = items.pluck(:pdf_destination, :id).to_h item_destinations = item_id_map.keys - attrs = %i(medium_id pdf_destination section_id sort page - description ref_number position quarantine) + attrs = [:medium_id, :pdf_destination, :section_id, :sort, :page, :description, :ref_number, + :position, :quarantine] item_details = items.pluck(*attrs).map { |i| attrs.zip(i).to_h } contents = [] @chapters.each do |c| contents.push( { medium_id: @medium.id, - pdf_destination: c['destination'], + pdf_destination: c["destination"], section_id: nil, - sort: 'chapter', - page: c['page'].to_i, - description: c['description'], - ref_number: c['label'], + sort: "chapter", + page: c["page"].to_i, + description: c["description"], + ref_number: c["label"], position: nil, quarantine: nil } ) @@ -167,27 +167,27 @@ def create_or_update_chapter_items! end def create_or_update_section_items! - destinations = @sections.map { |s| s['destination'] } - [''] + destinations = @sections.pluck("destination") - [""] items = Item.where(medium: @medium, pdf_destination: destinations, - sort: 'section') + sort: "section") item_id_map = items.pluck(:pdf_destination, :id).to_h item_destinations = item_id_map.keys - attrs = %i(medium_id pdf_destination section_id sort page - description ref_number position quarantine) + attrs = [:medium_id, :pdf_destination, :section_id, :sort, :page, :description, :ref_number, + :position, :quarantine] item_details = items.pluck(*attrs).map { |i| attrs.zip(i).to_h } contents = [] - # note that sections get a position -1 in order to place them ahead + # NOTE: that sections get a position -1 in order to place them ahead # of all content items within themseleves in #script_items_by_position @sections.each do |s| contents.push( { medium_id: @medium.id, - pdf_destination: s['destination'], - section_id: s['mampf_section'].id, - sort: 'section', - page: s['page'].to_i, - description: s['description'], - ref_number: s['label'], + pdf_destination: s["destination"], + section_id: s["mampf_section"].id, + sort: "section", + page: s["page"].to_i, + description: s["description"], + ref_number: s["label"], position: -1, quarantine: nil } ) @@ -200,28 +200,28 @@ def create_or_update_section_items! # in filter_boxes (which basically contains the information on whichk # content checkboxes have been checked) def create_or_update_content_items!(filter_boxes) - destinations = @content.map { |c| c['destination'] } - [''] + destinations = @content.pluck("destination") - [""] items = Item.where(medium: @medium, pdf_destination: destinations) item_id_map = items.pluck(:pdf_destination, :id).to_h item_destinations = item_id_map.keys - attrs = %i(medium_id pdf_destination section_id sort page - description ref_number position hidden quarantine) + attrs = [:medium_id, :pdf_destination, :section_id, :sort, :page, :description, :ref_number, + :position, :hidden, :quarantine] item_details = items.pluck(*attrs).map { |i| attrs.zip(i).to_h } contents = [] @content.each do |c| contents.push( { medium_id: @medium.id, - pdf_destination: c['destination'], + pdf_destination: c["destination"], section_id: @sections.find do |s| - c['section'] == s['section'] - end ['mampf_section']&.id, - sort: Item.internal_sort(c['sort']), - page: c['page'].to_i, - description: c['description'], - ref_number: c['label'], - position: c['counter'], - hidden: filter_boxes[c['counter']].third == false, + c["section"] == s["section"] + end ["mampf_section"]&.id, + sort: Item.internal_sort(c["sort"]), + page: c["page"].to_i, + description: c["description"], + ref_number: c["label"], + position: c["counter"], + hidden: filter_boxes[c["counter"]].third == false, quarantine: nil } ) end @@ -243,7 +243,7 @@ def create_or_update_items!(contents, item_details, item_destinations, @medium.item_ids << new_item_ids changed_contents = different_contents - new_contents changed_contents.each do |c| - Item.find_by_id(item_id_map[c[:pdf_destination]]) + Item.find_by(id: item_id_map[c[:pdf_destination]]) .update(c) end end @@ -255,11 +255,11 @@ def create_or_update_items!(contents, item_details, item_destinations, # be associated with the course of the manuscript's lecture) def update_tags!(filter_boxes) sections_with_content.each do |s| - section = s['mampf_section'] + section = s["mampf_section"] content_in_section(s).each do |c| # if tag for content already exists, add tag to the section and course - if c['tag_id'] - tag = Tag.find_by_id(c['tag_id']) + if c["tag_id"] + tag = Tag.find_by(id: c["tag_id"]) next unless tag next unless section next if section.in?(tag.sections) @@ -268,13 +268,13 @@ def update_tags!(filter_boxes) tag.courses |= [@lecture.course] next end - next unless filter_boxes[c['counter']].second + next unless filter_boxes[c["counter"]].second # if checkbox for tag creation is checked, create the tag, # associate it with course and section tag = Tag.new(courses: [@lecture.course], sections: [section]) - tag.notions.new(title: c['description'], + tag.notions.new(title: c["description"], locale: @lecture.locale || I18n.default_locale) tag.save end @@ -283,8 +283,8 @@ def update_tags!(filter_boxes) # pdf destinations as extracted from pdf metadata def destinations - bookmarks = @medium.manuscript.metadata['bookmarks'] || [] - bookmarks.map { |b| b['destination'] } + bookmarks = @medium.manuscript.metadata["bookmarks"] || [] + bookmarks.pluck("destination") end # pdf destinations together with their multiplicity @@ -307,47 +307,47 @@ def add_info_on_tag_ids .select { |x| x.first.in?(@content_descriptions.map(&:downcase)) } .to_h @content.each do |c| - c['tag_id'] = desc_hash[c['description'].downcase] + c["tag_id"] = desc_hash[c["description"].downcase] end end # add information on the item ids for manuscript content and hidden status def add_info_on_item_ids_and_hidden_status - destinations = @content.map { |c| c['destination'] } - [''] + destinations = @content.pluck("destination") - [""] items_hash = Item.where(medium: @medium, pdf_destination: destinations) .pluck(:pdf_destination, :id, :hidden) - .map { |c| [c[0], [c[1], c[2]]] }.to_h + .to_h { |c| [c[0], [c[1], c[2]]] } @content.each do |c| - c['item_id'] = items_hash[c['destination']]&.first - c['hidden'] = items_hash[c['destination']]&.second + c["item_id"] = items_hash[c["destination"]]&.first + c["hidden"] = items_hash[c["destination"]]&.second end end # private def get_chapters(bookmarks) - bookmarks.select { |b| b['sort'] == @chapter_marker } - .sort_by { |c| c['counter'] } - .each_with_index { |c, i| c['new_position'] = i + 1 } + bookmarks.select { |b| b["sort"] == @chapter_marker } + .sort_by { |c| c["counter"] } + .each_with_index { |c, i| c["new_position"] = i + 1 } end def get_sections(bookmarks) - bookmarks.select { |b| b['sort'] == @section_marker } - .sort_by { |s| s['counter'] } + bookmarks.select { |b| b["sort"] == @section_marker } + .sort_by { |s| s["counter"] } end def get_content(bookmarks) - bookmarks.reject { |b| b['sort'].in?([@chapter_marker, @section_marker]) } - .sort_by { |c| c['counter'] } + bookmarks.reject { |b| b["sort"].in?([@chapter_marker, @section_marker]) } + .sort_by { |c| c["counter"] } end def match_mampf_chapters @chapters.each do |c| mampf_chapter = chapter_in_mampf(c) - c['mampf_chapter'] = mampf_chapter - c['contradiction'] = if mampf_chapter.nil? || - mampf_chapter.title == c['description'] + c["mampf_chapter"] = mampf_chapter + c["contradiction"] = if mampf_chapter.nil? || + mampf_chapter.title == c["description"] false else :different_title @@ -357,16 +357,16 @@ def match_mampf_chapters def match_mampf_sections @sections.each do |s| - bookmarked_chapter_counters = @chapters.map { |c| c['chapter'] } - unless s['chapter'].in?(bookmarked_chapter_counters) - s['mampf_section'] = nil - s['contradiction'] = :missing_chapter + bookmarked_chapter_counters = @chapters.pluck("chapter") + unless s["chapter"].in?(bookmarked_chapter_counters) + s["mampf_section"] = nil + s["contradiction"] = :missing_chapter next end mampf_section = section_in_mampf(s) - s['mampf_section'] = mampf_section - s['contradiction'] = if mampf_section.nil? || - mampf_section.title == s['description'] + s["mampf_section"] = mampf_section + s["contradiction"] = if mampf_section.nil? || + mampf_section.title == s["description"] false else :different_title @@ -375,12 +375,12 @@ def match_mampf_sections end def check_content - bookmarked_section_counters = @sections.map { |s| s['section'] } - bookmarked_chapter_counters = @chapters.map { |c| c['chapter'] } + bookmarked_section_counters = @sections.pluck("section") + bookmarked_chapter_counters = @chapters.pluck("chapter") @content.each do |c| - c['contradiction'] = if !c['chapter'].in?(bookmarked_chapter_counters) + c["contradiction"] = if !c["chapter"].in?(bookmarked_chapter_counters) :missing_chapter - elsif !c['section'].in?(bookmarked_section_counters) + elsif !c["section"].in?(bookmarked_section_counters) :missing_section else false @@ -389,17 +389,17 @@ def check_content end def determine_contradictions - { 'chapters' => @chapters.select { |c| c['contradiction'] }, - 'sections' => @sections.select { |s| s['contradiction'] }, - 'content' => @content.select { |c| c['contradiction'] }, - 'multiplicities' => destinations_with_higher_multiplicities, - 'version' => version_info } + { "chapters" => @chapters.select { |c| c["contradiction"] }, + "sections" => @sections.select { |s| s["contradiction"] }, + "content" => @content.select { |c| c["contradiction"] }, + "multiplicities" => destinations_with_higher_multiplicities, + "version" => version_info } end def determine_contradiction_count - @contradictions['chapters'].size + @contradictions['sections'].size + - @contradictions['content'].size + @contradictions['multiplicities'].size + - @contradictions['version'].size + @contradictions["chapters"].size + @contradictions["sections"].size + + @contradictions["content"].size + @contradictions["multiplicities"].size + + @contradictions["version"].size end def version_info @@ -410,18 +410,17 @@ def version_info # chapters in the manuscript not represented in mampf def new_chapters - @chapters.select { |c| c['mampf_chapter'].nil? } - .map { |c| [c['new_position'], c['description'], c['counter']] } + @chapters.select { |c| c["mampf_chapter"].nil? } + .map { |c| [c["new_position"], c["description"], c["counter"]] } end # sections in a manuscript chapter not represented in mampf def new_sections_in_chapter(chapter) sections = sections_in_chapter(chapter) - sections.each_with_index - .map do |s, i| - [s['mampf_section'], i + 1, s['description'], s['counter']] - end - .select { |s| s.first.nil? } + sections = sections.each_with_index.map do |s, i| + [s["mampf_section"], i + 1, s["description"], s["counter"]] + end + sections.select { |s| s.first.nil? } .map { |s| [s.second, s.third, s.fourth] } end @@ -433,6 +432,6 @@ def sections_with_content # the manuscript as well def existing_tags Notion.where(locale: @lecture.locale || I18n.default_locale) - .pluck('title') & @content_descriptions + .pluck("title") & @content_descriptions end end diff --git a/app/models/medium.rb b/app/models/medium.rb index 27c69505e..0fe6faaa5 100644 --- a/app/models/medium.rb +++ b/app/models/medium.rb @@ -42,11 +42,13 @@ class Medium < ApplicationRecord has_many :imports, dependent: :destroy has_many :importing_lectures, through: :imports, - source: :teachable, source_type: 'Lecture' + source: :teachable, source_type: "Lecture" has_many :importing_courses, through: :imports, - source: :teachable, source_type: 'Course' + source: :teachable, source_type: "Course" - has_many :quiz_certificates, foreign_key: 'quiz_id', dependent: :destroy + has_many :quiz_certificates, foreign_key: "quiz_id", + dependent: :destroy, + inverse_of: :quiz # a medium can be in watchlists of multiple users has_many :watchlist_entries, dependent: :destroy @@ -93,20 +95,17 @@ class Medium < ApplicationRecord # if medium is associated to a nonpublished teachable, reset its published # property to nil before_save :reset_released_status - # some information about media are cached - # to find out whether the cache is out of date, always touch'em after saving - after_save :touch_teachable - # after creation, this creates an item of type 'self' that is just a wrapper # around this medium, so the medium itself can be referenced from other media # as an item as well after_create :create_self_item - # if medium is a question or remark, delete all quiz vertices that refer to it before_destroy :delete_vertices - # if medium is a question, delete all answers that belong to it after_destroy :delete_answers + # some information about media are cached + # to find out whether the cache is out of date, always touch'em after saving + after_save :touch_teachable # keep track of copies (in particular for Questions, Remarks) acts_as_tree @@ -118,13 +117,13 @@ class Medium < ApplicationRecord # locally visible media are published (without inheritance) and unlocked # (they may not be globally visible as their lecture may be unpublished) scope :published, -> { where.not(released: nil) } - scope :locally_visible, -> { where(released: ['all', 'users']) } - scope :potentially_visible, -> { - where(released: ['all', 'users', 'subscribers']) + scope :locally_visible, -> { where(released: ["all", "users"]) } + scope :potentially_visible, lambda { + where(released: ["all", "users", "subscribers"]) } - scope :proper, -> { where.not(sort: 'RandomQuiz') } - scope :expired, -> { - where(sort: 'RandomQuiz').where('created_at < ?', 1.day.ago) + scope :proper, -> { where.not(sort: "RandomQuiz") } + scope :expired, lambda { + where(sort: "RandomQuiz").where("created_at < ?", 1.day.ago) } searchable do @@ -163,50 +162,50 @@ class Medium < ApplicationRecord # these are all the sorts of food(=projects) we currently serve def self.sort_enum - %w[Kaviar Erdbeere Sesam Kiwi Nuesse Script Question Quiz - Reste Remark RandomQuiz] + ["Kaviar", "Erdbeere", "Sesam", "Kiwi", "Nuesse", "Script", "Question", "Quiz", "Reste", + "Remark", "RandomQuiz"] end # media sorts and their descriptions def self.sort_localized - { 'Kaviar' => I18n.t('categories.kaviar.singular'), - 'Sesam' => I18n.t('categories.sesam.singular'), - 'Nuesse' => I18n.t('categories.exercises.singular'), - 'Script' => I18n.t('categories.script.singular'), - 'Kiwi' => I18n.t('categories.kiwi.singular'), - 'Quiz' => I18n.t('categories.quiz.singular'), - 'Question' => I18n.t('categories.question.singular'), - 'Remark' => I18n.t('categories.remark.singular'), - 'RandomQuiz' => I18n.t('categories.randomquiz.singular'), - 'Erdbeere' => I18n.t('categories.erdbeere.singular'), - 'Reste' => I18n.t('categories.reste.singular') } + { "Kaviar" => I18n.t("categories.kaviar.singular"), + "Sesam" => I18n.t("categories.sesam.singular"), + "Nuesse" => I18n.t("categories.exercises.singular"), + "Script" => I18n.t("categories.script.singular"), + "Kiwi" => I18n.t("categories.kiwi.singular"), + "Quiz" => I18n.t("categories.quiz.singular"), + "Question" => I18n.t("categories.question.singular"), + "Remark" => I18n.t("categories.remark.singular"), + "RandomQuiz" => I18n.t("categories.randomquiz.singular"), + "Erdbeere" => I18n.t("categories.erdbeere.singular"), + "Reste" => I18n.t("categories.reste.singular") } end # media sorts and their short descriptions def self.sort_localized_short - { 'Kaviar' => I18n.t('categories.kaviar.short'), - 'Sesam' => I18n.t('categories.sesam.short'), - 'Nuesse' => I18n.t('categories.exercises.short'), - 'Script' => I18n.t('categories.script.short'), - 'Kiwi' => I18n.t('categories.kiwi.short'), - 'Quiz' => I18n.t('categories.quiz.short'), - 'Question' => I18n.t('categories.question.short'), - 'Remark' => I18n.t('categories.remark.short'), - 'RandomQuiz' => I18n.t('categories.randomquiz.short'), - 'Erdbeere' => I18n.t('categories.erdbeere.short'), - 'Reste' => I18n.t('categories.reste.short') } + { "Kaviar" => I18n.t("categories.kaviar.short"), + "Sesam" => I18n.t("categories.sesam.short"), + "Nuesse" => I18n.t("categories.exercises.short"), + "Script" => I18n.t("categories.script.short"), + "Kiwi" => I18n.t("categories.kiwi.short"), + "Quiz" => I18n.t("categories.quiz.short"), + "Question" => I18n.t("categories.question.short"), + "Remark" => I18n.t("categories.remark.short"), + "RandomQuiz" => I18n.t("categories.randomquiz.short"), + "Erdbeere" => I18n.t("categories.erdbeere.short"), + "Reste" => I18n.t("categories.reste.short") } end def self.select_sorts - Medium.sort_localized.except('RandomQuiz').map { |k, v| [v, k] } + Medium.sort_localized.except("RandomQuiz").map { |k, v| [v, k] } end def self.advanced_sorts - ['Question', 'Remark', 'Erdbeere'] + ["Question", "Remark", "Erdbeere"] end def self.generic_sorts - ['Kaviar', 'Sesam', 'Nuesse', 'Script', 'Kiwi', 'Quiz', 'Reste'] + ["Kaviar", "Sesam", "Nuesse", "Script", "Kiwi", "Quiz", "Reste"] end def self.select_generic @@ -214,23 +213,23 @@ def self.select_generic end def self.select_quizzables - Medium.sort_localized.slice('Question', 'Remark').map { |k, v| [v, k] } + Medium.sort_localized.slice("Question", "Remark").map { |k, v| [v, k] } end def self.select_importables - Medium.sort_localized.except('RandomQuiz', 'Question', 'Remark', - 'Manuscript').map { |k, v| [v, k] } + Medium.sort_localized.except("RandomQuiz", "Question", "Remark", + "Manuscript").map { |k, v| [v, k] } end def self.select_question - Medium.sort_localized.slice('Question').map { |k, v| [v, k] } + Medium.sort_localized.slice("Question").map { |k, v| [v, k] } end # returns the array of all media subject to the conditions # provided by the params hash (keys: :id, :project) # :id represents the lecture id def self.search_all(params) - lecture = Lecture.find_by_id(params[:id]) + lecture = Lecture.find_by(id: params[:id]) return Medium.none if lecture.nil? media_in_project = Medium.media_in_project(params[:project]) @@ -246,16 +245,16 @@ def self.search_all(params) # returns the ARel of all media for the given project def self.media_in_project(project) - return Medium.none unless project.present? + return Medium.none if project.blank? - sort = project == 'keks' ? 'Quiz' : project.capitalize + sort = project == "keks" ? "Quiz" : project.capitalize Medium.where(sort: sort) end # returns the array of all media (by title), together with their ids # is used in options_for_select in form helpers. def self.select_by_name - Medium.where.not(sort: ['Question', 'Remark', 'RandomQuiz']) + Medium.where.not(sort: ["Question", "Remark", "RandomQuiz"]) .map { |m| [m.title_for_viewers, m.id] } end @@ -264,30 +263,28 @@ def self.select_by_name # value for :types is an array of integers which correspond to indices # in the sort_enum array def self.search_sorts(search_params) - unless search_params[:all_types] == '0' - return (Medium.sort_enum - ['RandomQuiz']) - end + return (Medium.sort_enum - ["RandomQuiz"]) unless search_params[:all_types] == "0" search_params[:types] || [] end def self.lecture_search_option { - '0' => 'all', - '1' => 'subscribed', - '2' => 'custom' + "0" => "all", + "1" => "subscribed", + "2" => "custom" } end # returns search results for the media search with search_params provided # by the controller - def self.search_by(search_params, page) + def self.search_by(search_params, _page) # If the search is initiated from the start page, you can only get # generic media sorts as results even if the 'all' radio button # is seleted - if search_params[:all_types] == '1' + if search_params[:all_types] == "1" search_params[:types] = - if search_params[:from] == 'start' + if search_params[:from] == "start" Medium.generic_sorts else [] @@ -295,72 +292,76 @@ def self.search_by(search_params, page) end search_params[:teachable_ids] = TeachableParser.new(search_params) .teachables_as_strings - search_params[:editor_ids] = - [] if search_params[:all_editors] == '1' || search_params[:all_editors].nil? + if search_params[:all_editors] == "1" || search_params[:all_editors].nil? + search_params[:editor_ids] = + [] + end # add media without term to current term - search_params[:all_terms] = '1' if search_params[:all_terms].blank? - search_params[:all_teachers] = '1' if search_params[:all_teachers].blank? - search_params[:term_ids].push('0') if search_params[:term_ids].present? - if search_params[:all_tags] == '1' && search_params[:tag_operator] == 'and' + search_params[:all_terms] = "1" if search_params[:all_terms].blank? + search_params[:all_teachers] = "1" if search_params[:all_teachers].blank? + search_params[:term_ids].push("0") if search_params[:term_ids].present? + if search_params[:all_tags] == "1" && search_params[:tag_operator] == "and" search_params[:tag_ids] = Tag.pluck(:id) end user = User.find_by(id: search_params[:user_id]) search = Sunspot.new_search(Medium) search.build do with(:sort, search_params[:types]) - without(:sort, 'RandomQuiz') + without(:sort, "RandomQuiz") without(:sort, Medium.advanced_sorts) unless user&.admin_or_editor? with(:editor_ids, search_params[:editor_ids]) with(:teachable_compact, search_params[:teachable_ids]) - with(:term_id, - search_params[:term_ids]) unless search_params[:all_terms] == '1' - with(:teacher_id, - search_params[:teacher_ids]) unless search_params[:all_teachers] == '1' + unless search_params[:all_terms] == "1" + with(:term_id, + search_params[:term_ids]) + end + unless search_params[:all_teachers] == "1" + with(:teacher_id, + search_params[:teacher_ids]) + end end - if search_params[:purpose] == 'clicker' + if search_params[:purpose] == "clicker" search.build do with(:clickerizable, true) end end - unless search_params[:answers_count] == 'irrelevant' + unless search_params[:answers_count] == "irrelevant" search.build do with(:answers_count, [-1, search_params[:answers_count].to_i]) end end - unless search_params[:access] == 'irrelevant' + unless search_params[:access] == "irrelevant" search.build do with(:release_state, search_params[:access]) end end - unless search_params[:all_tags] == '1' && - search_params[:tag_operator] == 'or' - if search_params[:tag_ids] - if search_params[:tag_operator] == 'or' || search_params[:all_tags] == '1' - search.build do - with(:tag_ids).any_of(search_params[:tag_ids]) - end - else - search.build do - with(:tag_ids).all_of(search_params[:tag_ids]) - end + if !search_params[:all_tags] == "1" && + !search_params[:tag_operator] == "or" && (search_params[:tag_ids]) + if search_params[:tag_operator] == "or" || search_params[:all_tags] == "1" + search.build do + with(:tag_ids).any_of(search_params[:tag_ids]) + end + else + search.build do + with(:tag_ids).all_of(search_params[:tag_ids]) end end end if search_params[:fulltext].present? search.build do - fulltext search_params[:fulltext] do - boost_fields :description => 2.0 + fulltext(search_params[:fulltext]) do + boost_fields(description: 2.0) end end end if search_params[:lecture_option].present? case Medium.lecture_search_option[search_params[:lecture_option]] - when 'subscribed' + when "subscribed" search.build do with(:subscribed_users, search_params[:user_id]) end - when 'custom' + when "custom" search.build do with(:lecture, search_params[:media_lectures]) end @@ -368,7 +369,7 @@ def self.search_by(search_params, page) end # this is needed for kaminari to function correctly search.build do - paginate page: 1, per_page: Medium.all.count + paginate(page: 1, per_page: Medium.count) end search end @@ -376,10 +377,10 @@ def self.search_by(search_params, page) def self.search_questions_by_tags(search_params) search = Sunspot.new_search(Medium) search.build do - with(:sort, 'Question') + with(:sort, "Question") with(:teachable_compact, search_params[:teachable_ids]) with(:tag_ids).all_of(search_params[:tag_ids]) - paginate per_page: Question.count + paginate(per_page: Question.count) end search end @@ -392,12 +393,6 @@ def self.similar_courses(search_string) end end - def restricted? - return false unless teachable - - teachable.restricted? - end - # protected items are items of type 'pdf_destination' inside associated to # this medium that are referred to from other media or from an entry # within the table of contents of the video associated to this medium. @@ -405,17 +400,17 @@ def restricted? # the user insists (this way they are protected for example in the situation # where the user temporarily commented out some part of the manuscript) def protected_items - return [] unless sort == 'Script' + return [] unless sort == "Script" pdf_items = Item.where(medium: self).where.not(pdf_destination: nil) Referral.where(item: pdf_items).map(&:item).uniq end def vanished_items - return [] unless sort == 'Script' + return [] unless sort == "Script" Item.where(medium: self) - .where.not(sort: 'self') + .where.not(sort: "self") .where.not(pdf_destination: manuscript_destinations) end @@ -443,11 +438,11 @@ def quarantine # - put items that correspond to missing destination in quarantine (and # return these) def update_pdf_destinations! - return unless sort == 'Script' + return unless sort == "Script" irrelevant_items.delete_all result = missing_items_outside_quarantine.pluck(:pdf_destination) - missing_items_outside_quarantine.update_all(quarantine: true) + missing_items_outside_quarantine.update(quarantine: true) result end @@ -466,15 +461,15 @@ def edited_with_inheritance_by?(user) return true if teachable&.lecture&.editors&.include?(user) return true if teachable&.lecture&.teacher == user return true if teachable&.course&.editors&.include?(user) - return true if teachable&.is_a?(Talk) && user.in?(teachable.speakers) + return true if teachable.is_a?(Talk) && user.in?(teachable.speakers) false end def editors_with_inheritance - return [] if sort == 'RandomQuiz' + return [] if sort == "RandomQuiz" - result = (editors&.to_a + teachable.lecture&.editors.to_a + + result = (editors&.to_a&.+ teachable.lecture&.editors.to_a + [teachable.lecture&.teacher] + teachable.course.editors.to_a).uniq.compact return result unless teachable.is_a?(Talk) @@ -486,9 +481,7 @@ def editors_with_inheritance def eligible_editors(user) result = editors_with_inheritance - if teachable.is_a?(Talk) && user.can_edit?(lecture) - result.concat(lecture.speakers) - end + result.concat(lecture.speakers) if teachable.is_a?(Talk) && user.can_edit?(lecture) result << user if user.admin? result.uniq @@ -497,11 +490,11 @@ def eligible_editors(user) # creates a .vtt tmp file (and returns it), which contains # all data needed by the thyme player to realize the toc def toc_to_vtt - file = Tempfile.new(['toc-', '.vtt'], encoding: 'UTF-8') - file.write vtt_start + file = Tempfile.new(["toc-", ".vtt"], encoding: "UTF-8") + file.write(vtt_start) proper_items_by_time.reject(&:hidden).each do |i| - file.write i.vtt_time_span - file.write i.vtt_reference + file.write(i.vtt_time_span) + file.write(i.vtt_reference) end file end @@ -510,12 +503,12 @@ def toc_to_vtt # all data needed by the thyme player to realize references # Note: Only references to unlocked media will be incorporated. def references_to_vtt - file = Tempfile.new(['ref-', '.vtt'], encoding: 'UTF-8') - file.write vtt_start + file = Tempfile.new(["ref-", ".vtt"], encoding: "UTF-8") + file.write(vtt_start) referrals_by_time.select { |r| r.item_published? && !r.item_locked? } .each do |r| - file.write r.vtt_time_span - file.write JSON.pretty_generate(r.vtt_properties) + "\n\n" + file.write(r.vtt_time_span) + file.write("#{JSON.pretty_generate(r.vtt_properties)}\n\n") end file end @@ -528,7 +521,7 @@ def create_vtt_container! # some plain methods for items and referrals def proper_items - items.where.not(sort: ['self', 'pdf_destination']) + items.where.not(sort: ["self", "pdf_destination"]) .where.not(start_time: nil) end @@ -547,11 +540,11 @@ def referrals_by_time def screenshot_url_with_host return screenshot_url(host: host) unless screenshot(:normalized) - return screenshot_url(:normalized, host: host) + screenshot_url(:normalized, host: host) end def video_url - return unless video.present? + return if video.blank? video.url(host: host) end @@ -561,45 +554,45 @@ def video_download_url end def video_filename - return unless video.present? + return if video.blank? - video.metadata['filename'] + video.metadata["filename"] end def video_size - return unless video.present? + return if video.blank? - video.metadata['size'] + video.metadata["size"] end def video_resolution - return unless video.present? + return if video.blank? - video.metadata['resolution'] + video.metadata["resolution"] end def video_duration - return unless video.present? + return if video.blank? - video.metadata['duration'] + video.metadata["duration"] end def video_duration_hms_string - return unless video.present? + return if video.blank? TimeStamp.new(total_seconds: video_duration).hms_string end def geogebra_filename - return unless geogebra.present? + return if geogebra.blank? - geogebra.metadata['filename'] + geogebra.metadata["filename"] end def geogebra_size - return unless geogebra.present? + return if geogebra.blank? - geogebra.metadata['size'] + geogebra.metadata["size"] end def geogebra_url_with_host @@ -611,13 +604,13 @@ def geogebra_download_url end def geogebra_screenshot_url - return '' unless geogebra.present? + return "" if geogebra.blank? geogebra_url(:screenshot, host: host) end def manuscript_url_with_host - return manuscript_url(host: host) + "/" + manuscript_filename if ENV["REWRITE_ENABLED"] == "1" + return "#{manuscript_url(host: host)}/#{manuscript_filename}" if ENV["REWRITE_ENABLED"] == "1" manuscript_url(host: host) end @@ -627,45 +620,45 @@ def manuscript_download_url end def manuscript_filename - return unless manuscript.present? + return if manuscript.blank? - return manuscript.metadata['filename'] + manuscript.metadata["filename"] end def manuscript_size - return unless manuscript.present? + return if manuscript.blank? - return manuscript.metadata['size'] + manuscript.metadata["size"] end def manuscript_pages - return unless manuscript.present? + return if manuscript.blank? - return manuscript.metadata['pages'] + manuscript.metadata["pages"] end def manuscript_screenshot_url - return '' unless manuscript.present? + return "" if manuscript.blank? manuscript_url(:screenshot, host: host) end def manuscript_destinations - return [] unless manuscript.present? && sort == 'Script' + return [] unless manuscript.present? && sort == "Script" - manuscript.metadata['destinations'] || [] + manuscript.metadata["destinations"] || [] end def video_width - return unless video.present? + return if video.blank? - video_resolution.split('x')[0].to_i + video_resolution.split("x")[0].to_i end def video_height - return unless video.present? + return if video.blank? - video_resolution.split('x')[1].to_i + video_resolution.split("x")[1].to_i end def video_aspect_ratio @@ -682,16 +675,14 @@ def video_scaled_height(new_width) def caption return description if description.present? - return '' unless sort == 'Kaviar' && teachable_type == 'Lesson' + return "" unless sort == "Kaviar" && teachable_type == "Lesson" - teachable.section_titles || '' + teachable.section_titles || "" end # methods that create card header and subheader for a medium card - def card_header - teachable.card_header - end + delegate :card_header, to: :teachable def card_header_teachable_path(user) teachable.card_header_path(user) @@ -702,9 +693,9 @@ def card_subheader end def card_tooltip - return Medium.sort_localized[sort] unless sort == 'Nuesse' && file_last_edited + return Medium.sort_localized[sort] unless sort == "Nuesse" && file_last_edited - I18n.t('categories.exercises.singular_updated') + I18n.t("categories.exercises.singular_updated") end def sort_localized @@ -712,12 +703,13 @@ def sort_localized end def subheader_style - return 'badge bg-secondary' unless sort == 'Nuesse' && file_last_edited - 'badge bg-danger' + return "badge bg-secondary" unless sort == "Nuesse" && file_last_edited + + "badge bg-danger" end def cache_key - super + '-' + I18n.locale.to_s + "#{super}-#{I18n.locale}" end def published? @@ -725,23 +717,23 @@ def published? end def locked? - released == 'locked' + released == "locked" end def restricted? - released == 'subscribers' + released == "subscribers" end def free? - released == 'all' + released == "all" end def for_users? - released == 'users' + released == "users" end def visible? - released.in?(['all', 'users', 'subscribers']) + released.in?(["all", "users", "subscribers"]) end def visible_for_user?(user) @@ -750,12 +742,12 @@ def visible_for_user?(user) return false unless published? return false if locked? - if teachable_type == 'Course' - return false if restricted? && !teachable.in?(user.courses) - end - if teachable_type.in?(['Lecture', 'Lesson', 'Talk']) - return false if restricted? && !teachable.lecture.in?(user.lectures) + return false if teachable_type == "Course" && (restricted? && !teachable.in?(user.courses)) + if teachable_type.in?(["Lecture", "Lesson", + "Talk"]) && (restricted? && !teachable.lecture.in?(user.lectures)) + return false end + true end @@ -783,9 +775,9 @@ def irrelevant? end def teachable_select - return nil unless teachable.present? + return nil if teachable.blank? - teachable_type + '-' + teachable_id.to_s + "#{teachable_type}-#{teachable_id}" end # media associated to the same teachable and of the same sort @@ -799,7 +791,7 @@ def siblings def compact_info_uncached return "#{sort_localized}.#{teachable.compact_title}" unless quizzy? - "#{sort_localized}.#{teachable.compact_title}.\##{id}" + "#{sort_localized}.#{teachable.compact_title}.##{id}" end def compact_info @@ -814,17 +806,17 @@ def compact_info def local_info_uncached return description if description.present? - return I18n.t('admin.medium.local_info.no_title') unless undescribable? + return I18n.t("admin.medium.local_info.no_title") unless undescribable? - if sort == 'Kaviar' && teachable_type == 'Lesson' - return I18n.t('admin.medium.local_info.to_session', + if sort == "Kaviar" && teachable_type == "Lesson" + return I18n.t("admin.medium.local_info.to_session", number: teachable.number, date: teachable.date_localized) - elsif sort == 'Script' - return I18n.t('categories.script.singular') + elsif sort == "Script" + return I18n.t("categories.script.singular") end - "#{sort_localized} \##{id}" + "#{sort_localized} ##{id}" end def local_info @@ -836,7 +828,7 @@ def local_info def local_info_for_admins_uncached return local_info unless quizzy? - "\##{id}.#{local_info}" + "##{id}.#{local_info}" end def local_info_for_admins @@ -848,12 +840,10 @@ def local_info_for_admins # returns description if present, otherwise '' def details_uncached - return description unless description.blank? - unless undescribable? - return "#{I18n.t('admin.medium.local_info.no_title')}.ID#{id}" - end + return description if description.present? + return "#{I18n.t("admin.medium.local_info.no_title")}.ID#{id}" unless undescribable? - '' + "" end def details @@ -862,12 +852,6 @@ def details end end - def title_uncached - return compact_info if details.blank? - - compact_info + '.' + details - end - def title Rails.cache.fetch("#{cache_key_with_version}/title") do title_uncached @@ -877,8 +861,8 @@ def title # returns info made from sort, teachable title and description def title_for_viewers_uncached - sort_localized + ', ' + teachable&.title_for_viewers.to_s + - (description.present? ? ', ' + description : '') + description_str = description.present? ? ", #{description}" : "" + "#{sort_localized}, #{teachable&.title_for_viewers}#{description_str}" end def title_for_viewers @@ -893,16 +877,6 @@ def scoped_teachable_title end end - # returns info made from sort and description - def local_title_for_viewers_uncached - return "#{sort_localized}, #{description}" if description.present? - if sort == 'Kaviar' && teachable.class.to_s == 'Lesson' - return "#{I18n.t('categories.kaviar.singular')}, #{teachable.local_title_for_viewers}" - end - - "#{sort_localized}, #{I18n.t('admin.medium.local_info.no_title')}" - end - # returns info made from sort and description def local_title_for_viewers Rails.cache.fetch("#{cache_key_with_version}/local_title_for_viewers") do @@ -913,7 +887,7 @@ def local_title_for_viewers # this is used in dropdowns for compact info def extended_label Rails.cache.fetch("#{cache_key_with_version}/extended_label") do - "#{teachable.compact_title}.\##{id}.#{description}" + "#{teachable.compact_title}.##{id}.#{description}" end end @@ -937,7 +911,7 @@ def items_with_references end def proper? - return true unless sort == 'RandomQuiz' + return true unless sort == "RandomQuiz" false end @@ -947,23 +921,23 @@ def locale_with_inheritance end def sanitize_type! - update(type: 'Quiz') if sort.in?(['Quiz', 'RandomQuiz']) - update(type: sort) if sort.in?(['Question', 'Remark']) - update(type: nil) if !sort.in?(['Quiz', 'Question', 'Remark', 'RandomQuiz']) + update(type: "Quiz") if sort.in?(["Quiz", "RandomQuiz"]) + update(type: sort) if sort.in?(["Question", "Remark"]) + update(type: nil) unless sort.in?(["Quiz", "Question", "Remark", "RandomQuiz"]) end def select_sorts result = if new_record? - Medium.sort_localized.except('RandomQuiz') - elsif sort.in?(['Kaviar', 'Sesam', 'Erdbeere', 'Kiwi', 'Nuesse', - 'Reste']) - Medium.sort_localized.except('RandomQuiz', 'Script', 'Quiz', - 'Question', 'Remark') + Medium.sort_localized.except("RandomQuiz") + elsif sort.in?(["Kaviar", "Sesam", "Erdbeere", "Kiwi", "Nuesse", + "Reste"]) + Medium.sort_localized.except("RandomQuiz", "Script", "Quiz", + "Question", "Remark") else Medium.sort_localized.slice(sort) end - if teachable_type == 'Talk' - result.except!('RandomQuiz', 'Question', 'Remark', 'Erdbeere', 'Script') + if teachable_type == "Talk" + result.except!("RandomQuiz", "Question", "Remark", "Erdbeere", "Script") end result.map { |k, v| [v, k] } end @@ -973,12 +947,12 @@ def select_sorts_with_self end def extracted_linked_media - video_links = Medium.where(id: referenced_items.where(sort: 'self') + video_links = Medium.where(id: referenced_items.where(sort: "self") .where.not(medium: nil) .pluck(:medium_id)) - return video_links unless manuscript.present? + return video_links if manuscript.blank? - manuscript_media_ids = manuscript.metadata['linked_media'] || [] + manuscript_media_ids = manuscript.metadata["linked_media"] || [] manuscript_links = Medium.where(id: manuscript_media_ids) video_links.or(manuscript_links) end @@ -994,38 +968,38 @@ def linked_media_ids_cached end def toc_items - return [] unless sort == 'Script' + return [] unless sort == "Script" - items.where(sort: ['chapter', 'section']) + items.where(sort: ["chapter", "section"]) .natural_sort_by { |x| [x.page, x.ref_number] } end def tags_outside_lesson - return Tag.none unless teachable_type == 'Lesson' + return Tag.none unless teachable_type == "Lesson" tags.where.not(id: teachable.tag_ids) end def extended_content result = [] - if teachable_type == 'Lesson' && teachable.details.present? - result.push I18n.t('admin.medium.lesson_details_html') + teachable.details + if teachable_type == "Lesson" && teachable.details.present? + result.push(I18n.t("admin.medium.lesson_details_html") + teachable.details) end - result.push content unless content.blank? + result.push(content) if content.present? result end def script_items_importable? - return unless teachable_type == 'Lesson' - return unless teachable.lecture.content_mode == 'manuscript' - return unless teachable.script_items.any? + return false unless teachable_type == "Lesson" + return false unless teachable.lecture.content_mode == "manuscript" + return false unless teachable.script_items.any? true end def import_script_items! - return unless teachable_type == 'Lesson' - return unless teachable.lecture.content_mode == 'manuscript' + return unless teachable_type == "Lesson" + return unless teachable.lecture.content_mode == "manuscript" items = teachable.script_items return unless items.any? @@ -1073,55 +1047,48 @@ def planned_comment_lock? end def becomes_quizzable - return unless type.in?(['Question', 'Remark']) - return becomes(Question) if type == 'Question' + return unless type.in?(["Question", "Remark"]) + return becomes(Question) if type == "Question" becomes(Remark) end - def containingWatchlists(user) - Watchlist.where(id: WatchlistEntry.where(medium: self).pluck(:watchlist_id), + def containing_watchlists(user) + Watchlist.where(id: WatchlistEntry.where(medium: self).select(:watchlist_id), user: user) end - def containingWatchlistsNames(user) - watchlists = containingWatchlists(user) - if !watchlists.empty? - containingWatchlists(user).pluck(:name) + def containing_watchlists_names(user) + watchlists = containing_watchlists(user) + if watchlists.empty? + "" else - '' + watchlists.pluck(:name) end end def collects_statistics - video.present? || manuscript.present? || sort == 'Quiz' + video.present? || manuscript.present? || sort == "Quiz" end def term_id - teachable.term_id if teachable.class.to_s == 'Lecture' - return unless teachable.class.to_s == 'Lesson' + teachable.term_id if teachable.instance_of?(::Lecture) + return unless teachable.instance_of?(::Lesson) Lecture.find_by(id: teachable.lecture_id).term_id end def supervising_teacher_id - return teachable.teacher_id if teachable.class.to_s == 'Lecture' - return unless teachable.class.to_s == 'Lesson' - - Lecture.find_by(id: teachable.lecture_id).teacher_id - end - - def supervising_teacher_id - return teachable.teacher_id if teachable.class.to_s == 'Lecture' - return unless teachable.class.to_s == 'Lesson' + return teachable.teacher_id if teachable.instance_of?(::Lecture) + return unless teachable.instance_of?(::Lesson) Lecture.find_by(id: teachable.lecture_id).teacher_id end def subscribed_users - return teachable.user_ids if ['Lecture', - 'Course'].include? teachable.class.to_s - return unless teachable.class.to_s == 'Lesson' + return teachable.user_ids if ["Lecture", + "Course"].include?(teachable.class.to_s) + return unless teachable.instance_of?(::Lesson) Lecture.find_by(id: teachable.lecture_id).user_ids end @@ -1131,35 +1098,34 @@ def subscribed_users # media of type kaviar associated to a lesson and script do not require # a description def undescribable? - (sort == 'Kaviar' && teachable.class.to_s == 'Lesson') || - sort == 'Script' + (sort == "Kaviar" && teachable.instance_of?(::Lesson)) || + sort == "Script" end def quizzy? - sort.in?(['Quiz', 'Question', 'Remark']) + sort.in?(["Quiz", "Question", "Remark"]) end def title_uncached return compact_info if details.blank? - compact_info + '.' + details + "#{compact_info}.#{details}" end + # returns info made from sort and description def local_title_for_viewers_uncached return "#{sort_localized}, #{description}" if description.present? - if sort == 'Kaviar' && teachable.class.to_s == 'Lesson' - return "#{I18n.t('categories.kaviar.singular')}, #{teachable.local_title_for_viewers}" + if sort == "Kaviar" && teachable.instance_of?(::Lesson) + return "#{I18n.t("categories.kaviar.singular")}, #{teachable.local_title_for_viewers}" end - "#{sort_localized}, #{I18n.t('admin.medium.local_info.no_title')}" + "#{sort_localized}, #{I18n.t("admin.medium.local_info.no_title")}" end def touch_teachable return if teachable.nil? - if teachable.course.present? && teachable.course.persisted? - teachable.course.touch - end + teachable.course.touch if teachable.course.present? && teachable.course.persisted? optional_touches end @@ -1170,12 +1136,8 @@ def reset_released_status end def optional_touches - if teachable.lecture.present? && teachable.lecture.persisted? - teachable.lecture.touch - end - if teachable.lesson.present? && teachable.lesson.persisted? - teachable.lesson.touch - end + teachable.lecture.touch if teachable.lecture.present? && teachable.lecture.persisted? + teachable.lesson.touch if teachable.lesson.present? && teachable.lesson.persisted? return unless teachable.talk.present? && teachable.talk.persisted? teachable.talk.touch @@ -1186,34 +1148,34 @@ def vtt_start end def belongs_to_course?(lecture) - teachable_type == 'Course' && teachable == lecture.course + teachable_type == "Course" && teachable == lecture.course end def belongs_to_lecture?(lecture) - teachable_type == 'Lecture' && teachable == lecture + teachable_type == "Lecture" && teachable == lecture end def belongs_to_lesson?(lecture) - teachable_type == 'Lesson' && teachable.lecture == lecture + teachable_type == "Lesson" && teachable.lecture == lecture end def create_self_item - return if sort.in?(['Question', 'Remark', 'RandomQuiz']) + return if sort.in?(["Question", "Remark", "RandomQuiz"]) - Item.create(sort: 'self', medium: self) + Item.create(sort: "self", medium: self) end def local_items - return teachable.items - items if teachable_type == 'Course' + return teachable.items - items if teachable_type == "Course" teachable.lecture.items - items end def at_most_one_manuscript - return true unless teachable_type == 'Lecture' - return true unless sort == 'Script' + return true unless teachable_type == "Lecture" + return true unless sort == "Script" - if (Medium.where(sort: 'Script', + if (Medium.where(sort: "Script", teachable: teachable).to_a - [self]).size.positive? errors.add(:sort, :lecture_manuscript_exists) return false @@ -1222,27 +1184,27 @@ def at_most_one_manuscript end def script_only_for_lectures - return true if teachable_type == 'Lecture' - return true unless sort == 'Script' + return true if teachable_type == "Lecture" + return true unless sort == "Script" errors.add(:sort, :lecture_only) false end def no_video_for_script - return true unless sort == 'Script' - return true unless video.present? + return true unless sort == "Script" + return true if video.blank? errors.add(:sort, :no_video) false end def no_changing_sort_to_or_from_script - if sort_was == 'Script' && sort != 'Script' + if sort_was == "Script" && sort != "Script" errors.add(:sort, :no_conversion_from_script) return false end - if persisted? && sort_was != 'Script' && sort == 'Script' + if persisted? && sort_was != "Script" && sort == "Script" errors.add(:sort, :no_conversion_to_script) return false end @@ -1250,16 +1212,16 @@ def no_changing_sort_to_or_from_script end def no_tags_for_scripts - return true unless sort == 'Script' && tags.any? + return true unless sort == "Script" && tags.any? errors.add(:tags, :no_tags_allowed) false end def delete_vertices - return unless type.in?(['Question', 'Remark']) + return unless type.in?(["Question", "Remark"]) - if type == 'Question' + if type == "Question" becomes(Question).delete_vertices return end @@ -1267,26 +1229,26 @@ def delete_vertices end def delete_answers - return unless type == 'Question' + return unless type == "Question" becomes(Question).answers.delete_all end def text_join - return unless type.in?(['Question', 'Remark']) - return text if type == 'Remark' + return unless type.in?(["Question", "Remark"]) + return text if type == "Remark" - "#{text} #{becomes(Question).answers&.map(&:text_join)&.join(' ')}" + "#{text} #{becomes(Question).answers&.map(&:text_join)&.join(" ")}" end def release_state return released unless released.nil? - 'unpublished' + "unpublished" end def clickerizable? - return false unless type == 'Question' + return false unless type == "Question" question = becomes(Question) return false unless question.answers.count.in?((2..6)) @@ -1295,7 +1257,7 @@ def clickerizable? end def answers_count - return -1 unless type == 'Question' + return -1 unless type == "Question" becomes(Question).answers.count end diff --git a/app/models/medium_publisher.rb b/app/models/medium_publisher.rb index 1d5923632..3607359e3 100644 --- a/app/models/medium_publisher.rb +++ b/app/models/medium_publisher.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # PORO class that handles the publication of media class MediumPublisher attr_reader :medium_id, :user_id, :release_now, :release_for, :release_date, @@ -7,11 +5,11 @@ class MediumPublisher :assignment_file_type, :assignment_deadline, :assignment_deletion_date - def initialize(medium_id:, user_id:, release_now:, - release_for: 'all', release_date: nil, + def initialize(medium_id:, user_id:, release_now:, # rubocop:todo Metrics/ParameterLists + release_for: "all", release_date: nil, lock_comments: false, vertices: false, - create_assignment: false, assignment_title: '', - assignment_file_type: '', assignment_deadline: nil, + create_assignment: false, assignment_title: "", + assignment_file_type: "", assignment_deadline: nil, assignment_deletion_date: nil) @medium_id = medium_id @user_id = user_id @@ -45,27 +43,27 @@ def self.dump(medium_publisher) def self.parse(medium, user, params) begin - release_date = Time.zone.parse(params[:release_date] || '') + release_date = Time.zone.parse(params[:release_date] || "") rescue ArgumentError - puts 'Argument error for medium release date' + Rails.logger.debug("Argument error for medium release date") end begin - assignment_deadline = Time.zone.parse(params[:assignment_deadline] || '') + assignment_deadline = Time.zone.parse(params[:assignment_deadline] || "") rescue ArgumentError - puts 'Argument error for medium assignment deadline' + Rails.logger.debug("Argument error for medium assignment deadline") end begin - assignment_deletion_date = Time.zone.parse(params[:assignment_deletion_date] || '') + assignment_deletion_date = Time.zone.parse(params[:assignment_deletion_date] || "") rescue ArgumentError - puts 'Argument error for medium assignment deletion date' + Rails.logger.debug("Argument error for medium assignment deletion date") end MediumPublisher.new(medium_id: medium.id, user_id: user.id, - release_now: params[:release_now] == '1', + release_now: params[:release_now] == "1", release_for: params[:released], release_date: release_date, - lock_comments: params[:lock_comments] == '1', - vertices: params[:publish_vertices] == '1', - create_assignment: params[:create_assignment] == '1', + lock_comments: params[:lock_comments] == "1", + vertices: params[:publish_vertices] == "1", + create_assignment: params[:create_assignment] == "1", assignment_title: params[:assignment_title], assignment_file_type: params[:assignment_file_type], assignment_deadline: assignment_deadline, @@ -73,8 +71,8 @@ def self.parse(medium, user, params) end def publish! - @medium = Medium.find_by_id(@medium_id) - @user = User.find_by_id(@user_id) + @medium = Medium.find_by(id: @medium_id) + @user = User.find_by(id: @user_id) return unless @medium && @user && @medium.released_at.nil? return unless @user.can_edit?(@medium) @@ -122,7 +120,7 @@ def update_medium! def realize_optional_stuff! close_thread! if @lock_comments - publish_vertices! if @medium.sort == 'Quiz' && @vertices + publish_vertices! if @medium.sort == "Quiz" && @vertices create_assignment! if @create_assignment end @@ -131,12 +129,12 @@ def realize_optional_stuff! def create_notifications! @medium.teachable&.media_scope&.touch notifications = [] - @medium.teachable.media_scope.users.update_all(updated_at: Time.zone.now) + @medium.teachable.media_scope.users.touch_all @medium.teachable.media_scope.users.each do |u| notifications << Notification.new(recipient: u, notifiable_id: @medium.id, - notifiable_type: 'Medium', - action: 'create') + notifiable_type: "Medium", + action: "create") end Notification.import notifications end @@ -156,7 +154,7 @@ def send_notification_email! end def medium - Medium.find_by_id(@medium_id) + Medium.find_by(id: @medium_id) end def publish_vertices! @@ -191,27 +189,27 @@ def invalid_assignment_title? end def add_release_date_error - @errors[:release_date] = I18n.t('admin.medium.invalid_publish_date') + @errors[:release_date] = I18n.t("admin.medium.invalid_publish_date") end def add_assignment_deadline_error - @errors[:assignment_deadline] = I18n.t('admin.medium' \ - '.invalid_assignment_deadline') + @errors[:assignment_deadline] = I18n.t("admin.medium" \ + ".invalid_assignment_deadline") end def add_assignment_deletion_date_error - @errors[:assignment_deletion_date] = I18n.t('activerecord.errors.' \ - 'models.assignment.' \ - 'attributes.deletion_date.' \ - 'in_past') + @errors[:assignment_deletion_date] = I18n.t("activerecord.errors." \ + "models.assignment." \ + "attributes.deletion_date." \ + "in_past") end def add_assignment_title_error - @errors[:assignment_title] = I18n.t('admin.medium' \ - '.invalid_assignment_title') + @errors[:assignment_title] = I18n.t("admin.medium" \ + ".invalid_assignment_title") end def medium_without_notifications? - @medium.sort.in?(['Question', 'Remark', 'RandomQuiz']) + @medium.sort.in?(["Question", "Remark", "RandomQuiz"]) end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 278066c89..ee62b890c 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -4,7 +4,7 @@ class Notification < ApplicationRecord include ActionDispatch::Routing::PolymorphicRoutes include Rails.application.routes.url_helpers - belongs_to :recipient, class_name: 'User', touch: true + belongs_to :recipient, class_name: "User", touch: true belongs_to :notifiable, polymorphic: true, optional: true paginates_per 12 @@ -19,9 +19,9 @@ class Notification < ApplicationRecord # returns the lecture associated to a notification of type announcement, # and teachable for a notification of type medium, nil otherwise def teachable - return unless notifiable.present? - return if notifiable_type.in?(['Lecture', 'Course']) - return notifiable.lecture if notifiable_type == 'Announcement' + return if notifiable.blank? + return if notifiable_type.in?(["Lecture", "Course"]) + return notifiable.lecture if notifiable_type == "Announcement" # notifiable will be a medium, so return its teachable notifiable.teachable @@ -33,49 +33,47 @@ def teachable # news path for general announcements # all other cases: notifiable path def path(user) - return unless notifiable.present? - return edit_profile_path if notifiable_type.in?(['Course', 'Lecture']) + return if notifiable.blank? + return edit_profile_path if notifiable_type.in?(["Course", "Lecture"]) - if notifiable_type == 'Announcement' + if notifiable_type == "Announcement" return notifiable.lecture.path(user) if notifiable.lecture.present? return news_path end - if notifiable_type == 'Medium' && notifiable.sort == 'Quiz' - return medium_path(notifiable) - end + return medium_path(notifiable) if notifiable_type == "Medium" && notifiable.sort == "Quiz" polymorphic_url(notifiable, only_path: true) end def self.allowed_notifiable_types - ['Medium', 'Course', 'Lecture', 'Announcement'] + ["Medium", "Course", "Lecture", "Announcement"] end # the next methods are for the determination which kind of notification it is def medium? - return unless notifiable.present? + return false if notifiable.blank? - notifiable_type == 'Medium' + notifiable_type == "Medium" end def course? - return unless notifiable.present? + return false if notifiable.blank? - notifiable.class.to_s == 'Course' + notifiable.instance_of?(::Course) end def lecture? - return unless notifiable.present? + return false if notifiable.blank? - notifiable.class.to_s == 'Lecture' + notifiable.instance_of?(::Lecture) end def announcement? - return unless notifiable.present? + return false if notifiable.blank? - notifiable.class.to_s == 'Announcement' + notifiable.instance_of?(::Announcement) end def generic_announcement? diff --git a/app/models/notion.rb b/app/models/notion.rb index de98a93d9..15d339ef9 100644 --- a/app/models/notion.rb +++ b/app/models/notion.rb @@ -1,13 +1,13 @@ class Notion < ApplicationRecord belongs_to :tag, optional: true, touch: true - belongs_to :aliased_tag, class_name: 'Tag', optional: true, touch: true + belongs_to :aliased_tag, class_name: "Tag", optional: true, touch: true - validates :title, uniqueness: { scope: :locale } + validates :title, uniqueness: { scope: :locale } # rubocop:todo Rails/UniqueValidationWithoutIndex validates :title, presence: true validate :presence_of_tag, if: :persisted? - after_save :touch_tag_relations before_destroy :touch_tag_relations + after_save :touch_tag_relations def presence_of_tag return if tag || aliased_tag diff --git a/app/models/probe.rb b/app/models/probe.rb index 59b892754..7de065442 100644 --- a/app/models/probe.rb +++ b/app/models/probe.rb @@ -2,7 +2,7 @@ class Probe < InteractionsRecord scope :created_between, lambda { |start_date, end_date| where(created_at: start_date.beginning_of_day..end_date.end_of_day) } - require 'csv' + require "csv" def self.finished_quizzes(quiz) Probe.where(quiz_id: quiz.id, progress: -1).count @@ -46,8 +46,8 @@ def self.local_success_in_quiz(quiz) end def self.to_csv - attributes = %w{id session_id created_at quiz_id question_id remark_id - correct progress success study_participant input} + attributes = ["id", "session_id", "created_at", "quiz_id", "question_id", "remark_id", + "correct", "progress", "success", "study_participant", "input"] CSV.generate(headers: true) do |csv| csv << attributes diff --git a/app/models/question.rb b/app/models/question.rb index 8d3eb7535..521c20ac6 100644 --- a/app/models/question.rb +++ b/app/models/question.rb @@ -26,11 +26,11 @@ def quiz_ids end def proper_quiz_ids - Quiz.where(id: quiz_ids, sort: 'Quiz').pluck(:id) + Quiz.where(id: quiz_ids, sort: "Quiz").pluck(:id) end def duplicate - copy = self.dup + copy = dup copy.video_data = nil copy.manuscript_data = nil copy.screenshot_data = nil @@ -38,7 +38,7 @@ def duplicate copy.parent_id = id copy.save copy.update(description: copy.description + - I18n.t('admin.question.copy_marker') + + I18n.t("admin.question.copy_marker") + copy.id.to_s) answer_map = {} answers.each { |a| answer_map[a.id] = a.duplicate(copy).id } @@ -47,19 +47,19 @@ def duplicate def self.create_prefilled(label, teachable, editors) solution = Solution.new(MampfExpression.trivial_instance) - question = Question.new(sort: 'Question', + question = Question.new(sort: "Question", description: label, teachable: teachable, editors: editors, - text: I18n.t('admin.question.initial_text'), + text: I18n.t("admin.question.initial_text"), level: 1, independent: false, - question_sort: 'mc', + question_sort: "mc", solution: solution) return question if question.invalid? Answer.create(question: question, - text: '0', + text: "0", value: true) question end @@ -74,18 +74,18 @@ def delete_vertices vertices = quiz.quiz_graph.find_vertices(self) vertices.each do |v| quiz.update(quiz_graph: quiz.quiz_graph.destroy_vertex(v), - released: 'locked') + released: "locked") end end true end def multiple_choice? - question_sort == 'mc' + question_sort == "mc" end def free_answer? - question_sort == 'free' + question_sort == "free" end def parametrized? @@ -98,10 +98,10 @@ def parsed_text_with_params end def text_with_sample_params(parameters) - return text unless parameters.present? + return text if parameters.blank? result = text - parameters.keys.each do |p| + parameters.each_key do |p| result.gsub!(/\\para{#{Regexp.escape(p)},(.*?)}/, parameters[p].to_s) end result @@ -112,13 +112,14 @@ def parameters end def sample_parameters - parameters.inject({}) { |h, (k, v)| h[k] = v.to_a.sample; h } + parameters.transform_values do |v| + v.to_a.sample + end end def self.parameters_from_text(text) text.scan(/\\para{(\w+),(.*?)}/) - .map { |v| [v[0], v[1].to_a_or_range] } - .to_h + .to_h { |v| [v[0], v[1].to_a_or_range] } end private @@ -126,7 +127,7 @@ def self.parameters_from_text(text) def prelim_answer_table table = [] size = answer_ids.count - (0..2**size - 1).each do |i| + (0..(2**size) - 1).each do |i| hash = {} i.to_bool_a(size).each_with_index.map { |x, j| hash[answer_ids[j]] = x } table.push(hash) diff --git a/app/models/question_sampler.rb b/app/models/question_sampler.rb index 2a7dacf96..79b589401 100644 --- a/app/models/question_sampler.rb +++ b/app/models/question_sampler.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # QuestionSampler class # This is a service PORO model that is used in the generation of quizzes class QuestionSampler diff --git a/app/models/quiz.rb b/app/models/quiz.rb index bd7871991..4d03afe8c 100644 --- a/app/models/quiz.rb +++ b/app/models/quiz.rb @@ -5,7 +5,7 @@ def self.new_prefilled default_table: {}, hide_solution: [])) end - def self.create_prefilled(label) + def self.create_prefilled(_label) Quiz.create(level: 1, quiz_graph: QuizGraph.new(vertices: {}, edges: {}, root: 0, default_table: {}, hide_solution: [])) @@ -18,13 +18,13 @@ def label def publish_vertices!(user, release_state) return unless vertices - vertices.keys.each do |v| + vertices.each_key do |v| quizzable = quizzable(v) next if quizzable.published? - next if !quizzable.teachable.published? + next unless quizzable.teachable.published? next unless user.in?(quizzable.editors_with_inheritance) || user.admin - quizzable.update(released: release_state, released_at: Time.now) + quizzable.update(released: release_state, released_at: Time.zone.now) end end @@ -33,7 +33,7 @@ def quizzables end def quizzables_free? - quizzables.where(released: 'all').count == quizzables.count + quizzables.where(released: "all").count == quizzables.count end def quizzables_visible_for_user?(user) @@ -41,7 +41,7 @@ def quizzables_visible_for_user?(user) end def next_vertex(progress, input = {}) - return default_table[progress] if vertices[progress][:type] == 'Remark' + return default_table[progress] if vertices[progress][:type] == "Remark" target_vertex(progress, input) end @@ -78,31 +78,29 @@ def default_table def question_ids return [] unless quiz_graph && quiz_graph.vertices.present? - quiz_graph.vertices.select { |_k, v| v[:type] == 'Question' } - .values.map { |v| v[:id] }.uniq + quiz_graph.vertices.select { |_k, v| v[:type] == "Question" } + .values.pluck(:id).uniq end def remark_ids return [] unless quiz_graph && quiz_graph.vertices.present? - quiz_graph.vertices.select { |_k, v| v[:type] == 'Remark' }.values - .map { |v| v[:id] }.uniq + quiz_graph.vertices.select { |_k, v| v[:type] == "Remark" }.values + .pluck(:id).uniq end def crosses_to_input(vertex_id, crosses) vertex = vertices[vertex_id] input = {} - if vertex[:type] == 'Question' + if vertex[:type] == "Question" question = Question.find(vertex[:id]) crosses = crosses.map(&:to_i) - input = question.answers.map { |a| [a.id, crosses.include?(a.id)] }.to_h + input = question.answers.to_h { |a| [a.id, crosses.include?(a.id)] } end input end - def quizzable(vertex_id) - quiz_graph.quizzable(vertex_id) - end + delegate :quizzable, to: :quiz_graph def preselected_branch(vertex_id, crosses, fallback) successor = next_vertex(vertex_id, crosses) @@ -116,7 +114,7 @@ def preselected_hide_solution(vertex_id, crosses) end def questions - ids = quiz_graph&.vertices&.values&.select { |v| v[:type] == 'Question' } + ids = quiz_graph&.vertices&.values&.select { |v| v[:type] == "Question" } &.map { |v| v[:id] } Question.where(id: ids) end @@ -137,6 +135,6 @@ def find_errors def target_vertex(progress, input) edges.select { |e, t| e[0] == progress && t.include?(input) }&.keys - &.first&.second || default_table[progress] + &.first&.second || default_table[progress] end end diff --git a/app/models/quiz_certificate.rb b/app/models/quiz_certificate.rb index d08069209..e73fd7b6f 100644 --- a/app/models/quiz_certificate.rb +++ b/app/models/quiz_certificate.rb @@ -1,5 +1,5 @@ class QuizCertificate < ApplicationRecord - belongs_to :quiz, class_name: 'Medium', foreign_key: 'quiz_id' + belongs_to :quiz, class_name: "Medium", inverse_of: :quiz_certificate belongs_to :user, optional: true before_create :set_code diff --git a/app/models/quiz_graph.rb b/app/models/quiz_graph.rb index 4688aa013..cb2b61033 100644 --- a/app/models/quiz_graph.rb +++ b/app/models/quiz_graph.rb @@ -17,7 +17,7 @@ def self.dump(quiz_graph) end def update_vertex(vertex_id, branching, hide) - return if @vertices[vertex_id][:type] == 'Remark' + return if @vertices[vertex_id][:type] == "Remark" remove_edges_from!(vertex_id) update_edges_for_question!(vertex_id, branching) @@ -49,9 +49,7 @@ def update_default_target!(source, target) end def delete_edge!(source, target) - if @default_table[source] == target - @default_table[source] = 0 - end + @default_table[source] = 0 if @default_table[source] == target answers = @edges[[source, target]] return unless answers @@ -70,11 +68,11 @@ def delete_edge!(source, target) # by COPIES. def replace_reference!(old_quizzable, new_quizzable, answer_map = {}) - return self unless old_quizzable.class == new_quizzable.class + return self unless old_quizzable.instance_of?(new_quizzable.class) affected_vertices = referencing_vertices(old_quizzable) affected_vertices.each { |v| @vertices[v][:id] = new_quizzable.id } - return self unless new_quizzable.class.to_s == 'Question' + return self unless new_quizzable.instance_of?(::Question) affected_vertices.each do |v| bend_edges_rereferencing!(edges_from(v), answer_map) @@ -90,28 +88,28 @@ def reset_vertex_answers_change(id) end def find_errors - return [I18n.t('admin.quiz.no_vertices')] unless @vertices.present? + return [I18n.t("admin.quiz.no_vertices")] if @vertices.blank? - branch_undef = @default_table.values.include?(0) - no_end = default_table.values.exclude?(-1) && @edges.select { |e| + branch_undef = @default_table.value?(0) + no_end = default_table.values.exclude?(-1) && @edges.select do |e| e[1] == -1 - }.blank? + end.blank? no_root = @root.blank? || @root.zero? messages = [] - messages.push(I18n.t('admin.quiz.undefined_targets')) if branch_undef - messages.push(I18n.t('admin.quiz.no_end')) if no_end - messages.push(I18n.t('admin.quiz.no_start')) if no_root + messages.push(I18n.t("admin.quiz.undefined_targets")) if branch_undef + messages.push(I18n.t("admin.quiz.no_end")) if no_end + messages.push(I18n.t("admin.quiz.no_start")) if no_root messages end def warnings - return I18n.t('admin.quiz.unreleased_vertices') if unreleased_vertices? + I18n.t("admin.quiz.unreleased_vertices") if unreleased_vertices? end def quizzable(id) return unless id.in?(@vertices.keys) - @vertices[id][:type].constantize.find_by_id(@vertices[id][:id]) + @vertices[id][:type].constantize.find_by(id: @vertices[id][:id]) end def visible?(id) @@ -160,7 +158,7 @@ def remove_edges_from!(vertex_id) def update_edges_for_question!(vertex_id, branching) new_hash = Hash.new { |h, k| h[k] = [] } - new_edges = branching.each_with_object(new_hash) { |(k, v), h| h[v] << k } + branching.each_with_object(new_hash) { |(k, v), h| h[v] << k } default_edge = [vertex_id, @default_table[vertex_id]] @edges.merge!(new_hash.except(default_edge)) end @@ -196,11 +194,11 @@ def edges_to(id) end def incoming(id) - edges_to(id).map { |e| e[0] } + edges_to(id).pluck(0) end def neighbours(id) - edges_from_plus_default(id).map { |e| e[1] } + edges_from_plus_default(id).pluck(1) end def referencing_vertices(quizzable) @@ -211,21 +209,21 @@ def referencing_vertices(quizzable) end def new_vertex_id - return 1 unless @vertices.present? + return 1 if @vertices.blank? @vertices.keys.max + 1 end def edge_color_for_cytoscape(edge) - @default_table[edge[0]] == edge[1] ? '#32cd32' : '#f00' + @default_table[edge[0]] == edge[1] ? "#32cd32" : "#f00" end def border_color_for_cytoscape(id) quizzable = quizzable(id) - return 'orange' unless quizzable.visible? - return 'chocolate' if quizzable.restricted? + return "orange" unless quizzable.visible? + return "chocolate" if quizzable.restricted? - '#222' + "#222" end def linearize! @@ -248,8 +246,8 @@ def self.build_from_questions(question_ids) question_ids.each_with_index do |q, i| j = i + 1 k = j < size ? j + 1 : -1 - question = Question.find_by_id(q) - vertices[j] = { type: 'Question', id: q } + Question.find_by(id: q) + vertices[j] = { type: "Question", id: q } default_table[j] = k end QuizGraph.new(vertices: vertices, edges: edges, root: 1, @@ -258,32 +256,32 @@ def self.build_from_questions(question_ids) def to_cytoscape result = [] - result.push(data: { id: '-2', - label: I18n.t('admin.quiz.start'), - color: '#000', - background: 'yellowgreen', - borderwidth: '0', - bordercolor: 'grey', - shape: 'diamond' }) + result.push(data: { id: "-2", + label: I18n.t("admin.quiz.start"), + color: "#000", + background: "yellowgreen", + borderwidth: "0", + bordercolor: "grey", + shape: "diamond" }) # add vertices - @vertices.keys.each do |v| + @vertices.each_key do |v| result.push(data: cytoscape_vertex(v)) end - result.push(data: { id: '-1', - label: I18n.t('admin.quiz.end'), - color: '#000', - background: 'yellowgreen', - borderwidth: '0', - bordercolor: '#f4a460', - shape: 'diamond' }) + result.push(data: { id: "-1", + label: I18n.t("admin.quiz.end"), + color: "#000", + background: "yellowgreen", + borderwidth: "0", + bordercolor: "#f4a460", + shape: "diamond" }) # add edges if @root.in?(@vertices.keys) result.push(data: { id: "-2-#{@root}", source: -2, target: @root, - color: '#aaa' }) + color: "#aaa" }) end - @vertices.keys.each do |v| + @vertices.each_key do |v| edges_from_plus_default(v).each do |e| result.push(data: cytoscape_edge(e)) end @@ -299,11 +297,11 @@ def linear? def cytoscape_vertex(id) { id: id.to_s, label: quizzable(id).description, - color: '#000', - background: @vertices[id][:type] == 'Question' ? '#e1f5fe' : '#f9fbe7', - borderwidth: '2', + color: "#000", + background: @vertices[id][:type] == "Question" ? "#e1f5fe" : "#f9fbe7", + borderwidth: "2", bordercolor: border_color_for_cytoscape(id), - shape: @vertices[id][:type] == 'Question' ? 'ellipse' : 'rectangle', + shape: @vertices[id][:type] == "Question" ? "ellipse" : "rectangle", defaulttarget: @default_table[id] } end @@ -317,7 +315,7 @@ def cytoscape_edge(edge) end def questions_count - @vertices.values.select { |v| v[:type] == 'Question' }.count + @vertices.values.count { |v| v[:type] == "Question" } end def default?(edge) diff --git a/app/models/quiz_round.rb b/app/models/quiz_round.rb index 3cfdd9c81..eb53a1d0e 100644 --- a/app/models/quiz_round.rb +++ b/app/models/quiz_round.rb @@ -20,8 +20,8 @@ def initialize(params) progress_counter(params) @vertex = @quiz.vertices[@progress] @vertex_old = @vertex - question_details(params) if @vertex.present? && @vertex[:type] == 'Question' - remark_details(params) if @vertex.present? && @vertex[:type] == 'Remark' + question_details(params) if @vertex.present? && @vertex[:type] == "Question" + remark_details(params) if @vertex.present? && @vertex[:type] == "Remark" @answer_scheme ||= {} @answer_shuffle ||= [] @answer_shuffle_old = [] @@ -35,47 +35,47 @@ def update create_question_probe if @is_question create_remark_probe if @is_remark && @study_participant @progress = @quiz.next_vertex(@progress, @input) - create_certificate_final_probe if @progress == -1 && @quiz.sort == 'Quiz' + create_certificate_final_probe if @progress == -1 && @quiz.sort == "Quiz" @counter += 1 @hide_solution = @quiz.quiz_graph.hide_solution .include?([@progress_old, @input]) @vertex = @quiz.vertices[@progress] @answer_shuffle_old = @answer_shuffle - update_answer_shuffle if @vertex && @vertex[:type] == 'Question' + update_answer_shuffle if @vertex && @vertex[:type] == "Question" self end def round_id - 'round' + @progress.to_s + '-' + @counter.to_s + "round#{@progress}-#{@counter}" end def background - return 'bg-grey-lighten-4' if @hide_solution - return 'bg-correct' if @correct + return "bg-grey-lighten-4" if @hide_solution + return "bg-correct" if @correct - 'bg-incorrect' + "bg-incorrect" end def badge - 'badge bg-' + (@correct ? 'success' : 'danger') + "badge bg-#{@correct ? "success" : "danger"}" end def statement - return I18n.t('admin.quiz.correct_result') if @correct + return I18n.t("admin.quiz.correct_result") if @correct - I18n.t('admin.quiz.incorrect_result') + I18n.t("admin.quiz.incorrect_result") end def answers return [] unless @answer_shuffle - @answer_shuffle.map { |a| Answer.find_by_id(a) } + @answer_shuffle.map { |a| Answer.find_by(id: a) } end def answers_old return [] unless @answer_shuffle_old - @answer_shuffle_old.map { |a| Answer.find_by_id(a) } + @answer_shuffle_old.map { |a| Answer.find_by(id: a) } end private @@ -88,7 +88,7 @@ def progress_counter(params) end @progress ||= @quiz.root @counter ||= 0 - @session_id ||= SecureRandom.uuid.first(13).remove('-') + @session_id ||= SecureRandom.uuid.first(13).remove("-") @progress_old = @progress @counter_old = @counter @round_id_old = round_id @@ -98,27 +98,27 @@ def question_details(params) @is_question = true @question_id = @vertex[:id] @answer_scheme = Question.find(@question_id).answer_scheme - if params[:quiz].present? && params[:quiz][:answer_shuffle].present? - @answer_shuffle = JSON.parse(params[:quiz][:answer_shuffle]).map(&:to_i) + @answer_shuffle = if params[:quiz].present? && params[:quiz][:answer_shuffle].present? + JSON.parse(params[:quiz][:answer_shuffle]).map(&:to_i) else - @answer_shuffle = Question.find(@question_id).answers.map(&:id).shuffle + Question.find(@question_id).answers.map(&:id).shuffle end end - def remark_details(params) + def remark_details(_params) @is_remark = true @remark_id = @vertex[:id] end def update_answer_shuffle - @answer_shuffle = Question.find_by_id(@vertex[:id])&.answers&.map(&:id) - &.shuffle + @answer_shuffle = Question.find_by(id: @vertex[:id])&.answers&.map(&:id) + &.shuffle end def create_question_probe return unless @save_probe - quiz_id = @quiz.id unless @quiz.sort == 'RandomQuiz' + quiz_id = @quiz.id unless @quiz.sort == "RandomQuiz" input = @solution_input || @input.to_s if @study_participant ProbeSaver.perform_async(quiz_id, @question_id, nil, @correct, @progress, @session_id, @study_participant, input) @@ -127,7 +127,7 @@ def create_question_probe def create_remark_probe return unless @save_probe - quiz_id = @quiz.id unless @quiz.sort == 'RandomQuiz' + quiz_id = @quiz.id unless @quiz.sort == "RandomQuiz" ProbeSaver.perform_async(quiz_id, nil, @remark_id, nil, @progress, @session_id, @study_participant, @input_text) end diff --git a/app/models/reader.rb b/app/models/reader.rb index 0ba906da7..5fd10cab3 100644 --- a/app/models/reader.rb +++ b/app/models/reader.rb @@ -3,5 +3,5 @@ # making it possible to display whether there are new comments class Reader < ApplicationRecord belongs_to :user - belongs_to :thread, class_name: 'Commontator::Thread' + belongs_to :thread, class_name: "Commontator::Thread" end diff --git a/app/models/referral.rb b/app/models/referral.rb index ecf3e6f32..769d0a116 100644 --- a/app/models/referral.rb +++ b/app/models/referral.rb @@ -28,32 +28,30 @@ def explain # provide time span for vtt file def vtt_time_span - start_time.vtt_string + ' --> ' + end_time.vtt_string + "\n" + "#{start_time.vtt_string} --> #{end_time.vtt_string}\n" end # provide metadata for vtt file def vtt_properties - link = item.link.present? ? item.link : item.medium_link + link = (item.link.presence || item.medium_link) # at the moment, relations between items can be only of the form # script <-> video, which means that between them there will be at most # one script, one manuscript and one video - if item.medium&.sort == 'Script' + if item.medium&.sort == "Script" script = item.manuscript_link if item.related_items_visible? video = item.related_items&.first&.video_link manuscript = item.related_items&.first&.manuscript_link end else - if item.related_items_visible? - script = item.related_items&.first&.manuscript_link - end + script = item.related_items&.first&.manuscript_link if item.related_items_visible? manuscript = item.manuscript_link video = item.video_link end - { 'video' => video, 'manuscript' => manuscript, - 'script' => script, 'link' => link, 'quiz' => item.quiz_link, - 'reference' => item.vtt_meta_reference(medium), - 'text' => item.vtt_text, 'explanation' => vtt_explanation }.compact + { "video" => video, "manuscript" => manuscript, + "script" => script, "link" => link, "quiz" => item.quiz_link, + "reference" => item.vtt_meta_reference(medium), + "text" => item.vtt_text, "explanation" => vtt_explanation }.compact end # returns whether this referral's item has been referred to @@ -65,18 +63,18 @@ def reappears # initial description in the referral form def prefilled_description - item.present? ? item.description : '' + item.present? ? item.description : "" end # initial link in the referral form def prefilled_link - item.present? ? item.link : '' + item.present? ? item.link : "" end # returns true iff the referral's item's medium has an associated video, but # the item is not a pdf destination def video? - !!item&.video? && item.sort != 'pdf_destination' + !!item&.video? && item.sort != "pdf_destination" end def manuscript? @@ -110,7 +108,8 @@ def item_in_quarantine? # if the item is a link, otherwise nil def vtt_explanation return explanation if explanation.present? - return item.explanation if item.sort == 'link' && item.explanation.present? + + item.explanation if item.sort == "link" && item.explanation.present? end # some method that check for valid start and end time diff --git a/app/models/relation.rb b/app/models/relation.rb index a3775e48e..91f387882 100644 --- a/app/models/relation.rb +++ b/app/models/relation.rb @@ -2,14 +2,14 @@ # Join table for tag<->tag many-to-many-relation class Relation < ApplicationRecord belongs_to :tag - belongs_to :related_tag, class_name: 'Tag' + belongs_to :related_tag, class_name: "Tag" validates :related_tag, uniqueness: { scope: :tag } + before_destroy :touch_tag + after_destroy :destroy_inverses, if: :inverse? after_save :create_inverse, unless: :inverse? after_save :destroy, if: :self_inverse? after_save :touch_tag - before_destroy :touch_tag - after_destroy :destroy_inverses, if: :inverse? private diff --git a/app/models/remark.rb b/app/models/remark.rb index 2e9309d46..5f741477a 100644 --- a/app/models/remark.rb +++ b/app/models/remark.rb @@ -14,17 +14,17 @@ def quiz_ids end def self.create_prefilled(label, teachable, editors) - remark = Remark.new(sort: 'Remark', + remark = Remark.new(sort: "Remark", description: label, teachable: teachable, editors: editors, - text: I18n.t('admin.remark.initial_text')) + text: I18n.t("admin.remark.initial_text")) remark.save remark end def duplicate - copy = self.dup + copy = dup copy.video_data = nil copy.manuscript_data = nil copy.screenshot_data = nil @@ -32,7 +32,7 @@ def duplicate copy.parent_id = id copy.save copy.update(description: copy.description + - I18n.t('admin.remark.copy_marker') + copy.id.to_s) + I18n.t("admin.remark.copy_marker") + copy.id.to_s) copy end @@ -44,10 +44,10 @@ def delete_vertices quiz_ids.each do |q| quiz = Quiz.find(q) vertices = quiz.vertices - .select { |_k, v| v == { type: 'Remark', id: id } }.keys + .select { |_k, v| v == { type: "Remark", id: id } }.keys vertices.each do |v| quiz.update(quiz_graph: quiz.quiz_graph.destroy_vertex(v), - released: 'locked') + released: "locked") end end end diff --git a/app/models/section.rb b/app/models/section.rb index e6287dcfe..9c54309b4 100644 --- a/app/models/section.rb +++ b/app/models/section.rb @@ -23,6 +23,9 @@ class Section < ApplicationRecord # a section has many items, do not execute callbacks when section is destroyed has_many :items, dependent: :nullify + before_destroy :touch_toc + before_destroy :touch_lecture + before_destroy :touch_media # after saving or updating, touch lecture/media/self to keep cache up to date after_save :touch_lecture after_save :touch_media @@ -31,23 +34,19 @@ class Section < ApplicationRecord # if absolute numbering is enabled for the lecture, all chapters # and sections need to be touched because of possibly changed references after_save :touch_toc - before_destroy :touch_toc - - before_destroy :touch_lecture - before_destroy :touch_media def lecture chapter&.lecture end def reference_number - return calculated_number unless display_number.present? + return calculated_number if display_number.blank? display_number end def displayed_number - '§' + reference_number + "§#{reference_number}" end def reference @@ -61,15 +60,15 @@ def reference # chapters def calculated_number return relative_position unless lecture.absolute_numbering - return absolute_position.to_s unless lecture.start_section.present? + return absolute_position.to_s if lecture.start_section.blank? (absolute_position + lecture.start_section - 1).to_s end def to_label - return displayed_number + '. ' + title unless hidden_with_inheritance? + return "#{displayed_number}. #{title}" unless hidden_with_inheritance? - '*' + displayed_number + '. ' + title + "*#{displayed_number}. #{title}" end # section's media are media that are contained in one of the @@ -152,12 +151,12 @@ def script_items_by_position end def visible_items_by_time - lessons.order(:date).map { |l| l.visible_items }.flatten + lessons.order(:date).map(&:visible_items).flatten .select { |i| i.section == self } end def visible_items - return visible_items_by_time if lecture.content_mode == 'video' + return visible_items_by_time if lecture.content_mode == "video" script_items_by_position end @@ -167,7 +166,7 @@ def hidden_with_inheritance? end def cache_key - super + '-' + I18n.locale.to_s + "#{super}-#{I18n.locale}" end def duplicate_in_chapter(new_chapter, import_tags) @@ -189,7 +188,7 @@ def touch_lecture end def touch_media - lecture.media_with_inheritance.update_all(updated_at: Time.current) + lecture.media_with_inheritance.touch_all touch end @@ -200,12 +199,12 @@ def touch_self def touch_toc return unless lecture.absolute_numbering - lecture.chapters.update_all(updated_at: Time.now) - lecture.sections.update_all(updated_at: Time.now) + lecture.chapters.touch_all + lecture.sections.touch_all end def relative_position - chapter.displayed_number + '.' + position.to_s + "#{chapter.displayed_number}.#{position}" end def absolute_position diff --git a/app/models/solution.rb b/app/models/solution.rb index 010ca8f2d..5a24a4d0e 100644 --- a/app/models/solution.rb +++ b/app/models/solution.rb @@ -32,20 +32,20 @@ def nerd end def tex - return '' unless @content.tex + return "" unless @content.tex - '$$' + @content.tex + '$$' + "$$#{@content.tex}$$" end def tex_mc_answer - return '' unless @content.tex + return "" unless @content.tex - '$' + @content.tex + '$' + "$#{@content.tex}$" end def self.from_hash(solution_type, content) - return unless solution_type.in?(['MampfExpression', 'MampfMatrix', - 'MampfTuple', 'MampfSet']) + return unless solution_type.in?(["MampfExpression", "MampfMatrix", + "MampfTuple", "MampfSet"]) solution = Solution.new(solution_type.constantize.from_hash(content)) solution.explanation = content[:explanation] diff --git a/app/models/speaker_talk_join.rb b/app/models/speaker_talk_join.rb index 275b0aa0f..2248ae5d4 100644 --- a/app/models/speaker_talk_join.rb +++ b/app/models/speaker_talk_join.rb @@ -1,4 +1,4 @@ class SpeakerTalkJoin < ApplicationRecord belongs_to :talk - belongs_to :speaker, class_name: 'User', foreign_key: 'speaker_id' + belongs_to :speaker, class_name: "User", inverse_of: :speaker_talk_joins end diff --git a/app/models/submission.rb b/app/models/submission.rb index 8b352da41..61c640286 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -23,43 +23,43 @@ def partners_of_user(user) end def team - users.map(&:tutorial_name).natural_sort.join(', ') + users.map(&:tutorial_name).natural_sort.join(", ") end def manuscript_filename - return unless manuscript.present? + return if manuscript.blank? - manuscript.metadata['filename'] + manuscript.metadata["filename"] end def manuscript_size - return unless manuscript.present? + return if manuscript.blank? - manuscript.metadata['size'] + manuscript.metadata["size"] end def manuscript_mime_type - return unless manuscript.present? + return if manuscript.blank? - manuscript.metadata['mime_type'] + manuscript.metadata["mime_type"] end def correction_filename - return unless correction.present? + return if correction.blank? - correction.metadata['filename'] + correction.metadata["filename"] end def correction_mime_type - return unless correction.present? + return if correction.blank? - correction.metadata['mime_type'] + correction.metadata["mime_type"] end def correction_size - return unless correction.present? + return if correction.blank? - correction.metadata['size'] + correction.metadata["size"] end def preceding_tutorial(user) @@ -105,28 +105,29 @@ def not_updatable? # correction.to_io.path # end - def filename_for_bulk_download(end_of_file = '') - (team.first(180) + '-' + - last_modification_by_users_at.strftime("%F-%H%M") + - (too_late? ? '-LATE' : '') + - + '-ID-' + id + end_of_file + - assignment.accepted_file_type) - .gsub(/[\x00\/\\:\*\?\"<>\|]/, '_') - .gsub(/^.*(\\|\/)/, '') + def filename_for_bulk_download(end_of_file = "") + start_str = "#{team.first(180)}-#{last_modification_by_users_at.strftime("%F-%H%M")}" + too_late_str = too_late? ? "-LATE" : "" + id_str = "-ID-#{id}" + end_of_file_str = "#{end_of_file}#{assignment.accepted_file_type}" + + "#{start_str}#{too_late_str}#{id_str}#{end_of_file_str}" + .gsub(%r{[\x00/\\:\*\?\"<>\|]}, "_") + .gsub(%r{^.*(\\|/)}, "") # Strip out the non-ascii characters - .gsub(/[^0-9A-Za-z.\-]/, '_') + .gsub(/[^0-9A-Za-z.\-]/, "_") end - def self.zip(submissions, downloadables, end_of_file = '') + def self.zip(submissions, downloadables, end_of_file = "") begin archived_filestream = Zip::OutputStream.write_buffer do |stream| submissions.zip(downloadables).each do |s, d| stream.put_next_entry(s.filename_for_bulk_download(end_of_file)) - stream.write IO.read(d.to_io.path) + stream.write(File.read(d.to_io.path)) end end archived_filestream.rewind - rescue => e + rescue StandardError => e archived_filestream = e.message end archived_filestream @@ -135,20 +136,20 @@ def self.zip(submissions, downloadables, end_of_file = '') def self.zip_submissions!(tutorial, assignment) submissions = Submission.where(tutorial: tutorial, assignment: assignment).proper - manuscripts = submissions.collect { |s| - s.manuscript if s.manuscript.present? - } + manuscripts = submissions.collect do |s| + s.manuscript.presence + end zip(submissions, manuscripts) end def self.zip_corrections!(tutorial, assignment) submissions = Submission.where(tutorial: tutorial, assignment: assignment).proper - corrections = submissions.collect { |s| - s.correction if s.correction.present? - } + corrections = submissions.collect do |s| + s.correction.presence + end - zip(submissions, corrections, '-correction') + zip(submissions, corrections, "-correction") end ### @@ -156,69 +157,69 @@ def self.zip_corrections!(tutorial, assignment) ### def check_file_properties_any(metadata, sort) errors = [] - if sort == :submission && metadata['size'] > 10 * 1024 * 1024 - errors.push I18n.t('submission.manuscript_size_too_big', - max_size: '10 MB') + if sort == :submission && metadata["size"] > 10 * 1024 * 1024 + errors.push(I18n.t("submission.manuscript_size_too_big", + max_size: "10 MB")) end - if sort == :correction && metadata['size'] > 15 * 1024 * 1024 - errors.push I18n.t('submission.manuscript_size_too_big', - max_size: '15 MB') + if sort == :correction && metadata["size"] > 15 * 1024 * 1024 + errors.push(I18n.t("submission.manuscript_size_too_big", + max_size: "15 MB")) end - file_name = metadata['filename'] + file_name = metadata["filename"] file_type = File.extname(file_name) - if !file_type.in?(['.cc', '.hh', '.m', ".mlx", '.pdf', '.zip', ".txt"]) - errors.push I18n.t('submission.wrong_file_type', + unless file_type.in?([".cc", ".hh", ".m", ".mlx", ".pdf", ".zip", ".txt"]) + errors.push(I18n.t("submission.wrong_file_type", file_type: file_type, - accepted_file_type: assignment.accepted_file_type) + accepted_file_type: assignment.accepted_file_type)) end - return {} unless errors.present? + return {} if errors.blank? { sort => errors } end def check_file_properties(metadata, sort) errors = [] - if sort == :submission && metadata['size'] > 10 * 1024 * 1024 - errors.push I18n.t('submission.manuscript_size_too_big', - max_size: '10 MB') + if sort == :submission && metadata["size"] > 10 * 1024 * 1024 + errors.push(I18n.t("submission.manuscript_size_too_big", + max_size: "10 MB")) end - if sort == :correction && metadata['size'] > 15 * 1024 * 1024 - errors.push I18n.t('submission.manuscript_size_too_big', - max_size: '15 MB') + if sort == :correction && metadata["size"] > 15 * 1024 * 1024 + errors.push(I18n.t("submission.manuscript_size_too_big", + max_size: "15 MB")) end - file_name = metadata['filename'] + file_name = metadata["filename"] file_type = File.extname(file_name) if file_type != assignment.accepted_file_type && - assignment.accepted_file_type != '.tar.gz' - errors.push I18n.t('submission.wrong_file_type', + assignment.accepted_file_type != ".tar.gz" + errors.push(I18n.t("submission.wrong_file_type", file_type: file_type, - accepted_file_type: assignment.accepted_file_type) + accepted_file_type: assignment.accepted_file_type)) end - if assignment.accepted_file_type == '.tar.gz' - if file_type == '.gz' - reduced_type = File.extname(File.basename(file_name, '.gz')) - if reduced_type != '.tar' - errors.push I18n.t('submission.wrong_file_type', - file_type: '.gz', - accepted_file_type: '.tar.gz') + if assignment.accepted_file_type == ".tar.gz" + if file_type == ".gz" + reduced_type = File.extname(File.basename(file_name, ".gz")) + if reduced_type != ".tar" + errors.push(I18n.t("submission.wrong_file_type", + file_type: ".gz", + accepted_file_type: ".tar.gz")) end else - errors.push I18n.t('submission.wrong_file_type', + errors.push(I18n.t("submission.wrong_file_type", file_type: file_type, - accepted_file_type: '.tar.gz') + accepted_file_type: ".tar.gz")) end end - if (!assignment.accepted_file_type.in?(['.cc', '.hh', '.m']) && - !metadata['mime_type'].in?(assignment.accepted_mime_types)) || - (assignment.accepted_file_type.in?(['.cc', '.hh', '.m']) && - (!metadata['mime_type'].starts_with?('text/') && - metadata['mime_type'] != 'application/octet-stream')) - errors.push I18n.t('submission.wrong_mime_type', - mime_type: metadata['mime_type'], + if (!assignment.accepted_file_type.in?([".cc", ".hh", ".m"]) && + !metadata["mime_type"].in?(assignment.accepted_mime_types)) || + (assignment.accepted_file_type.in?([".cc", ".hh", ".m"]) && + (!metadata["mime_type"].starts_with?("text/") && + metadata["mime_type"] != "application/octet-stream")) + errors.push(I18n.t("submission.wrong_mime_type", + mime_type: metadata["mime_type"], accepted_mime_types: assignment.accepted_mime_types - .join(', ')) + .join(", "))) end - return {} unless errors.present? + return {} if errors.blank? { sort => errors } end @@ -229,18 +230,18 @@ def self.bulk_corrections!(tutorial, assignment, files) report = { successful_saves: [], submissions: submissions.size, invalid_filenames: [], invalid_id: [], in_subfolder: [], no_decision: [], rejected: [], invalid_file: [] } - tmp_folder = Dir.mktmpdir + Dir.mktmpdir begin files.each do |file_shrine| filename = file_shrine["metadata"]["filename"] - if !'-ID-'.in?(filename) + unless "-ID-".in?(filename) report[:invalid_filenames].push(filename) next end - submission_id = File.basename(filename.split('-ID-').last, - File.extname(filename.split('-ID-').last)) - submission = Submission.find_by_id(submission_id) - if !submission + submission_id = File.basename(filename.split("-ID-").last, + File.extname(filename.split("-ID-").last)) + submission = Submission.find_by(id: submission_id) + unless submission report[:invalid_id].push(filename) next end @@ -253,59 +254,59 @@ def self.bulk_corrections!(tutorial, assignment, files) next end submission.update(correction: file_shrine.to_json) - if !submission.valid? + unless submission.valid? report[:invalid_file].push(filename) next end report[:successful_saves].push(submission) end - rescue => e - report[:errors] = "#{e.message}" + rescue StandardError => e + report[:errors] = e.message.to_s end report end - private + def self.number_of_submissions(tutorial, assignment) + Submission.where(tutorial: tutorial, assignment: assignment) + .where.not(manuscript_data: nil).size + end - def matching_lecture - return true if tutorial&.lecture == assignment&.lecture + def self.number_of_corrections(tutorial, assignment) + Submission.where(tutorial: tutorial, assignment: assignment) + .where.not(correction_data: nil).size + end - errors.add(:tutorial, :lecture_not_matching) - end + def self.number_of_late_submissions(tutorial, assignment) + Submission.where(tutorial: tutorial, assignment: assignment) + .where.not(manuscript_data: nil) + .count(&:too_late?) + end - def set_token - self.token = Submission.generate_token - end + def self.submissions_total(assignment) + Submission.where(assignment: assignment) + .where.not(manuscript_data: nil).size + end - def self.number_of_submissions(tutorial, assignment) - Submission.where(tutorial: tutorial, assignment: assignment) - .where.not(manuscript_data: nil).size - end + def self.corrections_total(assignment) + Submission.where(assignment: assignment) + .where.not(correction_data: nil).size + end - def self.number_of_corrections(tutorial, assignment) - Submission.where(tutorial: tutorial, assignment: assignment) - .where.not(correction_data: nil).size - end + def self.late_submissions_total(assignment) + Submission.where(assignment: assignment) + .where.not(manuscript_data: nil) + .count(&:too_late?) + end - def self.number_of_late_submissions(tutorial, assignment) - Submission.where(tutorial: tutorial, assignment: assignment) - .where.not(manuscript_data: nil) - .select { |s| s.too_late? }.size - end + private - def self.submissions_total(assignment) - Submission.where(assignment: assignment) - .where.not(manuscript_data: nil).size - end + def matching_lecture + return true if tutorial&.lecture == assignment&.lecture - def self.corrections_total(assignment) - Submission.where(assignment: assignment) - .where.not(correction_data: nil).size + errors.add(:tutorial, :lecture_not_matching) end - def self.late_submissions_total(assignment) - Submission.where(assignment: assignment) - .where.not(manuscript_data: nil) - .select { |s| s.too_late? }.size + def set_token + self.token = Submission.generate_token end end diff --git a/app/models/submission_cleaner.rb b/app/models/submission_cleaner.rb index 5a91644cc..05c416e27 100644 --- a/app/models/submission_cleaner.rb +++ b/app/models/submission_cleaner.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # PORO class that handles the cleaning of submissions # in order to fulfill GDPR regulations class SubmissionCleaner diff --git a/app/models/tag.rb b/app/models/tag.rb index 42d1499a0..c92002ec0 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -29,26 +29,30 @@ class Tag < ApplicationRecord has_many :related_tags, through: :relations, after_remove: :destroy_relations # a tag has different notions in different languages - has_many :notions, foreign_key: 'tag_id', - after_remove: :touch_relations, - after_add: :touch_relations, - dependent: :destroy - has_many :aliases, foreign_key: 'aliased_tag_id', class_name: 'Notion' + has_many :notions, + after_remove: :touch_relations, + after_add: :touch_relations, + dependent: :destroy, + inverse_of: :tag + has_many :aliases, + foreign_key: "aliased_tag_id", + class_name: "Notion", + inverse_of: :aliased_tag serialize :realizations, Array accepts_nested_attributes_for :notions, reject_if: lambda { |attributes| - attributes['title'].blank? + attributes["title"].blank? }, allow_destroy: true - validates_presence_of :notions + validates :notions, presence: true validates_associated :notions accepts_nested_attributes_for :aliases, reject_if: lambda { |attributes| - attributes['title'].blank? + attributes["title"].blank? }, allow_destroy: true @@ -79,19 +83,15 @@ def title end def extended_title_uncached - unless other_titles_uncached.any? || aliases.any? - return local_title_uncached - end - unless aliases.any? - return local_title_uncached + " (#{other_titles_uncached.join(', ')})" - end + return local_title_uncached unless other_titles_uncached.any? || aliases.any? + return local_title_uncached + " (#{other_titles_uncached.join(", ")})" unless aliases.any? unless other_titles_uncached.any? - return local_title_uncached + " (#{aliases.pluck(:title).join(', ')})" + return local_title_uncached + " (#{aliases.pluck(:title).join(", ")})" end local_title_uncached + - " (#{aliases.pluck(:title).join(', ')}," + - " #{other_titles_uncached.join(', ')})" + " (#{aliases.pluck(:title).join(", ")}, " \ + "#{other_titles_uncached.join(", ")})" end def extended_title @@ -133,7 +133,7 @@ def extended_title_id_hash end def locale_title_hash - notions.map { |n| [n.locale, n.title] }.to_h + notions.to_h { |n| [n.locale, n.title] } end def self.select_with_substring(search_string) @@ -142,11 +142,11 @@ def self.select_with_substring(search_string) search = Sunspot.new_search(Tag) search.build do - fulltext search_string + fulltext(search_string) end search.execute - result = search.results - .map { |t| { value: t.id, text: t.title } } + search.results + .map { |t| { value: t.id, text: t.title } } end # returns all tags whose title is close to the given search string @@ -169,7 +169,7 @@ def self.select_by_title_cached # returns the array of all tags (sorted by title) together with # their ids def self.select_by_title - Tag.all.map { |t| t.extended_title_id_hash } + Tag.all.map(&:extended_title_id_hash) .natural_sort_by { |t| t[:title] }.map { |t| [t[:title], t[:id]] } end @@ -177,7 +177,7 @@ def self.select_by_title # arel of tags together with def self.select_by_title_except(excluded_tags) Tag.where.not(id: excluded_tags.pluck(:id)) - .map { |t| t.extended_title_id_hash } + .map(&:extended_title_id_hash) .natural_sort_by { |t| t[:title] }.map { |t| [t[:title], t[:id]] } end @@ -211,7 +211,7 @@ def realizations_cached # returns the ARel of all tags or whose id is among a given array of ids # search params is a hash having keys :all_tags, :tag_ids def self.search_tags(search_params) - return Tag.all unless search_params[:all_tags] == '0' + return Tag.all unless search_params[:all_tags] == "0" tag_ids = search_params[:tag_ids] || [] Tag.where(id: tag_ids) @@ -244,7 +244,7 @@ def tags_in_neighbourhood def short_title(max_letters = 30) return title unless title.length > max_letters - title[0, max_letters - 3] + '...' + "#{title[0, max_letters - 3]}..." end def in_lecture?(lecture) @@ -274,10 +274,12 @@ def create_random_quiz!(user) question_ids = questions.pluck(:id).sample(5) quiz_graph = QuizGraph.build_from_questions(question_ids) - quiz = Quiz.new(description: "#{I18n.t('categories.randomquiz.singular')} #{title} #{Time.now}", + + quiz_i18n = I18n.t("categories.randomquiz.singular") + quiz = Quiz.new(description: "#{quiz_i18n} #{title} #{Time.zone.now}", level: 1, quiz_graph: quiz_graph, - sort: 'RandomQuiz') + sort: "RandomQuiz") quiz.save return quiz.errors unless quiz.valid? @@ -287,19 +289,19 @@ def create_random_quiz!(user) # returns the vertex title color of the tag in the neighbourhood graph of # the given marked tag def color(marked_tag, highlight_related_tags: true) - return '#f00' if self == marked_tag - return '#ff8c00' if highlight_related_tags && in?(marked_tag.related_tags) + return "#f00" if self == marked_tag + return "#ff8c00" if highlight_related_tags && in?(marked_tag.related_tags) - '#000' + "#000" end # returns the vertex color of the tag in the neighbourhood graph of # the given marked tag def background(marked_tag, highlight_related_tags: true) - return '#f00' if self == marked_tag - return '#ff8c00' if highlight_related_tags && in?(marked_tag.related_tags) + return "#f00" if self == marked_tag + return "#ff8c00" if highlight_related_tags && in?(marked_tag.related_tags) - '#666' + "#666" end # returns the cytoscape hash describing the tag's vertex in the neighbourhood @@ -323,27 +325,25 @@ def cytoscape_edge(related_tag) # published sections are sections that belong to a published lecture def visible_sections(user) - user.filter_sections(sections).select { |s| + user.filter_sections(sections).select do |s| s.lecture.visible_for_user?(user) - } + end end def cache_key - super + '-' + I18n.locale.to_s + "#{super}-#{I18n.locale}" end def touch_lectures - Lecture.where(id: sections.map(&:lecture) - .map(&:id)).update_all updated_at: Time.now + Lecture.where(id: sections.map { |section| section.lecture.id }).touch_all end def touch_sections - sections.update_all updated_at: Time.now + sections.touch_all end def touch_chapters - Chapter.where(id: sections.map(&:chapter) - .map(&:id)).update_all updated_at: Time.now + Chapter.where(id: sections.map { |section| section.chapter.id }).touch_all end def identify_with!(tag) @@ -354,7 +354,7 @@ def identify_with!(tag) related_tags << (tag.related_tags - related_tags) related_tags.delete(tag) tag.sections.each do |s| - next unless self.in?(s.tags) + next unless in?(s.tags) old_section_tag = SectionTagJoin.find_by(section: s, tag: tag) position = old_section_tag.tag_position @@ -362,7 +362,7 @@ def identify_with!(tag) new_section_tag.insert_at(position) old_section_tag.move_to_bottom end - tag.aliases.update_all(aliased_tag_id: id) + tag.aliases.update(aliased_tag_id: id) end def common_titles(tag) @@ -371,24 +371,24 @@ def common_titles(tag) result[l] = [locale_title_hash[l.to_s]] + [tag.locale_title_hash[l.to_s]] result[l].delete(nil) result[:contradictions].push(l) if result[l].count > 1 - result.delete(l) unless result[l].present? + result.delete(l) if result[l].blank? end result end def visible_questions(user) - user.filter_visible_media(user.filter_media(media.where(type: 'Question'))) + user.filter_visible_media(user.filter_media(media.where(type: "Question"))) end private - def touch_relations(notion) - if persisted? - touch - touch_lectures - touch_sections - touch_chapters - end + def touch_relations(_notion) + return unless persisted? + + touch + touch_lectures + touch_sections + touch_chapters end # simulates the after_destroy callback for relations @@ -398,9 +398,9 @@ def destroy_relations(related_tag) end def title_join - result = notions.pluck(:title).join(' ') + result = notions.pluck(:title).join(" ") return result unless aliases.any? - result + ' ' + aliases.pluck(:title).join(' ') + "#{result} #{aliases.pluck(:title).join(" ")}" end end diff --git a/app/models/talk.rb b/app/models/talk.rb index 5b528e139..2afef84a1 100644 --- a/app/models/talk.rb +++ b/app/models/talk.rb @@ -8,13 +8,14 @@ class Talk < ApplicationRecord # being a teachable (course/lecture/lesson), a talk has associated media has_many :media, -> { order(position: :asc) }, as: :teachable, - dependent: :destroy + dependent: :destroy, + inverse_of: :teachable # a talk has many tags has_many :talk_tag_joins, dependent: :destroy has_many :tags, through: :talk_tag_joins - after_save :remove_duplicate_dates + before_save :remove_duplicate_dates after_save :touch_lecture # the talks of a lecture form an ordered list @@ -31,7 +32,7 @@ def lesson end def to_label - I18n.t('talk', number: position, title: title) + I18n.t("talk", number: position, title: title) end def long_title @@ -52,7 +53,7 @@ def given_by?(user) def title_for_viewers Rails.cache.fetch("#{cache_key_with_version}/title_for_viewers") do - lecture.title_for_viewers + ', ' + to_label + "#{lecture.title_for_viewers}, #{to_label}" end end @@ -76,7 +77,7 @@ def media_scope end def compact_title - lecture.compact_title + '.V' + position.to_s + "#{lecture.compact_title}.V#{position}" end def number @@ -92,7 +93,7 @@ def next end def proper_media - media.where.not(sort: ['Question', 'Remark']) + media.where.not(sort: ["Question", "Remark"]) end def editors_with_inheritance @@ -111,6 +112,6 @@ def talk_path end def remove_duplicate_dates - update_columns(dates: dates.uniq) + dates.uniq! # TODO: replace dates array by a set to avoid this end end diff --git a/app/models/teachable_parser.rb b/app/models/teachable_parser.rb index 85793f256..2a3af0d1d 100644 --- a/app/models/teachable_parser.rb +++ b/app/models/teachable_parser.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # Teachable parser class # This is a service PORO model that is used in the media search class TeachableParser @@ -8,8 +6,8 @@ class TeachableParser # or 'Course-' followed by the id def initialize(params) @teachable_ids = params[:teachable_ids] || [] - @all_teachables = params[:all_teachables] == '1' - @inheritance = params[:teachable_inheritance] == '1' + @all_teachables = params[:all_teachables] == "1" + @inheritance = params[:teachable_inheritance] == "1" end # returns all courses, lectures and lessons that are associated @@ -30,8 +28,8 @@ def teachables_as_strings private def lecture_ids - @teachable_ids.select { |t| t.start_with?('Lecture') } - .map { |t| t.remove('Lecture-') }.map(&:to_i) + @teachable_ids.select { |t| t.start_with?("Lecture") } + .map { |t| t.remove("Lecture-") }.map(&:to_i) end def lectures @@ -39,8 +37,8 @@ def lectures end def course_ids - @teachable_ids.select { |t| t.start_with?('Course') } - .map { |t| t.remove('Course-') }.map(&:to_i) + @teachable_ids.select { |t| t.start_with?("Course") } + .map { |t| t.remove("Course-") }.map(&:to_i) end def courses diff --git a/app/models/term.rb b/app/models/term.rb index 36009cad4..28999bcea 100644 --- a/app/models/term.rb +++ b/app/models/term.rb @@ -4,8 +4,8 @@ class Term < ApplicationRecord has_many :lectures # season can only be SS/WS, and there can be only one of this type each year - validates :season, presence: true, - inclusion: { in: %w[SS WS] }, + validates :season, presence: true, # rubocop:todo Rails/UniqueValidationWithoutIndex + inclusion: { in: ["SS", "WS"] }, uniqueness: { scope: :year } # a year >=2000 needs to be present validates :year, presence: true, @@ -13,7 +13,7 @@ class Term < ApplicationRecord greater_than_or_equal_to: 2000 } # only one term can be active - validates_uniqueness_of :active, if: :active + validates :active, uniqueness: { if: :active } # some information about lectures, lessons and media are cached # to find out whether the cache is out of date, always touch'em after saving @@ -31,23 +31,23 @@ def active end def begin_date - season == 'SS' ? Date.new(year, 4, 1) : Date.new(year, 10, 1) + season == "SS" ? Date.new(year, 4, 1) : Date.new(year, 10, 1) end def end_date - season == 'SS' ? Date.new(year, 9, 30) : Date.new(year + 1, 3, 31) + season == "SS" ? Date.new(year, 9, 30) : Date.new(year + 1, 3, 31) end # label contains season and year(s) with all digits def to_label - return unless season.present? + return if season.blank? - season + ' ' + year_corrected + "#{season} #{year_corrected}" end # short label contains season and year(s) with two digits def to_label_short - season + ' ' + year_corrected_short + "#{season} #{year_corrected_short}" end def compact_title @@ -55,8 +55,8 @@ def compact_title end def previous - previous_year = season == 'WS' ? year : year - 1 - previous_season = season == 'WS' ? 'SS' : 'WS' + previous_year = season == "WS" ? year : year - 1 + previous_season = season == "WS" ? "SS" : "WS" Term.find_by(year: previous_year, season: previous_season) end @@ -102,48 +102,47 @@ def self.possible_deletion_dates end def self.possible_deletion_dates_localized - possible_deletion_dates.map { |d| - d.strftime(I18n.t('date.formats.concise')) - } + possible_deletion_dates.map do |d| + d.strftime(I18n.t("date.formats.concise")) + end end # array of all terms together with their ids for use in options_for_select - def self.select_terms(independent = false) - return ['bla', nil] if independent + def self.select_terms(independent: false) + return ["bla", nil] if independent Term.all.sort_by(&:begin_date).reverse.map { |t| [t.to_label, t.id] } end def self.previous_by_date(date) - season = date.month.in?((4..9)) ? 'SS' : 'WS' + season = date.month.in?((4..9)) ? "SS" : "WS" year = date.year - previous_year = season == 'WS' ? year : year - 1 - previous_season = season == 'WS' ? 'SS' : 'WS' + previous_year = season == "WS" ? year : year - 1 + previous_season = season == "WS" ? "SS" : "WS" Term.find_by(year: previous_year, season: previous_season) end private def year_corrected - return year.to_s unless season == 'WS' + return year.to_s unless season == "WS" - year.to_s + '/' + ((year % 100) + 1).to_s + "#{year}/#{(year % 100) + 1}" end def year_corrected_short - return (year % 100).to_s unless season == 'WS' + return (year % 100).to_s unless season == "WS" - (year % 100).to_s + '/' + ((year % 100) + 1).to_s + "#{year % 100}/#{(year % 100) + 1}" end def touch_lectures_and_lessons - lectures.update_all(updated_at: Time.now) - Lesson.where(lecture: lectures).update_all(updated_at: Time.now) + lectures.touch_all + Lesson.where(lecture: lectures).touch_all end def touch_media - Medium.where(teachable: lectures).update_all(updated_at: Time.now) - Medium.where(teachable: Lesson.where(lecture: lectures)) - .update_all(updated_at: Time.now) + Medium.where(teachable: lectures).touch_all + Medium.where(teachable: Lesson.where(lecture: lectures)).touch_all end end diff --git a/app/models/time_stamp.rb b/app/models/time_stamp.rb index 1924e36bb..8df7d5a06 100644 --- a/app/models/time_stamp.rb +++ b/app/models/time_stamp.rb @@ -8,9 +8,11 @@ class TimeStamp # extract from YAML def self.load(text) + return if text.blank? + YAML.safe_load(text, permitted_classes: [TimeStamp, ActiveModel::Errors], - aliases: true) if text.present? + aliases: true) end # store as YAML (for serialization) @@ -46,37 +48,45 @@ def initialize(params) # t.vtt_string # => "03:15:20.729" def vtt_string - format('%02d:%02d:%02d.%03d', @hours, @minutes, @seconds, @milliseconds) + # rubocop:disable Style/FormatStringToken + format("%02d:%02d:%02d.%03d", @hours, @minutes, @seconds, @milliseconds) + # rubocop:enable Style/FormatStringToken end # t.simple_vtt_string # => "3:15:20.729" def simple_vtt_string - format('%01d:%02d:%02d.%03d', @hours, @minutes, @seconds, @milliseconds) + # rubocop:disable Style/FormatStringToken + format("%01d:%02d:%02d.%03d", @hours, @minutes, @seconds, @milliseconds) + # rubocop:enable Style/FormatStringToken end # t.hms_string # => "3h15m20s" def hms_string - format('%01dh%02dm%02ds', @hours, @minutes, @seconds) + # rubocop:disable Style/FormatStringToken + format("%01dh%02dm%02ds", @hours, @minutes, @seconds) + # rubocop:enable Style/FormatStringToken end # t.hms_colon_string # => "3:15:20" def hms_colon_string - format('%01d:%02d:%02d', @hours, @minutes, @seconds) + # rubocop:disable Style/FormatStringToken + format("%01d:%02d:%02d", @hours, @minutes, @seconds) + # rubocop:enable Style/FormatStringToken end # t.floor_seconds # => 11720 def floor_seconds - @hours * 3600 + @minutes * 60 + @seconds + (@hours * 3600) + (@minutes * 60) + @seconds end # t.total_seconds # => 11720.729 def total_seconds - floor_seconds + @milliseconds / 1000.0 + floor_seconds + (@milliseconds / 1000.0) end private diff --git a/app/models/tutor_tutorial_join.rb b/app/models/tutor_tutorial_join.rb index 0e57281e6..416d289c8 100644 --- a/app/models/tutor_tutorial_join.rb +++ b/app/models/tutor_tutorial_join.rb @@ -1,4 +1,4 @@ class TutorTutorialJoin < ApplicationRecord belongs_to :tutorial - belongs_to :tutor, class_name: 'User', foreign_key: 'tutor_id' + belongs_to :tutor, class_name: "User", inverse_of: :tutor_tutorial_joins end diff --git a/app/models/tutorial.rb b/app/models/tutorial.rb index 60611c8a7..824f390cf 100644 --- a/app/models/tutorial.rb +++ b/app/models/tutorial.rb @@ -1,6 +1,6 @@ # Tutorial model class Tutorial < ApplicationRecord - require 'csv' + require "csv" belongs_to :lecture, touch: true @@ -11,10 +11,12 @@ class Tutorial < ApplicationRecord before_destroy :check_destructibility, prepend: true + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :title, uniqueness: { scope: [:lecture_id] }, presence: true + # rubocop:enable Rails/UniqueValidationWithoutIndex def title_with_tutors - return "#{title}, #{I18n.t('basics.tba')}" unless tutors.any? + return "#{title}, #{I18n.t("basics.tba")}" unless tutors.any? "#{title}, #{tutor_names}" end @@ -22,7 +24,7 @@ def title_with_tutors def tutor_names return unless tutors.any? - tutors.map(&:tutorial_name).join(', ') + tutors.map(&:tutorial_name).join(", ") end def destructible? diff --git a/app/models/user.rb b/app/models/user.rb index ad0b3f59c..7745f2260 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -17,39 +17,56 @@ class User < ApplicationRecord source: :lecture # a user has many courses as an editor - has_many :editable_user_joins, foreign_key: :user_id, dependent: :destroy + has_many :editable_user_joins, dependent: :destroy has_many :edited_courses, through: :editable_user_joins, - source: :editable, source_type: 'Course' + source: :editable, source_type: "Course" # a user has many lectures as an editor has_many :edited_lectures, through: :editable_user_joins, - source: :editable, source_type: 'Lecture' + source: :editable, source_type: "Lecture" # a user has many media as an editor has_many :edited_media, through: :editable_user_joins, - source: :editable, source_type: 'Medium' + source: :editable, source_type: "Medium" # a user has many lectures as a teacher - has_many :given_lectures, class_name: 'Lecture', foreign_key: 'teacher_id' + has_many :given_lectures, + class_name: "Lecture", + foreign_key: "teacher_id", + inverse_of: :teacher # a user has many tutorials as a tutor - has_many :tutor_tutorial_joins, foreign_key: 'tutor_id', dependent: :destroy + has_many :tutor_tutorial_joins, + foreign_key: "tutor_id", + dependent: :destroy, + inverse_of: :tutor has_many :given_tutorials, -> { order(:title) }, through: :tutor_tutorial_joins, source: :tutorial # a user has many given talks - has_many :speaker_talk_joins, foreign_key: 'speaker_id', dependent: :destroy + has_many :speaker_talk_joins, + foreign_key: "speaker_id", + dependent: :destroy, + inverse_of: :speaker has_many :talks, through: :speaker_talk_joins # a user has many notifications as recipient - has_many :notifications, foreign_key: 'recipient_id' + has_many :notifications, + foreign_key: "recipient_id", + inverse_of: :recipient # a user has many announcements as announcer - has_many :announcements, foreign_key: 'announcer_id', dependent: :destroy + has_many :announcements, + foreign_key: "announcer_id", + dependent: :destroy, + inverse_of: :announcer # a user has many clickers as editor - has_many :clickers, foreign_key: 'editor_id', dependent: :destroy + has_many :clickers, + foreign_key: "editor_id", + dependent: :destroy, + inverse_of: :editor # a user has many submissions (of assignments) has_many :user_submission_joins, dependent: :destroy @@ -75,10 +92,9 @@ class User < ApplicationRecord # set some default values before saving if they are not set before_save :set_defaults - before_destroy :destroy_single_submissions, prepend: true - # add timestamp for DSGVO consent after_create :set_consented_at + before_destroy :destroy_single_submissions, prepend: true # users can comment stuff acts_as_commontator @@ -104,7 +120,7 @@ class User < ApplicationRecord # returns the array of all teachers def self.teachers - User.where(id: Lecture.pluck(:teacher_id).uniq) + User.where(id: Lecture.distinct.select(:teacher_id)) end def self.select_teachers @@ -113,13 +129,13 @@ def self.select_teachers # returns the array of all editors def self.editors - User.where(id: EditableUserJoin.pluck(:user_id).uniq) + User.where(id: EditableUserJoin.distinct.select(:user_id)) end # returns the array of all editors minus those that are only editors of talks def self.proper_editors - talk_media_ids = Medium.where(teachable_type: 'Talk').pluck(:id) - talk_media_joins = EditableUserJoin.where(editable_type: 'Medium', + talk_media_ids = Medium.where(teachable_type: "Talk").pluck(:id) + talk_media_joins = EditableUserJoin.where(editable_type: "Medium", editable_id: talk_media_ids) User.where(id: EditableUserJoin.where.not(id: talk_media_joins.pluck(:id)) .pluck(:user_id).uniq) @@ -136,7 +152,7 @@ def self.only_editors_selection # given array of ids # search params is a hash having keys :all_editors, :editor_ids def self.search_editors(search_params) - return User.editors unless search_params[:all_editors] == '0' + return User.editors unless search_params[:all_editors] == "0" editor_ids = search_params[:editor_ids] || [] User.where(id: editor_ids) @@ -165,18 +181,18 @@ def self.preferred_name_or_email_like(search_string) return User.none unless search_string return User.none unless search_string.length >= 2 - where(name_in_tutorials: [nil, '']).name_or_email_like(search_string) + where(name_in_tutorials: [nil, ""]).name_or_email_like(search_string) .or(where.not(name_in_tutorials: [nil, - '']) + ""]) .name_in_tutorials_or_email_like(search_string)) end def self.values_for_select pluck(:id, :name, :name_in_tutorials, :email) - .map { |u| + .map do |u| { value: u.first, text: "#{u.third.presence || u.second} (#{u.fourth})" } - } + end end def courses @@ -192,10 +208,8 @@ def related_courses(overrule_subscription_type: false) return if subscription_type.nil? selection_type = overrule_subscription_type || subscription_type - if selection_type == 1 - return Course.where(id: preceding_course_ids).includes(:lectures) - end - return Course.all.includes(:lectures) if selection_type == 2 + return Course.where(id: preceding_course_ids).includes(:lectures) if selection_type == 1 + return Course.includes(:lectures) if selection_type == 2 courses end @@ -220,10 +234,10 @@ def related_lectures # returns ARel of all those tags from the given tags that belong to # the user's related lectures def filter_tags(tags) - Tag.where(id: tags.select { |t| + Tag.where(id: tags.select do |t| t.in_lectures?(related_lectures) || t.in_courses?(related_courses) - } + end .map(&:id)) end @@ -278,7 +292,7 @@ def active_notifications(lecture) end def active_media_notifications(lecture) - notifications.where(notifiable_type: 'Medium') + notifications.where(notifiable_type: "Medium") .where(notifiable_id: lecture.media_with_inheritance .pluck(:id)) end @@ -286,7 +300,7 @@ def active_media_notifications(lecture) # returns the array of those notifications that are related to MaMpf news # (i.e. announcements without a lecture) def active_news - notifications.where(notifiable_type: 'Announcement') + notifications.where(notifiable_type: "Announcement") .select { |n| n.notifiable.lecture.nil? } end @@ -323,24 +337,23 @@ def active_teachable_editor? return false unless can_edit_teachables? return true if admin || course_editor? || teacher? - edited_lectures.select { |l| l.term.nil? || !l.stale? } - .any? + edited_lectures.any? { |l| l.term.nil? || !l.stale? } end # a user is an editor iff he/she is a teachable editor or an # editor of media that are not associated to talks def editor? teachable_editor? || - edited_media.where.not(teachable_type: 'Talk').any? + edited_media.where.not(teachable_type: "Talk").any? end # the next methods return information about the user extracted from # email and name def info_uncached - return email unless name.present? + return email if name.blank? - (name_in_tutorials.presence || name) + ' (' + email + ')' + "#{name_in_tutorials.presence || name} (#{email})" end def info @@ -350,9 +363,9 @@ def info end def tutorial_info_uncached - return email unless tutorial_name.present? + return email if tutorial_name.blank? - tutorial_name + ' (' + email + ')' + "#{tutorial_name} (#{email})" end def tutorial_info @@ -362,7 +375,7 @@ def tutorial_info end def name_or_email - return name unless name.blank? + return name if name.present? email end @@ -372,7 +385,7 @@ def tutorial_name end def short_info - return email unless name.present? + return email if name.blank? name end @@ -497,7 +510,7 @@ def filter_visible_media(media) Course.where(id: Course.pluck(:id) - courses.pluck(:id)) nonsubscribed_lectures = Lecture.where(id: Lecture.pluck(:id) - lectures.pluck(:id), - released: ['all']) + released: ["all"]) lessons = Lesson.where(lecture: lectures) nonsubscribed_lessons = Lesson.where(lecture: nonsubscribed_lectures) edited_lessons = Lesson.where(lecture: teaching_related_lectures) @@ -506,21 +519,21 @@ def filter_visible_media(media) edited_talks = Talk.where(lecture: teaching_related_lectures) return media if admin - media.where(teachable: courses, released: ['all', 'subscribers', 'users']) + media.where(teachable: courses, released: ["all", "subscribers", "users"]) .or(media.where(teachable: nonsubscribed_courses, - released: ['all', 'users'])) + released: ["all", "users"])) .or(media.where(teachable: lectures, - released: ['all', 'subscribers', 'users'])) + released: ["all", "subscribers", "users"])) .or(media.where(teachable: nonsubscribed_lectures, - released: ['all', 'users'])) + released: ["all", "users"])) .or(media.where(teachable: lessons, - released: ['all', 'subscribers', 'users'])) + released: ["all", "subscribers", "users"])) .or(media.where(teachable: nonsubscribed_lessons, - released: ['all', 'users'])) + released: ["all", "users"])) .or(media.where(teachable: talks, - released: ['all', 'subscribers', 'users'])) + released: ["all", "subscribers", "users"])) .or(media.where(teachable: nonsubscribed_talks, - released: ['all', 'users'])) + released: ["all", "users"])) .or(media.where(teachable: edited_courses)) .or(media.where(teachable: teaching_related_lectures)) .or(media.where(teachable: edited_lessons)) @@ -529,23 +542,22 @@ def filter_visible_media(media) def subscribed_commentable_media_with_comments lessons = Lesson.where(lecture: lectures) - filter_media(Medium.where.not(sort: ['RandomQuiz', 'Question', 'Erdbeere', - 'Remark']) + filter_media(Medium.where.not(sort: ["RandomQuiz", "Question", "Erdbeere", + "Remark"]) .where(teachable: courses + lectures + lessons)) .includes(commontator_thread: :comments) .select { |m| m.commontator_thread.comments.any? } end def media_latest_comments - subscribed_commentable_media_with_comments - .map { |m| + media = subscribed_commentable_media_with_comments + .map do |m| { medium: m, thread: m.commontator_thread, latest_comment: m.commontator_thread - .comments.sort_by(&:created_at) - .last } - } - .sort_by { |x| x[:latest_comment].created_at }.reverse + .comments.max_by(&:created_at) } + end + media.sort_by { |x| x[:latest_comment].created_at }.reverse end # lecture that are in the active term @@ -572,7 +584,7 @@ def subscribe_lecture!(lecture) lectures << lecture # make sure subscribed_users is updated in media - Sunspot.index! lecture.media + Sunspot.index!(lecture.media) true end @@ -585,7 +597,7 @@ def unsubscribe_lecture!(lecture) favorite_lectures.delete(lecture) # make sure subscribed_users is updated in media - Sunspot.index! lecture.media + Sunspot.index!(lecture.media) true end @@ -640,16 +652,12 @@ def tutorials(lecture) given_tutorials.where(lecture: lecture) end - def has_tutorials?(lecture) - !given_tutorials.where(lecture: lecture).empty? - end - def proper_submissions_count submissions.proper.size end def proper_single_submissions_count - submissions.proper.select { |s| s.users.size == 1 }.size + submissions.proper.count { |s| s.users.size == 1 } end def proper_team_submissions_count @@ -692,26 +700,26 @@ def normalized_image_url_with_host def image_filename return unless image - image.metadata['filename'] + image.metadata["filename"] end def image_size return unless image - image.metadata['size'] + image.metadata["size"] end def image_resolution return unless image - "#{image.metadata['width']}x#{image.metadata['height']}" + "#{image.metadata["width"]}x#{image.metadata["height"]}" end def can_edit?(something) unless something.is_a?(Lecture) || something.is_a?(Course) || something.is_a?(Medium) || something.is_a?(Lesson) || something.is_a?(Talk) - raise 'can_edit? was called with incompatible class' + raise("can_edit? was called with incompatible class") end return true if admin @@ -723,9 +731,9 @@ def speaker? end def layout - return 'administration' if admin_or_editor? + return "administration" if admin_or_editor? - 'application_no_sidebar' + "application_no_sidebar" end def course_editor? @@ -746,9 +754,9 @@ def can_update_personell?(lecture) return false unless can_edit?(lecture) return true if can_edit?(lecture.course) || lecture.teacher == self return true if lecture.course.term_independent - return true if !lecture.stale? + return true unless lecture.stale? - return false + false end # see https://github.com/heartcombo/devise/issues/4849#issuecomment-534733131 @@ -771,19 +779,19 @@ def current_sign_in_ip=(_ip) def set_defaults self.subscription_type ||= 1 self.admin ||= false - self.name ||= email.split('@').first + self.name ||= email.split("@").first self.locale ||= I18n.default_locale.to_s end # sets time for DSGVO consent to current time def set_consented_at - update(consented_at: Time.now) + update(consented_at: Time.zone.now) end # returns array of ids of all courses that preced the subscribed courses def preceding_course_ids courses.all.map { |l| l.preceding_courses.pluck(:id) }.flatten + - courses.all.pluck(:id) + courses.pluck(:id) end def destroy_single_submissions @@ -792,16 +800,16 @@ def destroy_single_submissions end def archive_email - splitting = DefaultSetting::PROJECT_EMAIL.split('@') + splitting = DefaultSetting::PROJECT_EMAIL.split("@") "#{splitting.first}-archive-#{id}@#{splitting.second}" end def transfer_contributions_to(user) - return false unless user && user.valid? && user != self + return false unless user&.valid? && user != self - given_lectures.update_all(teacher_id: user.id) - EditableUserJoin.where(user: self, editable_type: 'Medium') - .update_all(user_id: user.id) + given_lectures.update(teacher_id: user.id) + EditableUserJoin.where(user: self, editable_type: "Medium") + .update(user_id: user.id) end def archive_user(archive_name) @@ -809,8 +817,8 @@ def archive_user(archive_name) email: archive_email, password: SecureRandom.base58(12), consents: true, - consented_at: Time.now, - confirmed_at: Time.now, + consented_at: Time.zone.now, + confirmed_at: Time.zone.now, archived: true) end end diff --git a/app/models/user_cleaner.rb b/app/models/user_cleaner.rb index 9c40eecba..877760420 100644 --- a/app/models/user_cleaner.rb +++ b/app/models/user_cleaner.rb @@ -3,9 +3,9 @@ class UserCleaner attr_accessor :imap, :email_dict, :hash_dict def login - @imap = Net::IMAP.new(ENV['IMAPSERVER'], port: 993, ssl: true) - @imap.authenticate('LOGIN', ENV['PROJECT_EMAIL_USERNAME'], - ENV['PROJECT_EMAIL_PASSWORD']) + @imap = Net::IMAP.new(ENV.fetch("IMAPSERVER", nil), port: 993, ssl: true) + @imap.authenticate("LOGIN", ENV.fetch("PROJECT_EMAIL_USERNAME", nil), + ENV.fetch("PROJECT_EMAIL_PASSWORD", nil)) end def logout @@ -15,40 +15,44 @@ def logout def search_emails_and_hashes @email_dict = {} @hash_dict = {} - @imap.examine(ENV['PROJECT_EMAIL_MAILBOX']) + @imap.examine(ENV.fetch("PROJECT_EMAIL_MAILBOX", nil)) # Mails containing multiple email addresses (Subject: "Undelivered Mail Returned to Sender") - @imap.search(['SUBJECT', - 'Undelivered Mail Returned to Sender']).each do |message_id| + @imap.search(["SUBJECT", + "Undelivered Mail Returned to Sender"]).each do |message_id| body = @imap.fetch(message_id, "BODY[TEXT]")[0].attr["BODY[TEXT]"].squeeze(" ") - if match = body.scan(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})[\s\S]*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})[\s\S]*?User has moved to ERROR: Account expired/) - match = match.flatten.uniq - match.each do |email| - add_mail(email, message_id) + # rubocop:disable Layout/LineLength + next unless (match = body.scan(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})[\s\S]*?([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})[\s\S]*?User has moved to ERROR: Account expired/)) + # rubocop:enable Layout/LineLength - try_get_hash(body, email) - end + match = match.flatten.uniq + match.each do |email| + add_mail(email, message_id) + + try_get_hash(body, email) end end # Mails containing single email addresses (Subject: "Delivery Status Notification (Failure)") # define array containing all used regex patterns patterns = [ '([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})>[\s\S]*?Unknown recipient', + # rubocop:disable Layout/LineLength '([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})>[\s\S]*?User unknown in virtual mailbox table' + # rubocop:enable Layout/LineLength ] - @imap.search(['SUBJECT', - 'Delivery Status Notification (Failure)']).each do |message_id| + @imap.search(["SUBJECT", + "Delivery Status Notification (Failure)"]).each do |message_id| body = @imap.fetch(message_id, "BODY[TEXT]")[0].attr["BODY[TEXT]"].squeeze(" ") patterns.each do |pattern| - if match = body.scan(/#{pattern}/) - match = match.flatten.uniq - match.each do |email| - add_mail(email, message_id) + next unless (match = body.scan(/#{pattern}/)) - try_get_hash(body, email) - end + match = match.flatten.uniq + match.each do |email| + add_mail(email, message_id) + + try_get_hash(body, email) end end end @@ -68,8 +72,8 @@ def try_get_hash(body, email) begin hash = body.match(/Hash:([a-zA-Z0-9]*)/).captures @hash_dict[email] = hash - rescue - return + rescue StandardError + nil end end @@ -87,7 +91,7 @@ def send_hashes def delete_ghosts @hash_dict.each do |mail, hash| u = User.find_by(email: mail, ghost_hash: hash) - move_mail(@email_dict[mail]) if u.present? and @email_dict.present? + move_mail(@email_dict[mail]) if u.present? && @email_dict.present? u.destroy! if u&.generic? end end @@ -96,15 +100,13 @@ def move_mail(message_ids, attempt = 0) return if message_ids.blank? message_ids = Array(message_ids) - if attempt > 3 - return - end + return if attempt > 3 begin - @imap.examine(ENV['PROJECT_EMAIL_MAILBOX']) + @imap.examine(ENV.fetch("PROJECT_EMAIL_MAILBOX", nil)) @imap.move(message_ids, "Other Users/mampf/handled_bounces") rescue Net::IMAP::BadResponseError - move_mail(message_ids, attempt = attempt + 1) + move_mail(message_ids, attempt + 1) end end diff --git a/app/models/user_submission_join.rb b/app/models/user_submission_join.rb index 9218519c6..92e0f6c61 100644 --- a/app/models/user_submission_join.rb +++ b/app/models/user_submission_join.rb @@ -5,9 +5,7 @@ class UserSubmissionJoin < ApplicationRecord validate :only_one_per_assignment, on: :create validate :max_team_size, on: :create - def assignment - submission.assignment - end + delegate :assignment, to: :submission private diff --git a/app/models/watchlist.rb b/app/models/watchlist.rb index 1602b95be..d64b358b1 100644 --- a/app/models/watchlist.rb +++ b/app/models/watchlist.rb @@ -3,9 +3,11 @@ class Watchlist < ApplicationRecord has_many :watchlist_entries, dependent: :destroy has_many :media, through: :watchlist_entries + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :name, presence: true, uniqueness: { scope: :user_id } + # rubocop:enable Rails/UniqueValidationWithoutIndex - def owned_by?(otherUser) - user == otherUser + def owned_by?(other_user) + user == other_user end end diff --git a/app/models/watchlist_entry.rb b/app/models/watchlist_entry.rb index 104c7f755..47bebdf87 100644 --- a/app/models/watchlist_entry.rb +++ b/app/models/watchlist_entry.rb @@ -4,7 +4,7 @@ class WatchlistEntry < ApplicationRecord belongs_to :medium acts_as_list scope: :watchlist, top_of_list: 0, column: :medium_position - validates :medium, presence: true - validates :watchlist, presence: true + # rubocop:todo Rails/UniqueValidationWithoutIndex validates :medium_id, uniqueness: { scope: :watchlist_id } + # rubocop:enable Rails/UniqueValidationWithoutIndex end diff --git a/app/models/xkcd.rb b/app/models/xkcd.rb index 55752c45b..6ede1962b 100644 --- a/app/models/xkcd.rb +++ b/app/models/xkcd.rb @@ -1,7 +1,7 @@ # Xkcd model for getting Xkcd images class Xkcd def self.random - max = JSON.parse(URI.open('https://xkcd.com/info.0.json').read)['num'] + max = JSON.parse(URI.open("https://xkcd.com/info.0.json").read)["num"] comic_num = 1 + rand(max - 1) comic_num = 1 if comic_num == 404 # Avoid 404th comic ;) JSON.parse(URI.open("https://xkcd.com/#{comic_num}/info.0.json").read) diff --git a/app/uploaders/correction_uploader.rb b/app/uploaders/correction_uploader.rb index 6653ad459..332ad1216 100644 --- a/app/uploaders/correction_uploader.rb +++ b/app/uploaders/correction_uploader.rb @@ -1,4 +1,4 @@ -require 'image_processing/mini_magick' +require "image_processing/mini_magick" # UserPdfUploader Class class CorrectionUploader < Shrine @@ -11,6 +11,6 @@ class CorrectionUploader < Shrine Attacher.validate do # Reject empty file uploads # at least 1 byte - validate_min_size 1, message: I18n.t('submission.upload_failure_empty_file') + validate_min_size 1, message: I18n.t("submission.upload_failure_empty_file") end end diff --git a/app/uploaders/geogebra_uploader.rb b/app/uploaders/geogebra_uploader.rb index 32fe00ff5..27bf968f4 100644 --- a/app/uploaders/geogebra_uploader.rb +++ b/app/uploaders/geogebra_uploader.rb @@ -1,4 +1,4 @@ -require 'zip' +require "zip" # GeogebraUploader class # used for storing geogebra files @@ -9,17 +9,17 @@ class GeogebraUploader < Shrine plugin :derivatives Attacher.validate do - validate_mime_type_inclusion %w[application/zip], - message: 'falscher MIME-Typ' + validate_mime_type_inclusion ["application/zip"], + message: "falscher MIME-Typ" end # extract a screenshot from the ggb file and store it beside the ggb file Attacher.derivatives_processor do |original| - unzipped = '' + unzipped = "" Zip::File.open(original) do |zip_file| - destination = Dir.mktmpdir('geogebra') - zipped = zip_file.find { |f| f.name == 'geogebra_thumbnail.png' } - unzipped = File.join(destination, 'geogebra_thumbnail.png') + destination = Dir.mktmpdir("geogebra") + zipped = zip_file.find { |f| f.name == "geogebra_thumbnail.png" } + unzipped = File.join(destination, "geogebra_thumbnail.png") zip_file.extract(zipped, unzipped) end { screenshot: File.open(unzipped) } diff --git a/app/uploaders/pdf_uploader.rb b/app/uploaders/pdf_uploader.rb index bacdb9bcc..a79b7d9ff 100644 --- a/app/uploaders/pdf_uploader.rb +++ b/app/uploaders/pdf_uploader.rb @@ -1,4 +1,4 @@ -require 'image_processing/mini_magick' +require "image_processing/mini_magick" # PdfUploader Class class PdfUploader < Shrine @@ -20,7 +20,7 @@ class PdfUploader < Shrine temp_file = Tempfile.new temp_folder = Dir.mktmpdir structure_path = "#{temp_folder}/structure.mampf" - cmd = "pdftk #{file.path} dump_data_utf8 output #{temp_file.path} && "\ + cmd = "pdftk #{file.path} dump_data_utf8 output #{temp_file.path} && " \ "pdftk #{file.path} unpack_files output #{temp_folder}" exit_status = system(cmd) if exit_status @@ -30,11 +30,11 @@ class PdfUploader < Shrine # extract lines that correspond to MaMpf-Label entries from LaTEX # package mampf.sty structure = if File.file?(structure_path) - open(structure_path, "r") do |io| + File.open(structure_path, "r") do |io| io.read.encode("UTF-8", invalid: :replace) end end - structure ||= '' + structure ||= "" bookmarks = structure.scan(/MaMpf-Label\|(.*?)\n/).flatten result = [] bookmarks.each_with_index do |b, i| @@ -42,40 +42,40 @@ class PdfUploader < Shrine # line may look like this: # defn:erster-Tag|Definition|1.1|Erster Tag|1 data = /(.*?)\|(.*?)\|(.*?)\|(.*?)\|(.*)\|(.*)\|(.*)\|(.*)/.match(b) - details = { 'destination' => data[1], 'sort' => data[2], - 'label' => data[3], 'description' => data[4], - 'chapter' => data[5], 'section' => data[6], - 'subsection' => data[7], 'page' => data[8], - 'counter' => i } - details['sort'] = 'Markierung' if details['sort'].blank? + details = { "destination" => data[1], "sort" => data[2], + "label" => data[3], "description" => data[4], + "chapter" => data[5], "section" => data[6], + "subsection" => data[7], "page" => data[8], + "counter" => i } + details["sort"] = "Markierung" if details["sort"].blank? result.push(details) end linked_media = structure.scan(/MaMpf-Link\|(.*?)\n/) .flatten.map(&:to_i) - [0] mampf_sty_version = structure.scan(/MaMpf-Version\|(.*?)\n/).flatten .first - { 'pages' => pages, - 'destinations' => result.map { |b| b['destination'] }, - 'bookmarks' => result, - 'linked_media' => linked_media, - 'version' => mampf_sty_version } + { "pages" => pages, + "destinations" => result.pluck("destination"), + "bookmarks" => result, + "linked_media" => linked_media, + "version" => mampf_sty_version } else - { 'pages' => nil, 'destinations' => nil, 'bookmarks' => nil, - 'version' => nil } + { "pages" => nil, "destinations" => nil, "bookmarks" => nil, + "version" => nil } end end end end Attacher.validate do - validate_mime_type_inclusion %w[application/pdf], - message: 'falscher MIME-Typ' + validate_mime_type_inclusion ["application/pdf"], + message: "falscher MIME-Typ" end # extract a screenshot from pdf and store it beside the pdf Attacher.derivatives_processor do |original| screenshot = ImageProcessing::MiniMagick.source(original).loader(page: 0) - .convert('png') + .convert("png") .resize_to_limit!(400, 565) { screenshot: screenshot } end diff --git a/app/uploaders/profileimage_uploader.rb b/app/uploaders/profileimage_uploader.rb index 18aac9e70..aa5f86a64 100644 --- a/app/uploaders/profileimage_uploader.rb +++ b/app/uploaders/profileimage_uploader.rb @@ -1,4 +1,4 @@ -require 'image_processing/mini_magick' +require "image_processing/mini_magick" # ProfileimageUploader class # used for storing profile images class ProfileimageUploader < Shrine @@ -11,8 +11,8 @@ class ProfileimageUploader < Shrine plugin :derivatives Attacher.validate do - validate_mime_type_inclusion %w[image/jpeg image/png image/gif], - message: 'falscher MIME-Typ' + validate_mime_type_inclusion ["image/jpeg", "image/png", "image/gif"], + message: "falscher MIME-Typ" end # store a resized version of the screenshot diff --git a/app/uploaders/screenshot_uploader.rb b/app/uploaders/screenshot_uploader.rb index 9db2a7a98..6ef5cbef7 100644 --- a/app/uploaders/screenshot_uploader.rb +++ b/app/uploaders/screenshot_uploader.rb @@ -1,4 +1,4 @@ -require 'image_processing/mini_magick' +require "image_processing/mini_magick" # ScreenshotUploader class # used for storing video thumbnails class ScreenshotUploader < Shrine @@ -11,8 +11,8 @@ class ScreenshotUploader < Shrine plugin :derivatives Attacher.validate do - validate_mime_type_inclusion %w[image/jpeg image/png image/gif], - message: 'falscher MIME-Typ' + validate_mime_type_inclusion ["image/jpeg", "image/png", "image/gif"], + message: "falscher MIME-Typ" end # store a resized version of the screenshot diff --git a/app/uploaders/submission_uploader.rb b/app/uploaders/submission_uploader.rb index ad029ab3d..afeb16e87 100644 --- a/app/uploaders/submission_uploader.rb +++ b/app/uploaders/submission_uploader.rb @@ -1,4 +1,4 @@ -require 'image_processing/mini_magick' +require "image_processing/mini_magick" # SubmissionUploader Class class SubmissionUploader < Shrine @@ -12,6 +12,6 @@ class SubmissionUploader < Shrine Attacher.validate do # Reject empty file uploads # at least 1 byte - validate_min_size 1, message: I18n.t('submission.upload_failure_empty_file') + validate_min_size 1, message: I18n.t("submission.upload_failure_empty_file") end end diff --git a/app/uploaders/video_uploader.rb b/app/uploaders/video_uploader.rb index 4e42b3134..bcfe7aff2 100644 --- a/app/uploaders/video_uploader.rb +++ b/app/uploaders/video_uploader.rb @@ -1,4 +1,4 @@ -require 'streamio-ffmpeg' +require "streamio-ffmpeg" # VideoUploader class class VideoUploader < Shrine @@ -12,18 +12,17 @@ class VideoUploader < Shrine # add metadata to uploaded video: duration, bitrate, resolution, framerate add_metadata do |io, **options| - pp options[:action] if options[:action] != :upload movie = Shrine.with_file(io) { |file| FFMPEG::Movie.new(file.path) } - { 'duration' => movie.duration, - 'bitrate' => movie.bitrate, - 'resolution' => movie.resolution, - 'frame_rate' => movie.frame_rate } + { "duration" => movie.duration, + "bitrate" => movie.bitrate, + "resolution" => movie.resolution, + "frame_rate" => movie.frame_rate } end end Attacher.validate do - validate_mime_type_inclusion %w[video/mp4], message: 'wrong type' + validate_mime_type_inclusion ["video/mp4"], message: "wrong type" end end diff --git a/app/uploaders/zip_uploader.rb b/app/uploaders/zip_uploader.rb index 353280167..9e361ddc2 100644 --- a/app/uploaders/zip_uploader.rb +++ b/app/uploaders/zip_uploader.rb @@ -7,11 +7,11 @@ class ZipUploader < Shrine plugin :default_storage, cache: :submission_cache, store: :submission_store Attacher.validate do - validate_mime_type_inclusion %w[application/zip], + validate_mime_type_inclusion ["application/zip"], message: - I18n.t('package.no_zip') + I18n.t("package.no_zip") # maximum size of 1 GB validate_max_size 1024 * 1024 * 1024, - message: I18n.t('package.too_big') + message: I18n.t("package.too_big") end end diff --git a/app/validators/http_url_validator.rb b/app/validators/http_url_validator.rb index 7dcf6ac83..9c5ed15eb 100644 --- a/app/validators/http_url_validator.rb +++ b/app/validators/http_url_validator.rb @@ -7,8 +7,8 @@ def self.compliant?(value) end def validate_each(record, attribute, value) - unless value.present? && self.class.compliant?(value) - record.errors.add(attribute, I18n.t('activerecord.errors.no_valid_url')) - end + return if value.present? && self.class.compliant?(value) + + record.errors.add(attribute, I18n.t("activerecord.errors.no_valid_url")) end end diff --git a/app/views/clickers/open.coffee b/app/views/clickers/open.coffee index eed12a7ae..5de1b12f2 100644 --- a/app/views/clickers/open.coffee +++ b/app/views/clickers/open.coffee @@ -1,5 +1,5 @@ getClickerVotes = -> - $.ajax Routes.get_votes_count_path(<%= @clicker.id %>), + $.ajax Routes.votes_count_path(<%= @clicker.id %>), type: 'GET' dataType: 'json' success: (result) -> diff --git a/app/views/layouts/devise.html.erb b/app/views/layouts/devise.html.erb index a93ca6147..d50a568e4 100644 --- a/app/views/layouts/devise.html.erb +++ b/app/views/layouts/devise.html.erb @@ -16,7 +16,7 @@ <%# Announcements %> <% if Announcement.active_on_main.exists? %>
- <%= get_announcements().html_safe %> + <%= main_page_announcements().html_safe %>
<% end %> diff --git a/app/views/media/_medium.html.erb b/app/views/media/_medium.html.erb index 15096261a..d25bd05c1 100644 --- a/app/views/media/_medium.html.erb +++ b/app/views/media/_medium.html.erb @@ -62,16 +62,16 @@ title="<%= t('medium.waiting_for_tag') %>"> <% end %> - <% if !medium.containingWatchlists(current_user).empty? %> + <% if !medium.containing_watchlists(current_user).empty? %> <%= link_to '', - watchlist_path(medium.containingWatchlists(current_user).first), + watchlist_path(medium.containing_watchlists(current_user).first), class: 'fas fa-bookmark text-light me-2', style: 'text-decoration: none;', data: { toggle: 'tooltip', placement: 'bottom' }, title: t('watchlist_entry.list', - count: medium.containingWatchlists(current_user).size, - watchlists: medium.containingWatchlistsNames(current_user).join(", ")) %> + count: medium.containing_watchlists(current_user).size, + watchlists: medium.containing_watchlists_names(current_user).join(", ")) %> <% end %> <%= link_to '', add_medium_to_watchlist_path(medium), diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb index f52aab1a9..18cc82648 100644 --- a/app/views/shared/_navbar.html.erb +++ b/app/views/shared/_navbar.html.erb @@ -16,7 +16,7 @@ <% is_home_active = !get_class_for_any_path_startswith([start_path(), lectures_path()]).empty?\ || !get_class_for_path(root_path()).empty? %> diff --git a/app/views/submissions/_card.html.erb b/app/views/submissions/_card.html.erb index 4f36f31d3..2eda338ed 100644 --- a/app/views/submissions/_card.html.erb +++ b/app/views/submissions/_card.html.erb @@ -27,7 +27,7 @@ <% if assignment&.medium&.visible_for_user?(current_user) && - (assignment.medium.tags.present? || assignment.has_documents?) %> + (assignment.medium.tags.present? || assignment.documents?) %>