diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index c80dac1e1..000000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "parserOptions": {
- "ecmaVersion": 2017,
- "sourceType": "module",
- "ecmaFeatures": {
- }
- },
- "rules": {
- "semi": "error"
- }
-}
\ No newline at end of file
diff --git a/.github/actions/changed_files/action.yml b/.github/actions/changed_files/action.yml
new file mode 100644
index 000000000..0af0b6e91
--- /dev/null
+++ b/.github/actions/changed_files/action.yml
@@ -0,0 +1,42 @@
+# Why is this file in a subdirectory? Because GitHub Actions requires it to be :(
+# see: https://github.com/orgs/community/discussions/26245#discussioncomment-5962450
+name: "Get changed files"
+description: "Checks out the code and returns the filenames of files that have changed in the pull request"
+
+inputs:
+ file-extensions:
+ # for example: "\.rb$" or something like "\.js$|\.js.erb$"
+ description: "Regex expressions for grep to filter for specific files"
+ required: true
+
+outputs:
+ changed-files:
+ description: "A space-separated list of the files that have changed in the pull request"
+ value: ${{ steps.get-changed-files.outputs.files }}
+
+runs:
+ using: "composite"
+ steps:
+ # This has to be done in the main workflow, not in the action, because
+ # otherwise this reusable action is not available in the workflow.
+ # - name: "Checkout code (on a PR branch)"
+ # uses: actions/checkout@v4
+ # with:
+ # fetch-depth: 2 # to also fetch parent of PR
+
+ # Adapted from this great comment [1]. Git diff adapted from [2].
+ # "|| test $? = 1;" is used to ignore the exit code of grep when no files
+ # are found matching the pattern. For the "three dots" ... syntax, see [3].
+ #
+ # Resources:
+ # number [1] being most important
+ # [1] https://github.com/actions/checkout/issues/520#issuecomment-1167205721
+ # [2] https://robertfaldo.medium.com/commands-to-run-rubocop-and-specs-you-changed-in-your-branch-e6d2f2e4110b
+ # [3] https://community.atlassian.com/t5/Bitbucket-questions/Git-diff-show-different-files-than-PR-Pull-Request/qaq-p/2331786
+ - name: Get changed files
+ shell: bash
+ id: get-changed-files
+ run: |
+ files_pretty=$(git diff --name-only --diff-filter=ACMR -r HEAD^1...HEAD | egrep '${{inputs.file-extensions}}' || test $? = 1;)
+ printf "🎴 Changed files: \n$files_pretty"
+ echo "files=$(echo ${files_pretty} | xargs)" >> $GITHUB_OUTPUT
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index ff55d914e..e40132f75 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -1,87 +1,75 @@
-on: [push]
+name: Linting
+
+# Trigger each time HEAD branch is updated in a pull request
+# see https://github.com/orgs/community/discussions/26366
+on:
+ pull_request:
+ types: [opened, reopened, synchronize, ready_for_review]
jobs:
+
rubocop:
+ name: RuboCop (Ruby)
runs-on: ubuntu-latest
- name: A job to check rubocop linter errors
steps:
- - uses: actions/checkout@v2
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2 # to also fetch parent of PR (used to get changed files)
+
+ - name: Get changed files
+ id: rb-changed
+ uses: ./.github/actions/changed_files/
+ with:
+ file-extensions: \.rb$
+
- name: Set up Ruby 3
+ if: ${{ steps.rb-changed.outputs.changed-files != ''}}
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.4
- - name: Install gems # usual step to install the gems.
+ bundler-cache: true
+
+ - name: Run RuboCop
+ if: ${{ steps.rb-changed.outputs.changed-files != ''}}
run: |
- bin/bundle config path vendor/bundle
- bin/bundle config set without 'default doc job cable storage ujs test db'
- bin/bundle install --jobs 4 --retry 3
- - name: Linter count
- id: hello
- uses: henrixapp/linter-less-or-equal-action@v1.19
- with:
- name: Rubocop
- command: bin/bundle exec rubocop app config lib spec
- total_regexp: \d+ offenses detected
- errors_regexp: \d+ offenses detected
- warnings_regexp: \d+ offenses detected
- compare_branch: mampf-next
- mode: changed
- include: .rb
+ echo "🚨 Running RuboCop version: $(bundle info rubocop | head -1)"
+ bundle exec rubocop --format github --fail-level 'convention' --force-exclusion -- $CHANGED_FILES
+
eslint:
- runs-on: ubuntu-latest
- name: A job to check eslint linter errors
- steps:
- - uses: actions/checkout@v2
- - name: Linter count
- id: hello
- uses: henrixapp/linter-less-or-equal-action@v1.19
- with:
- name: EsLint
- command: npx eslint
- total_regexp: \d+ problems
- errors_regexp: \d+ errors
- warnings_regexp: \d+ warnings
- compare_branch: mampf-next
- mode: changed
- include: .js
- coffee:
- runs-on: ubuntu-latest
- name: A job to check coffee linter errors
- steps:
- - uses: actions/checkout@v2
- - name: Linter count
- id: hello
- uses: henrixapp/linter-less-or-equal-action@v1.19
- with:
- name: Coffee
- command: npx coffeelint
- total_regexp: \d+ errors
- errors_regexp: \d+ errors
- warnings_regexp: \d+ warnings
- compare_branch: mampf-next
- mode: changed
- include: .coffee
- # erblint:
- # runs-on: ubuntu-latest
- # name: A job to check erblint linter errors
- # steps:
- # - uses: actions/checkout@v2
- # - name: Set up Ruby 2.7
- # uses: ruby/setup-ruby@v1
- # with:
- # ruby-version: 2.7
- # - name: Install gems # usual step to install the gems.
- # run: |
- # bin/bundle config path vendor/bundle
- # bin/bundle config set without 'default doc job cable storage ujs test db'
- # bin/bundle install --jobs 4 --retry 3
- # - name: Linter count
- # id: hello
- # uses: henrixapp/linter-less-or-equal-action@v1.1
- # with:
- # name: Erblint
- # command: bin/bundle exec erblint .
- # total_regexp: \d+ error(s)
- # errors_regexp: \d+ error(s)
- # warnings_regexp: \d+ error(s)
- # compare_branch: mampf-next
+ name: ESLint (JS)
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 2 # to also fetch parent of PR (used to get changed files)
+
+ - name: Get changed files
+ id: js-changed
+ uses: ./.github/actions/changed_files/
+ with:
+ # .(mjs is only used for eslint.config.mjs as of January 2024)
+ file-extensions: \.js$|\.mjs$|\.js.erb$
+
+ - name: Setup Node.js
+ if: ${{ steps.js-changed.outputs.changed-files != ''}}
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20' # End of Life (EOL): April 2026
+ cache: 'yarn'
+
+ - name: Install dependencies
+ if: ${{ steps.js-changed.outputs.changed-files != ''}}
+ run: yarn install
+
+ # with ESLint v9 --ignore-path does not exist anymore
+ # see [1] for the PR. However, my feeling for this is totally reflected
+ # by [2]. Hopefully, it will come back in future versions.
+ # [1] https://github.com/eslint/eslint/pull/16355
+ # [2] https://github.com/eslint/eslint/issues/16264#issuecomment-1292858747
+ - name: Run ESLint
+ if: ${{ steps.js-changed.outputs.changed-files != ''}}
+ run: |
+ echo "🚨 Running ESLint version: $(yarn run --silent eslint --version)"
+ yarn run eslint --max-warnings 0 --no-warn-ignored ${{ steps.js-changed.outputs.changed-files }}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
deleted file mode 100644
index ac0fb3280..000000000
--- a/.github/workflows/tests.yml
+++ /dev/null
@@ -1,84 +0,0 @@
-name: Tests
-
-on:
- push:
- branches:
- - main
- - mampf-next
- - production
- - experimental
- pull_request:
-
-jobs:
- unit-test-job:
- name: Execute unit tests & upload to Codecov
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
- with:
- submodules: recursive
-
- - name: Pull docker images
- run: docker compose pull --ignore-buildable
- working-directory: docker/run_cypress_tests
-
- - name: Use Docker layer caching # https://github.com/jpribyl/action-docker-layer-caching
- uses: jpribyl/action-docker-layer-caching@v0.1.1
- continue-on-error: true
-
- - name: Build docker containers
- run: docker compose build
- working-directory: docker/run_cypress_tests
- - name: Create and migrate DB
- run: docker compose run --entrypoint "" mampf sh -c "rake db:create db:migrate db:test:prepare"
- working-directory: docker/run_cypress_tests
- - name: Reindex sunspot
- working-directory: docker/run_cypress_tests
- run: |
- docker compose run --entrypoint="" mampf sh -c "RAILS_ENV=test rake sunspot:reindex"
-
- - name: Run unit tests
- working-directory: docker/run_cypress_tests
- run: docker compose run --entrypoint="" mampf sh -c "RAILS_ENV=test rails spec"
- - name: Send test coverage report to Codecov
- uses: codecov/codecov-action@v3
- with:
- files: ./coverage/coverage.xml
- fail_ci_if_error: true
- verbose: true
- e2e-test-job:
- name: Run E2E tests & upload results to Cypress
- runs-on: ubuntu-latest
- timeout-minutes: 30
- needs: unit-test-job
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
- with:
- submodules: recursive
-
- - name: Pull docker images
- run: docker compose pull --ignore-buildable
- working-directory: docker/run_cypress_tests
-
- - name: Use Docker layer caching # https://github.com/jpribyl/action-docker-layer-caching
- uses: jpribyl/action-docker-layer-caching@v0.1.1
- continue-on-error: true
-
- - name: Build docker containers
- run: docker compose build
- working-directory: docker/run_cypress_tests
- - name: Create and migrate DB
- run: docker compose run --entrypoint "" mampf sh -c "rake db:create db:migrate db:test:prepare"
- working-directory: docker/run_cypress_tests
- - name: Run integration tests
- working-directory: docker/run_cypress_tests
- env:
- # pass the Dashboard record key as an environment variable
- CYPRESS_baseUrl: http://mampf:3000
- CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
- # pass GitHub token to allow accurately detecting a build vs a re-run build
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: docker compose run -e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} --entrypoint="" cypress_runner sh -c "while ! curl http://mampf:3000 ; do echo waiting for MaMpf to come online at http://mampf:3000; sleep 3; done; cypress run --record --key ${{ secrets.CYPRESS_RECORD_KEY }}"
-
diff --git a/.rubocop.yml b/.rubocop.yml
index d7b548944..c625d63ec 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,341 +1,123 @@
+# SEVERITY LEVELS
+# In the GitHub CI/CD, we can only distinguish between "error" and "warning".
+# In Rubocop this is much more granular: "info", "refactor", "convention",
+# "warning", "error" and "fatal".
+# From the docs: "The level is normally 'warning' for Lint and 'convention' for
+# all the others, but this can be changed in user configuration."
+# However, we don't want to set the severity for each cop individually, so instead
+# we set the fatal level as "convention" in the CI/CD,
+# i.e. severity: "convention" and up will be treated as an error on GitHub
+# and below as warning such that the check will still pass (with a warning).
+#
+# Overview:
+# | RuboCop | GitHub |
+# error error -> check fails
+# warning error -> check fails
+# convention error -> check fails
+# refactor warning -> check passes
+# info warning -> check passes
+#
+# also see the RuboCop docs on severity
+# https://docs.rubocop.org/rubocop/configuration.html#severity
+
+# "Rubocop defaults" are by default required/included:
+# https://github.com/rubocop/rubocop/blob/master/config/default.yml
require:
- - rubocop-packaging
- rubocop-performance
- rubocop-rails
AllCops:
- TargetRubyVersion: 3.0
- # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
- # to ignore them, so only the ones explicitly set in this file are enabled.
- DisabledByDefault: false
- Exclude:
- - '**/tmp/**/*'
- - '**/templates/**/*'
- - '**/vendor/**/*'
- - 'actionpack/lib/action_dispatch/journey/parser.rb'
- - 'actionmailbox/test/dummy/**/*'
- - 'actiontext/test/dummy/**/*'
- - '**/node_modules/**/*'
+ # While default cops are automatically included, they are not "configured"
+ # by default (only on each next major version of RuboCop). Therefore, we
+ # have to explicitly configure them here.
+ # see https://docs.rubocop.org/rubocop/configuration.html#defaults
+ # and https://docs.rubocop.org/rubocop/1.2/versioning.html#pending-cops
+ NewCops: enable
+ # Ruby version is determined automatically from the Gemfile.lock
+
+#############################################
+# Layout
+#############################################
-Performance:
- Exclude:
- - '**/test/**/*'
-
-# Prefer assert_not over assert !
-Rails/AssertNot:
- Include:
- - '**/test/**/*'
-
-# Prefer assert_not_x over refute_x
-Rails/RefuteMethods:
- Include:
- - '**/test/**/*'
-
-Rails/IndexBy:
- Enabled: true
-
-Rails/IndexWith:
- Enabled: true
-
-# Prefer &&/|| over and/or.
-Style/AndOr:
- Enabled: true
-
-Layout/ArgumentAlignment:
- Enabled: true
-
-Layout/ArrayAlignment:
- Enabled: true
-
-Layout/BlockAlignment:
- Enabled: true
-
-# Align `when` with `case`.
-Layout/CaseIndentation:
- Enabled: true
-
-Layout/ClosingHeredocIndentation:
- Enabled: true
-
-# Align comments with method definitions.
-Layout/CommentIndentation:
- Enabled: true
-
-Layout/ElseAlignment:
- Enabled: true
-
-# Align `end` with the matching keyword or starting expression except for
-# assignments, where it should be aligned with the LHS.
Layout/EndAlignment:
- Enabled: true
EnforcedStyleAlignWith: variable
- AutoCorrect: true
-
-Layout/EmptyLines:
- Enabled: true
-Layout/EmptyLineAfterMagicComment:
- Enabled: true
-
-Layout/EmptyLinesAroundAccessModifier:
- Enabled: true
-
-Layout/EmptyLinesAroundBlockBody:
- Enabled: true
-
-# In a regular class definition, no empty lines around the body.
-Layout/EmptyLinesAroundClassBody:
- Enabled: true
-
-# In a regular method definition, no empty lines around the body.
-Layout/EmptyLinesAroundMethodBody:
- Enabled: true
-
-# In a regular module definition, no empty lines around the body.
-Layout/EmptyLinesAroundModuleBody:
- Enabled: true
-
-Layout/ExtraSpacing:
- Enabled: true
-
-Layout/LineLength:
- Max: 80
-
-# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
-Style/HashSyntax:
- Enabled: true
-
-Layout/FirstArgumentIndentation:
- Enabled: true
-
-Layout/HashAlignment:
- Enabled: true
-
-# Method definitions after `private` or `protected` isolated calls need one
-# extra level of indentation.
Layout/IndentationConsistency:
- Enabled: true
EnforcedStyle: indented_internal_methods
-# Two spaces, no tabs (for indentation).
-Layout/IndentationWidth:
- Enabled: true
-
-Layout/LeadingCommentSpace:
- Enabled: true
-
-Layout/MultilineOperationIndentation:
- Enabled: true
-
-Layout/SpaceAfterColon:
- Enabled: true
+Layout/LineLength:
+ Max: 100
-Layout/SpaceAfterComma:
- Enabled: true
+#############################################
+# Metrics
+#############################################
-Layout/SpaceAfterSemicolon:
- Enabled: true
+Metrics:
+ Enabled: false
-Layout/SpaceAroundEqualsInParameterDefault:
+Metrics/ParameterLists:
Enabled: true
-Layout/SpaceAroundKeyword:
- Enabled: true
+#############################################
+# Performance
+#############################################
-Layout/SpaceBeforeComma:
- Enabled: true
+Performance:
+ Severity: refactor # a warning in CI/CD
-Layout/SpaceBeforeComment:
- Enabled: true
+Performance/FlatMap:
+ Severity: warning # an error in CI/CD
-Layout/SpaceBeforeFirstArg:
- Enabled: true
-Layout/SpaceInsideArrayLiteralBrackets:
- Enabled: true
+#############################################
+# Rails
+#############################################
-Style/BlockDelimiters:
- Enabled: true
+Rails/HasManyOrHasOneDependent:
+ Enabled: false
-Style/BlockComments:
- Enabled: true
+Rails/SkipsModelValidations:
+ AllowedMethods: ["touch", "touch_all"]
-Style/ConditionalAssignment:
- Enabled: true
+Rails/UnknownEnv:
+ Environments: ["development", "test", "production", "docker_development"]
-Style/DefWithParentheses:
- Enabled: true
+#############################################
+# Style
+#############################################
Style/Documentation:
- Enabled: true
-
-# Defining a method with parameters needs parentheses.
-Style/MethodDefParentheses:
- Enabled: true
-
-Style/MethodCallWithoutArgsParentheses:
- Enabled: true
-
-Style/FrozenStringLiteralComment:
- Enabled: true
- EnforcedStyle: always
- Exclude:
- - 'actionview/test/**/*.builder'
- - 'actionview/test/**/*.ruby'
- - 'actionpack/test/**/*.builder'
- - 'actionpack/test/**/*.ruby'
- - 'activestorage/db/migrate/**/*.rb'
- - 'activestorage/db/update_migrate/**/*.rb'
- - 'actionmailbox/db/migrate/**/*.rb'
- - 'actiontext/db/migrate/**/*.rb'
-
-Style/NumericLiterals:
- Enabled: true
-
-Style/NumericPredicate:
- Enabled: true
-
-Style/RedundantFreeze:
- Enabled: true
-
-Style/SymbolProc:
- Enabled: true
-
-Style/SymbolArray:
Enabled: false
-# Use `foo {}` not `foo{}`.
-Layout/SpaceBeforeBlockBraces:
- Enabled: true
-
-# Use `foo { bar }` not `foo {bar}`.
-Layout/SpaceInsideBlockBraces:
- Enabled: true
- EnforcedStyleForEmptyBraces: space
-
-# Use `{ a: 1 }` not `{a:1}`.
-Layout/SpaceInsideHashLiteralBraces:
- Enabled: true
-
-Layout/SpaceInsideParens:
- Enabled: true
-
-# Check quotes usage according to lint rule below.
-Style/StringLiterals:
- Enabled: true
- EnforcedStyle: single_quotes
-
-# Detect hard tabs, no hard tabs.
-Layout/IndentationStyle:
- Enabled: true
-
-# Empty lines should not have any spaces.
-Layout/TrailingEmptyLines:
- Enabled: true
-
-# No trailing whitespace.
-Layout/TrailingWhitespace:
- Enabled: true
-
-# Use quotes for string literals when they are enough.
-Style/RedundantPercentQ:
- Enabled: true
+Style/EmptyMethod:
+ EnforcedStyle: expanded
-Lint/AmbiguousOperator:
- Enabled: true
-
-Lint/AmbiguousRegexpLiteral:
- Enabled: true
-
-Lint/ErbNewArguments:
- Enabled: true
-
-# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
-Lint/RequireParentheses:
- Enabled: true
-
-Lint/ShadowingOuterLocalVariable:
- Enabled: true
-
-Lint/RedundantStringCoercion:
- Enabled: true
-
-Lint/UriEscapeUnescape:
- Enabled: true
-
-Lint/UselessAssignment:
- Enabled: true
-
-Lint/DeprecatedClassMethods:
- Enabled: true
-
-Naming/VariableNumber:
- Enabled: true
-
-Style/ParenthesesAroundCondition:
- Enabled: true
-
-Style/HashTransformKeys:
- Enabled: true
+Style/FrozenStringLiteralComment:
+ EnforcedStyle: never
-Style/HashTransformValues:
- Enabled: true
+Style/HashSyntax:
+ EnforcedShorthandSyntax: never
-Style/RedundantBegin:
+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:
- Enabled: true
AllowMultipleReturnValues: true
-Style/Semicolon:
- Enabled: true
- AllowAsExpressionSeparator: true
-
-# Prefer Foo.method over Foo::method
-Style/ColonMethodCall:
- Enabled: true
-
-Style/TrivialAccessors:
- Enabled: true
-
-Performance/FlatMap:
- Enabled: true
-
-Performance/RedundantMerge:
- Enabled: true
-
-Performance/StartWith:
- Enabled: true
-
-Performance/EndWith:
- Enabled: true
-
-Performance/RegexpMatch:
- Enabled: true
-
-Performance/ReverseEach:
- Enabled: true
-
-Performance/UnfreezeString:
- Enabled: true
-
-Performance/DeletePrefix:
- Enabled: true
-
-Performance/DeleteSuffix:
- Enabled: true
+Style/StringLiterals:
+ EnforcedStyle: double_quotes
-Metrics/ModuleLength:
- Enabled: false
+Style/StringLiteralsInInterpolation:
+ EnforcedStyle: double_quotes
-Metrics/ClassLength:
- Enabled: false
-
-Metrics/MethodLength:
- Exclude:
- - 'app/abilities/**/*'
+Style/SymbolArray:
+ EnforcedStyle: brackets
-Metrics/AbcSize:
- Exclude:
- - 'app/abilities/**/*'
\ No newline at end of file
+Style/WordArray:
+ EnforcedStyle: brackets
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..baa13b8bb
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,8 @@
+{
+ "recommendations": [
+ "shopify.ruby-lsp",
+ "dbaeumer.vscode-eslint",
+ "streetsidesoftware.code-spell-checker",
+ "streetsidesoftware.code-spell-checker-german"
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..21983ff1e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,87 @@
+{
+ "editor.formatOnSave": false, // it still autosaves with the options below
+ //////////////////////////////////////
+ // JS (ESLint)
+ //////////////////////////////////////
+ // https://eslint.style/guide/faq#how-to-auto-format-on-save
+ // https://github.com/microsoft/vscode-eslint#settings-options
+ "[javascript]": {
+ "editor.formatOnSave": false, // to avoid formatting twice (ESLint + VSCode)
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
+ },
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+ "eslint.format.enable": true, // use ESLint as formatter
+ "eslint.experimental.useFlatConfig": true,
+ // this disables VSCode built-in formatter (instead we want to use ESLint)
+ "javascript.validate.enable": false,
+ //////////////////////////////////////
+ // HTML
+ //////////////////////////////////////
+ "[html]": {
+ "editor.formatOnSave": false // TODO: activate once HTML formatter installed
+ },
+ //////////////////////////////////////
+ // Ruby (Rubocop)
+ //////////////////////////////////////
+ "[ruby]": {
+ "editor.defaultFormatter": "Shopify.ruby-lsp",
+ "editor.formatOnSave": true
+ },
+ "rubyLsp.formatter": "rubocop",
+ "rubyLsp.rubyVersionManager": "rbenv",
+ "rubyLsp.enabledFeatures": {
+ "codeActions": true,
+ "diagnostics": true,
+ "documentHighlights": true,
+ "documentLink": true,
+ "documentSymbols": true,
+ "foldingRanges": true,
+ "formatting": true,
+ "hover": true,
+ "inlayHint": true,
+ "onTypeFormatting": true,
+ "selectionRanges": true,
+ "semanticHighlighting": true,
+ "completion": true,
+ "codeLens": true,
+ "definition": true
+ },
+ "rubyLsp.enableExperimentalFeatures": true,
+ //////////////////////////////////////
+ // Files
+ //////////////////////////////////////
+ "files.exclude": {
+ "node_modules/": true,
+ "pdfcomprezzor/": true,
+ "coverage/": true,
+ "solr/": true
+ },
+ "files.associations": {
+ "*.js.erb": "javascript",
+ "*.html.erb": "html"
+ },
+ //////////////////////////////////////
+ // Editor
+ //////////////////////////////////////
+ "editor.wordWrap": "wordWrapColumn",
+ "editor.wordWrapColumn": 100, // toggle via Alt + Z shortcut
+ "editor.mouseWheelZoom": true,
+ "editor.rulers": [
+ {
+ "column": 80, // soft limit
+ "color": "#e5e5e5"
+ },
+ {
+ "column": 100, // hard limit
+ "color": "#c9c9c9"
+ }
+ ],
+ //////////////////////////////////////
+ // Spell Checker
+ //////////////////////////////////////
+ "cSpell.words": [
+ "turbolinks"
+ ]
+}
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 228b9e901..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,99 +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.50", require: false
- gem "rubocop-packaging", require: false
- gem "rubocop-performance", require: false
- gem "rubocop-rails", require: false
- gem "erb_lint", require: false
- gem "pgreset"
- gem "marcel"
# gem 'bullet'
end
@@ -132,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/Gemfile.lock b/Gemfile.lock
index c1296235d..946c11b31 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -132,13 +132,6 @@ GEM
execjs (~> 2.0)
barby (0.6.8)
bcrypt (3.1.18)
- better_html (2.0.1)
- actionview (>= 6.0)
- activesupport (>= 6.0)
- ast (~> 2.0)
- erubi (~> 1.4)
- parser (>= 2.4)
- smart_properties
bindex (0.8.1)
bootsnap (1.16.0)
msgpack (~> 1.2)
@@ -205,13 +198,6 @@ GEM
unf (>= 0.0.5, < 1.0.0)
down (5.4.0)
addressable (~> 2.8)
- erb_lint (0.4.0)
- activesupport
- better_html (>= 2.0.1)
- parser (>= 2.7.1.4)
- rainbow
- rubocop
- smart_properties
erubi (1.12.0)
erubis (2.7.0)
et-orbi (1.2.7)
@@ -324,6 +310,7 @@ GEM
kramdown (~> 2.0)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
+ language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
listen (3.0.8)
@@ -373,8 +360,9 @@ GEM
orm_adapter (0.5.0)
pairing_heap (3.0.0)
parallel (1.23.0)
- parser (3.2.2.0)
+ parser (3.2.2.4)
ast (~> 2.4.1)
+ racc
pdf-reader (2.11.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
@@ -455,7 +443,7 @@ GEM
ffi (~> 1.0)
redis-client (0.14.1)
connection_pool
- regexp_parser (2.8.0)
+ regexp_parser (2.8.2)
request_store (1.5.1)
rack (>= 1.4)
responders (3.1.0)
@@ -496,27 +484,26 @@ GEM
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-support (3.12.0)
- rubocop (1.50.2)
+ rubocop (1.57.2)
json (~> 2.3)
+ language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
- parser (>= 3.2.0.0)
+ parser (>= 3.2.2.4)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
- rubocop-ast (>= 1.28.0, < 2.0)
+ rubocop-ast (>= 1.28.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
- rubocop-ast (1.28.0)
+ rubocop-ast (1.30.0)
parser (>= 3.2.1.0)
- rubocop-packaging (0.5.1)
- rubocop (>= 0.89, < 2.0)
- rubocop-performance (1.10.2)
- rubocop (>= 0.90.0, < 2.0)
+ rubocop-performance (1.19.1)
+ rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
- rubocop-rails (2.9.1)
+ rubocop-rails (2.22.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
- rubocop (>= 0.90.0, < 2.0)
+ rubocop (>= 1.33.0, < 2.0)
ruby-graphviz (1.2.5)
rexml
ruby-progressbar (1.13.0)
@@ -564,7 +551,6 @@ GEM
simplecov (~> 0.19)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
- smart_properties (1.17.0)
spring (2.1.1)
spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0)
@@ -627,7 +613,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
- unicode-display_width (2.4.2)
+ unicode-display_width (2.5.0)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.0)
@@ -677,7 +663,6 @@ DEPENDENCIES
database_cleaner
devise
devise-bootstrap-views
- erb_lint
erubis
exception_handler (~> 0.8.0.0)
factory_bot_rails
@@ -716,10 +701,9 @@ DEPENDENCIES
rgl
rqrcode
rspec-rails
- rubocop (~> 1.50)
- rubocop-packaging
- rubocop-performance
- rubocop-rails
+ rubocop (~> 1.57)
+ rubocop-performance (~> 1.16)
+ rubocop-rails (~> 2.22, >= 2.22.1)
rubyzip (~> 2.3.0)
sass-rails (>= 6)
selenium-webdriver
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/config/manifest.js b/app/assets/config/manifest.js
index 1a4a43a20..ff4d5c580 100644
--- a/app/assets/config/manifest.js
+++ b/app/assets/config/manifest.js
@@ -2,4 +2,6 @@
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
//= link commontator/manifest.js
-//= link thredded_katex.css
\ No newline at end of file
+//= link thredded_katex.css
+//= link monotile/geometry.js
+//= link monotile/hat.js
\ No newline at end of file
diff --git a/app/assets/images/landing-background.jpg b/app/assets/images/landing-background.jpg
deleted file mode 100644
index 8824cdb2d..000000000
Binary files a/app/assets/images/landing-background.jpg and /dev/null differ
diff --git a/app/assets/javascripts/_selectize_turbolinks_fix.js b/app/assets/javascripts/_selectize_turbolinks_fix.js
index eaef1d817..dfe418cf2 100644
--- a/app/assets/javascripts/_selectize_turbolinks_fix.js
+++ b/app/assets/javascripts/_selectize_turbolinks_fix.js
@@ -5,130 +5,140 @@
// transfer knowledge about selected items from selectize to html options
var resetSelectized;
-resetSelectized = function(index, select) {
+resetSelectized = function (index, select) {
var i, len, selectedValue, val;
selectedValue = select.tomselect.getValue();
select.tomselect.destroy();
- $(select).find('option').attr('selected', null);
- if ($(select).prop('multiple')) {
+ $(select).find("option").attr("selected", null);
+ if ($(select).prop("multiple")) {
for (i = 0, len = selectedValue.length; i < len; i++) {
val = selectedValue[i];
- if (val !== '') {
- $(select).find("option[value='" + val + "']").attr('selected', true);
+ if (val !== "") {
+ $(select).find("option[value='" + val + "']").attr("selected", true);
}
}
- } else {
- if (selectedValue !== '') {
- $(select).find("option[value='" + selectedValue + "']").attr('selected', true);
+ }
+ else {
+ if (selectedValue !== "") {
+ $(select).find("option[value='" + selectedValue + "']").attr("selected", true);
}
}
};
-this.fillOptionsByAjax = function($selectizedSelection) {
- $selectizedSelection.each(function() {
- var courseId, existing_values, fill_path, loaded, locale, model_select, plugins, send_data, parent;
- if (this.dataset.drag === 'true') {
- plugins = ['remove_button', 'drag_drop'];
- } else {
- plugins = ['remove_button'];
+function fillOptionsByAjax($selectizedSelection) {
+ // TODO: this function definitely needs some refactoring
+ $selectizedSelection.each(function () {
+ let plugins = [];
+ let send_data = false;
+ let fill_path = "";
+ let courseId = 0;
+ let loaded = false;
+ let locale = null;
+
+ if (this.dataset.drag === "true") {
+ plugins = ["remove_button", "drag_drop"];
+ }
+ else {
+ plugins = ["remove_button"];
}
- if (this.dataset.ajax === 'true' && this.dataset.filled === 'false') {
- model_select = this;
+ if (this.dataset.ajax === "true" && this.dataset.filled === "false") {
+ const model_select = this;
courseId = 0;
- placeholder = this.dataset.placeholder;
- no_result_msg = this.dataset.noResults;
- existing_values = Array.apply(null, model_select.options).map(function(o) {
- return o.value;
- });
+ const placeholder = this.dataset.placeholder;
+ const no_result_msg = this.dataset.noResults;
send_data = false;
loaded = false;
- parent = this.dataset.modal === undefined ? document.body : null;
- if (this.dataset.model === 'tag') {
+ if (this.dataset.model === "tag") {
locale = this.dataset.locale;
fill_path = Routes.fill_tag_select_path({
- locale: locale
+ locale: locale,
});
send_data = true;
- } else if (this.dataset.model === 'user') {
+ }
+ else if (this.dataset.model === "user") {
fill_path = Routes.fill_user_select_path();
send_data = true;
- } else if (this.dataset.model === 'user_generic') {
+ }
+ else if (this.dataset.model === "user_generic") {
fill_path = Routes.list_generic_users_path();
- } else if (this.dataset.model === 'teachable') {
+ }
+ else if (this.dataset.model === "teachable") {
fill_path = Routes.fill_teachable_select_path();
- } else if (this.dataset.model === 'medium') {
+ }
+ else if (this.dataset.model === "medium") {
fill_path = Routes.fill_media_select_path();
- } else if (this.dataset.model === 'course_tag') {
+ }
+ else if (this.dataset.model === "course_tag") {
courseId = this.dataset.course;
fill_path = Routes.fill_course_tags_path();
}
- (function() {
- class MinimumLengthSelect extends TomSelect{
-
- refreshOptions(triggerDropdown=true){
+ (function () {
+ class MinimumLengthSelect extends TomSelect {
+ refreshOptions(triggerDropdown = true) {
var query = this.inputValue();
- if( query.length < 2){
+ if (query.length < 2) {
this.close(false);
return;
}
super.refreshOptions(triggerDropdown);
}
-
}
new MinimumLengthSelect("#" + model_select.id, {
- plugins: plugins,
- valueField: 'value',
- labelField: 'name',
- searchField: 'name',
- maxOptions: null,
- placeholder: placeholder,
- closeAfterSelect: true,
- load: function(query, callback) {
- var url;
- if (send_data || !loaded) {
- url = fill_path + "?course_id=" + courseId + "&q=" + encodeURIComponent(query);
- fetch(url).then(function(response) {
- return response.json();
- }).then(function(json) {
- loaded = true;
- return callback(json.map(function(item) {
- return {
- name: item.text,
- value: item.value
- };
- }));
- })["catch"](function() {
- callback();
- });
- }
- callback();
- },
- render: {
- option: function(data, escape) {
- return '
' + '' + escape(data.name) + '' + '
';
+ plugins: plugins,
+ valueField: "value",
+ labelField: "name",
+ searchField: "name",
+ maxOptions: null,
+ placeholder: placeholder,
+ closeAfterSelect: true,
+ load: function (query, callback) {
+ var url;
+ if (send_data || !loaded) {
+ url = fill_path + "?course_id=" + courseId + "&q=" + encodeURIComponent(query);
+ fetch(url).then(function (response) {
+ return response.json();
+ }).then(function (json) {
+ loaded = true;
+ return callback(json.map(function (item) {
+ return {
+ name: item.text,
+ value: item.value,
+ };
+ }));
+ })["catch"](function () {
+ callback();
+ });
+ }
+ callback();
},
- item: function(item, escape) {
- return '' + escape(item.name) + '
';
+ render: {
+ option: function (data, escape) {
+ return "" + '' + escape(data.name) + "" + "
";
+ },
+ item: function (item, escape) {
+ return '' + escape(item.name) + "
";
+ },
+ no_results: function (data, escape) {
+ return '' + escape(no_result_msg) + "
";
+ },
},
- no_results: function(data, escape) {
- return ''+ escape(no_result_msg) + '
';
- }
- }
- });
- })();} else {
+ });
+ })();
+ }
+ else {
return new TomSelect("#" + this.id, {
plugins: plugins,
- maxOptions: null
+ maxOptions: null,
});
}
});
-};
+}
-$(document).on('turbolinks:before-cache', function() {
- $('.tomselected').each(resetSelectized);
+$(document).on("turbolinks:before-cache", function () {
+ $(".tomselected").each(resetSelectized);
});
-$(document).on('turbolinks:load', function() {
- fillOptionsByAjax($('.selectize'));
+$(document).on("turbolinks:load", function () {
+ fillOptionsByAjax($(".selectize"));
});
diff --git a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js
index d050707d7..15f5d432d 100644
--- a/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js
+++ b/app/assets/javascripts/bootstrap_modal_turbolinks_fix.js
@@ -1,16 +1,16 @@
-$(document).on('turbolinks:load', function () {
- // show all active modals
- $('.activeModal').modal('show');
- // remove active status (this needs to be reestablished before caching)
- $('.activeModal').removeClass('activeModal');
+$(document).on("turbolinks:load", function () {
+ // show all active modals
+ $(".activeModal").modal("show");
+ // remove active status (this needs to be reestablished before caching)
+ $(".activeModal").removeClass("activeModal");
});
-$(document).on('turbolinks:before-cache', function () {
- // if some modal is open
- if ($('body').hasClass('modal-open')) {
- $('.modal.show').addClass('activeModal');
- $('.modal.show').modal('hide');
- // remove the greyed out background
- $('.modal-backdrop').remove();
- }
+$(document).on("turbolinks:before-cache", function () {
+ // if some modal is open
+ if ($("body").hasClass("modal-open")) {
+ $(".modal.show").addClass("activeModal");
+ $(".modal.show").modal("hide");
+ // remove the greyed out background
+ $(".modal-backdrop").remove();
+ }
});
diff --git a/app/assets/javascripts/bootstrap_popovers.js b/app/assets/javascripts/bootstrap_popovers.js
index 8bc3d7cd9..16cfe7d5c 100644
--- a/app/assets/javascripts/bootstrap_popovers.js
+++ b/app/assets/javascripts/bootstrap_popovers.js
@@ -1,18 +1,18 @@
-$(document).on('turbolinks:load', function () {
- initBootstrapPopovers();
+$(document).on("turbolinks:load", function () {
+ initBootstrapPopovers();
});
/**
* Initializes all Bootstrap popovers on the page.
- *
+ *
* This function might be used for the first initialization of popovers as well
* as for reinitialization on page changes.
*
* See: https://getbootstrap.com/docs/5.3/components/popovers/#enable-popovers
*/
function initBootstrapPopovers() {
- const popoverHtmlElements = document.querySelectorAll('[data-bs-toggle="popover"]');
- for (const element of popoverHtmlElements) {
- new bootstrap.Popover(element);
- }
-}
\ No newline at end of file
+ const popoverHtmlElements = document.querySelectorAll('[data-bs-toggle="popover"]');
+ for (const element of popoverHtmlElements) {
+ new bootstrap.Popover(element);
+ }
+}
diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js
index 739aa5f02..170e304a6 100644
--- a/app/assets/javascripts/cable.js
+++ b/app/assets/javascripts/cable.js
@@ -1,13 +1,15 @@
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//
+// disable eslint
+/* eslint-disable */
//= require action_cable
//= require_self
//= require_tree ./channels
+/* eslint-enable */
-(function() {
+(function () {
this.App || (this.App = {});
App.cable = ActionCable.createConsumer();
-
}).call(this);
diff --git a/app/assets/javascripts/datetimepicker.js b/app/assets/javascripts/datetimepicker.js
index 7730f99a1..9d6376d70 100644
--- a/app/assets/javascripts/datetimepicker.js
+++ b/app/assets/javascripts/datetimepicker.js
@@ -1,126 +1,129 @@
+/* global tempusDominus */
+
// Initialize on page load (when js file is dynamically loaded)
$(document).ready(startInitialization);
// On page change (e.g. go back and forth in browser)
-$(document).on('turbolinks:before-cache', () => {
- // Remove stale datetimepickers
- $('.tempus-dominus-widget').remove();
+$(document).on("turbolinks:before-cache", () => {
+ // Remove stale datetimepickers
+ $(".tempus-dominus-widget").remove();
});
function startInitialization() {
- const pickerElements = $('.td-picker');
- if (pickerElements.length == 0) {
- console.error('No datetimepicker element found on page, although requested.');
- return;
- }
+ const pickerElements = $(".td-picker");
+ if (pickerElements.length == 0) {
+ console.error("No datetimepicker element found on page, although requested.");
+ return;
+ }
- pickerElements.each((i, element) => {
- element = $(element);
- const datetimePicker = initDatetimePicker(element);
- registerErrorHandlers(datetimePicker, element);
- registerFocusHandlers(datetimePicker, element);
- });
+ pickerElements.each((i, element) => {
+ element = $(element);
+ const datetimePicker = initDatetimePicker(element);
+ registerErrorHandlers(datetimePicker, element);
+ registerFocusHandlers(datetimePicker, element);
+ });
}
function getDateTimePickerIcons() {
- // At the moment: continue to use FontAwesome 5 icons
- // see https://getdatepicker.com/6/plugins/fa5.html
- // see https://github.com/Eonasdan/tempus-dominus/blob/master/dist/plugins/fa-five.js
- return {
- type: 'icons',
- time: 'fas fa-clock',
- date: 'fas fa-calendar',
- up: 'fas fa-arrow-up',
- down: 'fas fa-arrow-down',
- previous: 'fas fa-chevron-left',
- next: 'fas fa-chevron-right',
- today: 'fas fa-calendar-check',
- clear: 'fas fa-trash',
- close: 'fas fa-times',
- }
+ // At the moment: continue to use FontAwesome 5 icons
+ // see https://getdatepicker.com/6/plugins/fa5.html
+ // see https://github.com/Eonasdan/tempus-dominus/blob/master/dist/plugins/fa-five.js
+ return {
+ type: "icons",
+ time: "fas fa-clock",
+ date: "fas fa-calendar",
+ up: "fas fa-arrow-up",
+ down: "fas fa-arrow-down",
+ previous: "fas fa-chevron-left",
+ next: "fas fa-chevron-right",
+ today: "fas fa-calendar-check",
+ clear: "fas fa-trash",
+ close: "fas fa-times",
+ };
}
function initDatetimePicker(element) {
- // see https://getdatepicker.com
- return new tempusDominus.TempusDominus(
- element.get(0),
- {
- display: {
- sideBySide: true, // clock to the right of the calendar
- icons: getDateTimePickerIcons(),
- },
- localization: {
- startOfTheWeek: 1,
- // choose format to be compliant with backend time format
- format: 'yyyy-MM-dd HH:mm',
- hourCycle: 'h23',
- }
- }
- );
+ // see https://getdatepicker.com
+ return new tempusDominus.TempusDominus(
+ element.get(0),
+ {
+ display: {
+ sideBySide: true, // clock to the right of the calendar
+ icons: getDateTimePickerIcons(),
+ },
+ localization: {
+ startOfTheWeek: 1,
+ // choose format to be compliant with backend time format
+ format: "yyyy-MM-dd HH:mm",
+ hourCycle: "h23",
+ },
+ },
+ );
}
function registerErrorHandlers(datetimePicker, element) {
- // Catch Tempus Dominus error when user types in invalid date
- // this is rather hacky at the moment, see this discussion:
- // https://github.com/Eonasdan/tempus-dominus/discussions/2656
- datetimePicker.dates.oldParseInput = datetimePicker.dates.parseInput;
- datetimePicker.dates.parseInput = (input) => {
- try {
- return datetimePicker.dates.oldParseInput(input);
- } catch (err) {
- const errorMsg = element.find('.td-error').data('td-invalid-date');
- element.find('.td-error').text(errorMsg).show();
- datetimePicker.dates.clear();
- }
- };
+ // Catch Tempus Dominus error when user types in invalid date
+ // this is rather hacky at the moment, see this discussion:
+ // https://github.com/Eonasdan/tempus-dominus/discussions/2656
+ datetimePicker.dates.oldParseInput = datetimePicker.dates.parseInput;
+ datetimePicker.dates.parseInput = (input) => {
+ try {
+ return datetimePicker.dates.oldParseInput(input);
+ }
+ catch (err) {
+ const errorMsg = element.find(".td-error").data("td-invalid-date");
+ element.find(".td-error").text(errorMsg).show();
+ datetimePicker.dates.clear();
+ }
+ };
- datetimePicker.subscribe(tempusDominus.Namespace.events.change, (e) => {
- // see https://getdatepicker.com/6/namespace/events.html#change
+ datetimePicker.subscribe(tempusDominus.Namespace.events.change, (e) => {
+ // see https://getdatepicker.com/6/namespace/events.html#change
- // Clear error message
- if (e.isValid && !e.isClear) {
- element.find('.td-error').empty();
- }
+ // Clear error message
+ if (e.isValid && !e.isClear) {
+ element.find(".td-error").empty();
+ }
- // If date was selected, close datetimepicker.
- // However: leave the datetimepicker open if user only changed time
- if (e.oldDate && e.date && !hasUserChangedDate(e.oldDate, e.date)) {
- datetimePicker.hide();
- }
- });
+ // If date was selected, close datetimepicker.
+ // However: leave the datetimepicker open if user only changed time
+ if (e.oldDate && e.date && !hasUserChangedDate(e.oldDate, e.date)) {
+ datetimePicker.hide();
+ }
+ });
}
function hasUserChangedDate(oldDate, newDate) {
- return oldDate.getHours() != newDate.getHours()
- || oldDate.getMinutes() != newDate.getMinutes();
+ return oldDate.getHours() != newDate.getHours()
+ || oldDate.getMinutes() != newDate.getMinutes();
}
function registerFocusHandlers(datetimePicker, element) {
- // Show datetimepicker when user clicks in text field next to button
- // or when input field receives focus
- var isButtonInvokingFocus = false;
+ // Show datetimepicker when user clicks in text field next to button
+ // or when input field receives focus
+ var isButtonInvokingFocus = false;
- element.find('.td-input').on('click focusin', (e) => {
- try {
- if (!isButtonInvokingFocus) {
- datetimePicker.show();
- }
- }
- finally {
- isButtonInvokingFocus = false;
- }
- });
+ element.find(".td-input").on("click focusin", (_e) => {
+ try {
+ if (!isButtonInvokingFocus) {
+ datetimePicker.show();
+ }
+ }
+ finally {
+ isButtonInvokingFocus = false;
+ }
+ });
- element.find('.td-picker-button').on('click', () => {
- isButtonInvokingFocus = true;
- element.find('.td-input').focus();
- });
+ element.find(".td-picker-button").on("click", () => {
+ isButtonInvokingFocus = true;
+ element.find(".td-input").focus();
+ });
- // Hide datetimepicker when input field loses focus
- element.find('.td-input').blur((e) => {
- if (!e.relatedTarget) {
- return;
- }
- datetimePicker.hide();
- });
+ // Hide datetimepicker when input field loses focus
+ element.find(".td-input").blur((e) => {
+ if (!e.relatedTarget) {
+ return;
+ }
+ datetimePicker.hide();
+ });
}
diff --git a/app/assets/javascripts/footer_modal.js b/app/assets/javascripts/footer_modal.js
index 28d02804f..523aa3785 100644
--- a/app/assets/javascripts/footer_modal.js
+++ b/app/assets/javascripts/footer_modal.js
@@ -1,5 +1,5 @@
document.addEventListener("turbolinks:load", () => {
- if (window.location.hash == "#sponsors") {
- $('#sponsors').modal('show');
- }
+ if (window.location.hash == "#sponsors") {
+ $("#sponsors").modal("show");
+ }
});
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/assets/javascripts/monotile/geometry.js b/app/assets/javascripts/monotile/geometry.js
new file mode 100644
index 000000000..aee489858
--- /dev/null
+++ b/app/assets/javascripts/monotile/geometry.js
@@ -0,0 +1,94 @@
+// BSD-3-Clause licensed by Craig S. Kaplan
+// adapted from: https://github.com/isohedral/hatviz
+
+// This file is mostly code that will get replaced anyways since
+// the monotiles will probably not stay on the front screen forever.
+// Having to properly use modules here and import/export the respective variables
+// would be overkill. This is mostly external code from the monotile project.
+// It works as intended and is pretty much unrelated to the rest of our code base.
+// For these reasons, we disable some ESLint rules for this file.
+/* eslint-disable no-undef, no-unused-vars */
+
+const r3 = 1.7320508075688772;
+const hr3 = 0.8660254037844386;
+const ident = [1, 0, 0, 0, 1, 0];
+
+function pt(x, y) {
+ return { x: x, y: y };
+}
+
+function hexPt(x, y) {
+ return pt(x + 0.5 * y, hr3 * y);
+}
+
+// Affine matrix inverse
+function inv(T) {
+ const det = T[0] * T[4] - T[1] * T[3];
+ return [T[4] / det, -T[1] / det, (T[1] * T[5] - T[2] * T[4]) / det,
+ -T[3] / det, T[0] / det, (T[2] * T[3] - T[0] * T[5]) / det];
+}
+
+// Affine matrix multiply
+function mul(A, B) {
+ return [A[0] * B[0] + A[1] * B[3],
+ A[0] * B[1] + A[1] * B[4],
+ A[0] * B[2] + A[1] * B[5] + A[2],
+
+ A[3] * B[0] + A[4] * B[3],
+ A[3] * B[1] + A[4] * B[4],
+ A[3] * B[2] + A[4] * B[5] + A[5]];
+}
+
+function padd(p, q) {
+ return { x: p.x + q.x, y: p.y + q.y };
+}
+
+function psub(p, q) {
+ return { x: p.x - q.x, y: p.y - q.y };
+}
+
+// Rotation matrix
+function trot(ang) {
+ const c = cos(ang);
+ const s = sin(ang);
+ return [c, -s, 0, s, c, 0];
+}
+
+// Translation matrix
+function ttrans(tx, ty) {
+ return [1, 0, tx, 0, 1, ty];
+}
+
+function rotAbout(p, ang) {
+ return mul(ttrans(p.x, p.y),
+ mul(trot(ang), ttrans(-p.x, -p.y)));
+}
+
+// Matrix * point
+function transPt(M, P) {
+ return pt(M[0] * P.x + M[1] * P.y + M[2], M[3] * P.x + M[4] * P.y + M[5]);
+}
+
+// Match unit interval to line segment p->q
+function matchSeg(p, q) {
+ return [q.x - p.x, p.y - q.y, p.x, q.y - p.y, q.x - p.x, p.y];
+}
+
+// Match line segment p1->q1 to line segment p2->q2
+function matchTwo(p1, q1, p2, q2) {
+ return mul(matchSeg(p2, q2), inv(matchSeg(p1, q1)));
+}
+
+// Intersect two lines defined by segments p1->q1 and p2->q2
+function intersect(p1, q1, p2, q2) {
+ const d = (q2.y - p2.y) * (q1.x - p1.x) - (q2.x - p2.x) * (q1.y - p1.y);
+ const uA = ((q2.x - p2.x) * (p1.y - p2.y) - (q2.y - p2.y) * (p1.x - p2.x)) / d;
+ return pt(p1.x + uA * (q1.x - p1.x), p1.y + uA * (q1.y - p1.y));
+}
+
+const hat_outline = [
+ hexPt(0, 0), hexPt(-1, -1), hexPt(0, -2), hexPt(2, -2),
+ hexPt(2, -1), hexPt(4, -2), hexPt(5, -1), hexPt(4, 0),
+ hexPt(3, 0), hexPt(2, 2), hexPt(0, 3), hexPt(0, 2),
+ hexPt(-1, 2),
+];
diff --git a/app/assets/javascripts/monotile/hat.js b/app/assets/javascripts/monotile/hat.js
new file mode 100644
index 000000000..820116752
--- /dev/null
+++ b/app/assets/javascripts/monotile/hat.js
@@ -0,0 +1,479 @@
+// BSD-3-Clause licensed by Craig S. Kaplan
+// adapted from: https://github.com/isohedral/hatviz
+
+// This file is mostly code that will get replaced anyways since
+// the monotiles will probably not stay on the front screen forever.
+// Having to properly use modules here and import/export the respective variables
+// would be overkill. This is mostly external code from the monotile project.
+// It works as intended and is pretty much unrelated to the rest of our code base.
+// For these reasons, we disable some ESLint rules for this file.
+/* eslint-disable no-undef, no-unused-vars */
+
+// A bit of a "hacky" fix. This is the only way I found that works
+// so that the canvas is loaded even if the site is left
+// e.g. user clicks on "Register" or changes language from "De" to "En"
+// with the button on the landing page.
+// Also see the following link for a possible workaround, which does not work
+// for us in conjunction with turbolink:
+// https://github.com/processing/p5.js/wiki/p5.js-overview#why-cant-i-assign-variables-using-p5-functions-and-variables-before-setup
+$(document).on("turbolinks:load", () => {
+ try {
+ setup();
+ }
+ catch (err) {
+ if (err instanceof ReferenceError) {
+ return;
+ }
+ console.error(err);
+ }
+});
+
+const INITIAL_TO_SCREEN = [45, 0, 0, 0, -45, 0];
+let to_screen = INITIAL_TO_SCREEN;
+let tiles;
+let level;
+let box_height;
+
+let monotile_btn;
+let reset_btn;
+
+const colors = {
+ H1: [0, 137, 212],
+ H: [148, 205, 235],
+ T: [251, 251, 251],
+ P: [250, 250, 250],
+ F: [220, 220, 220],
+};
+let black = [0, 0, 0];
+
+function drawPolygon(shape, T, f, s, w) {
+ if (f != null) {
+ fill(f);
+ }
+ else {
+ noFill();
+ }
+ if (s != null) {
+ stroke(s);
+ strokeWeight(w);
+ }
+ else {
+ noStroke();
+ }
+ beginShape();
+ for (let p of shape) {
+ const tp = transPt(T, p);
+ vertex(tp.x, tp.y);
+ }
+ endShape(CLOSE);
+}
+
+// The base level of the scene, a single hat tile, including a label
+// for colouring
+class HatTile {
+ constructor(label) {
+ this.label = label;
+ }
+
+ draw(S, level) {
+ drawPolygon(
+ hat_outline, S, colors[this.label], black, 1);
+ }
+}
+
+// A group that collects a list of transformed children and an outline
+class MetaTile {
+ constructor(shape, width) {
+ this.shape = shape;
+ this.width = width;
+ this.children = [];
+ }
+
+ addChild(T, geom) {
+ this.children.push({ T: T, geom: geom });
+ }
+
+ evalChild(n, i) {
+ return transPt(this.children[n].T, this.children[n].geom.shape[i]);
+ }
+
+ draw(S, level) {
+ if (level > 0) {
+ for (let g of this.children) {
+ g.geom.draw(mul(S, g.T), level - 1);
+ }
+ }
+ else {
+ drawPolygon(this.shape, S, null, black, this.width);
+ }
+ }
+
+ recentre() {
+ let cx = 0;
+ let cy = 0;
+ for (let p of this.shape) {
+ cx += p.x;
+ cy += p.y;
+ }
+ cx /= this.shape.length;
+ cy /= this.shape.length;
+ const tr = pt(-cx, -cy);
+
+ for (let idx = 0; idx < this.shape.length; ++idx) {
+ this.shape[idx] = padd(this.shape[idx], tr);
+ }
+
+ const M = ttrans(-cx, -cy);
+ for (let ch of this.children) {
+ ch.T = mul(M, ch.T);
+ }
+ }
+}
+
+const H1_hat = new HatTile("H1");
+const H_hat = new HatTile("H");
+const T_hat = new HatTile("T");
+const P_hat = new HatTile("P");
+const F_hat = new HatTile("F");
+
+function initH() {
+ const H_outline = [
+ pt(0, 0), pt(4, 0), pt(4.5, hr3),
+ pt(2.5, 5 * hr3), pt(1.5, 5 * hr3), pt(-0.5, hr3)];
+ const meta = new MetaTile(H_outline, 2);
+
+ meta.addChild(
+ matchTwo(
+ hat_outline[5], hat_outline[7], H_outline[5], H_outline[0]),
+ H_hat);
+ meta.addChild(
+ matchTwo(
+ hat_outline[9], hat_outline[11], H_outline[1], H_outline[2]),
+ H_hat);
+ meta.addChild(
+ matchTwo(
+ hat_outline[5], hat_outline[7], H_outline[3], H_outline[4]),
+ H_hat);
+ meta.addChild(
+ mul(ttrans(2.5, hr3),
+ mul(
+ [-0.5, -hr3, 0, hr3, -0.5, 0],
+ [0.5, 0, 0, 0, -0.5, 0])),
+ H1_hat);
+
+ return meta;
+}
+
+function initT() {
+ const T_outline = [
+ pt(0, 0), pt(3, 0), pt(1.5, 3 * hr3)];
+ const meta = new MetaTile(T_outline, 2);
+
+ meta.addChild(
+ [0.5, 0, 0.5, 0, 0.5, hr3],
+ T_hat);
+
+ return meta;
+}
+
+function initP() {
+ const P_outline = [
+ pt(0, 0), pt(4, 0),
+ pt(3, 2 * hr3),
+ pt(-1, 2 * hr3),
+ ];
+ const meta = new MetaTile(P_outline, 2);
+
+ meta.addChild([0.5, 0, 1.5, 0, 0.5, hr3], P_hat);
+ meta.addChild(
+ mul(ttrans(0, 2 * hr3),
+ mul([0.5, hr3, 0, -hr3, 0.5, 0],
+ [0.5, 0.0, 0.0, 0.0, 0.5, 0.0])),
+ P_hat);
+
+ return meta;
+}
+
+function initF() {
+ const F_outline = [
+ pt(0, 0), pt(3, 0),
+ pt(3.5, hr3), pt(3, 2 * hr3), pt(-1, 2 * hr3),
+ ];
+ const meta = new MetaTile(F_outline, 2);
+
+ meta.addChild(
+ [0.5, 0, 1.5, 0, 0.5, hr3],
+ F_hat);
+ meta.addChild(
+ mul(ttrans(0, 2 * hr3),
+ mul([0.5, hr3, 0, -hr3, 0.5, 0],
+ [0.5, 0.0, 0.0, 0.0, 0.5, 0.0])),
+ F_hat);
+
+ return meta;
+}
+
+function constructPatch(H, T, P, F) {
+ const rules = [
+ ["H"],
+ [0, 0, "P", 2],
+ [1, 0, "H", 2],
+ [2, 0, "P", 2],
+ [3, 0, "H", 2],
+ [4, 4, "P", 2],
+ [0, 4, "F", 3],
+ [2, 4, "F", 3],
+ [4, 1, 3, 2, "F", 0],
+ [8, 3, "H", 0],
+ [9, 2, "P", 0],
+ [10, 2, "H", 0],
+ [11, 4, "P", 2],
+ [12, 0, "H", 2],
+ [13, 0, "F", 3],
+ [14, 2, "F", 1],
+ [15, 3, "H", 4],
+ [8, 2, "F", 1],
+ [17, 3, "H", 0],
+ [18, 2, "P", 0],
+ [19, 2, "H", 2],
+ [20, 4, "F", 3],
+ [20, 0, "P", 2],
+ [22, 0, "H", 2],
+ [23, 4, "F", 3],
+ [23, 0, "F", 3],
+ [16, 0, "P", 2],
+ [9, 4, 0, 2, "T", 2],
+ [4, 0, "F", 3],
+ ];
+
+ ret = new MetaTile([], H.width);
+ shapes = { H: H, T: T, P: P, F: F };
+
+ for (let r of rules) {
+ if (r.length == 1) {
+ ret.addChild(ident, shapes[r[0]]);
+ }
+ else if (r.length == 4) {
+ const poly = ret.children[r[0]].geom.shape;
+ const T = ret.children[r[0]].T;
+ const P = transPt(T, poly[(r[1] + 1) % poly.length]);
+ const Q = transPt(T, poly[r[1]]);
+ const nshp = shapes[r[2]];
+ const npoly = nshp.shape;
+
+ ret.addChild(
+ matchTwo(npoly[r[3]], npoly[(r[3] + 1) % npoly.length], P, Q),
+ nshp);
+ }
+ else {
+ const chP = ret.children[r[0]];
+ const chQ = ret.children[r[2]];
+
+ const P = transPt(chQ.T, chQ.geom.shape[r[3]]);
+ const Q = transPt(chP.T, chP.geom.shape[r[1]]);
+ const nshp = shapes[r[4]];
+ const npoly = nshp.shape;
+
+ ret.addChild(
+ matchTwo(npoly[r[5]], npoly[(r[5] + 1) % npoly.length], P, Q),
+ nshp);
+ }
+ }
+
+ return ret;
+}
+
+// You can read the paper preprint if you'd like to know how this works:
+// https://arxiv.org/abs/2303.10798
+function constructMetatiles(patch) {
+ const bps1 = patch.evalChild(8, 2);
+ const bps2 = patch.evalChild(21, 2);
+ const rbps = transPt(rotAbout(bps1, -2.0 * PI / 3.0), bps2);
+
+ const p72 = patch.evalChild(7, 2);
+ const p252 = patch.evalChild(25, 2);
+
+ const llc = intersect(bps1, rbps,
+ patch.evalChild(6, 2), p72);
+ let w = psub(patch.evalChild(6, 2), llc);
+
+ const new_H_outline = [llc, bps1];
+ w = transPt(trot(-PI / 3), w);
+ new_H_outline.push(padd(new_H_outline[1], w));
+ new_H_outline.push(patch.evalChild(14, 2));
+ w = transPt(trot(-PI / 3), w);
+ new_H_outline.push(psub(new_H_outline[3], w));
+ new_H_outline.push(patch.evalChild(6, 2));
+
+ const new_H = new MetaTile(new_H_outline, patch.width * 2);
+ for (let ch of [0, 9, 16, 27, 26, 6, 1, 8, 10, 15]) {
+ new_H.addChild(patch.children[ch].T, patch.children[ch].geom);
+ }
+
+ const new_P_outline = [p72, padd(p72, psub(bps1, llc)), bps1, llc];
+ const new_P = new MetaTile(new_P_outline, patch.width * 2);
+ for (let ch of [7, 2, 3, 4, 28]) {
+ new_P.addChild(patch.children[ch].T, patch.children[ch].geom);
+ }
+
+ const new_F_outline = [
+ bps2, patch.evalChild(24, 2), patch.evalChild(25, 0),
+ p252, padd(p252, psub(llc, bps1))];
+ const new_F = new MetaTile(new_F_outline, patch.width * 2);
+ for (let ch of [21, 20, 22, 23, 24, 25]) {
+ new_F.addChild(patch.children[ch].T, patch.children[ch].geom);
+ }
+
+ const AAA = new_H_outline[2];
+ const BBB = padd(new_H_outline[1],
+ psub(new_H_outline[4], new_H_outline[5]));
+ const CCC = transPt(rotAbout(BBB, -PI / 3), AAA);
+ const new_T_outline = [BBB, CCC, AAA];
+ const new_T = new MetaTile(new_T_outline, patch.width * 2);
+ new_T.addChild(patch.children[11].T, patch.children[11].geom);
+
+ new_H.recentre();
+ new_P.recentre();
+ new_F.recentre();
+ new_T.recentre();
+
+ return [new_H, new_T, new_P, new_F];
+}
+
+function isButtonActive(but) {
+ return but.elt.style.border.length > 0;
+}
+
+function setButtonActive(but, b) {
+ but.elt.style.border = (b ? "3px solid black" : "");
+}
+
+function addButton(name, f) {
+ const ret = createButton(name);
+ ret.position(10, box_height);
+ ret.size(125, 25);
+ ret.mousePressed(f);
+ box_height += 40;
+
+ return ret;
+}
+
+function setup() {
+ box_height = 10;
+
+ let canvas = createCanvas(windowWidth, windowHeight);
+ canvas.id("einstein-monotile-canvas");
+ canvas.parent("einstein-monotile");
+
+ tiles = [initH(), initT(), initP(), initF()];
+ level = 1;
+
+ // Reset button
+ reset_btn = addButton("Reset", () => reset());
+ reset_btn.class("btn btn-light monotile-btn");
+
+ // Monotile button
+ monotile_btn = addButton("Monotile", () => monotile());
+ monotile_btn.class("btn btn-light monotile-btn disabled");
+
+ // Find out more button
+ info_btn = addButton("Info", () => {
+ window.open("https://cs.uwaterloo.ca/~csk/hat/", "_blank");
+ });
+ info_btn.class("btn btn-light monotile-btn");
+
+ // Little animation at the beginning
+ monotile();
+ monotile();
+ monotile();
+}
+
+function reset() {
+ tiles = [initH(), initT(), initP(), initF()];
+ level = 1;
+ to_screen = INITIAL_TO_SCREEN;
+ monotile();
+ monotile_btn.removeClass("disabled");
+ loop();
+}
+
+function monotile() {
+ // Don't go past a certain level to avoid using too much memory
+ // and therefore crashing the browser.
+ if (level == 5) {
+ monotile_btn.addClass("disabled");
+ return;
+ }
+
+ const patch = constructPatch(...tiles);
+ tiles = constructMetatiles(patch);
+ level++;
+ loop();
+}
+
+function draw() {
+ background(255); // white background
+ push();
+ translate(width / 2, height / 2);
+ tiles[0].draw(to_screen, level);
+ pop();
+
+ // Draw black overlay
+ fill(0, 0, 0, 70);
+ noStroke();
+ rect(0, 0, windowWidth, windowHeight);
+
+ noLoop();
+}
+
+function windowResized() {
+ resizeCanvas(windowWidth, windowHeight);
+}
+
+/* Mouse movement */
+let dragging = false;
+DELTA_THRESHOLD = 5;
+
+function mousePressed() {
+ dragging = true;
+ loop();
+}
+
+function touchMoved() {
+ // Do nothing.
+ // This prevents the canvas from being draggable on mobile devices
+ // where this would lead to the page not being able to scroll at all.
+ // see more: https://p5js.org/reference/#/p5/touchMoved
+}
+
+function mouseDragged(event) {
+ if (!dragging) {
+ return true;
+ }
+
+ // Only move if the mouse has moved a certain amount
+ const diffX = mouseX - pmouseX;
+ const diffY = mouseY - pmouseY;
+ if (diffX <= DELTA_THRESHOLD && diffY == DELTA_THRESHOLD) {
+ return true;
+ }
+
+ // Recalculate the transformation matrix
+ to_screen = mul(ttrans(diffX, diffY), to_screen);
+
+ loop();
+ return false;
+}
+
+$(document).ready(function () {
+ // Prevent mousemove on divs to propagate to canvas
+ $("#signin-box").on("mousemove", (event) => {
+ event.stopPropagation();
+ });
+ $("#footer-bar").on("mousemove", (event) => {
+ event.stopPropagation();
+ });
+ $("#announcement-box").on("mousemove", (event) => {
+ event.stopPropagation();
+ });
+});
diff --git a/app/assets/stylesheets/landing.scss b/app/assets/stylesheets/landing.scss
index ea65b186a..01bd03317 100644
--- a/app/assets/stylesheets/landing.scss
+++ b/app/assets/stylesheets/landing.scss
@@ -15,10 +15,14 @@
}
#signin-box {
+ z-index: 2;
padding: 70px 40px;
+ background: linear-gradient(7deg,
+ rgba(255, 255, 255, 0.85) 0%, white 70%);
}
#announcement-box {
+ z-index: 1;
display: flex;
align-items: baseline;
flex-wrap: wrap;
@@ -49,9 +53,12 @@
}
}
-#landing-body {
- background-image: image-url('landing-background.jpg');
- background-size: cover;
+footer {
+ z-index: 1;
+}
+
+#footer-bar {
+ background-color: rgba(255, 255, 255, 0.95);
}
.ul-no-indentation {
@@ -92,4 +99,34 @@
#remember-me-text {
white-space: normal;
display: contents;
+}
+
+#einstein-monotile-canvas {
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 0;
+ cursor: grab;
+}
+
+.monotile-btn {
+ background-color: rgba(255, 255, 255, 0.4);
+ height: 35px !important;
+ font-weight: 400;
+
+ &:hover {
+ background-color: white;
+ }
+
+ &.disabled {
+ background-color: rgba(255, 255, 255, 0.6);
+ font-weight: 350;
+ }
+}
+
+// Disable monotile buttons on "small" devices
+@include media-breakpoint-down(xxl) {
+ .monotile-btn {
+ display: none;
+ }
}
\ No newline at end of file
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/javascript/packs/application.js b/app/javascript/packs/application.js
index 5c9da133f..0586df987 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -7,7 +7,6 @@
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb
-
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
@@ -16,40 +15,39 @@
// const imagePath = (name) => images(name, true)
import {
- WidgetInstance
+ WidgetInstance,
} from "friendly-challenge";
-var friendlyChallengeWidgetInstance = WidgetInstance
document.addEventListener("turbolinks:load", function () {
- var doneCallback, element, options, widget;
+ var doneCallback, element, options;
- doneCallback = function (solution) {
- console.log(solution);
- document.querySelector("#register-user").disabled = false;
+ doneCallback = function (solution) {
+ console.log(solution);
+ document.querySelector("#register-user").disabled = false;
+ };
+ const errorCallback = (err) => {
+ console.log("There was an error when trying to solve the Captcha.");
+ console.log(err);
+ };
+ element = document.querySelector("#captcha-widget");
+ if (element != null) {
+ options = {
+ doneCallback: doneCallback,
+ errorCallback,
+ puzzleEndpoint: $("#captcha-widget").data("captcha-url"),
+ startMode: "auto",
+ language: $("#captcha-widget").data("lang"),
};
- const errorCallback = (err) => {
- console.log('There was an error when trying to solve the Captcha.');
- console.log(err);
- }
- element = document.querySelector('#captcha-widget');
- if (element != null) {
- options = {
- doneCallback: doneCallback,
- errorCallback,
- puzzleEndpoint: $('#captcha-widget').data("captcha-url"),
- startMode: "auto",
- language: $('#captcha-widget').data("lang")
- };
- console.log(options)
- widget = new WidgetInstance(element, options);
- //DO not uncomment, evil
- // widget.reset();
- }
+ console.log(options);
+ new WidgetInstance(element, options);
+ // DO not uncomment, evil
+ // widget.reset();
+ }
- // Init Masonry grid system
- // see https://getbootstrap.com/docs/5.0/examples/masonry/
- // and official documentation: https://masonry.desandro.com/
- $('.masonry-grid').masonry({
- percentPosition: true
- });
-})
\ No newline at end of file
+ // Init Masonry grid system
+ // see https://getbootstrap.com/docs/5.0/examples/masonry/
+ // and official documentation: https://masonry.desandro.com/
+ $(".masonry-grid").masonry({
+ percentPosition: true,
+ });
+});
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..80c9fa8ed 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_certificates
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 d90b05bb8..7745f2260 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -3,7 +3,7 @@ class User < ApplicationRecord
include ApplicationHelper
# use devise for authentification, include the following modules
- devise :database_authenticatable, :registerable,
+ devise :database_authenticatable, :registerable, :trackable,
:recoverable, :rememberable, :validatable, :confirmable, :lockable
# a user has many subscribed lectures
@@ -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,24 @@ 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?
+
+ false
+ end
+
+ # see https://github.com/heartcombo/devise/issues/4849#issuecomment-534733131
+ # We use the Devise::Trackable module to track sign-in count and current/last
+ # sign-in timestamp. However, we don't want to track IP address, but Trackable
+ # tries to, so we have to manually override the accessor methods so they do
+ # nothing.
+
+ def current_sign_in_ip
+ end
+
+ def last_sign_in_ip=(_ip)
+ end
- return false
+ def current_sign_in_ip=(_ip)
end
private
@@ -756,19 +779,19 @@ def can_update_personell?(lecture)
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
@@ -777,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)
@@ -794,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/assignments/new.js.erb b/app/views/assignments/new.js.erb
index c7872b491..3f1853678 100644
--- a/app/views/assignments/new.js.erb
+++ b/app/views/assignments/new.js.erb
@@ -1,18 +1,18 @@
-$('#newAssignmentButton').hide();
+$("#newAssignmentButton").hide();
-$('#assignmentListHeader').show()
- .after('<%= j render partial: "assignments/form", locals: { assignment: @assignment } %>');
+$("#assignmentListHeader").show()
+ .after("<%= j render partial: "assignments/form", locals: { assignment: @assignment } %>");
-new TomSelect('#assignment_medium_id_', {
+new TomSelect("#assignment_medium_id_", {
sortField: {
- field: 'text',
- direction: 'asc'
+ field: "text",
+ direction: "asc",
},
render: {
- no_results: function(data, escape) {
+ no_results: function (_data, _escape) {
return '<%= t("basics.no_results") %>
';
- }
- }
+ },
+ },
});
-$('#assignment_medium_id_').val(null).trigger('change');
+$("#assignment_medium_id_").val(null).trigger("change");
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/commontator/comments/cancel.js.erb b/app/views/commontator/comments/cancel.js.erb
index d9f34dcc6..71688f1d6 100644
--- a/app/views/commontator/comments/cancel.js.erb
+++ b/app/views/commontator/comments/cancel.js.erb
@@ -1,15 +1,15 @@
<% if @comment.nil? || @comment.new_record? %>
- <%
+<%
id = @comment.nil? || @comment.parent.nil? ?
"commontator-thread-#{@commontator_thread.id}-new-comment" :
"commontator-comment-#{@comment.parent.id}-reply"
%>
- $("#<%= id %>").hide();
+$("#<%= id %>").hide();
- $("#<%= id %>-link").fadeIn();
+$("#<%= id %>-link").fadeIn();
<% else %>
- $("#commontator-comment-<%= @comment.id %>-body").html("<%= escape_javascript(
+$("#commontator-comment-<%= @comment.id %>-body").html("<%= escape_javascript(
render partial: 'body', locals: { comment: @comment }
) %>");
<% end %>
diff --git a/app/views/commontator/comments/create.js.erb b/app/views/commontator/comments/create.js.erb
index 4754a6fef..3d9cbb2e1 100644
--- a/app/views/commontator/comments/create.js.erb
+++ b/app/views/commontator/comments/create.js.erb
@@ -18,11 +18,11 @@
%>
<% if @commontator_new_comment.nil? %>
- $("#<%= id %>").hide();
+$("#<%= id %>").hide();
- $("#<%= id %>-link").fadeIn();
+$("#<%= id %>-link").fadeIn();
<% else %>
- $("#<%= id %>").html("<%= escape_javascript(
+$("#<%= id %>").html("<%= escape_javascript(
render partial: 'form', locals: {
comment: @commontator_new_comment, thread: @commontator_thread
}
@@ -30,12 +30,12 @@
<% end %>
<% if @update_icon %>
-$('#commentsIcon').addClass('new-comment');
+$("#commentsIcon").addClass("new-comment");
<% end %>
var commontatorComment = $("#commontator-comment-<%= @comment.id %>").hide().fadeIn();
-$('html, body').animate(
- { scrollTop: commontatorComment.offset().top - window.innerHeight/2 }, 'fast'
+$("html, body").animate(
+ { scrollTop: commontatorComment.offset().top - window.innerHeight / 2 }, "fast",
);
<%= javascript_proc %>
diff --git a/app/views/commontator/comments/edit.js.erb b/app/views/commontator/comments/edit.js.erb
index 4bb3f28e0..0714bd3e3 100644
--- a/app/views/commontator/comments/edit.js.erb
+++ b/app/views/commontator/comments/edit.js.erb
@@ -2,6 +2,6 @@ $("#commontator-comment-<%= @comment.id %>-body").html("<%= escape_javascript(
render partial: 'form', locals: { comment: @comment }
) %>");
-$('#commontator-comment-<%= @comment.id %>-edit-body').focus();
+$("#commontator-comment-<%= @comment.id %>-edit-body").focus();
<%= javascript_proc %>
diff --git a/app/views/commontator/comments/new.js.erb b/app/views/commontator/comments/new.js.erb
index 0ec489036..38ad2afbe 100644
--- a/app/views/commontator/comments/new.js.erb
+++ b/app/views/commontator/comments/new.js.erb
@@ -6,11 +6,11 @@
var commontatorForm = $("#<%= id %>").html("<%= escape_javascript(
render partial: 'form', locals: { comment: @comment, thread: @commontator_thread }
) %>").hide().fadeIn();
-$('html, body').animate({ scrollTop: commontatorForm.offset().top - window.innerHeight/2 }, 'fast');
+$("html, body").animate({ scrollTop: commontatorForm.offset().top - window.innerHeight / 2 }, "fast");
-initBootstrapPopovers()
+initBootstrapPopovers();
$("#<%= id %>-link").hide();
-$('#<%= id %>-body').focus();
+$("#<%= id %>-body").focus();
<%= javascript_proc %>
diff --git a/app/views/commontator/comments/show.js.erb b/app/views/commontator/comments/show.js.erb
index 0274f80c9..ca0dc8332 100644
--- a/app/views/commontator/comments/show.js.erb
+++ b/app/views/commontator/comments/show.js.erb
@@ -1,8 +1,8 @@
var commontatorOldCommentIds = $("#commontator-comment-<%=
@comment.id
-%>-children").children().map(function() {
- return '#' + $(this).attr('id');
-}).toArray().join(',');
+%>-children").children().map(function () {
+ return "#" + $(this).attr("id");
+}).toArray().join(",");
<%=
render partial: 'show', locals: {
@@ -17,8 +17,8 @@ var commontatorOldCommentIds = $("#commontator-comment-<%=
var commontatorNewComments = $("#commontator-comment-<%=
@comment.id
%>-children").children().not(commontatorOldCommentIds).hide().fadeIn();
-$('html, body').animate(
- { scrollTop: commontatorNewComments.offset().top - window.innerHeight/2 }, 'fast'
+$("html, body").animate(
+ { scrollTop: commontatorNewComments.offset().top - window.innerHeight / 2 }, "fast",
);
<%= javascript_proc %>
diff --git a/app/views/commontator/threads/_hide_show_links.js.erb b/app/views/commontator/threads/_hide_show_links.js.erb
index 2d5dd862d..8b8f405d4 100644
--- a/app/views/commontator/threads/_hide_show_links.js.erb
+++ b/app/views/commontator/threads/_hide_show_links.js.erb
@@ -3,19 +3,19 @@
thread
%>
-$("#commontator-thread-<%= thread.id %>-hide-link").click(function() {
+$("#commontator-thread-<%= thread.id %>-hide-link").click(function () {
$("#commontator-thread-<%= thread.id %>-content").hide();
var commontatorLink = $("#commontator-thread-<%= thread.id %>-show").fadeIn();
- $('html, body').animate(
- { scrollTop: commontatorLink.offset().top - window.innerHeight/2 }, 'fast'
+ $("html, body").animate(
+ { scrollTop: commontatorLink.offset().top - window.innerHeight / 2 }, "fast",
);
});
-$("#commontator-thread-<%= thread.id %>-show-link").click(function() {
+$("#commontator-thread-<%= thread.id %>-show-link").click(function () {
var commontatorThread = $("#commontator-thread-<%= thread.id %>-content").fadeIn();
- $('html, body').animate(
- { scrollTop: commontatorThread.offset().top - window.innerHeight/2 }, 'fast'
+ $("html, body").animate(
+ { scrollTop: commontatorThread.offset().top - window.innerHeight / 2 }, "fast",
);
$("#commontator-thread-<%= thread.id %>-show").hide();
diff --git a/app/views/commontator/threads/_show.js.erb b/app/views/commontator/threads/_show.js.erb
index 8c94ea3d7..777c19d0c 100644
--- a/app/views/commontator/threads/_show.js.erb
+++ b/app/views/commontator/threads/_show.js.erb
@@ -6,7 +6,6 @@
show_all
%>
-
$("#commontator-thread-<%= thread.id %>").html("<%= escape_javascript(
render partial: 'commontator/threads/show', formats: [ :html ], locals: {
user: user, thread: thread, page: page, show_all: show_all
diff --git a/app/views/layouts/devise.html.erb b/app/views/layouts/devise.html.erb
index a93ca6147..2f8c2458a 100644
--- a/app/views/layouts/devise.html.erb
+++ b/app/views/layouts/devise.html.erb
@@ -3,12 +3,31 @@
<%= render partial: 'layouts/head' %>
<%= stylesheet_link_tag 'landing' %>
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= javascript_include_tag 'monotile/geometry' %>
+ <%= javascript_include_tag 'monotile/hat' %>
+
+
+
+
+
<%# Github corner %>
<%# from: https://github.com/tholman/github-corners %>
@@ -16,7 +35,7 @@
<%# Announcements %>
<% if Announcement.active_on_main.exists? %>
- <%= get_announcements().html_safe %>
+ <%= main_page_announcements().html_safe %>
<% end %>
@@ -29,7 +48,7 @@
<% end %>
<% end %>
-
+
-
+
<%= t('main.welcome') %>
@@ -85,10 +104,10 @@
-
+
-
-