From 931798a4f0224bab7268ccbc73370379c87c804c Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 18 Dec 2018 13:11:47 +0530 Subject: [PATCH 001/117] First steps for rails 5. - Changed test db to use mysql2 - Removed squeel from Gemfile, need to remove any usages --- Gemfile | 8 +- Gemfile.lock | 91 ++- app/models/ability.rb | 4 +- app/models/user.rb | 3 +- bin/rails | 2 +- bin/setup | 34 + bin/update | 29 + config/application.rb | 33 +- config/boot.rb | 5 +- config/cable.yml | 9 + config/database.yml | 17 +- config/environment.rb | 6 +- config/environments/development.rb | 84 +- config/environments/production.rb | 82 +- config/environments/test.rb | 15 +- .../application_controller_renderer.rb | 6 + config/initializers/assets.rb | 20 + config/initializers/cookies_serializer.rb | 5 + config/initializers/inflections.rb | 6 +- config/initializers/mime_types.rb | 2 - config/initializers/new_framework_defaults.rb | 23 + config/initializers/session_store.rb | 2 +- config/initializers/wrap_parameters.rb | 4 +- config/puma.rb | 99 ++- config/routes.rb | 13 +- config/secrets.yml | 32 +- config/spring.rb | 6 + db/schema.rb | 733 ++++++++---------- 28 files changed, 711 insertions(+), 662 deletions(-) create mode 100755 bin/setup create mode 100755 bin/update create mode 100644 config/cable.yml create mode 100644 config/initializers/application_controller_renderer.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/cookies_serializer.rb create mode 100644 config/initializers/new_framework_defaults.rb create mode 100644 config/spring.rb diff --git a/Gemfile b/Gemfile index a790d87a..9153c4f4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' -gem 'rails', '~> 4.2' -gem 'bootstrap-sass-backport', '~> 3.2.0' +gem 'rails', '>= 5.0.0.rc2', '< 5.1' +gem 'bootstrap-sass', '~> 3.2.0' gem 'jquery-rails' gem 'jquery-ui-rails' gem 'bootstrap-editable-rails' @@ -63,6 +63,7 @@ gem 'factory_bot_rails' gem 'log_file' group :test do + gem 'sqlite3' gem 'capybara' end @@ -98,9 +99,6 @@ gem 'bootstrap-wysihtml5-rails' gem 'momentjs-rails', '>= 2.9.0' gem 'bootstrap3-datetimepicker-rails', '~> 4.17.37' -#gem for improved WHERE querying -gem 'squeel', '~> 1.2' - #for nested forms gem 'cocoon' diff --git a/Gemfile.lock b/Gemfile.lock index 22995460..5c8a05bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,28 +26,32 @@ GIT GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.11.1) - actionpack (= 4.2.11.1) - actionview (= 4.2.11.1) - activejob (= 4.2.11.1) + actioncable (5.0.0.rc2) + actionpack (= 5.0.0.rc2) + nio4r (~> 1.2) + websocket-driver (~> 0.6.1) + actionmailer (5.0.0.rc2) + actionpack (= 5.0.0.rc2) + actionview (= 5.0.0.rc2) + activejob (= 5.0.0.rc2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.11.1) - actionview (= 4.2.11.1) - activesupport (= 4.2.11.1) - rack (~> 1.6) - rack-test (~> 0.6.2) + actionpack (5.0.0.rc2) + actionview (= 5.0.0.rc2) + activesupport (= 5.0.0.rc2) + rack (~> 2.x) + rack-test (~> 0.6.3) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.11.1) - activesupport (= 4.2.11.1) + actionview (5.0.0.rc2) + activesupport (= 5.0.0.rc2) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_record-acts_as (3.1.0) - activerecord (>= 4.2, < 5.2.2.rc1) - activesupport (>= 4.2, < 5.2.2.rc1) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_record-acts_as (3.0.2) + activerecord (>= 4.2) + activesupport (>= 4.2) active_record_union (1.3.0) activerecord (>= 4.0) activeadmin (1.4.3) @@ -62,20 +66,21 @@ GEM ransack (>= 1.8.7) sass (~> 3.1) sprockets (< 4.1) - activejob (4.2.11.1) - activesupport (= 4.2.11.1) - globalid (>= 0.3.0) - activemodel (4.2.11.1) - activesupport (= 4.2.11.1) - builder (~> 3.1) - activerecord (4.2.11.1) - activemodel (= 4.2.11.1) - activesupport (= 4.2.11.1) - arel (~> 6.0) - activesupport (4.2.11.1) + activejob (5.0.0.rc2) + activesupport (= 5.0.0.rc2) + globalid (>= 0.3.6) + activemodel (5.0.0.rc2) + activesupport (= 5.0.0.rc2) + activerecord (5.0.0.rc2) + activemodel (= 5.0.0.rc2) + activesupport (= 5.0.0.rc2) + arel (~> 7.0) + activerecord-import (0.17.2) + activerecord (>= 3.2) + activesupport (5.0.0.rc2) + concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) acts-as-taggable-on (5.0.0) activerecord (>= 4.2.8) @@ -90,8 +95,8 @@ GEM rake (>= 10.4, < 13.0) arbre (1.2.1) activesupport (>= 3.0.0) - arel (6.0.4) - autoprefixer-rails (9.6.0) + arel (7.1.4) + autoprefixer-rails (9.2.1) execjs awesome_print (1.8.0) bcrypt (3.1.13) @@ -330,20 +335,21 @@ GEM nio4r (~> 2.0) rabl (0.14.1) activesupport (>= 2.3.14) - rack (1.6.12) + rack (2.0.6) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.11.1) - actionmailer (= 4.2.11.1) - actionpack (= 4.2.11.1) - actionview (= 4.2.11.1) - activejob (= 4.2.11.1) - activemodel (= 4.2.11.1) - activerecord (= 4.2.11.1) - activesupport (= 4.2.11.1) + rails (5.0.0.rc2) + actioncable (= 5.0.0.rc2) + actionmailer (= 5.0.0.rc2) + actionpack (= 5.0.0.rc2) + actionview (= 5.0.0.rc2) + activejob (= 5.0.0.rc2) + activemodel (= 5.0.0.rc2) + activerecord (= 5.0.0.rc2) + activesupport (= 5.0.0.rc2) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.11.1) - sprockets-rails + railties (= 5.0.0.rc2) + sprockets-rails (>= 2.0.0) rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) rails-dom-testing (1.0.9) @@ -471,6 +477,9 @@ GEM wannabe_bool (0.7.1) warden (1.2.7) rack (>= 1.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) xpath (3.2.0) nokogiri (~> 1.8) @@ -537,7 +546,7 @@ DEPENDENCIES pry puma (~> 4.3.5) rabl - rails (~> 4.2) + rails (>= 5.0.0.rc2, < 5.1) rails-erd! redcarpet remotipart diff --git a/app/models/ability.rb b/app/models/ability.rb index e0047c7b..f7e39b26 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -42,8 +42,8 @@ def initialize(user) # A user should only be able to update himself or herself (assuming no # other permissions granted below by the global role). can [:read, :index], User, User.visible_to_user(user) do |u| - u == user || u.course_enrollments.where{ - course_role_id != CourseRole::STUDENT_ID}.any? + u == user || u.course_enrollments + .where('course_role_id != ?', CourseRole::STUDENT_ID).any? end can [:edit, :update], User, id: user.id diff --git a/app/models/user.rb b/app/models/user.rb index 0063ae51..94cd6dc3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -289,8 +289,7 @@ def managed_workout_offerings_in_term(workout, course, term) enrollments.map { |e| if workout.kind_of?(String) workouts_with_name = Workout.where('lower(name) = ?', workout) - e.course_offering.workout_offerings.where{ - workout_id.in(workouts_with_name.select{id}) } + e.course_offering.workout_offerings.where('workout_id in ?', workouts_with_name.select(:id)) else e.course_offering.workout_offerings.where(workout: workout) end diff --git a/bin/rails b/bin/rails index 728cd85a..07396602 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,4 @@ #!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) +APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' diff --git a/bin/setup b/bin/setup new file mode 100755 index 00000000..e620b4da --- /dev/null +++ b/bin/setup @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/bin/update b/bin/update new file mode 100755 index 00000000..a8e4462f --- /dev/null +++ b/bin/update @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a way to update your development environment automatically. + # Add necessary update steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + puts "\n== Updating database ==" + system! 'bin/rails db:migrate' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/config/application.rb b/config/application.rb index 4b34d59f..4f5b85e6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,44 +1,23 @@ -require File.expand_path('../boot', __FILE__) +require_relative 'boot' require 'rails/all' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) +Bundler.require(*Rails.groups) module CodeWorkout class Application < Rails::Application + # Initialize configuration defaults for originally generated Rails version + config.load_defaults 5.1 + Rails.application.config.active_record.belongs_to_required_by_default = false # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - # config.i18n.default_locale = :de - config.assets.precompile += [ - Proc.new { |filename, path| - path =~ /app\/assets/ && - path !~ /bootstrap-social/ && - path !~ /active_admin/ && - %w(.js .css).include?(File.extname(filename)) - } - ] - - # other kinds of assets - config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif *.mustache.html) - - # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib) - + # Timeout for feedback polls (milliseconds) # This is an initial value on application startup, but it might change config.feedback_timeout = 1700 config.feedback_timeout_padding = 300 - - config.active_record.raise_in_transactional_callbacks = true end end diff --git a/config/boot.rb b/config/boot.rb index 35967366..30f5120d 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,4 +1,3 @@ -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/config/cable.yml b/config/cable.yml new file mode 100644 index 00000000..0bbde6f7 --- /dev/null +++ b/config/cable.yml @@ -0,0 +1,9 @@ +development: + adapter: async + +test: + adapter: async + +production: + adapter: redis + url: redis://localhost:6379/1 diff --git a/config/database.yml b/config/database.yml index 01e132fc..fe4a5b6b 100644 --- a/config/database.yml +++ b/config/database.yml @@ -20,9 +20,16 @@ development: # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. +# test: +# adapter: sqlite3 +# database: db/test.sqlite3 +# # Pool: 16 puma threads + 10 SuckerPunch workers +# pool: 26 +# timeout: 5000 + test: - adapter: sqlite3 - database: db/test.sqlite3 - # Pool: 16 puma threads + 10 SuckerPunch workers - pool: 26 - timeout: 5000 + adapter: mysql2 + database: codeworkout_test + username: codeworkout + password: codeworkout + host: localhost diff --git a/config/environment.rb b/config/environment.rb index 03a2f462..426333bb 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,7 +1,5 @@ # Load the Rails application. -require File.expand_path('../application', __FILE__) -require 'rails_extensions' - +require_relative 'application' # Initialize the Rails application. -CodeWorkout::Application.initialize! +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 7fed104d..e64889cd 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,6 +1,5 @@ -CodeWorkout::Application.configure do - # Settings specified here will take precedence over those in - # config/application.rb. +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development @@ -10,32 +9,32 @@ # Do not eager load code on boot. config.eager_load = false - # Show full error reports and disable caching. - config.consider_all_requests_local = true - config.action_controller.perform_caching = false + # Show full error reports. + config.consider_all_requests_local = true - # For SSEs - config.preload_frameworks = true - config.allow_concurrency = true + # Enable/disable caching. By default caching is disabled. + if Rails.root.join('tmp/caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=172800' + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end # Don't care if the mailer can't send. - config.action_mailer.raise_delivery_errors = true - config.action_mailer.default_url_options = { :host => '192.168.33.20:3000' } - config.action_mailer.delivery_method = :smtp - config.action_mailer.smtp_settings = { - address: 'smtp.gmail.com', - port: 587, - domain: 'vt.edu', - authentication: 'plain', - enable_starttls_auto: true, - user_name: ENV['GMAIL_USERNAME'], - password: ENV['GMAIL_PASSWORD'] - } + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Raise an error on page load if there are pending migrations + # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Debug mode disables concatenation and preprocessing of assets. @@ -43,40 +42,13 @@ # number of complex assets. config.assets.debug = true - # Do not fallback to assets pipeline if a precompiled asset is missed. - # config.assets.compile = false + # Suppress logger output for asset requests. + config.assets.quiet = true - # Generate digests for assets URLs. - # config.assets.digest = true + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true - - config.assets.initialize_on_precompile = true - - config.middleware.use LogFile::Display - config.log_level = :info - config.log_formatter = proc do |severity, datetime, progname, msg| - if severity == 'DEBUG' && msg.blank? - '' - else - case severity - when 'DEBUG' - severity_colored = "\033[36;40m[DEBUG]\033[0m" # cyan - when 'INFO' - severity_colored = "\033[32;40m[INFO]\033[0m" # green - when 'WARN' - severity_colored = "\033[35;40m[WARNING]\033[0m" # magenta - when 'ERROR' - severity_colored = "\033[31;40m[ERROR]\033[0m" # red - when 'FATAL' - severity_colored = "\033[7;31;40m[FATAL]\033[0m" # black, red bg - else - severity_colored = "[#{severity}]" # none - end - "%s %s %s\n" % [ - datetime.strftime('%Y-%m-%d %H:%M:%S'), - severity_colored, - String === msg ? msg : msg.inspect - ] - end - end + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + # config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/config/environments/production.rb b/config/environments/production.rb index 9c602bb9..14f1f1f7 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,11 +1,11 @@ -CodeWorkout::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both thread web servers + # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true @@ -14,13 +14,9 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Enable Rack::Cache to put a simple HTTP cache in front of your application - # Add `rack-cache` to your Gemfile before enabling this. - # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. - # config.action_dispatch.rack_cache = true - - # Disable Rails's static asset server (Apache or nginx will already do this). - config.serve_static_files = false + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier @@ -29,64 +25,62 @@ # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Generate digests for assets URLs. - config.assets.digest = true + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb - # Version of your assets, change this if you want to expire all your assets. - config.assets.version = '1.0' + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # Set to :debug to see everything in the log. - config.log_level = :info - - config.log_formatter = proc do |severity, datetime, progname, msg| - if severity == 'DEBUG' && msg.blank? - '' - else - "%s [%s] %s\n" % [ - datetime.strftime('%Y-%m-%d %H:%M:%S'), - severity, - String === msg ? msg : msg.inspect - ] - end - end + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug # Prepend all log lines with the following tags. - # config.log_tags = [ :subdomain, :uuid ] - - # Use a different logger for distributed setups. - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + config.log_tags = [ :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = "http://assets.example.com" - - # Precompile additional assets. - # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. - # config.assets.precompile += %w( search.js ) + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "code_workout_#{Rails.env}" + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found). + # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - # Disable automatic flushing of the log to improve performance. - # config.autoflush_log = false + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end - config.action_mailer.default_url_options = { :host => 'http://codeworkout.cs.vt.edu/' } - config.middleware.use LogFile::Display + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index 357fba0c..30587ef6 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,4 @@ -CodeWorkout::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's @@ -12,9 +12,11 @@ # preloads Rails for running tests, you may have to set it to true. config.eager_load = false - # Configure static asset server for tests with Cache-Control for performance. - config.serve_static_files = true - config.static_cache_control = "public, max-age=3600" + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => 'public, max-age=3600' + } # Show full error reports and disable caching. config.consider_all_requests_local = true @@ -25,6 +27,7 @@ # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the @@ -34,6 +37,6 @@ # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr - config.action_mailer.default_url_options = { :host => 'localhost:3000' } - + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb new file mode 100644 index 00000000..51639b67 --- /dev/null +++ b/config/initializers/application_controller_renderer.rb @@ -0,0 +1,6 @@ +# Be sure to restart your server when you modify this file. + +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb new file mode 100644 index 00000000..ca44ed1f --- /dev/null +++ b/config/initializers/assets.rb @@ -0,0 +1,20 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) + +Rails.application.config.assets.precompile = [ + Proc.new { |filename, path| + path =~ /app\/assets/ && + path !~ /bootstrap-social/ && + path !~ /active_admin/ && + %w(.js .css).include?(File.extname(filename)) + }, /application.(css|.js)$/ +] diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb new file mode 100644 index 00000000..1389e86a --- /dev/null +++ b/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :marshal diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 12b845d8..ac033bf9 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -11,6 +11,6 @@ # end # These inflection rules are supported but not enabled by default: -ActiveSupport::Inflector.inflections(:en) do |inflect| - inflect.acronym 'IRT' -end +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 9acb8d70..dc189968 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -2,5 +2,3 @@ # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf -# Mime::Type.register_alias "text/html", :iphone -Mime::Type.register "application/x-yaml", :yml diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb new file mode 100644 index 00000000..f6e72335 --- /dev/null +++ b/config/initializers/new_framework_defaults.rb @@ -0,0 +1,23 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.0 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Rails 5.0 release notes for more info on each option. + +# Enable per-form CSRF tokens. Previous versions had false. +Rails.application.config.action_controller.per_form_csrf_tokens = false + +# Enable origin-checking CSRF mitigation. Previous versions had false. +Rails.application.config.action_controller.forgery_protection_origin_check = false + +# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. +# Previous versions had false. +ActiveSupport.to_time_preserves_timezone = false + +# Require `belongs_to` associations by default. Previous versions had false. +Rails.application.config.active_record.belongs_to_required_by_default = false + +# Do not halt callback chains when a callback returns false. Previous versions had true. +ActiveSupport.halt_callback_chains_on_return_false = true diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index f71b4efb..ce299f9d 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -CodeWorkout::Application.config.session_store :cookie_store, key: '_code-practice_session' +Rails.application.config.session_store :cookie_store, key: '_code_workout_session' diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb index 33725e95..bbfc3961 100644 --- a/config/initializers/wrap_parameters.rb +++ b/config/initializers/wrap_parameters.rb @@ -5,10 +5,10 @@ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] if respond_to?(:wrap_parameters) + wrap_parameters format: [:json] end # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do -# self.include_root_in_json = true +# self.include_root_in_json = true # end diff --git a/config/puma.rb b/config/puma.rb index e6fe5801..c7f311f8 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,52 +1,47 @@ -# Default to production -rails_env = rails_env || ENV['RAILS_ENV'] || 'production' -environment rails_env - -# For MRI, use workers instead of threads for greater parallelism -if rails_env == 'production' - workers 10 - threads 2, 2 - daemonize - preload_app! -elsif rails_env == 'staging' - workers 4 - threads 2, 8 - daemonize - preload_app! -else - # development settings are smaller; at least two workers, for SSE - workers 2 - threads 2, 4 -end - -app_dir = File.expand_path('../..', __FILE__) - -# Set up socket location -bind "unix://#{app_dir}/tmp/sockets/puma.sock" - -# Logging -stdout_redirect "#{app_dir}/log/puma.stdout.log", - "#{app_dir}/log/puma.stderr.log", - true - -puts "Running in evironment #{rails_env}" -puts "Running on platform #{RUBY_PLATFORM}" - -# Set master PID and state locations -pidfile "#{app_dir}/tmp/pids/puma.pid" -state_path "#{app_dir}/tmp/pids/puma.state" -activate_control_app - -on_worker_boot do - require 'active_record' - ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished - ActiveSupport.on_load(:active_record) do - ActiveRecord::Base.establish_connection( - YAML.load_file("#{app_dir}/config/database.yml")[rails_env]) - end -end - -before_fork do - require 'active_record' - ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished -end +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum, this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted this block will be run, if you are using `preload_app!` +# option you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end + +# Allow puma to be restarted by `rails restart` command. +plugin :tmp_restart diff --git a/config/routes.rb b/config/routes.rb index ce848831..0636b176 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,4 @@ -CodeWorkout::Application.routes.draw do +Rails.application.routes.draw do root 'home#index' @@ -186,9 +186,16 @@ end get 'help' => 'help#index' - match 'help/:action', to: 'help', via: [:get] - match 'static_pages/:action', controller: 'static_pages', via: [:get] + get 'help/exercise_format' => 'help#exercise_format' + get 'help/lti_configuration' => 'help/lti_configuration' + get 'static_pages/mockup1' => 'static_pages#mockup1' + get 'static_pages/mockup2' => 'static_pages#mockup2' + get 'static_pages/mockup3' => 'static_pages#mockup3' + get 'static_pages/thumbnails' => 'static_pages#thumbnails' + + # match 'help/:action', to: 'help', via: [:get] + # match 'static_pages/:action', to: 'static_pages', via: [:get] end #== Route Map diff --git a/config/secrets.yml b/config/secrets.yml index 6d4cb2ec..7e8ea854 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -1,16 +1,22 @@ -development: - secret_key_base: 14d33115b12aa4f9f07d8575fea6ae6a57223f64d13cf1475ec6614cfd78b0e8e8fbd19baa83649b99ff0494fd06c9265753961df252d4301602afd3cd4be92b - facebook_id: ID - facebook_secret: SECRET - google_id: ID - google_secret: SECRET - devise_pepper: '46d41a459192e0210aa267ccf13d12ecb1fda9ba3deb015bf9069106b5aea043ea59df22eaf8c26768ee257b84a47f0df1e03700956dd8d7963a98c995cca2f4' +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rails secret` to generate a secure secret key. +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: eff0aae935bd44c6013779062036ce365082fadabfa7944f1b1455e9468fbf3ed613a988b884590965e40105c8db9ee47f3777802fb839e089ad9e565690e126 test: - secret_key_base: bb105787c6ba010a44ec6554df779c7dcd83952dfe3c9175bb0a5ceec79705f23eb08b8d2e4c27aa71b1ee87d40018b6be27d1fe82d02129590ae172fdb3ef80 - facebook_id: ID - facebook_secret: SECRET - google_id: ID - google_secret: SECRET - devise_pepper: '46d41a459192e0210aa267ccf13d12ecb1fda9ba3deb015bf9069106b5aea043ea59df22eaf8c26768ee257b84a47f0df1e03700956dd8d7963a98c995cca2f4' \ No newline at end of file + secret_key_base: 0394fa40ba5b3ac258de3ac21e19844491cc15f84775b31cc081103e856203148bf0cab364e6c9dee9c59852ab063554c7c67af503d65ee46aaf18a5d76cde90 + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 00000000..c9119b40 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,6 @@ +%w( + .ruby-version + .rbenv-vars + tmp/restart.txt + tmp/caching-dev.txt +).each { |path| Spring.watch(path) } diff --git a/db/schema.rb b/db/schema.rb index ea40e41e..f59a963c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,4 +1,3 @@ -# encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -13,28 +12,27 @@ ActiveRecord::Schema.define(version: 20190712171251) do - create_table "active_admin_comments", force: :cascade do |t| - t.string "namespace", limit: 255 + create_table "active_admin_comments", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "namespace" t.text "body", limit: 65535 - t.string "resource_id", limit: 255, default: "", null: false - t.string "resource_type", limit: 255, default: "", null: false - t.integer "author_id", limit: 4 - t.string "author_type", limit: 255 + t.string "resource_id", default: "", null: false + t.string "resource_type", default: "", null: false + t.integer "author_id" + t.string "author_type" t.datetime "created_at" t.datetime "updated_at" + t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id", using: :btree + t.index ["namespace"], name: "index_active_admin_comments_on_namespace", using: :btree + t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree end - add_index "active_admin_comments", ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id", using: :btree - add_index "active_admin_comments", ["namespace"], name: "index_active_admin_comments_on_namespace", using: :btree - add_index "active_admin_comments", ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree - - create_table "attempts", force: :cascade do |t| - t.integer "user_id", limit: 4, null: false - t.integer "exercise_version_id", limit: 4, null: false - t.datetime "submit_time", null: false - t.integer "submit_num", limit: 4, null: false - t.float "score", limit: 24, default: 0.0 - t.integer "experience_earned", limit: 4 + create_table "attempts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "user_id", null: false + t.integer "exercise_version_id", null: false + t.datetime "submit_time", null: false + t.integer "submit_num", null: false + t.float "score", limit: 24, default: 0.0 + t.integer "experience_earned" t.datetime "created_at" t.datetime "updated_at" t.integer "workout_score_id", limit: 4 @@ -43,124 +41,115 @@ t.decimal "time_taken", precision: 10 t.decimal "feedback_timeout", precision: 10 t.decimal "worker_time", precision: 10 + t.index ["active_score_id"], name: "index_attempts_on_active_score_id", using: :btree + t.index ["exercise_version_id"], name: "index_attempts_on_exercise_version_id", using: :btree + t.index ["user_id"], name: "index_attempts_on_user_id", using: :btree + t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id", using: :btree end - add_index "attempts", ["active_score_id"], name: "index_attempts_on_active_score_id", using: :btree - add_index "attempts", ["exercise_version_id"], name: "index_attempts_on_exercise_version_id", using: :btree - add_index "attempts", ["user_id"], name: "index_attempts_on_user_id", using: :btree - add_index "attempts", ["workout_score_id"], name: "index_attempts_on_workout_score_id", using: :btree - - create_table "attempts_tag_user_scores", id: false, force: :cascade do |t| - t.integer "attempt_id", limit: 4 - t.integer "tag_user_score_id", limit: 4 + create_table "attempts_tag_user_scores", id: false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "attempt_id" + t.integer "tag_user_score_id" + t.index ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true, using: :btree + t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk", using: :btree end - add_index "attempts_tag_user_scores", ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true, using: :btree - add_index "attempts_tag_user_scores", ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk", using: :btree - - create_table "choices", force: :cascade do |t| - t.integer "multiple_choice_prompt_id", limit: 4, null: false - t.integer "position", limit: 4, null: false + create_table "choices", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "multiple_choice_prompt_id", null: false + t.integer "position", null: false t.text "feedback", limit: 65535 t.float "value", limit: 24, null: false t.datetime "created_at" t.datetime "updated_at" t.text "answer", limit: 65535, null: false + t.index ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id", using: :btree end - add_index "choices", ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id", using: :btree - - create_table "choices_multiple_choice_prompt_answers", id: false, force: :cascade do |t| - t.integer "choice_id", limit: 4 - t.integer "multiple_choice_prompt_answer_id", limit: 4 + create_table "choices_multiple_choice_prompt_answers", id: false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "choice_id" + t.integer "multiple_choice_prompt_answer_id" + t.index ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true, using: :btree + t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk", using: :btree end - add_index "choices_multiple_choice_prompt_answers", ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true, using: :btree - add_index "choices_multiple_choice_prompt_answers", ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk", using: :btree - - create_table "coding_prompt_answers", force: :cascade do |t| - t.text "answer", limit: 65535 - t.text "error", limit: 65535 - t.integer "error_line_no", limit: 4 + create_table "coding_prompt_answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.text "answer", limit: 65535 + t.text "error", limit: 65535 end - create_table "coding_prompts", force: :cascade do |t| + create_table "coding_prompts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.datetime "created_at" t.datetime "updated_at" - t.string "class_name", limit: 255 + t.string "class_name" t.text "wrapper_code", limit: 65535, null: false t.text "test_script", limit: 65535, null: false - t.string "method_name", limit: 255 + t.string "method_name" t.text "starter_code", limit: 65535 t.boolean "hide_examples", default: false, null: false end - create_table "course_enrollments", force: :cascade do |t| - t.integer "user_id", limit: 4, null: false - t.integer "course_offering_id", limit: 4, null: false - t.integer "course_role_id", limit: 4, null: false + create_table "course_enrollments", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "user_id", null: false + t.integer "course_offering_id", null: false + t.integer "course_role_id", null: false + t.index ["course_offering_id"], name: "index_course_enrollments_on_course_offering_id", using: :btree + t.index ["course_role_id"], name: "index_course_enrollments_on_course_role_id", using: :btree + t.index ["user_id", "course_offering_id"], name: "index_course_enrollments_on_user_id_and_course_offering_id", unique: true, using: :btree + t.index ["user_id"], name: "index_course_enrollments_on_user_id", using: :btree end - add_index "course_enrollments", ["course_offering_id"], name: "index_course_enrollments_on_course_offering_id", using: :btree - add_index "course_enrollments", ["course_role_id"], name: "index_course_enrollments_on_course_role_id", using: :btree - add_index "course_enrollments", ["user_id", "course_offering_id"], name: "index_course_enrollments_on_user_id_and_course_offering_id", unique: true, using: :btree - add_index "course_enrollments", ["user_id"], name: "index_course_enrollments_on_user_id", using: :btree - - create_table "course_exercises", force: :cascade do |t| - t.integer "course_id", limit: 4, null: false - t.integer "exercise_id", limit: 4, null: false + create_table "course_exercises", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "course_id", null: false + t.integer "exercise_id", null: false t.datetime "created_at" t.datetime "updated_at" + t.index ["course_id"], name: "course_exercises_course_id_fk", using: :btree + t.index ["exercise_id"], name: "course_exercises_exercise_id_fk", using: :btree end - add_index "course_exercises", ["course_id"], name: "course_exercises_course_id_fk", using: :btree - add_index "course_exercises", ["exercise_id"], name: "course_exercises_exercise_id_fk", using: :btree - - create_table "course_offerings", force: :cascade do |t| - t.integer "course_id", limit: 4, null: false - t.integer "term_id", limit: 4, null: false - t.string "label", limit: 255, default: "", null: false - t.string "url", limit: 255 + create_table "course_offerings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "course_id", null: false + t.integer "term_id", null: false + t.string "label", default: "", null: false + t.string "url" t.boolean "self_enrollment_allowed" t.datetime "created_at" t.datetime "updated_at" t.date "cutoff_date" - t.integer "lms_instance_id", limit: 4 + t.integer "lms_instance_id" + t.index ["course_id"], name: "index_course_offerings_on_course_id", using: :btree + t.index ["lms_instance_id"], name: "index_course_offerings_on_lms_instance_id", using: :btree + t.index ["term_id"], name: "index_course_offerings_on_term_id", using: :btree end - add_index "course_offerings", ["course_id"], name: "index_course_offerings_on_course_id", using: :btree - add_index "course_offerings", ["lms_instance_id"], name: "index_course_offerings_on_lms_instance_id", using: :btree - add_index "course_offerings", ["term_id"], name: "index_course_offerings_on_term_id", using: :btree - - create_table "course_roles", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false - t.boolean "can_manage_course", default: false, null: false - t.boolean "can_manage_assignments", default: false, null: false - t.boolean "can_grade_submissions", default: false, null: false - t.boolean "can_view_other_submissions", default: false, null: false - t.boolean "builtin", default: false, null: false + create_table "course_roles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false + t.boolean "can_manage_course", default: false, null: false + t.boolean "can_manage_assignments", default: false, null: false + t.boolean "can_grade_submissions", default: false, null: false + t.boolean "can_view_other_submissions", default: false, null: false + t.boolean "builtin", default: false, null: false end - create_table "courses", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false - t.string "number", limit: 255, default: "", null: false - t.integer "organization_id", limit: 4, null: false + create_table "courses", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false + t.string "number", default: "", null: false + t.integer "organization_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "creator_id", limit: 4 - t.string "slug", limit: 255, default: "", null: false - t.integer "user_group_id", limit: 4 - t.boolean "is_hidden", default: false + t.integer "creator_id" + t.string "slug", default: "", null: false + t.integer "user_group_id" + t.boolean "is_hidden", default: false + t.index ["organization_id"], name: "index_courses_on_organization_id", using: :btree + t.index ["slug"], name: "index_courses_on_slug", using: :btree + t.index ["user_group_id"], name: "index_courses_on_user_group_id", using: :btree end - add_index "courses", ["organization_id"], name: "index_courses_on_organization_id", using: :btree - add_index "courses", ["slug"], name: "index_courses_on_slug", using: :btree - add_index "courses", ["user_group_id"], name: "index_courses_on_user_group_id", using: :btree - - create_table "errors", force: :cascade do |t| - t.string "usable_type", limit: 255 - t.integer "usable_id", limit: 4 - t.string "class_name", limit: 255 + create_table "errors", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "usable_type" + t.integer "usable_id" + t.string "class_name" t.text "message", limit: 65535 t.text "trace", limit: 65535 t.text "target_url", limit: 65535 @@ -169,43 +158,40 @@ t.text "user_agent", limit: 65535 t.datetime "created_at" t.datetime "updated_at" + t.index ["class_name"], name: "index_errors_on_class_name", using: :btree + t.index ["created_at"], name: "index_errors_on_created_at", using: :btree end - add_index "errors", ["class_name"], name: "index_errors_on_class_name", using: :btree - add_index "errors", ["created_at"], name: "index_errors_on_created_at", using: :btree - - create_table "exercise_collections", force: :cascade do |t| - t.string "name", limit: 255 + create_table "exercise_collections", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name" t.text "description", limit: 65535 - t.integer "user_group_id", limit: 4 - t.integer "license_id", limit: 4 + t.integer "user_group_id" + t.integer "license_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "user_id", limit: 4 - t.integer "course_offering_id", limit: 4 + t.integer "user_id" + t.integer "course_offering_id" + t.index ["course_offering_id"], name: "index_exercise_collections_on_course_offering_id", using: :btree + t.index ["license_id"], name: "index_exercise_collections_on_license_id", using: :btree + t.index ["user_group_id"], name: "index_exercise_collections_on_user_group_id", using: :btree + t.index ["user_id"], name: "index_exercise_collections_on_user_id", using: :btree end - add_index "exercise_collections", ["course_offering_id"], name: "index_exercise_collections_on_course_offering_id", using: :btree - add_index "exercise_collections", ["license_id"], name: "index_exercise_collections_on_license_id", using: :btree - add_index "exercise_collections", ["user_group_id"], name: "index_exercise_collections_on_user_group_id", using: :btree - add_index "exercise_collections", ["user_id"], name: "index_exercise_collections_on_user_id", using: :btree - - create_table "exercise_families", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false + create_table "exercise_families", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" end - create_table "exercise_owners", force: :cascade do |t| - t.integer "exercise_id", limit: 4, null: false - t.integer "owner_id", limit: 4, null: false + create_table "exercise_owners", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "exercise_id", null: false + t.integer "owner_id", null: false + t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true, using: :btree + t.index ["owner_id"], name: "exercise_owners_owner_id_fk", using: :btree end - add_index "exercise_owners", ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true, using: :btree - add_index "exercise_owners", ["owner_id"], name: "exercise_owners_owner_id_fk", using: :btree - - create_table "exercise_versions", force: :cascade do |t| - t.integer "stem_id", limit: 4 + create_table "exercise_versions", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "stem_id" t.datetime "created_at" t.datetime "updated_at" t.integer "exercise_id", limit: 4, null: false @@ -213,107 +199,100 @@ t.integer "creator_id", limit: 4 t.integer "irt_data_id", limit: 4 t.text "text_representation", limit: 16777215 + t.index ["creator_id"], name: "exercise_versions_creator_id_fk", using: :btree + t.index ["exercise_id"], name: "index_exercise_versions_on_exercise_id", using: :btree + t.index ["irt_data_id"], name: "exercise_versions_irt_data_id_fk", using: :btree + t.index ["stem_id"], name: "index_exercise_versions_on_stem_id", using: :btree end - add_index "exercise_versions", ["creator_id"], name: "exercise_versions_creator_id_fk", using: :btree - add_index "exercise_versions", ["exercise_id"], name: "index_exercise_versions_on_exercise_id", using: :btree - add_index "exercise_versions", ["irt_data_id"], name: "exercise_versions_irt_data_id_fk", using: :btree - add_index "exercise_versions", ["stem_id"], name: "index_exercise_versions_on_stem_id", using: :btree - - create_table "exercise_versions_resource_files", id: false, force: :cascade do |t| - t.integer "exercise_version_id", limit: 4, null: false - t.integer "resource_file_id", limit: 4, null: false + create_table "exercise_versions_resource_files", id: false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "exercise_version_id", null: false + t.integer "resource_file_id", null: false + t.index ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id", using: :btree + t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id", using: :btree end - add_index "exercise_versions_resource_files", ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id", using: :btree - add_index "exercise_versions_resource_files", ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id", using: :btree - - create_table "exercise_workouts", force: :cascade do |t| - t.integer "exercise_id", limit: 4, null: false - t.integer "workout_id", limit: 4, null: false - t.integer "position", limit: 4, null: false + create_table "exercise_workouts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "exercise_id", null: false + t.integer "workout_id", null: false + t.integer "position", null: false t.float "points", limit: 24, default: 1.0 t.datetime "created_at" t.datetime "updated_at" + t.index ["exercise_id"], name: "exercise_workouts_exercise_id_fk", using: :btree + t.index ["workout_id"], name: "exercise_workouts_workout_id_fk", using: :btree end - add_index "exercise_workouts", ["exercise_id"], name: "exercise_workouts_exercise_id_fk", using: :btree - add_index "exercise_workouts", ["workout_id"], name: "exercise_workouts_workout_id_fk", using: :btree - - create_table "exercises", force: :cascade do |t| - t.integer "question_type", limit: 4, null: false - t.integer "current_version_id", limit: 4 + create_table "exercises", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "question_type", null: false + t.integer "current_version_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "versions", limit: 4 - t.integer "exercise_family_id", limit: 4 - t.string "name", limit: 255 - t.boolean "is_public", default: false, null: false - t.integer "experience", limit: 4, null: false - t.integer "irt_data_id", limit: 4 - t.string "external_id", limit: 255 - t.integer "exercise_collection_id", limit: 4 + t.integer "versions" + t.integer "exercise_family_id" + t.string "name" + t.boolean "is_public", default: false, null: false + t.integer "experience", null: false + t.integer "irt_data_id" + t.string "external_id" + t.integer "exercise_collection_id" + t.index ["current_version_id"], name: "index_exercises_on_current_version_id", using: :btree + t.index ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id", using: :btree + t.index ["exercise_family_id"], name: "index_exercises_on_exercise_family_id", using: :btree + t.index ["external_id"], name: "index_exercises_on_external_id", unique: true, using: :btree + t.index ["irt_data_id"], name: "exercises_irt_data_id_fk", using: :btree + t.index ["is_public"], name: "index_exercises_on_is_public", using: :btree end - add_index "exercises", ["current_version_id"], name: "index_exercises_on_current_version_id", using: :btree - add_index "exercises", ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id", using: :btree - add_index "exercises", ["exercise_family_id"], name: "index_exercises_on_exercise_family_id", using: :btree - add_index "exercises", ["external_id"], name: "index_exercises_on_external_id", unique: true, using: :btree - add_index "exercises", ["irt_data_id"], name: "exercises_irt_data_id_fk", using: :btree - add_index "exercises", ["is_public"], name: "index_exercises_on_is_public", using: :btree - - create_table "friendly_id_slugs", force: :cascade do |t| - t.string "slug", limit: 255, default: "", null: false - t.integer "sluggable_id", limit: 4, null: false + create_table "friendly_id_slugs", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "slug", default: "", null: false + t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 t.string "scope", limit: 255 t.datetime "created_at" + t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true, using: :btree + t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", using: :btree + t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id", using: :btree + t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree end - add_index "friendly_id_slugs", ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true, using: :btree - add_index "friendly_id_slugs", ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", using: :btree - add_index "friendly_id_slugs", ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id", using: :btree - add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree - - create_table "global_roles", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false - t.boolean "can_manage_all_courses", default: false, null: false - t.boolean "can_edit_system_configuration", default: false, null: false - t.boolean "builtin", default: false, null: false + create_table "global_roles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false + t.boolean "can_manage_all_courses", default: false, null: false + t.boolean "can_edit_system_configuration", default: false, null: false + t.boolean "builtin", default: false, null: false end - create_table "group_access_requests", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.integer "user_group_id", limit: 4 - t.boolean "pending", default: true + create_table "group_access_requests", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "user_id" + t.integer "user_group_id" + t.boolean "pending", default: true t.boolean "decision" t.datetime "created_at" t.datetime "updated_at" + t.index ["user_group_id"], name: "index_group_access_requests_on_user_group_id", using: :btree + t.index ["user_id"], name: "index_group_access_requests_on_user_id", using: :btree end - add_index "group_access_requests", ["user_group_id"], name: "index_group_access_requests_on_user_group_id", using: :btree - add_index "group_access_requests", ["user_id"], name: "index_group_access_requests_on_user_id", using: :btree - - create_table "identities", force: :cascade do |t| - t.integer "user_id", limit: 4, null: false - t.string "provider", limit: 255, default: "", null: false - t.string "uid", limit: 255, default: "", null: false + create_table "identities", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "user_id", null: false + t.string "provider", default: "", null: false + t.string "uid", default: "", null: false t.datetime "created_at" t.datetime "updated_at" + t.index ["uid", "provider"], name: "index_identities_on_uid_and_provider", using: :btree + t.index ["user_id"], name: "index_identities_on_user_id", using: :btree end - add_index "identities", ["uid", "provider"], name: "index_identities_on_uid_and_provider", using: :btree - add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree - - create_table "irt_data", force: :cascade do |t| - t.integer "attempt_count", limit: 4, null: false + create_table "irt_data", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "attempt_count", null: false t.float "sum_of_scores", limit: 24, null: false t.float "difficulty", limit: 24, null: false t.float "discrimination", limit: 24, null: false end - create_table "license_policies", force: :cascade do |t| - t.string "name", limit: 255 + create_table "license_policies", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name" t.text "description", limit: 65535 t.boolean "can_fork" t.boolean "is_public" @@ -321,61 +300,56 @@ t.datetime "updated_at" end - create_table "licenses", force: :cascade do |t| - t.string "name", limit: 255 + create_table "licenses", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name" t.text "description", limit: 65535 - t.string "url", limit: 255 - t.integer "license_policy_id", limit: 4 + t.string "url" + t.integer "license_policy_id" t.datetime "created_at" t.datetime "updated_at" + t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id", using: :btree end - add_index "licenses", ["license_policy_id"], name: "index_licenses_on_license_policy_id", using: :btree - - create_table "lms_instances", force: :cascade do |t| - t.string "consumer_key", limit: 255 - t.string "consumer_secret", limit: 255 + create_table "lms_instances", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "consumer_key" + t.string "consumer_secret" t.datetime "created_at" t.datetime "updated_at" - t.string "url", limit: 255 - t.integer "lms_type_id", limit: 4 - t.integer "organization_id", limit: 4 + t.string "url" + t.integer "lms_type_id" + t.integer "organization_id" + t.index ["lms_type_id"], name: "lms_instances_lms_type_id_fk", using: :btree + t.index ["organization_id"], name: "index_lms_instances_on_organization_id", using: :btree + t.index ["url"], name: "index_lms_instances_on_url", unique: true, using: :btree end - add_index "lms_instances", ["lms_type_id"], name: "lms_instances_lms_type_id_fk", using: :btree - add_index "lms_instances", ["organization_id"], name: "index_lms_instances_on_organization_id", using: :btree - add_index "lms_instances", ["url"], name: "index_lms_instances_on_url", unique: true, using: :btree - - create_table "lms_types", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false + create_table "lms_types", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" + t.index ["name"], name: "index_lms_types_on_name", unique: true, using: :btree end - add_index "lms_types", ["name"], name: "index_lms_types_on_name", unique: true, using: :btree - - create_table "lti_identities", force: :cascade do |t| - t.string "lti_user_id", limit: 255 - t.integer "user_id", limit: 4 - t.integer "lms_instance_id", limit: 4 + create_table "lti_identities", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "lti_user_id" + t.integer "user_id" + t.integer "lms_instance_id" t.datetime "created_at" t.datetime "updated_at" + t.index ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id", using: :btree + t.index ["user_id"], name: "index_lti_identities_on_user_id", using: :btree end - add_index "lti_identities", ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id", using: :btree - add_index "lti_identities", ["user_id"], name: "index_lti_identities_on_user_id", using: :btree - - create_table "lti_workouts", force: :cascade do |t| + create_table "lti_workouts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "workout_id", limit: 4 t.string "lms_assignment_id", limit: 255, null: false t.datetime "created_at" t.datetime "updated_at" t.integer "lms_instance_id", limit: 4 + t.index ["lms_instance_id"], name: "index_lti_workouts_on_lms_instance_id", using: :btree + t.index ["workout_id"], name: "index_lti_workouts_on_workout_id", using: :btree end - add_index "lti_workouts", ["lms_instance_id"], name: "index_lti_workouts_on_lms_instance_id", using: :btree - add_index "lti_workouts", ["workout_id"], name: "index_lti_workouts_on_workout_id", using: :btree - create_table "memberships", force: :cascade do |t| t.integer "user_id", limit: 4 t.integer "user_group_id", limit: 4 @@ -383,189 +357,177 @@ t.datetime "updated_at" end - create_table "multiple_choice_prompt_answers", force: :cascade do |t| + create_table "multiple_choice_prompt_answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| end - create_table "multiple_choice_prompts", force: :cascade do |t| + create_table "multiple_choice_prompts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.boolean "allow_multiple", default: false, null: false t.boolean "is_scrambled", default: true, null: false end - create_table "organizations", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false + create_table "organizations", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "abbreviation", limit: 255 - t.string "slug", limit: 255, default: "", null: false - t.boolean "is_hidden", default: false + t.string "abbreviation" + t.string "slug", default: "", null: false + t.boolean "is_hidden", default: false + t.index ["slug"], name: "index_organizations_on_slug", unique: true, using: :btree end - add_index "organizations", ["slug"], name: "index_organizations_on_slug", unique: true, using: :btree - - create_table "prompt_answers", force: :cascade do |t| - t.integer "attempt_id", limit: 4 - t.integer "prompt_id", limit: 4 - t.integer "actable_id", limit: 4 - t.string "actable_type", limit: 255 + create_table "prompt_answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "attempt_id" + t.integer "prompt_id" + t.integer "actable_id" + t.string "actable_type" + t.index ["actable_id"], name: "index_prompt_answers_on_actable_id", using: :btree + t.index ["attempt_id", "prompt_id"], name: "index_prompt_answers_on_attempt_id_and_prompt_id", unique: true, using: :btree + t.index ["attempt_id"], name: "index_prompt_answers_on_attempt_id", using: :btree + t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id", using: :btree end - add_index "prompt_answers", ["actable_id"], name: "index_prompt_answers_on_actable_id", using: :btree - add_index "prompt_answers", ["attempt_id", "prompt_id"], name: "index_prompt_answers_on_attempt_id_and_prompt_id", unique: true, using: :btree - add_index "prompt_answers", ["attempt_id"], name: "index_prompt_answers_on_attempt_id", using: :btree - add_index "prompt_answers", ["prompt_id"], name: "index_prompt_answers_on_prompt_id", using: :btree - - create_table "prompts", force: :cascade do |t| - t.integer "exercise_version_id", limit: 4, null: false + create_table "prompts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "exercise_version_id", null: false t.text "question", limit: 65535, null: false - t.integer "position", limit: 4, null: false + t.integer "position", null: false t.text "feedback", limit: 65535 t.datetime "created_at" t.datetime "updated_at" - t.integer "actable_id", limit: 4 - t.string "actable_type", limit: 255 - t.integer "irt_data_id", limit: 4 + t.integer "actable_id" + t.string "actable_type" + t.integer "irt_data_id" + t.index ["actable_id"], name: "index_prompts_on_actable_id", using: :btree + t.index ["exercise_version_id"], name: "index_prompts_on_exercise_version_id", using: :btree + t.index ["irt_data_id"], name: "prompts_irt_data_id_fk", using: :btree end - add_index "prompts", ["actable_id"], name: "index_prompts_on_actable_id", using: :btree - add_index "prompts", ["exercise_version_id"], name: "index_prompts_on_exercise_version_id", using: :btree - add_index "prompts", ["irt_data_id"], name: "prompts_irt_data_id_fk", using: :btree - - create_table "resource_files", force: :cascade do |t| - t.string "filename", limit: 255 - t.string "token", limit: 255, default: "", null: false - t.integer "user_id", limit: 4, null: false - t.boolean "public", default: true + create_table "resource_files", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "filename" + t.string "token", default: "", null: false + t.integer "user_id", null: false + t.boolean "public", default: true t.datetime "created_at" t.datetime "updated_at" + t.index ["token"], name: "index_resource_files_on_token", using: :btree + t.index ["user_id"], name: "index_resource_files_on_user_id", using: :btree end - add_index "resource_files", ["token"], name: "index_resource_files_on_token", using: :btree - add_index "resource_files", ["user_id"], name: "index_resource_files_on_user_id", using: :btree - - create_table "signups", force: :cascade do |t| - t.string "first_name", limit: 255 - t.string "last_name_name", limit: 255 - t.string "email", limit: 255 - t.string "institution", limit: 255 + create_table "signups", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "first_name" + t.string "last_name_name" + t.string "email" + t.string "institution" t.text "comments", limit: 65535 t.datetime "created_at" t.datetime "updated_at" end - create_table "stems", force: :cascade do |t| + create_table "stems", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.text "preamble", limit: 65535 t.datetime "created_at" t.datetime "updated_at" end - create_table "student_extensions", force: :cascade do |t| - t.integer "user_id", limit: 4 - t.integer "workout_offering_id", limit: 4 + create_table "student_extensions", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "user_id" + t.integer "workout_offering_id" t.datetime "soft_deadline" t.datetime "hard_deadline" t.datetime "created_at" t.datetime "updated_at" t.integer "time_limit", limit: 4 t.datetime "opening_date" + t.index ["user_id"], name: "index_student_extensions_on_user_id", using: :btree + t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id", using: :btree end - add_index "student_extensions", ["user_id"], name: "index_student_extensions_on_user_id", using: :btree - add_index "student_extensions", ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id", using: :btree - - create_table "tag_user_scores", force: :cascade do |t| - t.integer "user_id", limit: 4, null: false - t.integer "experience", limit: 4, default: 0 + create_table "tag_user_scores", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "user_id", null: false + t.integer "experience", default: 0 t.datetime "created_at" t.datetime "updated_at" - t.integer "completed_exercises", limit: 4, default: 0 + t.integer "completed_exercises", default: 0 + t.index ["user_id"], name: "index_tag_user_scores_on_user_id", using: :btree end - add_index "tag_user_scores", ["user_id"], name: "index_tag_user_scores_on_user_id", using: :btree - - create_table "taggings", force: :cascade do |t| - t.integer "tag_id", limit: 4 - t.integer "taggable_id", limit: 4 - t.string "taggable_type", limit: 255 - t.integer "tagger_id", limit: 4 - t.string "tagger_type", limit: 255 + create_table "taggings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" t.string "context", limit: 128 t.datetime "created_at" + t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree + t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree end - add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree - add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree - - create_table "tags", force: :cascade do |t| - t.string "name", limit: 255 - t.integer "taggings_count", limit: 4, default: 0 + create_table "tags", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name" + t.integer "taggings_count", default: 0 + t.index ["name"], name: "index_tags_on_name", unique: true, using: :btree end - add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree - - create_table "terms", force: :cascade do |t| - t.integer "season", limit: 4, null: false - t.date "starts_on", null: false - t.date "ends_on", null: false - t.integer "year", limit: 4, null: false + create_table "terms", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "season", null: false + t.date "starts_on", null: false + t.date "ends_on", null: false + t.integer "year", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "slug", limit: 255, default: "", null: false + t.string "slug", default: "", null: false + t.index ["slug"], name: "index_terms_on_slug", unique: true, using: :btree + t.index ["year", "season"], name: "index_terms_on_year_and_season", using: :btree end - add_index "terms", ["slug"], name: "index_terms_on_slug", unique: true, using: :btree - add_index "terms", ["year", "season"], name: "index_terms_on_year_and_season", using: :btree - - create_table "test_case_results", force: :cascade do |t| - t.integer "test_case_id", limit: 4, null: false - t.integer "user_id", limit: 4, null: false + create_table "test_case_results", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "test_case_id", null: false + t.integer "user_id", null: false t.text "execution_feedback", limit: 65535 - t.integer "feedback_line_no", limit: 4 t.datetime "created_at" t.datetime "updated_at" t.boolean "pass", null: false - t.integer "coding_prompt_answer_id", limit: 4 + t.integer "coding_prompt_answer_id" + t.index ["coding_prompt_answer_id"], name: "index_test_case_results_on_coding_prompt_answer_id", using: :btree + t.index ["test_case_id"], name: "index_test_case_results_on_test_case_id", using: :btree + t.index ["user_id"], name: "index_test_case_results_on_user_id", using: :btree end - add_index "test_case_results", ["coding_prompt_answer_id"], name: "index_test_case_results_on_coding_prompt_answer_id", using: :btree - add_index "test_case_results", ["test_case_id"], name: "index_test_case_results_on_test_case_id", using: :btree - add_index "test_case_results", ["user_id"], name: "index_test_case_results_on_user_id", using: :btree - - create_table "test_cases", force: :cascade do |t| + create_table "test_cases", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.text "negative_feedback", limit: 65535 t.float "weight", limit: 24, null: false t.text "description", limit: 65535 t.datetime "created_at" t.datetime "updated_at" - t.integer "coding_prompt_id", limit: 4, null: false + t.integer "coding_prompt_id", null: false t.text "input", limit: 65535, null: false t.text "expected_output", limit: 65535, null: false t.boolean "static", default: false, null: false t.boolean "screening", default: false, null: false t.boolean "example", default: false, null: false t.boolean "hidden", default: false, null: false + t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id", using: :btree end - add_index "test_cases", ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id", using: :btree - - create_table "time_zones", force: :cascade do |t| - t.string "name", limit: 255 - t.string "zone", limit: 255 - t.string "display_as", limit: 255 + create_table "time_zones", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name" + t.string "zone" + t.string "display_as" t.datetime "created_at" t.datetime "updated_at" end - create_table "user_groups", force: :cascade do |t| - t.string "name", limit: 255 + create_table "user_groups", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name" t.datetime "created_at" t.datetime "updated_at" t.text "description", limit: 65535 end - create_table "users", force: :cascade do |t| - t.string "email", limit: 255, default: "", null: false - t.string "encrypted_password", limit: 255, default: "", null: false - t.string "reset_password_token", limit: 255 + create_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", limit: 4, default: 0, null: false @@ -578,70 +540,65 @@ t.datetime "confirmation_sent_at" t.datetime "created_at" t.datetime "updated_at" - t.string "first_name", limit: 255 - t.string "last_name", limit: 255 - t.integer "global_role_id", limit: 4, null: false - t.string "avatar", limit: 255 - t.string "slug", limit: 255, default: "", null: false - t.integer "current_workout_score_id", limit: 4 - t.integer "time_zone_id", limit: 4 + t.string "first_name" + t.string "last_name" + t.integer "global_role_id", null: false + t.string "avatar" + t.string "slug", default: "", null: false + t.integer "current_workout_score_id" + t.integer "time_zone_id" + t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + t.index ["current_workout_score_id"], name: "index_users_on_current_workout_score_id", unique: true, using: :btree + t.index ["email"], name: "index_users_on_email", unique: true, using: :btree + t.index ["global_role_id"], name: "index_users_on_global_role_id", using: :btree + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + t.index ["slug"], name: "index_users_on_slug", unique: true, using: :btree + t.index ["time_zone_id"], name: "index_users_on_time_zone_id", using: :btree end - add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree - add_index "users", ["current_workout_score_id"], name: "index_users_on_current_workout_score_id", unique: true, using: :btree - add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["global_role_id"], name: "index_users_on_global_role_id", using: :btree - add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree - add_index "users", ["slug"], name: "index_users_on_slug", unique: true, using: :btree - add_index "users", ["time_zone_id"], name: "index_users_on_time_zone_id", using: :btree - - create_table "visualization_loggings", force: :cascade do |t| + create_table "visualization_loggings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id", limit: 4 t.integer "exercise_id", limit: 4 t.integer "workout_id", limit: 4 t.integer "workout_offering_id", limit: 4 t.datetime "created_at" t.datetime "updated_at" + t.index ["exercise_id"], name: "index_visualization_loggings_on_exercise_id", using: :btree + t.index ["user_id"], name: "index_visualization_loggings_on_user_id", using: :btree + t.index ["workout_id"], name: "index_visualization_loggings_on_workout_id", using: :btree + t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id", using: :btree end - add_index "visualization_loggings", ["exercise_id"], name: "index_visualization_loggings_on_exercise_id", using: :btree - add_index "visualization_loggings", ["user_id"], name: "index_visualization_loggings_on_user_id", using: :btree - add_index "visualization_loggings", ["workout_id"], name: "index_visualization_loggings_on_workout_id", using: :btree - add_index "visualization_loggings", ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id", using: :btree - - create_table "workout_offerings", force: :cascade do |t| - t.integer "course_offering_id", limit: 4, null: false - t.integer "workout_id", limit: 4, null: false + create_table "workout_offerings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "course_offering_id", null: false + t.integer "workout_id", null: false t.datetime "created_at" t.datetime "updated_at" t.datetime "opening_date" t.datetime "soft_deadline" t.datetime "hard_deadline" - t.boolean "published", default: true, null: false - t.integer "time_limit", limit: 4 - t.integer "workout_policy_id", limit: 4 - t.integer "continue_from_workout_id", limit: 4 - t.string "lms_assignment_id", limit: 255 - t.boolean "most_recent", default: true - t.string "lms_assignment_url", limit: 255 - t.integer "attempt_limit", limit: 4 - end - - add_index "workout_offerings", ["continue_from_workout_id"], name: "workout_offerings_continue_from_workout_id_fk", using: :btree - add_index "workout_offerings", ["course_offering_id"], name: "index_workout_offerings_on_course_offering_id", using: :btree - add_index "workout_offerings", ["lms_assignment_id"], name: "index_workout_offerings_on_lms_assignment_id", using: :btree - add_index "workout_offerings", ["workout_id"], name: "index_workout_offerings_on_workout_id", using: :btree - add_index "workout_offerings", ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id", using: :btree - - create_table "workout_owners", force: :cascade do |t| - t.integer "workout_id", limit: 4, null: false - t.integer "owner_id", limit: 4, null: false - end - - add_index "workout_owners", ["owner_id"], name: "workout_owners_owner_id_fk", using: :btree - add_index "workout_owners", ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true, using: :btree - - create_table "workout_policies", force: :cascade do |t| + t.boolean "published", default: true, null: false + t.integer "time_limit" + t.integer "workout_policy_id" + t.integer "continue_from_workout_id" + t.string "lms_assignment_id" + t.boolean "most_recent", default: true + t.string "lms_assignment_url" + t.index ["continue_from_workout_id"], name: "workout_offerings_continue_from_workout_id_fk", using: :btree + t.index ["course_offering_id"], name: "index_workout_offerings_on_course_offering_id", using: :btree + t.index ["lms_assignment_id"], name: "index_workout_offerings_on_lms_assignment_id", using: :btree + t.index ["workout_id"], name: "index_workout_offerings_on_workout_id", using: :btree + t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id", using: :btree + end + + create_table "workout_owners", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "workout_id", null: false + t.integer "owner_id", null: false + t.index ["owner_id"], name: "workout_owners_owner_id_fk", using: :btree + t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true, using: :btree + end + + create_table "workout_policies", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.boolean "hide_thumbnails_before_start" t.boolean "hide_feedback_before_finish" t.boolean "hide_compilation_feedback_before_finish" @@ -657,9 +614,9 @@ t.string "description", limit: 255 end - create_table "workout_scores", force: :cascade do |t| - t.integer "workout_id", limit: 4, null: false - t.integer "user_id", limit: 4, null: false + create_table "workout_scores", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.integer "workout_id", null: false + t.integer "user_id", null: false t.float "score", limit: 24 t.boolean "completed" t.datetime "completed_at" @@ -668,33 +625,29 @@ t.integer "exercises_remaining", limit: 4 t.datetime "created_at" t.datetime "updated_at" - t.integer "workout_offering_id", limit: 4 - t.string "lis_outcome_service_url", limit: 255 - t.string "lis_result_sourcedid", limit: 255 - t.integer "lti_workout_id", limit: 4 + t.integer "workout_offering_id" + t.string "lis_outcome_service_url" + t.string "lis_result_sourcedid" + t.index ["user_id"], name: "index_workout_scores_on_user_id", using: :btree + t.index ["workout_id"], name: "index_workout_scores_on_workout_id", using: :btree + t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk", using: :btree end - add_index "workout_scores", ["lti_workout_id"], name: "index_workout_scores_on_lti_workout_id", using: :btree - add_index "workout_scores", ["user_id"], name: "index_workout_scores_on_user_id", using: :btree - add_index "workout_scores", ["workout_id"], name: "index_workout_scores_on_workout_id", using: :btree - add_index "workout_scores", ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk", using: :btree - - create_table "workouts", force: :cascade do |t| - t.string "name", limit: 255, default: "", null: false + create_table "workouts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", default: "", null: false t.boolean "scrambled", default: false t.datetime "created_at" t.datetime "updated_at" t.text "description", limit: 65535 - t.integer "points_multiplier", limit: 4 - t.integer "creator_id", limit: 4 - t.string "external_id", limit: 255 + t.integer "points_multiplier" + t.integer "creator_id" + t.string "external_id" t.boolean "is_public" + t.index ["creator_id"], name: "workouts_creator_id_fk", using: :btree + t.index ["external_id"], name: "index_workouts_on_external_id", unique: true, using: :btree + t.index ["is_public"], name: "index_workouts_on_is_public", using: :btree end - add_index "workouts", ["creator_id"], name: "workouts_creator_id_fk", using: :btree - add_index "workouts", ["external_id"], name: "index_workouts_on_external_id", unique: true, using: :btree - add_index "workouts", ["is_public"], name: "index_workouts_on_is_public", using: :btree - add_foreign_key "attempts", "exercise_versions", name: "attempts_exercise_version_id_fk" add_foreign_key "attempts", "users", name: "attempts_user_id_fk" add_foreign_key "attempts", "workout_scores", column: "active_score_id", name: "attempts_active_score_id_fk" @@ -727,7 +680,7 @@ add_foreign_key "exercises", "irt_data", column: "irt_data_id", name: "exercises_irt_data_id_fk" add_foreign_key "identities", "users", name: "identities_user_id_fk" add_foreign_key "lms_instances", "lms_types", name: "lms_instances_lms_type_id_fk" - add_foreign_key "lti_workouts", "lms_instances" + add_foreign_key "lti_workouts", "lms_instances", name: "lti_workouts_lms_instance_id_fk" add_foreign_key "prompt_answers", "attempts", name: "prompt_answers_attempt_id_fk" add_foreign_key "prompt_answers", "prompts", name: "prompt_answers_prompt_id_fk" add_foreign_key "prompts", "exercise_versions", name: "prompts_exercise_version_id_fk" @@ -749,7 +702,7 @@ add_foreign_key "workout_offerings", "workouts", name: "workout_offerings_workout_id_fk" add_foreign_key "workout_owners", "users", column: "owner_id", name: "workout_owners_owner_id_fk" add_foreign_key "workout_owners", "workouts", name: "workout_owners_workout_id_fk" - add_foreign_key "workout_scores", "lti_workouts" + add_foreign_key "workout_scores", "lti_workouts", name: "workout_scores_lti_workout_id_fk" add_foreign_key "workout_scores", "users", name: "workout_scores_user_id_fk" add_foreign_key "workout_scores", "workout_offerings", name: "workout_scores_workout_offering_id_fk" add_foreign_key "workout_scores", "workouts", name: "workout_scores_workout_id_fk" From 72a75061091432ea7378e3e70aac8ec517310d1c Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 18 Dec 2018 13:44:26 +0530 Subject: [PATCH 002/117] Fix exercise-related active-record queries and asset precompilation which got overridden by bin/update --- app/models/exercise.rb | 19 ++++++++++--------- app/models/user.rb | 2 +- config/initializers/assets.rb | 3 ++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index ae9adacc..6c2781c1 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -98,11 +98,6 @@ class Exercise < ActiveRecord::Base 'C++' => 'cpp' } - - scope :visible_through_user, -> (u) { joins{exercise_owners.outer}.joins{exercise_collection.outer}. - where{ (exercise_owners.owner == u) | (exercise_collection.user == u) } } - - #~ Class methods ............................................................ # ------------------------------------------------------------- @@ -136,8 +131,13 @@ def self.search(terms, user = nil) end end + def self.visible_through_user(user) + return Exercise.left_outer_joins(:exercise_owners) + .left_outer_joins(:exercise_collection) + .where('exercise_owners.owner_id = ? or exercise_collections.user_id = ?', + user.id, user.id) + end - # ------------------------------------------------------------- # Get a list of Exercises that are visible to the specified user. # # It is the union of exercises that are publicly visible, created or owned by the user, @@ -163,9 +163,9 @@ def self.visible_to_user(user) visible_through_user_group = Exercise.visible_through_user_group(user) return visible_through_user - .union(publicly_visible) + .union(Exercise.publicly_visible) .union(visible_through_course_offering) - .union(visible_through_user_group) + .union(Exercise.visible_through_user_group(user)) end @@ -186,7 +186,8 @@ def self.publicly_visible public_exercise = Exercise.where(is_public: true) - return public_exercise.union(public_license) + return Exercise.joins(exercise_collection: [ license: :license_policy ]) + .where('(exercises.is_public is null and license_policies.is_public = true) or exercises.is_public = true') end diff --git a/app/models/user.rb b/app/models/user.rb index 94cd6dc3..6f557d97 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -107,7 +107,7 @@ class User < ActiveRecord::Base } scope :alphabetical, -> { order('last_name asc, first_name asc, email asc') } - scope :visible_to_user, -> (u) { joins{course_enrollments.outer}. + scope :visible_to_user, -> (u) { left_outer_joins(:course_enrollments) where{ (id == u.id) | (course_enrollments.course_role_id != CourseRole::STUDENT_ID) } } diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index ca44ed1f..8690d4a5 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -16,5 +16,6 @@ path !~ /bootstrap-social/ && path !~ /active_admin/ && %w(.js .css).include?(File.extname(filename)) - }, /application.(css|.js)$/ + }, /application.(css|.js)$/, + %w(*.png *.jpg *.jpeg *.gif *.mustache.html) ] From 2e81b94fed8d4ef25f8de30261e9008c14f66e41 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 18 Dec 2018 20:23:39 +0530 Subject: [PATCH 003/117] Use SQL instead of ActiveRecord hashes for complex where queries, because rails is not inferring table names correctly. --- app/models/user.rb | 28 +++++++++++++--------------- app/models/workout.rb | 7 ++++--- config/initializers/inflections.rb | 5 +++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 6f557d97..4135a9bf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -224,23 +224,21 @@ def managed_course_offerings(options = {}) course_enrollments.where(course_roles: { can_manage_course: true }). map(&:course_offering) elsif course.nil? - course_enrollments.joins(:course_offering). - where(course_roles: - { can_manage_course: true }, course_offering: - { term: term } - ).map(&:course_offering) + course_enrollments.joins(:course_offering) + .where('course_roles.can_manage_course = true and + course_offerings.term_id = ?', term.id) + .map(&:course_offering) elsif term.nil? - course_enrollments.joins(:course_offering). - where(course_roles: - { can_manage_course: true }, course_offering: - { course: course } - ).map(&:course_offering) + course_enrollments.joins(:course_offering) + .where('course_roles.can_manage_course = true and + course_offerings.course_id = ?', course.id) + .map(&:course_offering) else - course_enrollments.joins(:course_offering). - where(course_roles: - { can_manage_course: true }, course_offering: - { course: course, term: term } - ).map(&:course_offering) + course_enrollments.joins(:course_offering) + .where('course_roles.can_manage_course = true and + course_offerings.course_id = ? and course_offerings.term_id = ?', + course.id, term.id) + .map(&:course_offering) end end diff --git a/app/models/workout.rb b/app/models/workout.rb index 3e9897a2..973ea794 100644 --- a/app/models/workout.rb +++ b/app/models/workout.rb @@ -72,12 +72,13 @@ class Workout < ActiveRecord::Base #~ Hooks .................................................................... - scope :visible_to_user, -> (u) { where { (creator_id == u.id) | (is_public == true) } } # paginates_per 1 - #~ Class methods ............................................................ + def self.visible_to_user(user) + return Workout.where(creator_id: user.id).or(Workout.where(is_public: true)) + end #~ Instance methods ......................................................... @@ -359,7 +360,7 @@ def self.search(terms, user, course, searching_offerings) if user available_workouts = Workout.where( - id: (Workout.visible_to_user(user) + user.managed_workouts) + id: (Workout.visible_to_user(user).union(user.managed_workouts)) .map(&:id) ) diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ac033bf9..01553713 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -3,12 +3,13 @@ # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| +ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) -# end + inflect.acronym 'IRT' +end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| From b622a460d61675b100bdf714b5c0485fcaf039a2 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Thu, 20 Dec 2018 12:28:04 +0530 Subject: [PATCH 004/117] Replace squeel usage with vanilla active record. --- app/models/ability.rb | 26 +++++++++++++------------- app/models/workout_score.rb | 21 ++++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index f7e39b26..247374e0 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -210,12 +210,12 @@ def process_exercises(user) can :practice, Exercise do |e| now = Time.now - e.visible_to?(user) || WorkoutOffering. - joins{workout.exercises}.joins{course_offering.course_enrollments}. - where{ - ((starts_on == nil) | (starts_on <= now)) & - course_offering.course_enrollments.user_id == user.id - }.any? + e.visible_to?(user) || WorkoutOffering + .joins(workout: :exercises, course_offering: [:course_enrollments, :term]) + .where( + '(terms.starts_on is ? or terms.starts_on <= ?) and + course_enrollments.user_id = ?', nil, now, now + ).any? end can [ :gym_practice, :embed ], Exercise do |e| @@ -224,13 +224,13 @@ def process_exercises(user) can :evaluate, Exercise do |e| now = Time.now - WorkoutOffering. - joins{workout.exercises}.joins{course_offering.course_enrollments}. - where{ - ((starts_on == nil) | (starts_on <= now)) & - ((hard_deadline >= now) | (soft_deadline >= now)) & - course_offering.course_enrollments.user_id == user.id - }.any? + WorkoutOffering + .joins(workout: :exercises, course_offering: [:course_enrollments, :term]) + .where( + '(terms.starts_on is ? or terms.starts_on <= ?) + and (workout_offerings.hard_deadline >= ? or workout_offerings.soft_deadline >= ?) + and course_enrollments.user_id = ?', nil, now, now, now, user.id + ).any? end # can :create, Exercise if user.global_role.is_instructor? # can :edit, Exercise do |e| diff --git a/app/models/workout_score.rb b/app/models/workout_score.rb index 582a21fb..306cdfbc 100644 --- a/app/models/workout_score.rb +++ b/app/models/workout_score.rb @@ -183,16 +183,19 @@ def attempts_left_for_exercise_version(exercise_version) # ------------------------------------------------------------- def scoring_attempt_for(exercise) workout_score = self - Attempt.joins{exercise_version}. - where{(active_score_id == workout_score.id) & - (exercise_version.exercise_id == exercise.id)}.first + Attempt.joins(:exercise_version) + .where( + 'attempts.active_score_id = ? and + exericise_versions.exercise_id = ?', workout_score.id, exercise.id + ).first end # ------------------------------------------------------------- def previous_attempt_for(exercise) - attempts.joins{exercise_version}. - where{exercise_version.exercise_id == exercise.id}.first + attempts.joins(:exercise_version) + .where('exercise_versions.exercise_id = ?', exercise.id) + .first end @@ -309,7 +312,7 @@ def retotal # Class method to find workout scores that were computed after # they were closed. Outputs a list of workout scores. def self.late(options={}) - WorkoutScore.joins{ workout_offering } + WorkoutScore.joins(:workout_offering) .joins('inner join student_extensions on student_extensions.workout_offering_id = workout_offerings.id and student_extensions.user_id = workout_scores.user_id') .where('workout_scores.last_attempted_at > @@ -329,9 +332,9 @@ def self.score_fix1 end end ws.workout.exercises.each do |e| - a = ws.attempts.joins{exercise_version}. - where{(exercise_version.exercise_id == e.id)}. - order('submit_time DESC').first + a = ws.attempts.joins(:exercise_version) + .where('exercise_versions.exercise_id = ?', e.id) + .order('submit_time DESC').first if a a.active_score = ws if !a.save From 8562ecba65b218f5963e96d2d6d5a4d067966566 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 22 Jan 2019 11:12:19 -0500 Subject: [PATCH 005/117] Manually fix merge conflicts in lock file --- Gemfile.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5c8a05bd..1459baba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -284,6 +284,7 @@ GEM modernizr-rails (2.7.1) momentjs-rails (2.20.1) railties (>= 3.1) + momentjs-rails (2.20.1) multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.1.1) @@ -384,6 +385,7 @@ GEM actionpack (>= 4.2.0, < 6.0) railties (>= 4.2.0, < 6.0) rest-client (2.0.2) + diff-lcs (>= 1.2.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) @@ -472,11 +474,8 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.6) - unicode-display_width (1.6.0) - unicode_utils (1.4.0) wannabe_bool (0.7.1) warden (1.2.7) - rack (>= 1.0) websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) From 350854453ef356c0c3b01542b30fa350eabd83a2 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 22 Jan 2019 12:02:42 -0500 Subject: [PATCH 006/117] Update factory bot for incoming changes --- db/schema.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index f59a963c..46174b2e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -556,11 +556,11 @@ t.index ["time_zone_id"], name: "index_users_on_time_zone_id", using: :btree end - create_table "visualization_loggings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id", limit: 4 - t.integer "exercise_id", limit: 4 - t.integer "workout_id", limit: 4 - t.integer "workout_offering_id", limit: 4 + create_table "visualization_loggings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| + t.integer "user_id" + t.integer "exercise_id" + t.integer "workout_id" + t.integer "workout_offering_id" t.datetime "created_at" t.datetime "updated_at" t.index ["exercise_id"], name: "index_visualization_loggings_on_exercise_id", using: :btree From 0fd6b2293a3a63b7e78fe973dc7d48e5c9c44df8 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 22 Jan 2019 14:14:55 -0500 Subject: [PATCH 007/117] Rails 5.1 --- Gemfile | 9 +- Gemfile.lock | 129 +++++++++--------- app/helpers/application_helper.rb | 3 +- app/views/sse/_ajax_feedback.html.haml | 2 +- bin/setup | 4 + bin/yarn | 11 ++ config/application.rb | 2 +- config/cable.yml | 1 + config/environments/development.rb | 4 +- config/environments/production.rb | 7 +- config/environments/test.rb | 2 +- config/initializers/assets.rb | 8 +- config/initializers/cookies_serializer.rb | 2 +- .../new_framework_defaults_5_1.rb | 14 ++ config/locales/en.yml | 10 ++ config/puma.rb | 23 +++- config/secrets.yml | 18 ++- 17 files changed, 162 insertions(+), 87 deletions(-) create mode 100755 bin/yarn create mode 100644 config/initializers/new_framework_defaults_5_1.rb diff --git a/Gemfile b/Gemfile index 9153c4f4..8d53d5d2 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'rails', '>= 5.0.0.rc2', '< 5.1' +gem 'rails', '~> 5.1' gem 'bootstrap-sass', '~> 3.2.0' gem 'jquery-rails' gem 'jquery-ui-rails' @@ -12,7 +12,7 @@ gem 'formtastic-bootstrap' gem 'sucker_punch', '~> 1.0' gem 'haml', '>= 3.1.4' gem 'haml-rails' -gem 'coffee-rails', '~> 4.0.0' +gem 'coffee-rails', '~> 4.2' gem 'coffee-script-source' gem 'test-unit', '~> 3.0.9' gem 'nokogiri', '~> 1.10.4' @@ -48,14 +48,11 @@ group :development, :test do gem 'thin' gem 'byebug' gem 'sqlite3', '~> 1.3.0' + gem 'listen' gem 'rspec-rails' gem 'annotate' gem 'rails-erd', github: 'voormedia/rails-erd' gem 'faker' - # Needed for debugging support in Aptana Studio. Disabled, since these - # two gems do not support Ruby 2.0 yet :-(. - # gem 'ruby-debug-base' - # gem 'ruby-debug-ide' gem 'pry' gem 'request-log-analyzer' end diff --git a/Gemfile.lock b/Gemfile.lock index 1459baba..b19531ae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,29 +26,29 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.0.0.rc2) - actionpack (= 5.0.0.rc2) - nio4r (~> 1.2) + actioncable (5.1.1) + actionpack (= 5.1.1) + nio4r (~> 2.0) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.rc2) - actionpack (= 5.0.0.rc2) - actionview (= 5.0.0.rc2) - activejob (= 5.0.0.rc2) + actionmailer (5.1.1) + actionpack (= 5.1.1) + actionview (= 5.1.1) + activejob (= 5.1.1) mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (5.0.0.rc2) - actionview (= 5.0.0.rc2) - activesupport (= 5.0.0.rc2) - rack (~> 2.x) + rails-dom-testing (~> 2.0) + actionpack (5.1.1) + actionview (= 5.1.1) + activesupport (= 5.1.1) + rack (~> 2.0) rack-test (~> 0.6.3) - rails-dom-testing (~> 1.0, >= 1.0.5) + rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.rc2) - activesupport (= 5.0.0.rc2) + actionview (5.1.1) + activesupport (= 5.1.1) builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) active_record-acts_as (3.0.2) activerecord (>= 4.2) activesupport (>= 4.2) @@ -66,18 +66,18 @@ GEM ransack (>= 1.8.7) sass (~> 3.1) sprockets (< 4.1) - activejob (5.0.0.rc2) - activesupport (= 5.0.0.rc2) + activejob (5.1.1) + activesupport (= 5.1.1) globalid (>= 0.3.6) - activemodel (5.0.0.rc2) - activesupport (= 5.0.0.rc2) - activerecord (5.0.0.rc2) - activemodel (= 5.0.0.rc2) - activesupport (= 5.0.0.rc2) - arel (~> 7.0) + activemodel (5.1.1) + activesupport (= 5.1.1) + activerecord (5.1.1) + activemodel (= 5.1.1) + activesupport (= 5.1.1) + arel (~> 8.0) activerecord-import (0.17.2) activerecord (>= 3.2) - activesupport (5.0.0.rc2) + activesupport (5.1.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) @@ -95,7 +95,7 @@ GEM rake (>= 10.4, < 13.0) arbre (1.2.1) activesupport (>= 3.0.0) - arel (7.1.4) + arel (8.0.0) autoprefixer-rails (9.2.1) execjs awesome_print (1.8.0) @@ -155,9 +155,9 @@ GEM codemirror-rails (5.16.0) railties (>= 3.0, < 6.0) coderay (1.1.2) - coffee-rails (4.0.1) + coffee-rails (4.2.2) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.0) + railties (>= 4.0.0) coffee-script (2.4.1) coffee-script-source execjs @@ -179,7 +179,7 @@ GEM diff-lcs (1.3) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - equatable (0.6.1) + erubi (1.8.0) erubis (2.7.0) eventmachine (1.2.7) exception_handler (0.8.0.0) @@ -268,6 +268,10 @@ GEM activerecord kaminari-core (= 1.2.1) kaminari-core (1.2.1) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) log_file (0.0.3) loofah (2.5.0) crass (~> 1.0.2) @@ -293,9 +297,12 @@ GEM net-ssh (>= 2.6.5, < 6.0.0) net-ssh (5.2.0) netrc (0.11.0) - nio4r (2.5.2) - nokogiri (1.10.9) - mini_portile2 (~> 2.4.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + nio4r (2.3.1) + nokogiri (1.8.1) + mini_portile2 (~> 2.3.0) oauth (0.5.4) oauth2 (1.4.1) faraday (>= 0.8, < 0.16.0) @@ -339,29 +346,27 @@ GEM rack (2.0.6) rack-test (0.6.3) rack (>= 1.0) - rails (5.0.0.rc2) - actioncable (= 5.0.0.rc2) - actionmailer (= 5.0.0.rc2) - actionpack (= 5.0.0.rc2) - actionview (= 5.0.0.rc2) - activejob (= 5.0.0.rc2) - activemodel (= 5.0.0.rc2) - activerecord (= 5.0.0.rc2) - activesupport (= 5.0.0.rc2) + rails (5.1.1) + actioncable (= 5.1.1) + actionmailer (= 5.1.1) + actionpack (= 5.1.1) + actionview (= 5.1.1) + activejob (= 5.1.1) + activemodel (= 5.1.1) + activerecord (= 5.1.1) + activesupport (= 5.1.1) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.rc2) + railties (= 5.1.1) sprockets-rails (>= 2.0.0) - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.9) - activesupport (>= 4.2.0, < 5.0) - nokogiri (~> 1.6) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (4.2.11.1) - actionpack (= 4.2.11.1) - activesupport (= 4.2.11.1) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.1.1) + actionpack (= 5.1.1) + activesupport (= 5.1.1) + method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (12.3.3) @@ -405,9 +410,10 @@ GEM rspec-expectations (~> 3.8.0) rspec-mocks (~> 3.8.0) rspec-support (~> 3.8.0) - rspec-support (3.8.2) - ruby-graphviz (1.2.4) - ruby_parser (3.13.1) + rspec-support (3.8.0) + ruby-graphviz (1.2.3) + ruby_dep (1.5.0) + ruby_parser (3.11.0) sexp_processor (~> 4.9) rubyzip (1.3.0) sass (3.7.4) @@ -509,7 +515,7 @@ DEPENDENCIES capybara cocoon codemirror-rails - coffee-rails (~> 4.0.0) + coffee-rails (~> 4.2) coffee-script-source css3-progress-bar-rails csv_shaper @@ -529,7 +535,8 @@ DEPENDENCIES jquery-rails jquery-ui-rails js-routes - kaminari (~> 1.2.1) + kaminari + listen log_file loofah (>= 2.3.1) modernizr-rails @@ -545,7 +552,7 @@ DEPENDENCIES pry puma (~> 4.3.5) rabl - rails (>= 5.0.0.rc2, < 5.1) + rails (~> 5.1) rails-erd! redcarpet remotipart diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 66e36d00..4c8553b2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -205,7 +205,8 @@ def button_to_with_style( puts "html_options = #{html_options}" button_to_without_style(name, options, html_options, &block) end - alias_method_chain :button_to, :style + alias_method :button_to_without_style, :button_to + alias_method :button_to, :button_to_with_style # ------------------------------------------------------------- diff --git a/app/views/sse/_ajax_feedback.html.haml b/app/views/sse/_ajax_feedback.html.haml index 0b4d5f3f..1172ca74 100644 --- a/app/views/sse/_ajax_feedback.html.haml +++ b/app/views/sse/_ajax_feedback.html.haml @@ -49,7 +49,7 @@ %th Behavior %th Result %tbody - - answer.test_case_results(true).each do |tcr| + - answer.test_case_results.each do |tcr| %tr - if tcr.pass %td.pass diff --git a/bin/setup b/bin/setup index e620b4da..78c4e861 100755 --- a/bin/setup +++ b/bin/setup @@ -18,6 +18,10 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/yarn b/bin/yarn new file mode 100755 index 00000000..c2bacef8 --- /dev/null +++ b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +VENDOR_PATH = File.expand_path('..', __dir__) +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config/application.rb b/config/application.rb index 4f5b85e6..d95398a5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -8,7 +8,7 @@ module CodeWorkout class Application < Rails::Application - # Initialize configuration defaults for originally generated Rails version + # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.1 Rails.application.config.active_record.belongs_to_required_by_default = false # Settings in config/environments/* take precedence over those specified here. diff --git a/config/cable.yml b/config/cable.yml index 0bbde6f7..156e7a0b 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -7,3 +7,4 @@ test: production: adapter: redis url: redis://localhost:6379/1 + channel_prefix: code_workout_production diff --git a/config/environments/development.rb b/config/environments/development.rb index e64889cd..5187e221 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -18,7 +18,7 @@ config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=172800' + 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" } else config.action_controller.perform_caching = false @@ -50,5 +50,5 @@ # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - # config.file_watcher = ActiveSupport::EventedFileUpdateChecker + config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/config/environments/production.rb b/config/environments/production.rb index 14f1f1f7..0b701344 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -14,6 +14,11 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Attempt to read encrypted secrets from `config/secrets.yml.enc`. + # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or + # `config/secrets.yml.key`. + config.read_encrypted_secrets = true + # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? @@ -78,7 +83,7 @@ if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter - config.logger = ActiveSupport::TaggedLogging.new(logger) + config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. diff --git a/config/environments/test.rb b/config/environments/test.rb index 30587ef6..8e5cbde5 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -15,7 +15,7 @@ # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => 'public, max-age=3600' + 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" } # Show full error reports and disable caching. diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 8690d4a5..05850aa8 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -10,6 +10,12 @@ # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # Rails.application.config.assets.precompile += %w( search.js ) +# Add additional assets to the asset load path. +# Rails.application.config.assets.paths << Emoji.images_path +# Add Yarn node_modules folder to the asset load path. +Rails.application.config.assets.paths << Rails.root.join('node_modules') +Rails.application.config.assets.paths << Rails.root.join('public') + Rails.application.config.assets.precompile = [ Proc.new { |filename, path| path =~ /app\/assets/ && @@ -17,5 +23,5 @@ path !~ /active_admin/ && %w(.js .css).include?(File.extname(filename)) }, /application.(css|.js)$/, - %w(*.png *.jpg *.jpeg *.gif *.mustache.html) + %w(*.png *.jpg *.jpeg *.gif *.mustache.html, *.ico) ] diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb index 1389e86a..5a6a32d3 100644 --- a/config/initializers/cookies_serializer.rb +++ b/config/initializers/cookies_serializer.rb @@ -2,4 +2,4 @@ # Specify a serializer for the signed and encrypted cookie jars. # Valid options are :json, :marshal, and :hybrid. -Rails.application.config.action_dispatch.cookies_serializer = :marshal +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/config/initializers/new_framework_defaults_5_1.rb b/config/initializers/new_framework_defaults_5_1.rb new file mode 100644 index 00000000..9010abd5 --- /dev/null +++ b/config/initializers/new_framework_defaults_5_1.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.1 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Make `form_with` generate non-remote forms. +Rails.application.config.action_view.form_with_generates_remote_forms = false + +# Unknown asset fallback will return the path passed in when the given +# asset is not present in the asset pipeline. +# Rails.application.config.assets.unknown_asset_fallback = false diff --git a/config/locales/en.yml b/config/locales/en.yml index b1934892..4a4a2d6e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -16,6 +16,16 @@ # # This would use the information in config/locales/es.yml. # +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +# 'true': 'foo' +# # To learn more, please read the Rails Internationalization guide # available at http://guides.rubyonrails.org/i18n.html. diff --git a/config/puma.rb b/config/puma.rb index c7f311f8..1e19380d 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,13 +1,13 @@ # Puma can serve each request in a thread from an internal thread pool. -# The `threads` method setting takes two numbers a minimum and maximum. +# The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum -# and maximum, this matches the default thread size of Active Record. +# and maximum; this matches the default thread size of Active Record. # -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count -# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 3000 } @@ -32,16 +32,25 @@ # # preload_app! +# If you are preloading your application and using Active Record, it's +# recommended that you close any connections to the database before workers +# are forked to prevent connection leakage. +# +# before_fork do +# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) +# end + # The code in the `on_worker_boot` will be called if you are using # clustered mode by specifying a number of `workers`. After each worker -# process is booted this block will be run, if you are using `preload_app!` -# option you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, Ruby +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby # cannot share connections between processes. # # on_worker_boot do # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) # end +# # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/secrets.yml b/config/secrets.yml index 7e8ea854..01d254be 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -10,13 +10,23 @@ # Make sure the secrets in this file are kept private # if you're sharing your code publicly. +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + development: - secret_key_base: eff0aae935bd44c6013779062036ce365082fadabfa7944f1b1455e9468fbf3ed613a988b884590965e40105c8db9ee47f3777802fb839e089ad9e565690e126 + secret_key_base: 99d725b98ec18eac5e0491b3bfa7dc56c397dbd9ac48e72b55cf642fb6f89fb0214795e4d4b243d9d6337f14a5463c6d974c8d02146c663e03da838c5ff9c3db test: - secret_key_base: 0394fa40ba5b3ac258de3ac21e19844491cc15f84775b31cc081103e856203148bf0cab364e6c9dee9c59852ab063554c7c67af503d65ee46aaf18a5d76cde90 + secret_key_base: 2f5b62caef54d44556fea8ffe3b0a57b1c670b4dee5c338b3c65f4b5a0bda073d9cc51888ef8101ce274640df51e6cce32c50489dbde5eb0ba712a65302c81d8 + +# Do not keep production secrets in the unencrypted secrets file. +# Instead, either read values from the environment. +# Or, use `bin/rails secrets:setup` to configure encrypted secrets +# and move the `production:` environment over there. -# Do not keep production secrets in the repository, -# instead read values from the environment. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> From aeb2902bc9ef8ed53c5b7a5228a5c328d0d5ff71 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 22 Jan 2019 15:14:39 -0500 Subject: [PATCH 008/117] Rails 5.2 --- Gemfile | 3 +- Gemfile.lock | 206 +++++++++--------- bin/bundle | 2 +- bin/setup | 4 +- bin/update | 6 +- bin/yarn | 6 +- config/boot.rb | 1 + config/cable.yml | 2 +- config/environments/development.rb | 11 +- config/environments/production.rb | 11 +- config/environments/test.rb | 6 +- .../application_controller_renderer.rb | 10 +- .../initializers/content_security_policy.rb | 25 +++ .../new_framework_defaults_5_2.rb | 38 ++++ config/storage.yml | 34 +++ 15 files changed, 242 insertions(+), 123 deletions(-) create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/new_framework_defaults_5_2.rb create mode 100644 config/storage.yml diff --git a/Gemfile b/Gemfile index 8d53d5d2..5a308b73 100644 --- a/Gemfile +++ b/Gemfile @@ -18,7 +18,7 @@ gem 'test-unit', '~> 3.0.9' gem 'nokogiri', '~> 1.10.4' gem 'csv_shaper' gem 'andand', github: 'raganwald/andand' -gem 'responders' # Can't move above 1.1 until migrating to rails 4.2+ +gem 'responders' gem 'friendly_id', '~> 5' gem 'active_record-acts_as' gem 'acts_as_list' @@ -32,6 +32,7 @@ gem 'active_record_union' gem 'mysql2', '~> 0.3.20' gem 'modernizr-rails' gem 'rubyzip', '>= 1.3.0' +gem 'bootsnap' # Added during Rails 5.2 upgrade # For JSON support gem 'rabl' diff --git a/Gemfile.lock b/Gemfile.lock index b19531ae..6f984859 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,13 @@ +GIT + remote: git://github.com/activeadmin-plugins/active_admin_import.git + revision: 859d94e2fd00620034bffd803d528c8beefdc7ce + specs: + active_admin_import (4.1.0) + activeadmin (>= 1.0.0.pre2) + activerecord-import (~> 0.27) + rchardet (~> 1.6) + rubyzip (~> 1.2) + GIT remote: git://github.com/raganwald/andand.git revision: d6c4545b6649c70495c26e2038206c5fdb2d14d6 @@ -15,7 +25,7 @@ GIT GIT remote: git://github.com/voormedia/rails-erd.git - revision: 0fbb1cdf2c84b06afd12974baace8d512bb798da + revision: e666706a11b703607542f1b14576204c7987ca67 specs: rails-erd (1.6.0) activerecord (>= 4.2) @@ -26,25 +36,25 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.1.1) - actionpack (= 5.1.1) + actioncable (5.2.2) + actionpack (= 5.2.2) nio4r (~> 2.0) - websocket-driver (~> 0.6.1) - actionmailer (5.1.1) - actionpack (= 5.1.1) - actionview (= 5.1.1) - activejob (= 5.1.1) + websocket-driver (>= 0.6.1) + actionmailer (5.2.2) + actionpack (= 5.2.2) + actionview (= 5.2.2) + activejob (= 5.2.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.1.1) - actionview (= 5.1.1) - activesupport (= 5.1.1) + actionpack (5.2.2) + actionview (= 5.2.2) + activesupport (= 5.2.2) rack (~> 2.0) - rack-test (~> 0.6.3) + rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.1) - activesupport (= 5.1.1) + actionview (5.2.2) + activesupport (= 5.2.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -66,40 +76,46 @@ GEM ransack (>= 1.8.7) sass (~> 3.1) sprockets (< 4.1) - activejob (5.1.1) - activesupport (= 5.1.1) + activejob (5.2.2) + activesupport (= 5.2.2) globalid (>= 0.3.6) - activemodel (5.1.1) - activesupport (= 5.1.1) - activerecord (5.1.1) - activemodel (= 5.1.1) - activesupport (= 5.1.1) - arel (~> 8.0) - activerecord-import (0.17.2) + activemodel (5.2.2) + activesupport (= 5.2.2) + activerecord (5.2.2) + activemodel (= 5.2.2) + activesupport (= 5.2.2) + arel (>= 9.0) + activerecord-import (0.28.1) activerecord (>= 3.2) - activesupport (5.1.1) + activestorage (5.2.2) + actionpack (= 5.2.2) + activerecord (= 5.2.2) + marcel (~> 0.3.1) + activesupport (5.2.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - acts-as-taggable-on (5.0.0) - activerecord (>= 4.2.8) - acts_as_list (0.9.19) + acts-as-taggable-on (6.0.0) + activerecord (~> 5.0) + acts_as_list (0.9.17) activerecord (>= 3.0) addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) - airbrussh (1.3.2) + airbrussh (1.3.1) sshkit (>= 1.6.1, != 1.7.0) annotate (2.7.5) activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 13.0) arbre (1.2.1) activesupport (>= 3.0.0) - arel (8.0.0) - autoprefixer-rails (9.2.1) + arel (9.0.0) + autoprefixer-rails (9.4.6) execjs awesome_print (1.8.0) - bcrypt (3.1.13) + bcrypt (3.1.12) + bootsnap (1.3.2) + msgpack (~> 1.0) bootstrap-editable-rails (0.0.9) railties (>= 3.1) sass-rails @@ -117,7 +133,7 @@ GEM i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.6.0) + capistrano-bundler (1.5.0) capistrano (~> 3.1) capistrano-rails (1.4.0) capistrano (~> 3.1) @@ -125,7 +141,7 @@ GEM capistrano-rvm (0.1.2) capistrano (~> 3.0) sshkit (~> 1.2) - capybara (3.15.1) + capybara (3.12.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -194,16 +210,11 @@ GEM railties (>= 4.2.0) faker (1.9.4) i18n (>= 0.7) - pastel (~> 0.7.2) - thor (~> 0.20.0) - tty-pager (~> 0.12.0) - tty-screen (~> 0.6.5) - tty-tree (~> 0.3.0) faraday (0.15.4) multipart-post (>= 1.2, < 3) - ffi (1.11.1) - font-awesome-rails (4.7.0.5) - railties (>= 3.2, < 6.1) + ffi (1.10.0) + font-awesome-rails (4.7.0.4) + railties (>= 3.2, < 6.0) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic-bootstrap (3.1.1) @@ -234,7 +245,7 @@ GEM ruby_parser (~> 3.5) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (0.9.5) + i18n (1.5.3) concurrent-ruby (~> 1.0) immigrant (0.3.6) activerecord (>= 3.0) @@ -278,28 +289,28 @@ GEM nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) method_source (0.9.2) mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) + mime-types-data (3.2018.0812) + mimemagic (0.3.3) mini_mime (1.0.1) mini_portile2 (2.4.0) minitest (5.14.1) modernizr-rails (2.7.1) momentjs-rails (2.20.1) railties (>= 3.1) - momentjs-rails (2.20.1) + msgpack (1.2.6) multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.1.1) mysql2 (0.3.21) - net-scp (2.0.0) - net-ssh (>= 2.6.5, < 6.0.0) - net-ssh (5.2.0) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-ssh (5.1.0) netrc (0.11.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) nio4r (2.3.1) nokogiri (1.8.1) mini_portile2 (~> 2.3.0) @@ -321,7 +332,7 @@ GEM omniauth (~> 1.2) omniauth-facebook (5.0.0) omniauth-oauth2 (~> 1.2) - omniauth-google-oauth2 (0.7.0) + omniauth-google-oauth2 (0.6.0) jwt (>= 2.0) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.5) @@ -329,59 +340,55 @@ GEM oauth2 (~> 1.1) omniauth (~> 1.9) orm_adapter (0.5.0) - pastel (0.7.3) - equatable (~> 0.6) - tty-color (~> 0.5) - polyamorous (1.1.0) - activerecord (>= 3.0) - power_assert (1.1.4) + power_assert (1.1.3) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) - public_suffix (3.1.1) - puma (4.3.5) - nio4r (~> 2.0) - rabl (0.14.1) + public_suffix (3.0.3) + puma (3.12.0) + rabl (0.14.0) activesupport (>= 2.3.14) rack (2.0.6) - rack-test (0.6.3) - rack (>= 1.0) - rails (5.1.1) - actioncable (= 5.1.1) - actionmailer (= 5.1.1) - actionpack (= 5.1.1) - actionview (= 5.1.1) - activejob (= 5.1.1) - activemodel (= 5.1.1) - activerecord (= 5.1.1) - activesupport (= 5.1.1) - bundler (>= 1.3.0, < 2.0) - railties (= 5.1.1) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.2) + actioncable (= 5.2.2) + actionmailer (= 5.2.2) + actionpack (= 5.2.2) + actionview (= 5.2.2) + activejob (= 5.2.2) + activemodel (= 5.2.2) + activerecord (= 5.2.2) + activestorage (= 5.2.2) + activesupport (= 5.2.2) + bundler (>= 1.3.0) + railties (= 5.2.2) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.0.4) loofah (~> 2.2, >= 2.2.2) - railties (5.1.1) - actionpack (= 5.1.1) - activesupport (= 5.1.1) + railties (5.2.2) + actionpack (= 5.2.2) + activesupport (= 5.2.2) method_source rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (12.3.3) - ransack (1.8.10) - actionpack (>= 3.0, < 5.2) - activerecord (>= 3.0, < 5.2) - activesupport (>= 3.0, < 5.2) + thor (>= 0.19.0, < 2.0) + rake (12.3.2) + ransack (2.1.1) + actionpack (>= 5.0) + activerecord (>= 5.0) + activesupport (>= 5.0) i18n rb-fsevent (0.10.3) rb-inotify (0.10.0) ffi (~> 1.0) + rchardet (1.8.0) rdoc (6.1.1) redcarpet (3.4.0) - regexp_parser (1.5.1) - remotipart (1.4.3) + regexp_parser (1.3.0) + remotipart (1.4.2) representable (2.4.1) declarative (~> 0.0.5) uber (~> 0.0.15) @@ -390,16 +397,15 @@ GEM actionpack (>= 4.2.0, < 6.0) railties (>= 4.2.0, < 6.0) rest-client (2.0.2) - diff-lcs (>= 1.2.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rspec-core (3.8.2) + rspec-core (3.8.0) rspec-support (~> 3.8.0) - rspec-expectations (3.8.4) + rspec-expectations (3.8.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) - rspec-mocks (3.8.1) + rspec-mocks (3.8.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-rails (3.8.2) @@ -411,12 +417,12 @@ GEM rspec-mocks (~> 3.8.0) rspec-support (~> 3.8.0) rspec-support (3.8.0) - ruby-graphviz (1.2.3) + ruby-graphviz (1.2.4) ruby_dep (1.5.0) - ruby_parser (3.11.0) + ruby_parser (3.12.0) sexp_processor (~> 4.9) - rubyzip (1.3.0) - sass (3.7.4) + rubyzip (1.2.2) + sass (3.7.3) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -438,11 +444,7 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.13) - squeel (1.2.3) - activerecord (>= 3.0) - activesupport (>= 3.0) - polyamorous (~> 1.1.0) - sshkit (1.19.1) + sshkit (1.18.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) strings (0.1.5) @@ -481,8 +483,9 @@ GEM unf_ext unf_ext (0.0.7.6) wannabe_bool (0.7.1) - warden (1.2.7) - websocket-driver (0.6.5) + warden (1.2.8) + rack (>= 2.0.6) + websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) xpath (3.2.0) @@ -501,6 +504,7 @@ DEPENDENCIES annotate autoprefixer-rails awesome_print + bootsnap bootstrap-editable-rails bootstrap-sass-backport (~> 3.2.0) bootstrap-wysihtml5-rails diff --git a/bin/bundle b/bin/bundle index 66e9889e..f19acf5b 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/setup b/bin/setup index 78c4e861..94fd4d79 100755 --- a/bin/setup +++ b/bin/setup @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -21,7 +20,6 @@ chdir APP_ROOT do # Install JavaScript dependencies if using Yarn # system('bin/yarn') - # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/update b/bin/update index a8e4462f..58bfaed5 100755 --- a/bin/update +++ b/bin/update @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -18,6 +17,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/bin/yarn b/bin/yarn index c2bacef8..460dd565 100755 --- a/bin/yarn +++ b/bin/yarn @@ -1,8 +1,8 @@ #!/usr/bin/env ruby -VENDOR_PATH = File.expand_path('..', __dir__) -Dir.chdir(VENDOR_PATH) do +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do begin - exec "yarnpkg #{ARGV.join(" ")}" + exec "yarnpkg", *ARGV rescue Errno::ENOENT $stderr.puts "Yarn executable was not detected in the system." $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" diff --git a/config/boot.rb b/config/boot.rb index 30f5120d..b9e460ce 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,3 +1,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. +require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/cable.yml b/config/cable.yml index 156e7a0b..546768c1 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -6,5 +6,5 @@ test: production: adapter: redis - url: redis://localhost:6379/1 + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> channel_prefix: code_workout_production diff --git a/config/environments/development.rb b/config/environments/development.rb index 5187e221..1311e3e4 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -13,12 +13,13 @@ config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. - if Rails.root.join('tmp/caching-dev.txt').exist? + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -26,6 +27,9 @@ config.cache_store = :null_store end + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false @@ -37,6 +41,9 @@ # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. diff --git a/config/environments/production.rb b/config/environments/production.rb index 0b701344..0da40324 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -14,10 +14,9 @@ config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Attempt to read encrypted secrets from `config/secrets.yml.enc`. - # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or - # `config/secrets.yml.key`. - config.read_encrypted_secrets = true + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. @@ -39,6 +38,9 @@ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Mount Action Cable outside main process or domain # config.action_cable.mount_path = nil # config.action_cable.url = 'wss://example.com/cable' @@ -60,6 +62,7 @@ # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "code_workout_#{Rails.env}" + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. diff --git a/config/environments/test.rb b/config/environments/test.rb index 8e5cbde5..0a38fd3c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -15,7 +15,7 @@ # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. @@ -27,6 +27,10 @@ # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb index 51639b67..89d2efab 100644 --- a/config/initializers/application_controller_renderer.rb +++ b/config/initializers/application_controller_renderer.rb @@ -1,6 +1,8 @@ # Be sure to restart your server when you modify this file. -# ApplicationController.renderer.defaults.merge!( -# http_host: 'example.org', -# https: false -# ) +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 00000000..d3bcaa5e --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/new_framework_defaults_5_2.rb b/config/initializers/new_framework_defaults_5_2.rb new file mode 100644 index 00000000..c383d072 --- /dev/null +++ b/config/initializers/new_framework_defaults_5_2.rb @@ -0,0 +1,38 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.2 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Make Active Record use stable #cache_key alongside new #cache_version method. +# This is needed for recyclable cache keys. +# Rails.application.config.active_record.cache_versioning = true + +# Use AES-256-GCM authenticated encryption for encrypted cookies. +# Also, embed cookie expiry in signed or encrypted cookies for increased security. +# +# This option is not backwards compatible with earlier Rails versions. +# It's best enabled when your entire app is migrated and stable on 5.2. +# +# Existing cookies will be converted on read then written with the new scheme. +# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true + +# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages +# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true. +# Rails.application.config.active_support.use_authenticated_message_encryption = true + +# Add default protection from forgery to ActionController::Base instead of in +# ApplicationController. +# Rails.application.config.action_controller.default_protect_from_forgery = true + +# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and +# 'f' after migrating old data. +# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + +# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. +# Rails.application.config.active_support.use_sha1_digests = true + +# Make `form_with` generate id attributes for any generated HTML tags. +# Rails.application.config.action_view.form_with_generates_ids = true diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 00000000..d32f76e8 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] From 9a5b61879c54f25607d528b77bb6801f0391cf92 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 22 Jan 2019 15:29:39 -0500 Subject: [PATCH 009/117] Fix deploy syntax --- config/deploy/staging.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index fdf4cd0c..9fdcd097 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -17,7 +17,7 @@ # definition into the server list. The second argument # something that quacks like a hash can be used to set # extended properties on the server. -server 'codeworkoutdev.cs.vt.edu', user: 'codeworkout', roles: %w{web app db}, branch: 'staging', my_property: :my_value +server 'codeworkoutdev.cs.vt.edu', user: 'codeworkout', roles: %w{web app db}, branch: 'rails-5-upgrade', my_property: :my_value # you can set custom ssh options # it's possible to pass any option but you need to keep in mind that net/ssh understand limited list of options From e026bb37671b550e603010315061f9af4e85950d Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 22 Jan 2019 15:35:15 -0500 Subject: [PATCH 010/117] Remove obsolete config option; update mysql2 --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- config/initializers/new_framework_defaults.rb | 3 --- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 5a308b73..5dd67eae 100644 --- a/Gemfile +++ b/Gemfile @@ -29,7 +29,7 @@ gem 'loofah', '>= 2.3.1' gem 'truncate_html' gem 'tzinfo' # For timezone support gem 'active_record_union' -gem 'mysql2', '~> 0.3.20' +gem 'mysql2', '~> 0.4' gem 'modernizr-rails' gem 'rubyzip', '>= 1.3.0' gem 'bootsnap' # Added during Rails 5.2 upgrade diff --git a/Gemfile.lock b/Gemfile.lock index 6f984859..b81abe39 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -305,8 +305,8 @@ GEM msgpack (1.2.6) multi_json (1.13.1) multi_xml (0.6.0) - multipart-post (2.1.1) - mysql2 (0.3.21) + multipart-post (2.0.0) + mysql2 (0.5.2) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (5.1.0) @@ -545,8 +545,8 @@ DEPENDENCIES loofah (>= 2.3.1) modernizr-rails momentjs-rails (>= 2.9.0) - mysql2 (~> 0.3.20) - nokogiri (~> 1.10.4) + mysql2 (~> 0.4) + nokogiri (= 1.8.1) oj (~> 2.16) oj_mimic_json omniauth diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb index f6e72335..2cb708e7 100644 --- a/config/initializers/new_framework_defaults.rb +++ b/config/initializers/new_framework_defaults.rb @@ -18,6 +18,3 @@ # Require `belongs_to` associations by default. Previous versions had false. Rails.application.config.active_record.belongs_to_required_by_default = false - -# Do not halt callback chains when a callback returns false. Previous versions had true. -ActiveSupport.halt_callback_chains_on_return_false = true From 867339bdc9324f4834419b8bdd846804fb1d04dc Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 9 Jun 2020 15:36:22 -0400 Subject: [PATCH 011/117] Fix schema issues and start Dockerizing --- .ruby-version | 2 +- Dockerfile | 92 +++++++ Gemfile | 3 +- Gemfile.lock | 419 ++++++++++++++--------------- app/assets/config/manifest.js | 3 + code-workout.sublime-project | 8 - config/application.rb | 2 +- config/environments/development.rb | 1 + db/development.db | 0 db/schema.rb | 108 ++++---- docker-run.sh | 33 +++ runservers.sh | 2 +- start.sh | 45 ++++ 13 files changed, 433 insertions(+), 285 deletions(-) create mode 100644 Dockerfile create mode 100644 app/assets/config/manifest.js delete mode 100644 code-workout.sublime-project delete mode 100644 db/development.db create mode 100644 docker-run.sh create mode 100755 start.sh diff --git a/.ruby-version b/.ruby-version index bc4abe86..24ba9a38 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.8 +2.7.0 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f5f1ae75 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,92 @@ +FROM ruby:2.7.1 +#FROM ruby:2.3.3 + +MAINTAINER Jihane Najdi + +#Default environment +ARG RAILS_ENV='development' +ARG BASEDIR='/code-workout/' + +ENV TZ=America/New_York +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +#install 'development tools' build-essential dkms curl libxslt-dev libpq-dev python-dev python-pip python-feedvalidator python-software-properties python-sphinx libmariadbclient-dev libcurl4-gnutls-dev libevent-dev libffi-dev libssl-dev stunnel4 libsqlite3-dev +# libmariadbclient-dev +RUN apt-get update -qq \ + && apt-get install -y apt-utils build-essential libpq-dev vim cron curl \ + && apt-get install -y nodejs npm python-pip git-core zlib1g-dev libssl-dev libreadline-dev libyaml-dev libevent-dev libsqlite3-dev libsqlite3-dev libxml2-dev libxml2 libxslt1-dev libffi-dev libxslt-dev sqlite3 dkms python-dev python-feedvalidator python-sphinx \ + && pip install --upgrade pip +#// libcurl4-openssl-dev +# #// libcurl4 + +#RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 \ +# && apt-get install -y apt-transport-https ca-certificates \ +# && sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main > /etc/apt/sources.list.d/passenger.list' \ +# && apt-get update \ +# && apt-get install -y passenger + +# install rubygems +ENV GEM_HOME /usr/local/bundle +ENV PATH $GEM_HOME/bin:$PATH +ENV BUNDLER_VERSION 2.1.4 +ENV RAILS_ENV=$RAILS_ENV + +RUN gem install bundler -v $BUNDLER_VERSION \ + && bundle config --global path "$GEM_HOME" \ + && bundle config --global bin "$GEM_HOME/bin" \ + && bundle config git.allow_insecure true + +# Create a user with the same UID as host user so we have permissions +# RUN useradd -r -u ${UID} appuser +# USER appuser + +VOLUME ${BASEDIR} +WORKDIR ${BASEDIR} + +COPY Gemfile Gemfile +COPY Gemfile.lock Gemfile.lock + +RUN bundle update +RUN bundle check || bundle install + +COPY runservers.sh runservers.sh + +RUN find /code-workout -type d -exec chmod 2775 {} \; +RUN find /code-workout -type f -exec chmod 0644 {} \; +RUN find ./runservers.sh -type f -exec chmod +x {} \; + +EXPOSE 80 +#EXPOSE 3000 + +# Create the log file +# RUN touch /code-workout/log/development.log + +# Redirecting logs to Dockerlog collector accesslogs (/proc/1/fd/1) errorlogs (/proc/self/fd/2) +#RUN ln -sf /proc/1/fd/1 /opendsa-lti/log/development.log + +# Clone OpenDSA +# RUN mkdir /opendsa +# RUN git clone https://github.com/OpenDSA/OpenDSA.git /opendsa +# RUN pip install -r /opendsa/requirements.txt --upgrade +# RUN ln -s /opendsa /opendsa-lti/public/OpenDSA +# RUN ln -s "$(which nodejs)" /usr/local/bin/node +# RUN cp /opendsa-lti/postprocessor.py /opendsa-lti/public/OpenDSA/tools/postprocessor.py + +## JAVA INSTALLATION +RUN apt-get install -y default-jre + +### OpenDSA libraries for Python 2.7 +# RUN pip install --upgrade beautifulsoup4 +# RUN pip install --upgrade html5lib + +#ln -s /opendsa/RST /opendsa-lti/RST +#ln -s /opendsa/config /opendsa-lti/Configuration +#rm /opendsa-ltiConfiguration/config +#rm /opendsa-lti/RST/RST + +##RUN bundle +#RUN echo $PATH + +CMD ["bash", "./runservers.sh"] + +#CMD tail -f /dev/null & wait diff --git a/Gemfile b/Gemfile index 5dd67eae..842b93d7 100644 --- a/Gemfile +++ b/Gemfile @@ -56,13 +56,12 @@ group :development, :test do gem 'faker' gem 'pry' gem 'request-log-analyzer' + gem 'capybara', '~> 3.12.0' end gem 'factory_bot_rails' gem 'log_file' group :test do - gem 'sqlite3' - gem 'capybara' end group :production, :staging, :deploy do diff --git a/Gemfile.lock b/Gemfile.lock index b81abe39..b53b069b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,22 +1,12 @@ GIT - remote: git://github.com/activeadmin-plugins/active_admin_import.git - revision: 859d94e2fd00620034bffd803d528c8beefdc7ce - specs: - active_admin_import (4.1.0) - activeadmin (>= 1.0.0.pre2) - activerecord-import (~> 0.27) - rchardet (~> 1.6) - rubyzip (~> 1.2) - -GIT - remote: git://github.com/raganwald/andand.git + remote: https://github.com/raganwald/andand.git revision: d6c4545b6649c70495c26e2038206c5fdb2d14d6 specs: andand (1.3.3) GIT - remote: git://github.com/seuros/capistrano-puma.git - revision: 6112323390cff15539d947882d72d937622cfdf4 + remote: https://github.com/seuros/capistrano-puma.git + revision: 87bfd747bd43a19b5d214e0272b76fb723cf1f5f specs: capistrano3-puma (4.0.0) capistrano (~> 3.7) @@ -24,8 +14,8 @@ GIT puma (~> 4.0) GIT - remote: git://github.com/voormedia/rails-erd.git - revision: e666706a11b703607542f1b14576204c7987ca67 + remote: https://github.com/voormedia/rails-erd.git + revision: 0fbb1cdf2c84b06afd12974baace8d512bb798da specs: rails-erd (1.6.0) activerecord (>= 4.2) @@ -36,106 +26,103 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.2.2) - actionpack (= 5.2.2) + actioncable (5.2.4.3) + actionpack (= 5.2.4.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.2) - actionpack (= 5.2.2) - actionview (= 5.2.2) - activejob (= 5.2.2) + actionmailer (5.2.4.3) + actionpack (= 5.2.4.3) + actionview (= 5.2.4.3) + activejob (= 5.2.4.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.2) - actionview (= 5.2.2) - activesupport (= 5.2.2) - rack (~> 2.0) + actionpack (5.2.4.3) + actionview (= 5.2.4.3) + activesupport (= 5.2.4.3) + rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.2) - activesupport (= 5.2.2) + actionview (5.2.4.3) + activesupport (= 5.2.4.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_record-acts_as (3.0.2) + active_record-acts_as (4.0.1) activerecord (>= 4.2) activesupport (>= 4.2) active_record_union (1.3.0) activerecord (>= 4.0) - activeadmin (1.4.3) - arbre (>= 1.1.1) - coffee-rails + activeadmin (2.7.0) + arbre (~> 1.2, >= 1.2.1) formtastic (~> 3.1) - formtastic_i18n - inherited_resources (>= 1.9.0) - jquery-rails (>= 4.2.0) - kaminari (>= 0.15) - railties (>= 4.2, < 5.3) - ransack (>= 1.8.7) - sass (~> 3.1) - sprockets (< 4.1) - activejob (5.2.2) - activesupport (= 5.2.2) + formtastic_i18n (~> 0.4) + inherited_resources (~> 1.7) + jquery-rails (~> 4.2) + kaminari (~> 1.0, >= 1.0.1) + railties (>= 5.2, < 6.1) + ransack (~> 2.1, >= 2.1.1) + sassc-rails (~> 2.1) + sprockets (>= 3.0, < 4.1) + activejob (5.2.4.3) + activesupport (= 5.2.4.3) globalid (>= 0.3.6) - activemodel (5.2.2) - activesupport (= 5.2.2) - activerecord (5.2.2) - activemodel (= 5.2.2) - activesupport (= 5.2.2) + activemodel (5.2.4.3) + activesupport (= 5.2.4.3) + activerecord (5.2.4.3) + activemodel (= 5.2.4.3) + activesupport (= 5.2.4.3) arel (>= 9.0) - activerecord-import (0.28.1) - activerecord (>= 3.2) - activestorage (5.2.2) - actionpack (= 5.2.2) - activerecord (= 5.2.2) + activestorage (5.2.4.3) + actionpack (= 5.2.4.3) + activerecord (= 5.2.4.3) marcel (~> 0.3.1) - activesupport (5.2.2) + activesupport (5.2.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - acts-as-taggable-on (6.0.0) - activerecord (~> 5.0) - acts_as_list (0.9.17) - activerecord (>= 3.0) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) - airbrussh (1.3.1) + acts-as-taggable-on (6.5.0) + activerecord (>= 5.0, < 6.1) + acts_as_list (1.0.1) + activerecord (>= 4.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) - annotate (2.7.5) + annotate (3.1.1) activerecord (>= 3.2, < 7.0) - rake (>= 10.4, < 13.0) + rake (>= 10.4, < 14.0) arbre (1.2.1) activesupport (>= 3.0.0) arel (9.0.0) - autoprefixer-rails (9.4.6) + autoprefixer-rails (9.7.6) execjs awesome_print (1.8.0) - bcrypt (3.1.12) - bootsnap (1.3.2) + bcrypt (3.1.13) + bootsnap (1.4.6) msgpack (~> 1.0) bootstrap-editable-rails (0.0.9) railties (>= 3.1) sass-rails - bootstrap-sass-backport (3.2.0.2) + bootstrap-sass (3.2.0.4) sass (~> 3.2) bootstrap-wysihtml5-rails (0.3.3.8) railties (>= 3.0) bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) builder (3.2.4) - byebug (11.0.1) - cancancan (3.0.1) - capistrano (3.11.0) + byebug (11.1.3) + cancancan (3.1.0) + capistrano (3.14.0) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.5.0) + capistrano-bundler (1.6.0) capistrano (~> 3.1) - capistrano-rails (1.4.0) + capistrano-rails (1.5.0) capistrano (~> 3.1) capistrano-bundler (~> 1.1) capistrano-rvm (0.1.2) @@ -170,7 +157,7 @@ GEM cocoon (1.2.14) codemirror-rails (5.16.0) railties (>= 3.0, < 6.0) - coderay (1.1.2) + coderay (1.1.3) coffee-rails (4.2.2) coffee-script (>= 2.2.0) railties (>= 4.0.0) @@ -182,7 +169,7 @@ GEM crass (1.0.6) css3-progress-bar-rails (0.5.1) rails (>= 3.1) - csv_shaper (1.3.0) + csv_shaper (1.3.1) activesupport (>= 3.0.0) daemons (1.3.1) declarative (0.0.10) @@ -193,9 +180,9 @@ GEM responders warden (~> 1.2.3) diff-lcs (1.3) - domain_name (0.5.20180417) + domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - erubi (1.8.0) + erubi (1.9.0) erubis (2.7.0) eventmachine (1.2.7) exception_handler (0.8.0.0) @@ -203,67 +190,68 @@ GEM rails (>= 4.2.0) responders execjs (2.7.0) - factory_bot (5.0.2) + factory_bot (5.2.0) activesupport (>= 4.2.0) - factory_bot_rails (5.0.2) - factory_bot (~> 5.0.2) + factory_bot_rails (5.2.0) + factory_bot (~> 5.2.0) railties (>= 4.2.0) - faker (1.9.4) - i18n (>= 0.7) - faraday (0.15.4) + faker (2.12.0) + i18n (>= 1.6, < 2) + faraday (1.0.1) multipart-post (>= 1.2, < 3) - ffi (1.10.0) - font-awesome-rails (4.7.0.4) - railties (>= 3.2, < 6.0) + ffi (1.13.0) + font-awesome-rails (4.7.0.5) + railties (>= 3.2, < 6.1) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic-bootstrap (3.1.1) formtastic (>= 3.0) formtastic_i18n (0.6.0) - friendly_id (5.2.5) + friendly_id (5.3.0) activerecord (>= 4.0.0) globalid (0.4.2) activesupport (>= 4.2.0) gravtastic (3.2.6) - haml (5.1.1) + haml (5.1.2) temple (>= 0.8.0) tilt - haml-rails (1.0.0) - actionpack (>= 4.0.1) - activesupport (>= 4.0.1) + haml-rails (2.0.1) + actionpack (>= 5.1) + activesupport (>= 5.1) haml (>= 4.0.6, < 6.0) html2haml (>= 1.0.1) - railties (>= 4.0.1) + railties (>= 5.1) has_scope (0.7.2) actionpack (>= 4.1) activesupport (>= 4.1) - hashie (3.6.0) + hashie (4.1.0) html2haml (2.2.0) erubis (~> 2.7.0) haml (>= 4.0, < 6) nokogiri (>= 1.6.0) ruby_parser (~> 3.5) + http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (1.5.3) + i18n (1.8.3) concurrent-ruby (~> 1.0) immigrant (0.3.6) activerecord (>= 3.0) ims-lti (1.1.13) builder oauth (>= 0.4.5, < 0.6) - inherited_resources (1.9.0) - actionpack (>= 4.2, < 5.3) + inherited_resources (1.11.0) + actionpack (>= 5.0, < 6.1) has_scope (~> 0.6) - railties (>= 4.2, < 5.3) - responders - jquery-rails (4.3.5) + railties (>= 5.0, < 6.1) + responders (>= 2, < 4) + jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-ui-rails (6.0.1) railties (>= 3.2.16) - js-routes (1.4.7) + js-routes (1.4.9) railties (>= 4) sprockets-rails jwt (2.2.1) @@ -279,10 +267,9 @@ GEM activerecord kaminari-core (= 1.2.1) kaminari-core (1.2.1) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) + listen (3.2.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) log_file (0.0.3) loofah (2.5.0) crass (~> 1.0.2) @@ -291,152 +278,160 @@ GEM mini_mime (>= 0.1.1) marcel (0.3.3) mimemagic (~> 0.3.2) - method_source (0.9.2) - mime-types (3.2.2) + method_source (1.0.0) + mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2018.0812) - mimemagic (0.3.3) - mini_mime (1.0.1) + mime-types-data (3.2020.0512) + mimemagic (0.3.5) + mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.1) modernizr-rails (2.7.1) momentjs-rails (2.20.1) railties (>= 3.1) - msgpack (1.2.6) - multi_json (1.13.1) + msgpack (1.3.3) + multi_json (1.14.1) multi_xml (0.6.0) - multipart-post (2.0.0) - mysql2 (0.5.2) - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-ssh (5.1.0) + multipart-post (2.1.1) + mysql2 (0.5.3) + net-scp (3.0.0) + net-ssh (>= 2.6.5, < 7.0.0) + net-ssh (6.0.2) netrc (0.11.0) - nio4r (2.3.1) - nokogiri (1.8.1) - mini_portile2 (~> 2.3.0) + nio4r (2.5.2) + nokogiri (1.10.9) + mini_portile2 (~> 2.4.0) oauth (0.5.4) - oauth2 (1.4.1) - faraday (>= 0.8, < 0.16.0) + oauth2 (1.4.4) + faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) oj (2.18.5) oj_mimic_json (1.0.1) - omniauth (1.9.0) - hashie (>= 3.4.6, < 3.7.0) + omniauth (1.9.1) + hashie (>= 3.4.6) rack (>= 1.6.2, < 3) omniauth-cas (1.1.1) addressable (~> 2.3) nokogiri (~> 1.5) omniauth (~> 1.2) - omniauth-facebook (5.0.0) + omniauth-facebook (6.0.0) omniauth-oauth2 (~> 1.2) - omniauth-google-oauth2 (0.6.0) + omniauth-google-oauth2 (0.8.0) jwt (>= 2.0) omniauth (>= 1.1.1) - omniauth-oauth2 (>= 1.5) + omniauth-oauth2 (>= 1.6) omniauth-oauth2 (1.6.0) oauth2 (~> 1.1) omniauth (~> 1.9) orm_adapter (0.5.0) - power_assert (1.1.3) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - public_suffix (3.0.3) - puma (3.12.0) - rabl (0.14.0) + polyamorous (2.3.2) + activerecord (>= 5.2.1) + power_assert (1.2.0) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.5) + puma (4.3.5) + nio4r (~> 2.0) + rabl (0.14.3) activesupport (>= 2.3.14) - rack (2.0.6) + rack (2.2.2) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.2) - actioncable (= 5.2.2) - actionmailer (= 5.2.2) - actionpack (= 5.2.2) - actionview (= 5.2.2) - activejob (= 5.2.2) - activemodel (= 5.2.2) - activerecord (= 5.2.2) - activestorage (= 5.2.2) - activesupport (= 5.2.2) + rails (5.2.4.3) + actioncable (= 5.2.4.3) + actionmailer (= 5.2.4.3) + actionpack (= 5.2.4.3) + actionview (= 5.2.4.3) + activejob (= 5.2.4.3) + activemodel (= 5.2.4.3) + activerecord (= 5.2.4.3) + activestorage (= 5.2.4.3) + activesupport (= 5.2.4.3) bundler (>= 1.3.0) - railties (= 5.2.2) + railties (= 5.2.4.3) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.2.2) - actionpack (= 5.2.2) - activesupport (= 5.2.2) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.3) + actionpack (= 5.2.4.3) + activesupport (= 5.2.4.3) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) - rake (12.3.2) - ransack (2.1.1) - actionpack (>= 5.0) - activerecord (>= 5.0) - activesupport (>= 5.0) + rake (13.0.1) + ransack (2.3.2) + activerecord (>= 5.2.1) + activesupport (>= 5.2.1) i18n - rb-fsevent (0.10.3) - rb-inotify (0.10.0) + polyamorous (= 2.3.2) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) ffi (~> 1.0) - rchardet (1.8.0) - rdoc (6.1.1) - redcarpet (3.4.0) - regexp_parser (1.3.0) - remotipart (1.4.2) + rdoc (6.2.1) + redcarpet (3.5.0) + regexp_parser (1.7.1) + remotipart (1.4.4) representable (2.4.1) declarative (~> 0.0.5) uber (~> 0.0.15) request-log-analyzer (1.13.4) - responders (2.4.1) - actionpack (>= 4.2.0, < 6.0) - railties (>= 4.2.0, < 6.0) - rest-client (2.0.2) + responders (3.0.1) + actionpack (>= 5.0) + railties (>= 5.0) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) + rexml (3.2.4) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - ruby-graphviz (1.2.4) - ruby_dep (1.5.0) - ruby_parser (3.12.0) + rspec-support (~> 3.9.0) + rspec-rails (4.0.1) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) + rspec-support (3.9.3) + ruby-graphviz (1.2.5) + rexml + ruby_parser (3.14.2) sexp_processor (~> 4.9) - rubyzip (1.2.2) - sass (3.7.3) + rubyzip (2.3.0) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sdoc (1.0.0) + sass-rails (6.0.0) + sassc-rails (~> 2.1, >= 2.1.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + sdoc (1.1.0) rdoc (>= 5.0) - sexp_processor (4.12.1) - sprockets (3.7.2) + sexp_processor (4.14.1) + sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.1) @@ -444,50 +439,37 @@ GEM activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.13) - sshkit (1.18.0) + sshkit (1.21.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - strings (0.1.5) - strings-ansi (~> 0.1) - unicode-display_width (~> 1.5) - unicode_utils (~> 1.4) - strings-ansi (0.1.0) sucker_punch (1.6.0) celluloid (~> 0.17.2) - temple (0.8.1) + temple (0.8.2) test-unit (3.0.9) power_assert thin (1.7.2) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (0.20.3) + thor (1.0.1) thread_safe (0.3.6) - tilt (2.0.9) + tilt (2.0.10) timers (4.3.0) truncate_html (0.9.3) - tty-color (0.5.0) - tty-pager (0.12.1) - strings (~> 0.1.4) - tty-screen (~> 0.6) - tty-which (~> 0.4) - tty-screen (0.6.5) - tty-tree (0.3.0) - tty-which (0.4.1) tzinfo (1.2.7) thread_safe (~> 0.1) uber (0.0.15) - uglifier (4.1.20) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.7.6) + unf_ext (0.0.7.7) wannabe_bool (0.7.1) warden (1.2.8) rack (>= 2.0.6) - websocket-driver (0.7.0) + websocket-driver (0.7.2) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) + websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) @@ -506,7 +488,7 @@ DEPENDENCIES awesome_print bootsnap bootstrap-editable-rails - bootstrap-sass-backport (~> 3.2.0) + bootstrap-sass (~> 3.2.0) bootstrap-wysihtml5-rails bootstrap3-datetimepicker-rails (~> 4.17.37) byebug @@ -516,7 +498,7 @@ DEPENDENCIES capistrano-rails capistrano-rvm capistrano3-puma! - capybara + capybara (~> 3.12.0) cocoon codemirror-rails coffee-rails (~> 4.2) @@ -539,14 +521,14 @@ DEPENDENCIES jquery-rails jquery-ui-rails js-routes - kaminari + kaminari (~> 1.2.1) listen log_file loofah (>= 2.3.1) modernizr-rails momentjs-rails (>= 2.9.0) mysql2 (~> 0.4) - nokogiri (= 1.8.1) + nokogiri (~> 1.10.4) oj (~> 2.16) oj_mimic_json omniauth @@ -569,7 +551,6 @@ DEPENDENCIES sass-rails sdoc sqlite3 (~> 1.3.0) - squeel (~> 1.2) sucker_punch (~> 1.0) test-unit (~> 3.0.9) thin @@ -579,4 +560,4 @@ DEPENDENCIES wannabe_bool BUNDLED WITH - 1.17.3 + 2.1.4 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 00000000..b16e53d6 --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/code-workout.sublime-project b/code-workout.sublime-project deleted file mode 100644 index 24db3031..00000000 --- a/code-workout.sublime-project +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": - [ - { - "path": "." - } - ] -} diff --git a/config/application.rb b/config/application.rb index d95398a5..d784fc03 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ module CodeWorkout class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 5.1 + config.load_defaults 5.2 Rails.application.config.active_record.belongs_to_required_by_default = false # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers diff --git a/config/environments/development.rb b/config/environments/development.rb index 1311e3e4..2cd5bb4d 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,5 +1,6 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. + Rails.env = 'development' # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development diff --git a/db/development.db b/db/development.db deleted file mode 100644 index e69de29b..00000000 diff --git a/db/schema.rb b/db/schema.rb index 46174b2e..fcd92fe9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,7 @@ ActiveRecord::Schema.define(version: 20190712171251) do - create_table "active_admin_comments", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "active_admin_comments", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "namespace" t.text "body", limit: 65535 t.string "resource_id", default: "", null: false @@ -26,7 +26,7 @@ t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree end - create_table "attempts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "attempts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id", null: false t.integer "exercise_version_id", null: false t.datetime "submit_time", null: false @@ -54,7 +54,7 @@ t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk", using: :btree end - create_table "choices", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "choices", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "multiple_choice_prompt_id", null: false t.integer "position", null: false t.text "feedback", limit: 65535 @@ -72,12 +72,12 @@ t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk", using: :btree end - create_table "coding_prompt_answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "coding_prompt_answers", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.text "answer", limit: 65535 t.text "error", limit: 65535 end - create_table "coding_prompts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "coding_prompts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.datetime "created_at" t.datetime "updated_at" t.string "class_name" @@ -88,7 +88,7 @@ t.boolean "hide_examples", default: false, null: false end - create_table "course_enrollments", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "course_enrollments", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id", null: false t.integer "course_offering_id", null: false t.integer "course_role_id", null: false @@ -98,7 +98,7 @@ t.index ["user_id"], name: "index_course_enrollments_on_user_id", using: :btree end - create_table "course_exercises", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "course_exercises", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "course_id", null: false t.integer "exercise_id", null: false t.datetime "created_at" @@ -107,7 +107,7 @@ t.index ["exercise_id"], name: "course_exercises_exercise_id_fk", using: :btree end - create_table "course_offerings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "course_offerings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "course_id", null: false t.integer "term_id", null: false t.string "label", default: "", null: false @@ -122,7 +122,7 @@ t.index ["term_id"], name: "index_course_offerings_on_term_id", using: :btree end - create_table "course_roles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "course_roles", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.boolean "can_manage_course", default: false, null: false t.boolean "can_manage_assignments", default: false, null: false @@ -131,7 +131,7 @@ t.boolean "builtin", default: false, null: false end - create_table "courses", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "courses", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.string "number", default: "", null: false t.integer "organization_id", null: false @@ -146,7 +146,7 @@ t.index ["user_group_id"], name: "index_courses_on_user_group_id", using: :btree end - create_table "errors", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "errors", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "usable_type" t.integer "usable_id" t.string "class_name" @@ -162,7 +162,7 @@ t.index ["created_at"], name: "index_errors_on_created_at", using: :btree end - create_table "exercise_collections", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_collections", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" t.text "description", limit: 65535 t.integer "user_group_id" @@ -177,20 +177,20 @@ t.index ["user_id"], name: "index_exercise_collections_on_user_id", using: :btree end - create_table "exercise_families", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_families", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" end - create_table "exercise_owners", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_owners", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "exercise_id", null: false t.integer "owner_id", null: false t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true, using: :btree t.index ["owner_id"], name: "exercise_owners_owner_id_fk", using: :btree end - create_table "exercise_versions", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_versions", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "stem_id" t.datetime "created_at" t.datetime "updated_at" @@ -212,7 +212,7 @@ t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id", using: :btree end - create_table "exercise_workouts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_workouts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "exercise_id", null: false t.integer "workout_id", null: false t.integer "position", null: false @@ -223,7 +223,7 @@ t.index ["workout_id"], name: "exercise_workouts_workout_id_fk", using: :btree end - create_table "exercises", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercises", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "question_type", null: false t.integer "current_version_id" t.datetime "created_at" @@ -244,7 +244,7 @@ t.index ["is_public"], name: "index_exercises_on_is_public", using: :btree end - create_table "friendly_id_slugs", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "friendly_id_slugs", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "slug", default: "", null: false t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 @@ -256,14 +256,14 @@ t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree end - create_table "global_roles", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "global_roles", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.boolean "can_manage_all_courses", default: false, null: false t.boolean "can_edit_system_configuration", default: false, null: false t.boolean "builtin", default: false, null: false end - create_table "group_access_requests", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "group_access_requests", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id" t.integer "user_group_id" t.boolean "pending", default: true @@ -274,7 +274,7 @@ t.index ["user_id"], name: "index_group_access_requests_on_user_id", using: :btree end - create_table "identities", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "identities", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id", null: false t.string "provider", default: "", null: false t.string "uid", default: "", null: false @@ -284,14 +284,14 @@ t.index ["user_id"], name: "index_identities_on_user_id", using: :btree end - create_table "irt_data", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "irt_data", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "attempt_count", null: false t.float "sum_of_scores", limit: 24, null: false t.float "difficulty", limit: 24, null: false t.float "discrimination", limit: 24, null: false end - create_table "license_policies", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "license_policies", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" t.text "description", limit: 65535 t.boolean "can_fork" @@ -300,7 +300,7 @@ t.datetime "updated_at" end - create_table "licenses", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "licenses", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" t.text "description", limit: 65535 t.string "url" @@ -310,7 +310,7 @@ t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id", using: :btree end - create_table "lms_instances", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "lms_instances", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "consumer_key" t.string "consumer_secret" t.datetime "created_at" @@ -323,14 +323,14 @@ t.index ["url"], name: "index_lms_instances_on_url", unique: true, using: :btree end - create_table "lms_types", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "lms_types", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.index ["name"], name: "index_lms_types_on_name", unique: true, using: :btree end - create_table "lti_identities", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "lti_identities", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "lti_user_id" t.integer "user_id" t.integer "lms_instance_id" @@ -340,7 +340,7 @@ t.index ["user_id"], name: "index_lti_identities_on_user_id", using: :btree end - create_table "lti_workouts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "lti_workouts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "workout_id", limit: 4 t.string "lms_assignment_id", limit: 255, null: false t.datetime "created_at" @@ -350,22 +350,22 @@ t.index ["workout_id"], name: "index_lti_workouts_on_workout_id", using: :btree end - create_table "memberships", force: :cascade do |t| + create_table "memberships", id: :integer, force: :cascade do |t| t.integer "user_id", limit: 4 t.integer "user_group_id", limit: 4 t.datetime "created_at" t.datetime "updated_at" end - create_table "multiple_choice_prompt_answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "multiple_choice_prompt_answers", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| end - create_table "multiple_choice_prompts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "multiple_choice_prompts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.boolean "allow_multiple", default: false, null: false t.boolean "is_scrambled", default: true, null: false end - create_table "organizations", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "organizations", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" @@ -375,7 +375,7 @@ t.index ["slug"], name: "index_organizations_on_slug", unique: true, using: :btree end - create_table "prompt_answers", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "prompt_answers", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "attempt_id" t.integer "prompt_id" t.integer "actable_id" @@ -386,7 +386,7 @@ t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id", using: :btree end - create_table "prompts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "prompts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "exercise_version_id", null: false t.text "question", limit: 65535, null: false t.integer "position", null: false @@ -401,7 +401,7 @@ t.index ["irt_data_id"], name: "prompts_irt_data_id_fk", using: :btree end - create_table "resource_files", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "resource_files", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "filename" t.string "token", default: "", null: false t.integer "user_id", null: false @@ -412,7 +412,7 @@ t.index ["user_id"], name: "index_resource_files_on_user_id", using: :btree end - create_table "signups", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "signups", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "first_name" t.string "last_name_name" t.string "email" @@ -422,13 +422,13 @@ t.datetime "updated_at" end - create_table "stems", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "stems", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.text "preamble", limit: 65535 t.datetime "created_at" t.datetime "updated_at" end - create_table "student_extensions", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "student_extensions", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id" t.integer "workout_offering_id" t.datetime "soft_deadline" @@ -441,7 +441,7 @@ t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id", using: :btree end - create_table "tag_user_scores", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "tag_user_scores", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "user_id", null: false t.integer "experience", default: 0 t.datetime "created_at" @@ -450,7 +450,7 @@ t.index ["user_id"], name: "index_tag_user_scores_on_user_id", using: :btree end - create_table "taggings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "taggings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -462,13 +462,13 @@ t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree end - create_table "tags", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "tags", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" t.integer "taggings_count", default: 0 t.index ["name"], name: "index_tags_on_name", unique: true, using: :btree end - create_table "terms", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "terms", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "season", null: false t.date "starts_on", null: false t.date "ends_on", null: false @@ -480,7 +480,7 @@ t.index ["year", "season"], name: "index_terms_on_year_and_season", using: :btree end - create_table "test_case_results", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "test_case_results", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "test_case_id", null: false t.integer "user_id", null: false t.text "execution_feedback", limit: 65535 @@ -493,7 +493,7 @@ t.index ["user_id"], name: "index_test_case_results_on_user_id", using: :btree end - create_table "test_cases", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "test_cases", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.text "negative_feedback", limit: 65535 t.float "weight", limit: 24, null: false t.text "description", limit: 65535 @@ -509,7 +509,7 @@ t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id", using: :btree end - create_table "time_zones", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "time_zones", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" t.string "zone" t.string "display_as" @@ -517,14 +517,14 @@ t.datetime "updated_at" end - create_table "user_groups", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "user_groups", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" t.text "description", limit: 65535 end - create_table "users", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "users", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -556,7 +556,7 @@ t.index ["time_zone_id"], name: "index_users_on_time_zone_id", using: :btree end - create_table "visualization_loggings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| + create_table "visualization_loggings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| t.integer "user_id" t.integer "exercise_id" t.integer "workout_id" @@ -569,7 +569,7 @@ t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id", using: :btree end - create_table "workout_offerings", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "workout_offerings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "course_offering_id", null: false t.integer "workout_id", null: false t.datetime "created_at" @@ -591,14 +591,14 @@ t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id", using: :btree end - create_table "workout_owners", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "workout_owners", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "workout_id", null: false t.integer "owner_id", null: false t.index ["owner_id"], name: "workout_owners_owner_id_fk", using: :btree t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true, using: :btree end - create_table "workout_policies", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "workout_policies", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.boolean "hide_thumbnails_before_start" t.boolean "hide_feedback_before_finish" t.boolean "hide_compilation_feedback_before_finish" @@ -614,7 +614,7 @@ t.string "description", limit: 255 end - create_table "workout_scores", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "workout_scores", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.integer "workout_id", null: false t.integer "user_id", null: false t.float "score", limit: 24 @@ -628,12 +628,14 @@ t.integer "workout_offering_id" t.string "lis_outcome_service_url" t.string "lis_result_sourcedid" + t.integer "lti_workout_id", limit: 4 + t.index ["lti_workout_id"], name: "index_workout_scores_on_lti_workout_id", using: :btree t.index ["user_id"], name: "index_workout_scores_on_user_id", using: :btree t.index ["workout_id"], name: "index_workout_scores_on_workout_id", using: :btree t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk", using: :btree end - create_table "workouts", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "workouts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name", default: "", null: false t.boolean "scrambled", default: false t.datetime "created_at" diff --git a/docker-run.sh b/docker-run.sh new file mode 100644 index 00000000..1311af5e --- /dev/null +++ b/docker-run.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +container_name=code-workout + +echo "Stop Container: $container_name" +docker container stop $container_name + +echo "Remove Container: $container_name" +docker container rm $container_name + +echo "Build Container: $container_name" + +docker build \ + -t $container_name . \ + --build-arg RAILS_ENV='development' \ + --build-arg UID=$(echo $UID) + +echo "Start Container: $container_name" +docker run \ + -v $PWD:/code-workout \ + --name $container_name \ + -p 9292:9292 $container_name:latest + +#docker container ps +#docker exec -it $container_name /bin/bash + + + +#docker build -t opendsa-lti . --build-arg RAILS_ENV='development' +#docker run -p 3000:3000 -it opendsa-lti /bin/bash +# +#bundle exec thin start --ssl --ssl-key-file server.key --ssl-cert-file server.crt -p 3000 +#bundle exec thin start -p 3000 diff --git a/runservers.sh b/runservers.sh index 9c772d3f..ad5ebf1b 100755 --- a/runservers.sh +++ b/runservers.sh @@ -1 +1 @@ -bundle exec thin start --ssl --ssl-key-file server.key --ssl-cert-file server.crt -p 9292 --debug +RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start --ssl --ssl-key-file server.key --ssl-cert-file server.crt -p 9292 --debug diff --git a/start.sh b/start.sh new file mode 100755 index 00000000..9d398199 --- /dev/null +++ b/start.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +#LOCALHOST +APP_DIR="/code-workout/" +# EFS_DIR="/var/tmp/credentials" +#EFS_DIR="${APP_DIR}" + +PORT="80" +#PORT="3000" + +ENVIRONMENT=$RAILS_ENV + +ERROR_FOUND=false; + +# echo "Copying files from NFS conf directory" +# cp "${EFS_DIR}/database.yml" "${APP_DIR}/config/database.yml" || ERROR_FOUND=true +#chmod +x "${APP_DIR}/email_notification.sh" + + +#echo "rm -rf /opendsa/Books/" +#rm -rf /opendsa/Books/ +#cd /opendsa +#find -type l -delete + +# echo "ln -s /var/tmp/credentials/opendsa/Books /opendsa" +# ln -s /var/tmp/credentials/opendsa/Books /opendsa + +#echo "Script finished." + +if [[ "${ERROR_FOUND}" == true ]]; then exit 1; fi; + +echo "Start cron process in foreground." + +cd "${APP_DIR}" + +echo "RAILS_ENV=$RAILS_ENV bundle exec thin start -p ${PORT}" + +#lsof -t -i tcp:${PORT} | xargs kill -9 + +#echo "RAILS_ENV=$RAILS_ENV rails s -b 0.0.0.0 -p ${PORT}" +#RAILS_ENV=${ENVIRONMENT} rails s -b 0.0.0.0 -p ${PORT} >> /var/log/opendsa-lti.log 2>&1 + +# Start process ithe background - Executes delayed_jobs +nohup bash -c "rake jobs:work >> /code-workout/log/development.log 2>&1" +RAILS_ENV=${ENVIRONMENT} bundle exec thin start -p ${PORT} >> /var/log/code-workout.log 2>&1 From 0ad6022bc7fc95206fa93950f013bb8a0388af79 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 9 Jun 2020 15:59:09 -0400 Subject: [PATCH 012/117] Regress sprockets to < 4 for the time being --- Gemfile | 1 + Gemfile.lock | 3 ++- app/assets/config/manifest.js | 3 --- 3 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 app/assets/config/manifest.js diff --git a/Gemfile b/Gemfile index 842b93d7..5360d438 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source 'https://rubygems.org' gem 'rails', '~> 5.1' +gem 'sprockets', '~> 3.7.2' # 4.0 requires ruby 2.5 gem 'bootstrap-sass', '~> 3.2.0' gem 'jquery-rails' gem 'jquery-ui-rails' diff --git a/Gemfile.lock b/Gemfile.lock index b53b069b..5ed774d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -431,7 +431,7 @@ GEM sdoc (1.1.0) rdoc (>= 5.0) sexp_processor (4.14.1) - sprockets (4.0.2) + sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.1) @@ -550,6 +550,7 @@ DEPENDENCIES rubyzip (>= 1.3.0) sass-rails sdoc + sprockets (~> 3.7.2) sqlite3 (~> 1.3.0) sucker_punch (~> 1.0) test-unit (~> 3.0.9) diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js deleted file mode 100644 index b16e53d6..00000000 --- a/app/assets/config/manifest.js +++ /dev/null @@ -1,3 +0,0 @@ -//= link_tree ../images -//= link_directory ../javascripts .js -//= link_directory ../stylesheets .css From eb0e28a7f76cac484774a0dfbdd67a0d650136ac Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 9 Jun 2020 23:47:52 -0400 Subject: [PATCH 013/117] Docker-compose for databases --- .dockerignore | 8 ++++++ Dockerfile | 46 ++---------------------------- config/database.yml | 4 +-- config/environments/development.rb | 1 - docker-compose.yml | 37 ++++++++++++++++++++++++ 5 files changed, 50 insertions(+), 46 deletions(-) create mode 100644 .dockerignore create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8b248e71 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.bundle +.circleci +.git +vendor/bundle +log/* +!/log/.keep +tmp/* +!/tmp/.keep diff --git a/Dockerfile b/Dockerfile index f5f1ae75..8b59deb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ FROM ruby:2.7.1 -#FROM ruby:2.3.3 MAINTAINER Jihane Najdi -#Default environment +# Default environment ARG RAILS_ENV='development' +# Ruby changed the way optional params are done, but Rails hasn't caught up +ARG RUBYOPT='-W:no-deprecated' ARG BASEDIR='/code-workout/' ENV TZ=America/New_York @@ -16,14 +17,6 @@ RUN apt-get update -qq \ && apt-get install -y apt-utils build-essential libpq-dev vim cron curl \ && apt-get install -y nodejs npm python-pip git-core zlib1g-dev libssl-dev libreadline-dev libyaml-dev libevent-dev libsqlite3-dev libsqlite3-dev libxml2-dev libxml2 libxslt1-dev libffi-dev libxslt-dev sqlite3 dkms python-dev python-feedvalidator python-sphinx \ && pip install --upgrade pip -#// libcurl4-openssl-dev -# #// libcurl4 - -#RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7 \ -# && apt-get install -y apt-transport-https ca-certificates \ -# && sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger trusty main > /etc/apt/sources.list.d/passenger.list' \ -# && apt-get update \ -# && apt-get install -y passenger # install rubygems ENV GEM_HOME /usr/local/bundle @@ -36,10 +29,6 @@ RUN gem install bundler -v $BUNDLER_VERSION \ && bundle config --global bin "$GEM_HOME/bin" \ && bundle config git.allow_insecure true -# Create a user with the same UID as host user so we have permissions -# RUN useradd -r -u ${UID} appuser -# USER appuser - VOLUME ${BASEDIR} WORKDIR ${BASEDIR} @@ -56,37 +45,8 @@ RUN find /code-workout -type f -exec chmod 0644 {} \; RUN find ./runservers.sh -type f -exec chmod +x {} \; EXPOSE 80 -#EXPOSE 3000 - -# Create the log file -# RUN touch /code-workout/log/development.log - -# Redirecting logs to Dockerlog collector accesslogs (/proc/1/fd/1) errorlogs (/proc/self/fd/2) -#RUN ln -sf /proc/1/fd/1 /opendsa-lti/log/development.log - -# Clone OpenDSA -# RUN mkdir /opendsa -# RUN git clone https://github.com/OpenDSA/OpenDSA.git /opendsa -# RUN pip install -r /opendsa/requirements.txt --upgrade -# RUN ln -s /opendsa /opendsa-lti/public/OpenDSA -# RUN ln -s "$(which nodejs)" /usr/local/bin/node -# RUN cp /opendsa-lti/postprocessor.py /opendsa-lti/public/OpenDSA/tools/postprocessor.py ## JAVA INSTALLATION RUN apt-get install -y default-jre -### OpenDSA libraries for Python 2.7 -# RUN pip install --upgrade beautifulsoup4 -# RUN pip install --upgrade html5lib - -#ln -s /opendsa/RST /opendsa-lti/RST -#ln -s /opendsa/config /opendsa-lti/Configuration -#rm /opendsa-ltiConfiguration/config -#rm /opendsa-lti/RST/RST - -##RUN bundle -#RUN echo $PATH - CMD ["bash", "./runservers.sh"] - -#CMD tail -f /dev/null & wait diff --git a/config/database.yml b/config/database.yml index fe4a5b6b..b6ff2606 100644 --- a/config/database.yml +++ b/config/database.yml @@ -8,7 +8,7 @@ development: database: codeworkout username: codeworkout password: codeworkout - host: localhost + host: db_dev # development: # adapter: sqlite3 @@ -32,4 +32,4 @@ test: database: codeworkout_test username: codeworkout password: codeworkout - host: localhost + host: db_test diff --git a/config/environments/development.rb b/config/environments/development.rb index 2cd5bb4d..1311e3e4 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,6 +1,5 @@ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. - Rails.env = 'development' # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..ed42f107 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +version: '3.8' +services: + db_dev: + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_USER: codeworkout + MYSQL_PASSWORD: codeworkout + MYSQL_DATABASE: codeworkout + ports: + - "3307:3306" + db_test: + image: mysql:5.7 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_USER: codeworkout + MYSQL_PASSWORD: codeworkout + MYSQL_DATABASE: codeworkout_test + ports: + - "3308:3306" + web: + depends_on: + - "db_dev" + - "db_test" + build: . + ports: + - "9292:9292" + environment: + RUBYOPT: -W:no-deprecated + DB_USERNAME: codeworkout + DB_PASSWORD: codeworkout + DB_DATABASE: codeworkout + DB_PORT: 3306 + DB_HOST: db + RAILS_ENV: development + volumes: + - ".:/code-workout" From 5ea1de6f3f55e19e0b71a6bd560529abca3bd89d Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Thu, 2 Jul 2020 15:53:11 -0400 Subject: [PATCH 014/117] Dockerfile and Compose file improvements --- Dockerfile | 29 +++++++++++++++++++++++++++-- docker-compose.yml | 6 +++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8b59deb9..4c179147 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,8 +14,33 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone #install 'development tools' build-essential dkms curl libxslt-dev libpq-dev python-dev python-pip python-feedvalidator python-software-properties python-sphinx libmariadbclient-dev libcurl4-gnutls-dev libevent-dev libffi-dev libssl-dev stunnel4 libsqlite3-dev # libmariadbclient-dev RUN apt-get update -qq \ - && apt-get install -y apt-utils build-essential libpq-dev vim cron curl \ - && apt-get install -y nodejs npm python-pip git-core zlib1g-dev libssl-dev libreadline-dev libyaml-dev libevent-dev libsqlite3-dev libsqlite3-dev libxml2-dev libxml2 libxslt1-dev libffi-dev libxslt-dev sqlite3 dkms python-dev python-feedvalidator python-sphinx \ + && apt-get install -y \ + apt-utils \ + build-essential \ + libpq-dev \ + vim \ + cron \ + curl \ + nodejs \ + python-pip \ + git-core \ + zlib1g-dev \ + libssl-dev \ + libreadline-dev \ + libyaml-dev \ + libevent-dev \ + libsqlite3-dev \ + libsqlite3-dev \ + libxml2-dev \ + libxml2 \ + libxslt1-dev \ + libffi-dev \ + libxslt-dev \ + sqlite3 \ + dkms \ + python-dev \ + python-feedvalidator \ + python-sphinx \ && pip install --upgrade pip # install rubygems diff --git a/docker-compose.yml b/docker-compose.yml index ed42f107..18abd987 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: MYSQL_DATABASE: codeworkout ports: - "3307:3306" + volumes: + - dbdata:/var/lib/mysql:delegated db_test: image: mysql:5.7 environment: @@ -34,4 +36,6 @@ services: DB_HOST: db RAILS_ENV: development volumes: - - ".:/code-workout" + - .:/code-workout +volumes: + dbdata: From 947b93a40936df45d9a851172f1cf3cf80833ff6 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Thu, 2 Jul 2020 17:08:57 -0400 Subject: [PATCH 015/117] exception_handler config updates --- config/application.rb | 5 + db/migrate/20150331153411_create_errors.rb | 18 - db/schema.rb | 837 ++++++++++----------- 3 files changed, 422 insertions(+), 438 deletions(-) delete mode 100644 db/migrate/20150331153411_create_errors.rb diff --git a/config/application.rb b/config/application.rb index d784fc03..2ff74cf1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -14,6 +14,11 @@ class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + + # exception_handler configuration (no longer relies on an initializer) + config.exception_handler = { + db: true + } # Timeout for feedback polls (milliseconds) # This is an initial value on application startup, but it might change diff --git a/db/migrate/20150331153411_create_errors.rb b/db/migrate/20150331153411_create_errors.rb deleted file mode 100644 index 02d6fb77..00000000 --- a/db/migrate/20150331153411_create_errors.rb +++ /dev/null @@ -1,18 +0,0 @@ -class CreateErrors < ActiveRecord::Migration - def change - create_table :errors do |t| - t.string :usable_type - t.integer :usable_id - t.text :class_name - t.text :message - t.text :trace - t.text :target_url - t.text :referer_url - t.text :params - t.text :user_agent - t.timestamps - end - add_index :errors, :class_name - add_index :errors, :created_at - end -end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index fcd92fe9..8e4f824a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,644 +10,641 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190712171251) do +ActiveRecord::Schema.define(version: 2019_07_12_171251) do - create_table "active_admin_comments", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "namespace" - t.text "body", limit: 65535 - t.string "resource_id", default: "", null: false - t.string "resource_type", default: "", null: false - t.integer "author_id" - t.string "author_type" + create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "namespace" + t.text "body" + t.string "resource_id", default: "", null: false + t.string "resource_type", default: "", null: false + t.integer "author_id" + t.string "author_type" t.datetime "created_at" t.datetime "updated_at" - t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id", using: :btree - t.index ["namespace"], name: "index_active_admin_comments_on_namespace", using: :btree - t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id", using: :btree + t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id" + t.index ["namespace"], name: "index_active_admin_comments_on_namespace" + t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id" end - create_table "attempts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id", null: false - t.integer "exercise_version_id", null: false - t.datetime "submit_time", null: false - t.integer "submit_num", null: false - t.float "score", limit: 24, default: 0.0 - t.integer "experience_earned" + create_table "attempts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "exercise_version_id", null: false + t.datetime "submit_time", null: false + t.integer "submit_num", null: false + t.float "score", default: 0.0 + t.integer "experience_earned" t.datetime "created_at" t.datetime "updated_at" - t.integer "workout_score_id", limit: 4 - t.integer "active_score_id", limit: 4 - t.boolean "feedback_ready" - t.decimal "time_taken", precision: 10 - t.decimal "feedback_timeout", precision: 10 - t.decimal "worker_time", precision: 10 - t.index ["active_score_id"], name: "index_attempts_on_active_score_id", using: :btree - t.index ["exercise_version_id"], name: "index_attempts_on_exercise_version_id", using: :btree - t.index ["user_id"], name: "index_attempts_on_user_id", using: :btree - t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id", using: :btree + t.integer "workout_score_id" + t.integer "active_score_id" + t.boolean "feedback_ready" + t.decimal "time_taken", precision: 10 + t.decimal "feedback_timeout", precision: 10 + t.decimal "worker_time", precision: 10 + t.index ["active_score_id"], name: "index_attempts_on_active_score_id" + t.index ["exercise_version_id"], name: "index_attempts_on_exercise_version_id" + t.index ["user_id"], name: "index_attempts_on_user_id" + t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id" end - create_table "attempts_tag_user_scores", id: false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "tag_user_score_id" - t.index ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true, using: :btree - t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk", using: :btree + t.index ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true + t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk" end - create_table "choices", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "multiple_choice_prompt_id", null: false - t.integer "position", null: false - t.text "feedback", limit: 65535 - t.float "value", limit: 24, null: false + create_table "choices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "multiple_choice_prompt_id", null: false + t.integer "position", null: false + t.text "feedback" + t.float "value", null: false t.datetime "created_at" t.datetime "updated_at" - t.text "answer", limit: 65535, null: false - t.index ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id", using: :btree + t.text "answer", null: false + t.index ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id" end - create_table "choices_multiple_choice_prompt_answers", id: false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "choice_id" t.integer "multiple_choice_prompt_answer_id" - t.index ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true, using: :btree - t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk", using: :btree + t.index ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true + t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk" end - create_table "coding_prompt_answers", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.text "answer", limit: 65535 - t.text "error", limit: 65535 + create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.text "answer" + t.text "error" end - create_table "coding_prompts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.datetime "created_at" t.datetime "updated_at" - t.string "class_name" - t.text "wrapper_code", limit: 65535, null: false - t.text "test_script", limit: 65535, null: false - t.string "method_name" - t.text "starter_code", limit: 65535 - t.boolean "hide_examples", default: false, null: false + t.string "class_name" + t.text "wrapper_code", null: false + t.text "test_script", null: false + t.string "method_name" + t.text "starter_code" + t.boolean "hide_examples", default: false, null: false end - create_table "course_enrollments", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id", null: false + create_table "course_enrollments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "user_id", null: false t.integer "course_offering_id", null: false - t.integer "course_role_id", null: false - t.index ["course_offering_id"], name: "index_course_enrollments_on_course_offering_id", using: :btree - t.index ["course_role_id"], name: "index_course_enrollments_on_course_role_id", using: :btree - t.index ["user_id", "course_offering_id"], name: "index_course_enrollments_on_user_id_and_course_offering_id", unique: true, using: :btree - t.index ["user_id"], name: "index_course_enrollments_on_user_id", using: :btree + t.integer "course_role_id", null: false + t.index ["course_offering_id"], name: "index_course_enrollments_on_course_offering_id" + t.index ["course_role_id"], name: "index_course_enrollments_on_course_role_id" + t.index ["user_id", "course_offering_id"], name: "index_course_enrollments_on_user_id_and_course_offering_id", unique: true + t.index ["user_id"], name: "index_course_enrollments_on_user_id" end - create_table "course_exercises", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "course_id", null: false - t.integer "exercise_id", null: false + create_table "course_exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "course_id", null: false + t.integer "exercise_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.index ["course_id"], name: "course_exercises_course_id_fk", using: :btree - t.index ["exercise_id"], name: "course_exercises_exercise_id_fk", using: :btree + t.index ["course_id"], name: "course_exercises_course_id_fk" + t.index ["exercise_id"], name: "course_exercises_exercise_id_fk" end - create_table "course_offerings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "course_id", null: false - t.integer "term_id", null: false - t.string "label", default: "", null: false - t.string "url" - t.boolean "self_enrollment_allowed" + create_table "course_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "course_id", null: false + t.integer "term_id", null: false + t.string "label", default: "", null: false + t.string "url" + t.boolean "self_enrollment_allowed" t.datetime "created_at" t.datetime "updated_at" - t.date "cutoff_date" - t.integer "lms_instance_id" - t.index ["course_id"], name: "index_course_offerings_on_course_id", using: :btree - t.index ["lms_instance_id"], name: "index_course_offerings_on_lms_instance_id", using: :btree - t.index ["term_id"], name: "index_course_offerings_on_term_id", using: :btree + t.date "cutoff_date" + t.integer "lms_instance_id" + t.index ["course_id"], name: "index_course_offerings_on_course_id" + t.index ["lms_instance_id"], name: "index_course_offerings_on_lms_instance_id" + t.index ["term_id"], name: "index_course_offerings_on_term_id" end - create_table "course_roles", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false - t.boolean "can_manage_course", default: false, null: false - t.boolean "can_manage_assignments", default: false, null: false - t.boolean "can_grade_submissions", default: false, null: false + create_table "course_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false + t.boolean "can_manage_course", default: false, null: false + t.boolean "can_manage_assignments", default: false, null: false + t.boolean "can_grade_submissions", default: false, null: false t.boolean "can_view_other_submissions", default: false, null: false - t.boolean "builtin", default: false, null: false + t.boolean "builtin", default: false, null: false end - create_table "courses", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false - t.string "number", default: "", null: false - t.integer "organization_id", null: false + create_table "courses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false + t.string "number", default: "", null: false + t.integer "organization_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "creator_id" - t.string "slug", default: "", null: false - t.integer "user_group_id" - t.boolean "is_hidden", default: false - t.index ["organization_id"], name: "index_courses_on_organization_id", using: :btree - t.index ["slug"], name: "index_courses_on_slug", using: :btree - t.index ["user_group_id"], name: "index_courses_on_user_group_id", using: :btree + t.integer "creator_id" + t.string "slug", default: "", null: false + t.integer "user_group_id" + t.boolean "is_hidden", default: false + t.index ["organization_id"], name: "index_courses_on_organization_id" + t.index ["slug"], name: "index_courses_on_slug" + t.index ["user_group_id"], name: "index_courses_on_user_group_id" end - create_table "errors", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "usable_type" - t.integer "usable_id" - t.string "class_name" - t.text "message", limit: 65535 - t.text "trace", limit: 65535 - t.text "target_url", limit: 65535 - t.text "referer_url", limit: 65535 - t.text "params", limit: 65535 - t.text "user_agent", limit: 65535 - t.datetime "created_at" - t.datetime "updated_at" - t.index ["class_name"], name: "index_errors_on_class_name", using: :btree - t.index ["created_at"], name: "index_errors_on_created_at", using: :btree + create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.text "class_name" + t.text "status" + t.text "message" + t.text "trace" + t.text "target" + t.text "referrer" + t.text "params" + t.text "user_agent" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "exercise_collections", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name" - t.text "description", limit: 65535 - t.integer "user_group_id" - t.integer "license_id" + create_table "exercise_collections", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name" + t.text "description" + t.integer "user_group_id" + t.integer "license_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "user_id" - t.integer "course_offering_id" - t.index ["course_offering_id"], name: "index_exercise_collections_on_course_offering_id", using: :btree - t.index ["license_id"], name: "index_exercise_collections_on_license_id", using: :btree - t.index ["user_group_id"], name: "index_exercise_collections_on_user_group_id", using: :btree - t.index ["user_id"], name: "index_exercise_collections_on_user_id", using: :btree + t.integer "user_id" + t.integer "course_offering_id" + t.index ["course_offering_id"], name: "index_exercise_collections_on_course_offering_id" + t.index ["license_id"], name: "index_exercise_collections_on_license_id" + t.index ["user_group_id"], name: "index_exercise_collections_on_user_group_id" + t.index ["user_id"], name: "index_exercise_collections_on_user_id" end - create_table "exercise_families", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false + create_table "exercise_families", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" end - create_table "exercise_owners", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "exercise_id", null: false - t.integer "owner_id", null: false - t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true, using: :btree - t.index ["owner_id"], name: "exercise_owners_owner_id_fk", using: :btree + t.integer "owner_id", null: false + t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true + t.index ["owner_id"], name: "exercise_owners_owner_id_fk" end - create_table "exercise_versions", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "stem_id" + create_table "exercise_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "stem_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "exercise_id", limit: 4, null: false - t.integer "version", limit: 4, null: false - t.integer "creator_id", limit: 4 - t.integer "irt_data_id", limit: 4 - t.text "text_representation", limit: 16777215 - t.index ["creator_id"], name: "exercise_versions_creator_id_fk", using: :btree - t.index ["exercise_id"], name: "index_exercise_versions_on_exercise_id", using: :btree - t.index ["irt_data_id"], name: "exercise_versions_irt_data_id_fk", using: :btree - t.index ["stem_id"], name: "index_exercise_versions_on_stem_id", using: :btree + t.integer "exercise_id", null: false + t.integer "version", null: false + t.integer "creator_id" + t.integer "irt_data_id" + t.text "text_representation", limit: 16777215 + t.index ["creator_id"], name: "exercise_versions_creator_id_fk" + t.index ["exercise_id"], name: "index_exercise_versions_on_exercise_id" + t.index ["irt_data_id"], name: "exercise_versions_irt_data_id_fk" + t.index ["stem_id"], name: "index_exercise_versions_on_stem_id" end - create_table "exercise_versions_resource_files", id: false, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "exercise_versions_resource_files", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "exercise_version_id", null: false - t.integer "resource_file_id", null: false - t.index ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id", using: :btree - t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id", using: :btree + t.integer "resource_file_id", null: false + t.index ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id" + t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id" end - create_table "exercise_workouts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "exercise_id", null: false - t.integer "workout_id", null: false - t.integer "position", null: false - t.float "points", limit: 24, default: 1.0 + create_table "exercise_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "exercise_id", null: false + t.integer "workout_id", null: false + t.integer "position", null: false + t.float "points", default: 1.0 t.datetime "created_at" t.datetime "updated_at" - t.index ["exercise_id"], name: "exercise_workouts_exercise_id_fk", using: :btree - t.index ["workout_id"], name: "exercise_workouts_workout_id_fk", using: :btree + t.index ["exercise_id"], name: "exercise_workouts_exercise_id_fk" + t.index ["workout_id"], name: "exercise_workouts_workout_id_fk" end - create_table "exercises", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "question_type", null: false - t.integer "current_version_id" + create_table "exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "question_type", null: false + t.integer "current_version_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "versions" - t.integer "exercise_family_id" - t.string "name" - t.boolean "is_public", default: false, null: false - t.integer "experience", null: false - t.integer "irt_data_id" - t.string "external_id" - t.integer "exercise_collection_id" - t.index ["current_version_id"], name: "index_exercises_on_current_version_id", using: :btree - t.index ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id", using: :btree - t.index ["exercise_family_id"], name: "index_exercises_on_exercise_family_id", using: :btree - t.index ["external_id"], name: "index_exercises_on_external_id", unique: true, using: :btree - t.index ["irt_data_id"], name: "exercises_irt_data_id_fk", using: :btree - t.index ["is_public"], name: "index_exercises_on_is_public", using: :btree + t.integer "versions" + t.integer "exercise_family_id" + t.string "name" + t.boolean "is_public", default: false, null: false + t.integer "experience", null: false + t.integer "irt_data_id" + t.string "external_id" + t.integer "exercise_collection_id" + t.index ["current_version_id"], name: "index_exercises_on_current_version_id" + t.index ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id" + t.index ["exercise_family_id"], name: "index_exercises_on_exercise_family_id" + t.index ["external_id"], name: "index_exercises_on_external_id", unique: true + t.index ["irt_data_id"], name: "exercises_irt_data_id_fk" + t.index ["is_public"], name: "index_exercises_on_is_public" end - create_table "friendly_id_slugs", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "slug", default: "", null: false - t.integer "sluggable_id", null: false - t.string "sluggable_type", limit: 50 - t.string "scope", limit: 255 + create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "slug", default: "", null: false + t.integer "sluggable_id", null: false + t.string "sluggable_type", limit: 50 + t.string "scope" t.datetime "created_at" - t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true, using: :btree - t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", using: :btree - t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id", using: :btree - t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree + t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true + t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type" + t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id" + t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" end - create_table "global_roles", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false - t.boolean "can_manage_all_courses", default: false, null: false + create_table "global_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false + t.boolean "can_manage_all_courses", default: false, null: false t.boolean "can_edit_system_configuration", default: false, null: false - t.boolean "builtin", default: false, null: false + t.boolean "builtin", default: false, null: false end - create_table "group_access_requests", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id" - t.integer "user_group_id" - t.boolean "pending", default: true - t.boolean "decision" + create_table "group_access_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "user_id" + t.integer "user_group_id" + t.boolean "pending", default: true + t.boolean "decision" t.datetime "created_at" t.datetime "updated_at" - t.index ["user_group_id"], name: "index_group_access_requests_on_user_group_id", using: :btree - t.index ["user_id"], name: "index_group_access_requests_on_user_id", using: :btree + t.index ["user_group_id"], name: "index_group_access_requests_on_user_group_id" + t.index ["user_id"], name: "index_group_access_requests_on_user_id" end - create_table "identities", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id", null: false - t.string "provider", default: "", null: false - t.string "uid", default: "", null: false + create_table "identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "user_id", null: false + t.string "provider", default: "", null: false + t.string "uid", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.index ["uid", "provider"], name: "index_identities_on_uid_and_provider", using: :btree - t.index ["user_id"], name: "index_identities_on_user_id", using: :btree + t.index ["uid", "provider"], name: "index_identities_on_uid_and_provider" + t.index ["user_id"], name: "index_identities_on_user_id" end - create_table "irt_data", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "attempt_count", null: false - t.float "sum_of_scores", limit: 24, null: false - t.float "difficulty", limit: 24, null: false - t.float "discrimination", limit: 24, null: false + create_table "irt_data", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "attempt_count", null: false + t.float "sum_of_scores", null: false + t.float "difficulty", null: false + t.float "discrimination", null: false end - create_table "license_policies", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name" - t.text "description", limit: 65535 - t.boolean "can_fork" - t.boolean "is_public" + create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name" + t.text "description" + t.boolean "can_fork" + t.boolean "is_public" t.datetime "created_at" t.datetime "updated_at" end - create_table "licenses", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name" - t.text "description", limit: 65535 - t.string "url" - t.integer "license_policy_id" + create_table "licenses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name" + t.text "description" + t.string "url" + t.integer "license_policy_id" t.datetime "created_at" t.datetime "updated_at" - t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id", using: :btree + t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id" end - create_table "lms_instances", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "consumer_key" - t.string "consumer_secret" + create_table "lms_instances", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "consumer_key" + t.string "consumer_secret" t.datetime "created_at" t.datetime "updated_at" - t.string "url" - t.integer "lms_type_id" - t.integer "organization_id" - t.index ["lms_type_id"], name: "lms_instances_lms_type_id_fk", using: :btree - t.index ["organization_id"], name: "index_lms_instances_on_organization_id", using: :btree - t.index ["url"], name: "index_lms_instances_on_url", unique: true, using: :btree + t.string "url" + t.integer "lms_type_id" + t.integer "organization_id" + t.index ["lms_type_id"], name: "lms_instances_lms_type_id_fk" + t.index ["organization_id"], name: "index_lms_instances_on_organization_id" + t.index ["url"], name: "index_lms_instances_on_url", unique: true end - create_table "lms_types", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false + create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.index ["name"], name: "index_lms_types_on_name", unique: true, using: :btree + t.index ["name"], name: "index_lms_types_on_name", unique: true end - create_table "lti_identities", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "lti_user_id" - t.integer "user_id" - t.integer "lms_instance_id" + create_table "lti_identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "lti_user_id" + t.integer "user_id" + t.integer "lms_instance_id" t.datetime "created_at" t.datetime "updated_at" - t.index ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id", using: :btree - t.index ["user_id"], name: "index_lti_identities_on_user_id", using: :btree + t.index ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id" + t.index ["user_id"], name: "index_lti_identities_on_user_id" end - create_table "lti_workouts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "workout_id", limit: 4 - t.string "lms_assignment_id", limit: 255, null: false + create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "workout_id" + t.string "lms_assignment_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "lms_instance_id", limit: 4 - t.index ["lms_instance_id"], name: "index_lti_workouts_on_lms_instance_id", using: :btree - t.index ["workout_id"], name: "index_lti_workouts_on_workout_id", using: :btree + t.integer "lms_instance_id" + t.index ["lms_instance_id"], name: "index_lti_workouts_on_lms_instance_id" + t.index ["workout_id"], name: "index_lti_workouts_on_workout_id" end - create_table "memberships", id: :integer, force: :cascade do |t| - t.integer "user_id", limit: 4 - t.integer "user_group_id", limit: 4 + create_table "memberships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.integer "user_id" + t.integer "user_group_id" t.datetime "created_at" t.datetime "updated_at" end - create_table "multiple_choice_prompt_answers", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "multiple_choice_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| end - create_table "multiple_choice_prompts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "multiple_choice_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.boolean "allow_multiple", default: false, null: false - t.boolean "is_scrambled", default: true, null: false + t.boolean "is_scrambled", default: true, null: false end - create_table "organizations", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false + create_table "organizations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "abbreviation" - t.string "slug", default: "", null: false - t.boolean "is_hidden", default: false - t.index ["slug"], name: "index_organizations_on_slug", unique: true, using: :btree + t.string "abbreviation" + t.string "slug", default: "", null: false + t.boolean "is_hidden", default: false + t.index ["slug"], name: "index_organizations_on_slug", unique: true end - create_table "prompt_answers", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + create_table "prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "prompt_id" t.integer "actable_id" - t.string "actable_type" - t.index ["actable_id"], name: "index_prompt_answers_on_actable_id", using: :btree - t.index ["attempt_id", "prompt_id"], name: "index_prompt_answers_on_attempt_id_and_prompt_id", unique: true, using: :btree - t.index ["attempt_id"], name: "index_prompt_answers_on_attempt_id", using: :btree - t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id", using: :btree + t.string "actable_type" + t.index ["actable_id"], name: "index_prompt_answers_on_actable_id" + t.index ["attempt_id", "prompt_id"], name: "index_prompt_answers_on_attempt_id_and_prompt_id", unique: true + t.index ["attempt_id"], name: "index_prompt_answers_on_attempt_id" + t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id" end - create_table "prompts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "exercise_version_id", null: false - t.text "question", limit: 65535, null: false - t.integer "position", null: false - t.text "feedback", limit: 65535 + create_table "prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "exercise_version_id", null: false + t.text "question", null: false + t.integer "position", null: false + t.text "feedback" t.datetime "created_at" t.datetime "updated_at" - t.integer "actable_id" - t.string "actable_type" - t.integer "irt_data_id" - t.index ["actable_id"], name: "index_prompts_on_actable_id", using: :btree - t.index ["exercise_version_id"], name: "index_prompts_on_exercise_version_id", using: :btree - t.index ["irt_data_id"], name: "prompts_irt_data_id_fk", using: :btree + t.integer "actable_id" + t.string "actable_type" + t.integer "irt_data_id" + t.index ["actable_id"], name: "index_prompts_on_actable_id" + t.index ["exercise_version_id"], name: "index_prompts_on_exercise_version_id" + t.index ["irt_data_id"], name: "prompts_irt_data_id_fk" end - create_table "resource_files", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "filename" - t.string "token", default: "", null: false - t.integer "user_id", null: false - t.boolean "public", default: true + create_table "resource_files", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "filename" + t.string "token", default: "", null: false + t.integer "user_id", null: false + t.boolean "public", default: true t.datetime "created_at" t.datetime "updated_at" - t.index ["token"], name: "index_resource_files_on_token", using: :btree - t.index ["user_id"], name: "index_resource_files_on_user_id", using: :btree + t.index ["token"], name: "index_resource_files_on_token" + t.index ["user_id"], name: "index_resource_files_on_user_id" end - create_table "signups", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "first_name" - t.string "last_name_name" - t.string "email" - t.string "institution" - t.text "comments", limit: 65535 + create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "first_name" + t.string "last_name_name" + t.string "email" + t.string "institution" + t.text "comments" t.datetime "created_at" t.datetime "updated_at" end - create_table "stems", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.text "preamble", limit: 65535 + create_table "stems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.text "preamble" t.datetime "created_at" t.datetime "updated_at" end - create_table "student_extensions", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id" - t.integer "workout_offering_id" + create_table "student_extensions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "user_id" + t.integer "workout_offering_id" t.datetime "soft_deadline" t.datetime "hard_deadline" t.datetime "created_at" t.datetime "updated_at" - t.integer "time_limit", limit: 4 + t.integer "time_limit" t.datetime "opening_date" - t.index ["user_id"], name: "index_student_extensions_on_user_id", using: :btree - t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id", using: :btree + t.index ["user_id"], name: "index_student_extensions_on_user_id" + t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id" end - create_table "tag_user_scores", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "user_id", null: false - t.integer "experience", default: 0 + create_table "tag_user_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "experience", default: 0 t.datetime "created_at" t.datetime "updated_at" - t.integer "completed_exercises", default: 0 - t.index ["user_id"], name: "index_tag_user_scores_on_user_id", using: :btree + t.integer "completed_exercises", default: 0 + t.index ["user_id"], name: "index_tag_user_scores_on_user_id" end - create_table "taggings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context", limit: 128 + create_table "taggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", limit: 128 t.datetime "created_at" - t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree - t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree + t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true + t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" end - create_table "tags", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name" + create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name" t.integer "taggings_count", default: 0 - t.index ["name"], name: "index_tags_on_name", unique: true, using: :btree + t.index ["name"], name: "index_tags_on_name", unique: true end - create_table "terms", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "season", null: false - t.date "starts_on", null: false - t.date "ends_on", null: false - t.integer "year", null: false + create_table "terms", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "season", null: false + t.date "starts_on", null: false + t.date "ends_on", null: false + t.integer "year", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "slug", default: "", null: false - t.index ["slug"], name: "index_terms_on_slug", unique: true, using: :btree - t.index ["year", "season"], name: "index_terms_on_year_and_season", using: :btree + t.string "slug", default: "", null: false + t.index ["slug"], name: "index_terms_on_slug", unique: true + t.index ["year", "season"], name: "index_terms_on_year_and_season" end - create_table "test_case_results", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "test_case_id", null: false - t.integer "user_id", null: false - t.text "execution_feedback", limit: 65535 + create_table "test_case_results", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "test_case_id", null: false + t.integer "user_id", null: false + t.text "execution_feedback" t.datetime "created_at" t.datetime "updated_at" - t.boolean "pass", null: false - t.integer "coding_prompt_answer_id" - t.index ["coding_prompt_answer_id"], name: "index_test_case_results_on_coding_prompt_answer_id", using: :btree - t.index ["test_case_id"], name: "index_test_case_results_on_test_case_id", using: :btree - t.index ["user_id"], name: "index_test_case_results_on_user_id", using: :btree + t.boolean "pass", null: false + t.integer "coding_prompt_answer_id" + t.index ["coding_prompt_answer_id"], name: "index_test_case_results_on_coding_prompt_answer_id" + t.index ["test_case_id"], name: "index_test_case_results_on_test_case_id" + t.index ["user_id"], name: "index_test_case_results_on_user_id" end - create_table "test_cases", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.text "negative_feedback", limit: 65535 - t.float "weight", limit: 24, null: false - t.text "description", limit: 65535 + create_table "test_cases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.text "negative_feedback" + t.float "weight", null: false + t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.integer "coding_prompt_id", null: false - t.text "input", limit: 65535, null: false - t.text "expected_output", limit: 65535, null: false - t.boolean "static", default: false, null: false - t.boolean "screening", default: false, null: false - t.boolean "example", default: false, null: false - t.boolean "hidden", default: false, null: false - t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id", using: :btree + t.integer "coding_prompt_id", null: false + t.text "input", null: false + t.text "expected_output", null: false + t.boolean "static", default: false, null: false + t.boolean "screening", default: false, null: false + t.boolean "example", default: false, null: false + t.boolean "hidden", default: false, null: false + t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id" end - create_table "time_zones", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name" - t.string "zone" - t.string "display_as" + create_table "time_zones", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name" + t.string "zone" + t.string "display_as" t.datetime "created_at" t.datetime "updated_at" end - create_table "user_groups", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name" + create_table "user_groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.text "description", limit: 65535 + t.text "description" end - create_table "users", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", limit: 4, default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip", limit: 255 - t.string "last_sign_in_ip", limit: 255 - t.string "confirmation_token", limit: 255 + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.datetime "created_at" t.datetime "updated_at" - t.string "first_name" - t.string "last_name" - t.integer "global_role_id", null: false - t.string "avatar" - t.string "slug", default: "", null: false - t.integer "current_workout_score_id" - t.integer "time_zone_id" - t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree - t.index ["current_workout_score_id"], name: "index_users_on_current_workout_score_id", unique: true, using: :btree - t.index ["email"], name: "index_users_on_email", unique: true, using: :btree - t.index ["global_role_id"], name: "index_users_on_global_role_id", using: :btree - t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree - t.index ["slug"], name: "index_users_on_slug", unique: true, using: :btree - t.index ["time_zone_id"], name: "index_users_on_time_zone_id", using: :btree + t.string "first_name" + t.string "last_name" + t.integer "global_role_id", null: false + t.string "avatar" + t.string "slug", default: "", null: false + t.integer "current_workout_score_id" + t.integer "time_zone_id" + t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true + t.index ["current_workout_score_id"], name: "index_users_on_current_workout_score_id", unique: true + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["global_role_id"], name: "index_users_on_global_role_id" + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + t.index ["slug"], name: "index_users_on_slug", unique: true + t.index ["time_zone_id"], name: "index_users_on_time_zone_id" end - create_table "visualization_loggings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8" do |t| - t.integer "user_id" - t.integer "exercise_id" - t.integer "workout_id" - t.integer "workout_offering_id" + create_table "visualization_loggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + t.integer "user_id" + t.integer "exercise_id" + t.integer "workout_id" + t.integer "workout_offering_id" t.datetime "created_at" t.datetime "updated_at" - t.index ["exercise_id"], name: "index_visualization_loggings_on_exercise_id", using: :btree - t.index ["user_id"], name: "index_visualization_loggings_on_user_id", using: :btree - t.index ["workout_id"], name: "index_visualization_loggings_on_workout_id", using: :btree - t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id", using: :btree + t.index ["exercise_id"], name: "index_visualization_loggings_on_exercise_id" + t.index ["user_id"], name: "index_visualization_loggings_on_user_id" + t.index ["workout_id"], name: "index_visualization_loggings_on_workout_id" + t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id" end - create_table "workout_offerings", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "course_offering_id", null: false - t.integer "workout_id", null: false + create_table "workout_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "course_offering_id", null: false + t.integer "workout_id", null: false t.datetime "created_at" t.datetime "updated_at" t.datetime "opening_date" t.datetime "soft_deadline" t.datetime "hard_deadline" - t.boolean "published", default: true, null: false - t.integer "time_limit" - t.integer "workout_policy_id" - t.integer "continue_from_workout_id" - t.string "lms_assignment_id" - t.boolean "most_recent", default: true - t.string "lms_assignment_url" - t.index ["continue_from_workout_id"], name: "workout_offerings_continue_from_workout_id_fk", using: :btree - t.index ["course_offering_id"], name: "index_workout_offerings_on_course_offering_id", using: :btree - t.index ["lms_assignment_id"], name: "index_workout_offerings_on_lms_assignment_id", using: :btree - t.index ["workout_id"], name: "index_workout_offerings_on_workout_id", using: :btree - t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id", using: :btree - end - - create_table "workout_owners", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.boolean "published", default: true, null: false + t.integer "time_limit" + t.integer "workout_policy_id" + t.integer "continue_from_workout_id" + t.string "lms_assignment_id" + t.boolean "most_recent", default: true + t.string "lms_assignment_url" + t.index ["continue_from_workout_id"], name: "workout_offerings_continue_from_workout_id_fk" + t.index ["course_offering_id"], name: "index_workout_offerings_on_course_offering_id" + t.index ["lms_assignment_id"], name: "index_workout_offerings_on_lms_assignment_id" + t.index ["workout_id"], name: "index_workout_offerings_on_workout_id" + t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id" + end + + create_table "workout_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "workout_id", null: false - t.integer "owner_id", null: false - t.index ["owner_id"], name: "workout_owners_owner_id_fk", using: :btree - t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true, using: :btree + t.integer "owner_id", null: false + t.index ["owner_id"], name: "workout_owners_owner_id_fk" + t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true end - create_table "workout_policies", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.boolean "hide_thumbnails_before_start" - t.boolean "hide_feedback_before_finish" - t.boolean "hide_compilation_feedback_before_finish" - t.boolean "no_review_before_close" - t.boolean "hide_feedback_in_review_before_close" - t.boolean "hide_thumbnails_in_review_before_close" - t.boolean "no_hints" - t.boolean "no_faq" - t.string "name", limit: 255 + create_table "workout_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.boolean "hide_thumbnails_before_start" + t.boolean "hide_feedback_before_finish" + t.boolean "hide_compilation_feedback_before_finish" + t.boolean "no_review_before_close" + t.boolean "hide_feedback_in_review_before_close" + t.boolean "hide_thumbnails_in_review_before_close" + t.boolean "no_hints" + t.boolean "no_faq" + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.boolean "invisible_before_review" - t.string "description", limit: 255 + t.boolean "invisible_before_review" + t.string "description" end - create_table "workout_scores", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.integer "workout_id", null: false - t.integer "user_id", null: false - t.float "score", limit: 24 - t.boolean "completed" + create_table "workout_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.integer "workout_id", null: false + t.integer "user_id", null: false + t.float "score" + t.boolean "completed" t.datetime "completed_at" t.datetime "last_attempted_at" - t.integer "exercises_completed", limit: 4 - t.integer "exercises_remaining", limit: 4 + t.integer "exercises_completed" + t.integer "exercises_remaining" t.datetime "created_at" t.datetime "updated_at" - t.integer "workout_offering_id" - t.string "lis_outcome_service_url" - t.string "lis_result_sourcedid" - t.integer "lti_workout_id", limit: 4 - t.index ["lti_workout_id"], name: "index_workout_scores_on_lti_workout_id", using: :btree - t.index ["user_id"], name: "index_workout_scores_on_user_id", using: :btree - t.index ["workout_id"], name: "index_workout_scores_on_workout_id", using: :btree - t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk", using: :btree + t.integer "workout_offering_id" + t.string "lis_outcome_service_url" + t.string "lis_result_sourcedid" + t.integer "lti_workout_id" + t.index ["lti_workout_id"], name: "index_workout_scores_on_lti_workout_id" + t.index ["user_id"], name: "index_workout_scores_on_user_id" + t.index ["workout_id"], name: "index_workout_scores_on_workout_id" + t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk" end - create_table "workouts", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| - t.string "name", default: "", null: false - t.boolean "scrambled", default: false + create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + t.string "name", default: "", null: false + t.boolean "scrambled", default: false t.datetime "created_at" t.datetime "updated_at" - t.text "description", limit: 65535 - t.integer "points_multiplier" - t.integer "creator_id" - t.string "external_id" - t.boolean "is_public" - t.index ["creator_id"], name: "workouts_creator_id_fk", using: :btree - t.index ["external_id"], name: "index_workouts_on_external_id", unique: true, using: :btree - t.index ["is_public"], name: "index_workouts_on_is_public", using: :btree + t.text "description" + t.integer "points_multiplier" + t.integer "creator_id" + t.string "external_id" + t.boolean "is_public" + t.index ["creator_id"], name: "workouts_creator_id_fk" + t.index ["external_id"], name: "index_workouts_on_external_id", unique: true + t.index ["is_public"], name: "index_workouts_on_is_public" end add_foreign_key "attempts", "exercise_versions", name: "attempts_exercise_version_id_fk" From 2c3493130b319fcabd1f24f460846a5164a625ee Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Fri, 3 Jul 2020 10:56:20 -0400 Subject: [PATCH 016/117] Get README from staging --- README.md | 208 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 118 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 196ae49d..7f461442 100644 --- a/README.md +++ b/README.md @@ -1,121 +1,149 @@ -Setting Up a Vagrant Environment for CodeWorkout -============================================ +# CodeWorkout -## Introduction: +CodeWorkout is an online system for people learning a programming language for the first time. +It is a free, open-source solution for practicing small programming problems. +Students may practice coding exercises or multiple-choice-questions on a variety of programming concepts in a web browser and receive immediate feedback. -Vagrant is designed to run on multiple platforms, including Mac OS X, Microsoft Windows, Debian, Ubuntu, CentOS, RedHat and Fedora. In this document we describe how to configure and run CodeWorkout project virtual development environment through Vagrant. +CodeWorkout was inspired by many great systems built by others, but aims to bring together the best from earlier forerunners while adding important new features. +It provides comprehensive support for teachers who want to use coding exercises in their courses, while also maintaining flexibility for self-paced learners who aren't part of an organized course. -## Installation Steps: +Try it out at [https://code-workout.cs.vt.edu]. +You can play around without signing up if you like. -1. Install [Vagrant](https://www.vagrantup.com/downloads.html) -2. Install [VirtualBox](https://www.virtualbox.org/wiki/Downloads) -3. Clone this repository -4. `$ cd code-workout` -5. `$ vagrant up` -6. `$ vagrant ssh` -7. `$ cd /vagrant` -8. `$ rails server` -9. After the provisioning script is complete you can go to: +## Contents +* [Setting up a Development Environment Using Docker](#setting-up-a-development-environment-using-docker) + - [Clone this repository](#clone-this-repository) + - [Install Docker and build the containers](#install-docker-and-build-the-containers) + - [Set up some development data](#set-up-some-development-data) + - [Run servers](#run-servers) + - [Other notes](#other-notes) +* [Making an exercise](#making-an-exercise) - * https://192.168.33.20:3000 for CodeWorkout server +## Setting up a Development Environment Using Docker -## Shut Down The Virtual Machine: +> Note: If you are comfortable setting up a Rails application, i.e., installing Ruby (2.3.8), Rails (4.2), and MySQL (5.7) on your own machine, you can just do that instead of using Docker. You'll need to change the `host` keys in [config/database.yml](config/database.yml) to `localhost`. -After you finish your work, you need to turn the virtual machine off. +The following steps will help set up a development environment for CodeWorkout using Docker and Docker Compose. +You can do your editing on your own machine using an editor of your choice; changes will be reflected in the Docker container. -1. Exit the virtual machine terminal by typing `exit` -2. `$ cd code-workout` -3. `$ vagrant halt` +### Clone this repository -## Re-run Development Servers: +```bash +$ git clone git@github.com:web-cat/code-workout.git +$ cd code-workout +``` -If you decided to shut down the virtual machine using `vagrant halt`, you have to re-run the servers again after you do `vagrant up`. +Check out the `staging` branch. Most new changes won't be accepted directly into the `master` branch. +```bash +$ git checkout staging +``` -1. `$ cd code-workout` -2. `$ vagrant up` -3. `$ vagrant ssh` -4. `$ cd /vagrant` -5. `$ rails server` +### Install Docker and build the containers -## Reprovision The Virtual Machine: +Instructions to install Docker may be found at the [Docker website](https://docs.docker.com/get-docker/). -If anything went wrong or you want to reprovision your virtual machine for any reason, follow these steps. +These instructions were written with Docker version `19.03.8` in mind. We don't do anything fancy, so things should work as long as you're reasonably up to date. -1. `$ cd code-workout` -2. `$ git pull` -3. `$ vagrant destroy` -4. `$ vagrant up` +We use [Docker Compose](https://docs.docker.com/get-docker/) to tie multiple containers together. One for the web application, and one each for the test and development databases. +Docker Compose comes installed with Docker automatically. -## Virtual Machine sudo password: +Inside the `code-workout` directory, do the following: +```bash +$ docker-compose up # build and start containers for the web application and the databases +``` -sudo password is `vagrant` in case you need to execute any commands that require sudo. +This step builds the `web` container using the provided [Dockerfile](Dockerfile) to install Ruby, Rails, and required dependencies. It also builds the `db_dev` and `db_test` containers, each of which pull a ready-to-use MySQL image from DockerHub. Two databases are set up: +* `codeworkout`, running on port 3306 on the `db_dev` container and port 3307 on the host +* `codeworkout_test`, running on port 3306 on the `db_test` container and port 3308 on the host -## CodeWorkout Database Test Data +Credentials for both databases: +* username: `codeworkout` +* pwd: `codeworkout` -The initial database population is defined by lib/tasks/sample_data.rake. -It uses the factories defined in spec/factories/* to generate entities. -If you add new modael classes and want to generate test data in the -database, please add to the sample_data.rake file so that this population -will happen automatically for everyone. The sample_data.rake contains -only "sample/try out" data for use during development, and it won't -appear on the production server. Initial database contents provided -for all new installs, including the production server, is described -in db/seeds.rb instead. +The first time you do this, it will take a bit of time to build all the containers. Subsequent runs of this command will just start the existing containers. - - The initial database includes the following accounts: - - admin@codeworkout.org (pass: adminadmin), has admin-level access - - example-1@railstutorial.org (pass: hokiehokie), has instructor access - - example-*@railstutorial.org (pass: hokiehokie) 50 students +Output from the containers will appear in your console. Do `Ctrl-C` to exit and stop the containers. - It also includes the following other objects: - - six terms (spring, summer I, summer II, fall, and winter 2016), - - one organization (VT) - - one course (CS 1114) - - two offerings of 1114 (one each semester) - - one course offering is set up with the admin and instructor - as instructors, and all other sample accounts as students +Do the following if you want to run the containers in the background and get your terminal back: +```bash +$ docker-compose up -d +``` - - To reset the database to the initial state do the following: - - `$ cd code-workout` - - `$ vagrant ssh` - - `$ cd /vagrant` - - `$ rake db:populate` - - `$ rails server` +You can `docker-compose down` to stop and remove the containers (or just `docker-compose stop` to stop without removing). -## Keep code-workout repository up to date: +### Set up some development data -During development of code-workout, other developers might add new gems to the project or add new migrations etc. To keep your local version up to date with the latest version do the following: +This next step sets up the database and populates it with fake data for development. +Do the following in the `code-workout` directory on your machine. -- Open a new terminal -- `$ cd code-workout` -- `$ git pull` -- `$ vagrant reload` -- `$ vagrant ssh` -- `$ cd /vagrant` -- `$ sudo bundle install` -- `$ rake db:populate` **Note:** This step will place the database in a simple starter state. -- `$ rails server` +```bash +$ docker-compose run web rake db:populate +``` +The above command is telling Docker to "run the command `rake db:populate` on the `web` container and exit". +This rake task is defined in [lib/tasks/sample_data.rake](lib/tasks/sample_data.rake), and runs the following tasks in order: -## Upload and Test Programming Exercises: +```bash +$ rake db:drop # drop the database +$ rake db:create # create the database +$ rake db:schema:load # load the schema from db/schema.rb +$ rake db:seed # load the seeded data (like timezones, seasons, etc.) +$ rake db:populate # load sample data; this is a custom rake task +``` -- Log in using your admin account admin@codeworkout.org (pass: adminadmin) -- Click "Upload Exercises" in the navigation bar, then click "Choose File". Choose your exercise file and click "Submit File". -- Click "Create Workouts" in the navigation bar to create new workouts. -- Go to the admin area by clicking on the little wrench icon to the left of the admin email address in the top menu bar. Click the "University-oriented" menu and select "Workout offerings". Click on "New Workout Offering". Create your new workout offering by choosing the course offering and the workout (the one you have just created in the previous step). Then select appropriate opening, soft deadline, and hard deadline dates for the workout offering and hit "Create Workout Offering" button to save it. -- Log in using student account example-2@railstutorial.org (pass: hokiehokie). Then navigate to the course offering you have chosen in the previous step, you should see the new workout in the list of workouts for this course offering. +> Run `rake -T` in your project root to see a list of available rake tasks. [What's rake?](https://github.com/ruby/rake) -## Connect to CodeWorkout Database: +The initial database population is defined by [lib/tasks/sample_data.rake](lib/tasks/sample_data.rake). +It uses the factories defined in [spec/factories](spec/factories) to generate entities. +If you add new model classes and want to generate test data in the database, please add to the `sample_data.rake` file so that this population will happen automatically for everyone. +The `sample_data.rake` contains only "sample/try out" data for use during development, and it won't +appear on the production server. +Initial database contents provided for all new installs, including the production server, is described in db/seeds.rb instead. -During development it is convenient to connect to CodeWorkout database from your host machine using [MySQL Workbench](https://www.mysql.com/products/workbench/). Once you installed MySQL Workbench create a new connection to CodeWorkout Database in the Vagrant machine using the following setup: +- The initial database includes the following accounts: + - admin@codeworkout.org (pass: adminadmin), has admin-level access + - example-1@railstutorial.org (pass: hokiehokie), has instructor access + - example-*@railstutorial.org (pass: hokiehokie) 50 students -- Connection Name: CodeWorkout -- Connection Method: Standard TCP/IP over SSH -- SSH Hostname: 192.168.33.20 -- SSH Username: vagrant -- SSH Password: vagrant -- MySQL Hostname: 127.0.0.1 -- MySQL Server Port: 3306 -- Username: codeworkout -- Password: codeworkout + It also includes the following other objects: + - six terms (spring, summer I, summer II, fall, and winter of the current year), + - one organization (VT) + - one course (CS 1114) + - two offerings of 1114 (one each semester) + - one course offering is set up with the admin and instructor + as instructors, and all other sample accounts as students +- To reset the database to the initial state do the following: + - `$ cd code-workout` + - `$ docker-compose run web rake db:populate` + +**A note on setting up a development database.** + +We load the schema directly from [db/schema.rb](db/schema.rb), because database migrations tend to go stale over time—running over 100 migrations, many of which are years old, is likely to run into errors. Migrations are useful for making *new* changes or reversing *recent* changes to the schema. + +### Run servers +To run the development server, do the following in the `code-workout` directory: + +```bash +$ docker-compose up # this may take a minute +``` + +In your browser, navigate to [https://localhost:9292](https://localhost:9292) and you should see the CodeWorkout homepage. (Make sure you have `https` in front of the URL.) + +You can edit files on your own machine; changes will be reflected in container. + +### Other notes + +**NOTE 1**: Since the Rails application is running on the `web` container, your typical `rails` or `rake` commands are run as you see above, i.e., with `docker-compose run web` in front of it. For example, to generate a model you would do `docker-compose run web rails g model MyModel`. + +**NOTE 2**: To end up in a bash shell in the container (i.e., to "SSH" into the server), do the following: +```bash +docker-compose run web bash +``` +Note that this does not set up the port forwarding, so if you do this and manually run the server (`./runservers.sh`), you won't be able to access it from your browser. + +**NOTE 3**: These docs and the Docker setup are recent. Please submit an issue if something is missing, incorrect, or unclear. + +## Making an Exercise + +For instructions on how to make an exercise, see [making_an_exercise.md](making_an_exercise.md). Note that this functionality is not directly available through the web interface for most users. Please get in touch if you want to add exercises to CodeWorkout. From 1f51d4a933c203ae6a0e32b2f9b1b62c2ee0088c Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 7 Jul 2020 21:13:40 -0400 Subject: [PATCH 017/117] Set deploy branch to rails-5-upgrade --- config/deploy/staging.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index 9fdcd097..a094a694 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -1,5 +1,5 @@ set :stage, :staging -set :branch, 'staging' +set :branch, 'rails-5-upgrade' set :rails_env, 'staging' # Simple Role Syntax From 4f8c6f6e8edb98433f49a90e802c56879c3eb47f Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 22 Oct 2020 03:41:26 -0400 Subject: [PATCH 018/117] update models Signed-off-by: Alex Hicks --- app/models/application_record.rb | 3 +++ app/models/attempt.rb | 2 +- app/models/choice.rb | 2 +- app/models/coding_prompt.rb | 2 +- app/models/coding_prompt_answer.rb | 2 +- app/models/course.rb | 2 +- app/models/course_enrollment.rb | 2 +- app/models/course_exercise.rb | 2 +- app/models/course_offering.rb | 2 +- app/models/course_role.rb | 2 +- app/models/error.rb | 6 +++--- app/models/exercise.rb | 2 +- app/models/exercise_collection.rb | 2 +- app/models/exercise_family.rb | 2 +- app/models/exercise_owner.rb | 2 +- app/models/exercise_version.rb | 2 +- app/models/exercise_workout.rb | 2 +- app/models/global_role.rb | 2 +- app/models/group_access_request.rb | 2 +- app/models/identity.rb | 2 +- app/models/irt_data.rb | 2 +- app/models/license.rb | 2 +- app/models/license_policy.rb | 2 +- app/models/lms_instance.rb | 2 +- app/models/lms_type.rb | 2 +- app/models/lti_identity.rb | 2 +- app/models/lti_workout.rb | 2 +- app/models/membership.rb | 2 +- app/models/multiple_choice_prompt.rb | 2 +- app/models/multiple_choice_prompt_answer.rb | 2 +- app/models/organization.rb | 2 +- app/models/prompt.rb | 2 +- app/models/prompt_answer.rb | 2 +- app/models/resource_file.rb | 2 +- app/models/stem.rb | 2 +- app/models/student_extension.rb | 2 +- app/models/tag_user_score.rb | 2 +- app/models/term.rb | 2 +- app/models/test_case.rb | 2 +- app/models/test_case_result.rb | 2 +- app/models/time_zone.rb | 2 +- app/models/user.rb | 2 +- app/models/user_group.rb | 2 +- app/models/visualization_logging.rb | 2 +- app/models/workout.rb | 2 +- app/models/workout_offering.rb | 2 +- app/models/workout_owner.rb | 2 +- app/models/workout_policy.rb | 2 +- app/models/workout_score.rb | 2 +- 49 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 app/models/application_record.rb diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 00000000..10a4cba8 --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/attempt.rb b/app/models/attempt.rb index f314cb59..d944fb25 100644 --- a/app/models/attempt.rb +++ b/app/models/attempt.rb @@ -58,7 +58,7 @@ # active_score relation ... and that non-nil active_score should be the # same as its workout_score relation. # -class Attempt < ActiveRecord::Base +class Attempt < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/choice.rb b/app/models/choice.rb index d165ba8b..41736dc9 100644 --- a/app/models/choice.rb +++ b/app/models/choice.rb @@ -34,7 +34,7 @@ # have a non-zero value indicating the partial credit awarded for that single # choice. # -class Choice < ActiveRecord::Base +class Choice < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/coding_prompt.rb b/app/models/coding_prompt.rb index cb58423f..9d76e648 100644 --- a/app/models/coding_prompt.rb +++ b/app/models/coding_prompt.rb @@ -22,7 +22,7 @@ # acts_as (see the documentation on-line for the activerecord-acts_as # gem). # -class CodingPrompt < ActiveRecord::Base +class CodingPrompt < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/coding_prompt_answer.rb b/app/models/coding_prompt_answer.rb index 8a03d848..2ed30753 100644 --- a/app/models/coding_prompt_answer.rb +++ b/app/models/coding_prompt_answer.rb @@ -14,7 +14,7 @@ # PromptAnswer via acts_as (see the documentation on-line for the # activerecord-acts_as gem). # -class CodingPromptAnswer < ActiveRecord::Base +class CodingPromptAnswer < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/course.rb b/app/models/course.rb index 21159f91..6d20958e 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -25,7 +25,7 @@ # description. A course may have one or more course offerings in any # specific term. # -class Course < ActiveRecord::Base +class Course < ApplicationRecord extend FriendlyId friendly_id :number_without_spaces, use: [:history, :scoped], scope: :organization diff --git a/app/models/course_enrollment.rb b/app/models/course_enrollment.rb index 87751b1c..3e6efc3c 100644 --- a/app/models/course_enrollment.rb +++ b/app/models/course_enrollment.rb @@ -20,7 +20,7 @@ # indicating each user's role with respect to the course offerings in which # they are enrolled. # -class CourseEnrollment < ActiveRecord::Base +class CourseEnrollment < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/course_exercise.rb b/app/models/course_exercise.rb index 3a80e18d..fc1f0464 100644 --- a/app/models/course_exercise.rb +++ b/app/models/course_exercise.rb @@ -20,7 +20,7 @@ # If we really need many-to-many, though, since right now, exercises can # only belong to one course? # -class CourseExercise < ActiveRecord::Base +class CourseExercise < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/course_offering.rb b/app/models/course_offering.rb index 88fa804a..3bc4fa13 100644 --- a/app/models/course_offering.rb +++ b/app/models/course_offering.rb @@ -23,7 +23,7 @@ # ============================================================================= # Represents a single section (or offering) of a course in a specific term. # -class CourseOffering < ActiveRecord::Base +class CourseOffering < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/course_role.rb b/app/models/course_role.rb index ef9f03c2..36fb5165 100644 --- a/app/models/course_role.rb +++ b/app/models/course_role.rb @@ -15,7 +15,7 @@ # Represents the role a user has with respect to a course offering in which # the user is enrolled. Roles are stored in the CourseEnrollment relationship. # -class CourseRole < ActiveRecord::Base +class CourseRole < ApplicationRecord #~ Validation ............................................................... diff --git a/app/models/error.rb b/app/models/error.rb index 097fa9ea..89d9d96d 100644 --- a/app/models/error.rb +++ b/app/models/error.rb @@ -26,6 +26,6 @@ # This model class is here purely so that run-time errors can be tracked # in the database for development/debugging purposes. # -class Error < ActiveRecord::Base -# belongs_to :usable, polymorphic: true -end +class Error < ApplicationRecord +# belongs_to :usable, polymorphic: true +end diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 6c2781c1..bc918841 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -44,7 +44,7 @@ # in effect when they gave their answer. New users seeing an exercise # for the first time always see the newest version. # -class Exercise < ActiveRecord::Base +class Exercise < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/exercise_collection.rb b/app/models/exercise_collection.rb index e96de586..6d44e211 100644 --- a/app/models/exercise_collection.rb +++ b/app/models/exercise_collection.rb @@ -20,7 +20,7 @@ # index_exercise_collections_on_user_id (user_id) # -class ExerciseCollection < ActiveRecord::Base +class ExerciseCollection < ApplicationRecord belongs_to :user_group, inverse_of: :exercise_collection belongs_to :user belongs_to :license diff --git a/app/models/exercise_family.rb b/app/models/exercise_family.rb index 503e0a77..28467d15 100644 --- a/app/models/exercise_family.rb +++ b/app/models/exercise_family.rb @@ -21,7 +21,7 @@ # reflect the "closely related" nature of these variants of the same # question--they are different Exercises, but in the same family. # -class ExerciseFamily < ActiveRecord::Base +class ExerciseFamily < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/exercise_owner.rb b/app/models/exercise_owner.rb index 4e58284b..f846e6b5 100644 --- a/app/models/exercise_owner.rb +++ b/app/models/exercise_owner.rb @@ -18,7 +18,7 @@ # purpose of managing editing access to exercises, particularly "private" # exercises that are not publicly available in the gym. # -class ExerciseOwner < ActiveRecord::Base +class ExerciseOwner < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/exercise_version.rb b/app/models/exercise_version.rb index 3a09b64c..c439b699 100644 --- a/app/models/exercise_version.rb +++ b/app/models/exercise_version.rb @@ -26,7 +26,7 @@ # Represents one version of an exercise--a single snapshot in the exercise's # entire edit history. # -class ExerciseVersion < ActiveRecord::Base +class ExerciseVersion < ApplicationRecord #~ Accessor attr_accessor :answer_code diff --git a/app/models/exercise_workout.rb b/app/models/exercise_workout.rb index 5fcc5dd1..c65e313d 100644 --- a/app/models/exercise_workout.rb +++ b/app/models/exercise_workout.rb @@ -21,7 +21,7 @@ # indicating which exercises are included in a given workout, and in # what order. The order is determined by "position", which starts at 1. # -class ExerciseWorkout < ActiveRecord::Base +class ExerciseWorkout < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/global_role.rb b/app/models/global_role.rb index 0f62f54b..b459040b 100644 --- a/app/models/global_role.rb +++ b/app/models/global_role.rb @@ -13,7 +13,7 @@ # Represents a user's global permissions on the system. This has now # degenerated into what ammounts to a global "administrator" flag. # -class GlobalRole < ActiveRecord::Base +class GlobalRole < ApplicationRecord #~ Validation ............................................................... diff --git a/app/models/group_access_request.rb b/app/models/group_access_request.rb index f003105d..5c6806dd 100644 --- a/app/models/group_access_request.rb +++ b/app/models/group_access_request.rb @@ -16,7 +16,7 @@ # index_group_access_requests_on_user_id (user_id) # -class GroupAccessRequest < ActiveRecord::Base +class GroupAccessRequest < ApplicationRecord belongs_to :user belongs_to :user_group end diff --git a/app/models/identity.rb b/app/models/identity.rb index c5a7876c..df75d4d8 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -20,7 +20,7 @@ # authentication provider. A single user object may be associated with # multiple identities, each from a separate provider. # -class Identity < ActiveRecord::Base +class Identity < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/irt_data.rb b/app/models/irt_data.rb index 02674409..728123be 100644 --- a/app/models/irt_data.rb +++ b/app/models/irt_data.rb @@ -12,7 +12,7 @@ # ============================================================================= # Represents IRT data used for calculating exercise performance. # -class IRTData < ActiveRecord::Base +class IRTData < ApplicationRecord #~ Validation ............................................................... diff --git a/app/models/license.rb b/app/models/license.rb index 0efc6bf9..49cf0138 100644 --- a/app/models/license.rb +++ b/app/models/license.rb @@ -15,7 +15,7 @@ # index_licenses_on_license_policy_id (license_policy_id) # -class License < ActiveRecord::Base +class License < ApplicationRecord belongs_to :license_policy, inverse_of: :licenses has_many :exercise_collections end diff --git a/app/models/license_policy.rb b/app/models/license_policy.rb index af9b4e4d..423e759c 100644 --- a/app/models/license_policy.rb +++ b/app/models/license_policy.rb @@ -11,7 +11,7 @@ # updated_at :datetime # -class LicensePolicy < ActiveRecord::Base +class LicensePolicy < ApplicationRecord has_many :licenses, inverse_of: :license_policy accepts_nested_attributes_for :licenses end diff --git a/app/models/lms_instance.rb b/app/models/lms_instance.rb index 97b37eb2..3bfedae8 100644 --- a/app/models/lms_instance.rb +++ b/app/models/lms_instance.rb @@ -18,7 +18,7 @@ # lms_instances_lms_type_id_fk (lms_type_id) # -class LmsInstance < ActiveRecord::Base +class LmsInstance < ApplicationRecord belongs_to :lms_type, inverse_of: :lms_instances belongs_to :organization has_many :course_offerings, inverse_of: :lms_instance diff --git a/app/models/lms_type.rb b/app/models/lms_type.rb index 37313aaa..a58cab7d 100644 --- a/app/models/lms_type.rb +++ b/app/models/lms_type.rb @@ -12,7 +12,7 @@ # index_lms_types_on_name (name) UNIQUE # -class LmsType < ActiveRecord::Base +class LmsType < ApplicationRecord #~ Relationships ............................................................ has_many :lms_instances, inverse_of: :lms_types diff --git a/app/models/lti_identity.rb b/app/models/lti_identity.rb index b3927764..249d24c6 100644 --- a/app/models/lti_identity.rb +++ b/app/models/lti_identity.rb @@ -15,7 +15,7 @@ # index_lti_identities_on_user_id (user_id) # -class LtiIdentity < ActiveRecord::Base +class LtiIdentity < ApplicationRecord belongs_to :user belongs_to :lms_instance end diff --git a/app/models/lti_workout.rb b/app/models/lti_workout.rb index 925dae9a..e69c652a 100644 --- a/app/models/lti_workout.rb +++ b/app/models/lti_workout.rb @@ -15,7 +15,7 @@ # index_lti_workouts_on_workout_id (workout_id) # -class LtiWorkout < ActiveRecord::Base +class LtiWorkout < ApplicationRecord belongs_to :workout belongs_to :lms_instance has_many :workout_scores diff --git a/app/models/membership.rb b/app/models/membership.rb index 22cf5a10..124d7807 100644 --- a/app/models/membership.rb +++ b/app/models/membership.rb @@ -9,7 +9,7 @@ # updated_at :datetime # -class Membership < ActiveRecord::Base +class Membership < ApplicationRecord belongs_to :user belongs_to :user_group end diff --git a/app/models/multiple_choice_prompt.rb b/app/models/multiple_choice_prompt.rb index b7703e7d..9d7f95e7 100644 --- a/app/models/multiple_choice_prompt.rb +++ b/app/models/multiple_choice_prompt.rb @@ -18,7 +18,7 @@ # One multiple-choice prompt includes one or more choices, which are # represented at different objects. # -class MultipleChoicePrompt < ActiveRecord::Base +class MultipleChoicePrompt < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/multiple_choice_prompt_answer.rb b/app/models/multiple_choice_prompt_answer.rb index 9245cf87..e615f276 100644 --- a/app/models/multiple_choice_prompt_answer.rb +++ b/app/models/multiple_choice_prompt_answer.rb @@ -11,7 +11,7 @@ # PromptAnswer via acts_as (see the documentation on-line for the # activerecord-acts_as gem). # -class MultipleChoicePromptAnswer < ActiveRecord::Base +class MultipleChoicePromptAnswer < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/organization.rb b/app/models/organization.rb index bff8e4c0..ead3ba37 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -19,7 +19,7 @@ # Represents a university, college, school, or other organization that # has courses. # -class Organization < ActiveRecord::Base +class Organization < ApplicationRecord extend FriendlyId friendly_id :abbreviation, use: :history diff --git a/app/models/prompt.rb b/app/models/prompt.rb index 4af65fe8..3c96a380 100644 --- a/app/models/prompt.rb +++ b/app/models/prompt.rb @@ -42,7 +42,7 @@ # # Prompts are ordered by "position", which starts at 1. # -class Prompt < ActiveRecord::Base +class Prompt < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/prompt_answer.rb b/app/models/prompt_answer.rb index dd156d70..84eb9e92 100644 --- a/app/models/prompt_answer.rb +++ b/app/models/prompt_answer.rb @@ -30,7 +30,7 @@ # A prompt answer represents a user's answer for one prompt in one attempt # at an exercise. # -class PromptAnswer < ActiveRecord::Base +class PromptAnswer < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/resource_file.rb b/app/models/resource_file.rb index b4f5240f..70038dd4 100644 --- a/app/models/resource_file.rb +++ b/app/models/resource_file.rb @@ -20,7 +20,7 @@ # Represents a file uploaded by a user for use in exercises, such as an # image or video file. # -class ResourceFile < ActiveRecord::Base +class ResourceFile < ApplicationRecord include Tokenable #for unique non-serial url tokens diff --git a/app/models/stem.rb b/app/models/stem.rb index e2d29e95..9fffc26e 100644 --- a/app/models/stem.rb +++ b/app/models/stem.rb @@ -26,7 +26,7 @@ # ExerciseVersions on the same edit history for one exercise may refer # to the same stem, if the stem was not edited. # -class Stem < ActiveRecord::Base +class Stem < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/student_extension.rb b/app/models/student_extension.rb index e88352f8..72cd1c1d 100644 --- a/app/models/student_extension.rb +++ b/app/models/student_extension.rb @@ -18,7 +18,7 @@ # index_student_extensions_on_workout_offering_id (workout_offering_id) # -class StudentExtension < ActiveRecord::Base +class StudentExtension < ApplicationRecord belongs_to :user belongs_to :workout_offering diff --git a/app/models/tag_user_score.rb b/app/models/tag_user_score.rb index cf9c2f8f..4f401832 100644 --- a/app/models/tag_user_score.rb +++ b/app/models/tag_user_score.rb @@ -17,7 +17,7 @@ # ============================================================================= # Represents a user's xp score for a given tag. # -class TagUserScore < ActiveRecord::Base +class TagUserScore < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/term.rb b/app/models/term.rb index d6bbb5b5..828bb806 100644 --- a/app/models/term.rb +++ b/app/models/term.rb @@ -21,7 +21,7 @@ # Represents an academic term or semester, which indicates the time period # for one or more course offerings. # -class Term < ActiveRecord::Base +class Term < ApplicationRecord extend FriendlyId friendly_id :display_name, use: :history diff --git a/app/models/test_case.rb b/app/models/test_case.rb index a83f8a96..12ce18fa 100644 --- a/app/models/test_case.rb +++ b/app/models/test_case.rb @@ -31,7 +31,7 @@ # Represents a test case used to evaluate a student's answer to a coding # prompt. # -class TestCase < ActiveRecord::Base +class TestCase < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/test_case_result.rb b/app/models/test_case_result.rb index 877a1caf..3669e1b6 100644 --- a/app/models/test_case_result.rb +++ b/app/models/test_case_result.rb @@ -22,7 +22,7 @@ # ============================================================================= # Represents the outcome of a test case on a coding prompt. # -class TestCaseResult < ActiveRecord::Base +class TestCaseResult < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/time_zone.rb b/app/models/time_zone.rb index 34f20067..3fccef29 100644 --- a/app/models/time_zone.rb +++ b/app/models/time_zone.rb @@ -10,7 +10,7 @@ # updated_at :datetime # -class TimeZone < ActiveRecord::Base +class TimeZone < ApplicationRecord has_many :users def to_s return display_as diff --git a/app/models/user.rb b/app/models/user.rb index 4135a9bf..4785fcea 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -40,7 +40,7 @@ # ============================================================================= # Represents a single user account on the system. # -class User < ActiveRecord::Base +class User < ApplicationRecord include Gravtastic gravtastic secure: true, default: 'monsterid' diff --git a/app/models/user_group.rb b/app/models/user_group.rb index 4c10e95a..55e307e2 100644 --- a/app/models/user_group.rb +++ b/app/models/user_group.rb @@ -9,7 +9,7 @@ # description :text(65535) # -class UserGroup < ActiveRecord::Base +class UserGroup < ApplicationRecord has_many :memberships has_many :users, through: :memberships has_many :group_access_requests, inverse_of: :user_group diff --git a/app/models/visualization_logging.rb b/app/models/visualization_logging.rb index 99b34151..ff0fd415 100644 --- a/app/models/visualization_logging.rb +++ b/app/models/visualization_logging.rb @@ -18,7 +18,7 @@ # index_visualization_loggings_on_workout_offering_id (workout_offering_id) # -class VisualizationLogging < ActiveRecord::Base +class VisualizationLogging < ApplicationRecord belongs_to :user belongs_to :exercise belongs_to :workout diff --git a/app/models/workout.rb b/app/models/workout.rb index 973ea794..c3cd173e 100644 --- a/app/models/workout.rb +++ b/app/models/workout.rb @@ -35,7 +35,7 @@ # the "courses" associated with a workout are those for which course # offerings have been given workout offerings. # -class Workout < ActiveRecord::Base +class Workout < ApplicationRecord #~ Relationships ............................................................ acts_as_taggable_on :tags, :languages, :styles diff --git a/app/models/workout_offering.rb b/app/models/workout_offering.rb index 407bbb72..d6492ba5 100644 --- a/app/models/workout_offering.rb +++ b/app/models/workout_offering.rb @@ -35,7 +35,7 @@ # due dates that control when the students in the corresponding course # offering can take the workout (and thus, when they must complete it). # -class WorkoutOffering < ActiveRecord::Base +class WorkoutOffering < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/workout_owner.rb b/app/models/workout_owner.rb index 084c0a41..671b6cbb 100644 --- a/app/models/workout_owner.rb +++ b/app/models/workout_owner.rb @@ -18,7 +18,7 @@ # purpose of managing editing access to workouts, particularly "private" # workouts that are not publicly available in the gym. # -class WorkoutOwner < ActiveRecord::Base +class WorkoutOwner < ApplicationRecord #~ Relationships ............................................................ diff --git a/app/models/workout_policy.rb b/app/models/workout_policy.rb index d8a45ad4..3555b0a6 100644 --- a/app/models/workout_policy.rb +++ b/app/models/workout_policy.rb @@ -18,6 +18,6 @@ # description :string(255) # -class WorkoutPolicy < ActiveRecord::Base +class WorkoutPolicy < ApplicationRecord has_many :workout_offerings, inverse_of: :workout_policy, dependent: :nullify end diff --git a/app/models/workout_score.rb b/app/models/workout_score.rb index 306cdfbc..c7aefafd 100644 --- a/app/models/workout_score.rb +++ b/app/models/workout_score.rb @@ -69,7 +69,7 @@ # offering, meaning a second workout offering of the same workout). # We want to support all of these situations. # -class WorkoutScore < ActiveRecord::Base +class WorkoutScore < ApplicationRecord #~ Relationships ............................................................ From 705d76eb3a32f007927c09128b478d11df6e36e1 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 22 Oct 2020 03:41:43 -0400 Subject: [PATCH 019/117] update jobs Signed-off-by: Alex Hicks --- app/jobs/application_job.rb | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/jobs/application_job.rb diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 00000000..a009ace5 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end From aa1e2920d9a4583f725f946bdfcf782cd48454df Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 22 Oct 2020 03:42:03 -0400 Subject: [PATCH 020/117] update migrations Signed-off-by: Alex Hicks --- db/migrate/20130912181453_create_organizations.rb | 2 +- db/migrate/20130912182541_create_courses.rb | 2 +- db/migrate/20130912200825_create_terms.rb | 2 +- db/migrate/20130912202622_create_course_offerings.rb | 2 +- db/migrate/20130913163500_devise_create_users.rb | 2 +- db/migrate/20130915012131_create_course_roles.rb | 2 +- db/migrate/20130915030206_create_course_enrollments.rb | 2 +- db/migrate/20130915044012_create_tags.rb | 2 +- db/migrate/20130915050202_create_prompts.rb | 2 +- db/migrate/20130915055146_create_choices.rb | 2 +- db/migrate/20130915203004_create_languages.rb | 2 +- db/migrate/20130916142010_create_global_roles.rb | 2 +- db/migrate/20130916203844_add_initial_columns_to_users.rb | 2 +- db/migrate/20130918174151_create_stems.rb | 2 +- db/migrate/20130918190714_create_exercises.rb | 2 +- .../20130923164556_rename_exercises_type_to_question_type.rb | 2 +- db/migrate/20131001051705_create_experience_column.rb | 2 +- db/migrate/20131003025313_create_attempts.rb | 2 +- db/migrate/20131009161911_add_tagtype_to_tags.rb | 2 +- db/migrate/20131010184442_change_tags_in_exercise.rb | 2 +- ...1170027_create_total_exercise_and_total_experience_in_tag.rb | 2 +- db/migrate/20131015080408_create_tag_user_scores.rb | 2 +- db/migrate/20131015083558_rename_tag_name.rb | 2 +- ...20131015151339_add_completed_exercises_to_tag_user_scores.rb | 2 +- db/migrate/20131016023405_change_language_to_tags.rb | 2 +- db/migrate/20131016154908_create_workouts.rb | 2 +- db/migrate/20131016175805_create_tags_workouts.rb | 2 +- ...131119032619_rename_workouts_exercises_exercises_workouts.rb | 2 +- db/migrate/20131210061217_add_coding_exercise_columns.rb | 2 +- db/migrate/20140422205525_update_exercise.rb | 2 +- db/migrate/20140428202518_create_resource_files.rb | 2 +- db/migrate/20141019133633_add_name_to_users.rb | 2 +- db/migrate/20141019134233_create_identities.rb | 2 +- db/migrate/20141019235246_add_name_to_workouts.rb | 2 +- db/migrate/20141019235449_add_target_group_to_workouts.rb | 2 +- db/migrate/20141019235637_add_points_multiplier_to_workouts.rb | 2 +- db/migrate/20141019235708_add_opening_date_to_workouts.rb | 2 +- db/migrate/20141019235750_add_soft_deadline_to_workouts.rb | 2 +- db/migrate/20141019235803_add_hard_deadline_to_workouts.rb | 2 +- ...0141102232155_create_join_table_course_offerings_workouts.rb | 2 +- db/migrate/20141109193127_create_workout_offerings.rb | 2 +- db/migrate/20141109193909_add_deadlines_to_workout_offering.rb | 2 +- db/migrate/20141109202442_create_exercise_workouts.rb | 2 +- .../20141109204133_fix_order_column_in_exercise_workouts.rb | 2 +- db/migrate/20141110010213_remove_deadlines_from_workouts.rb | 2 +- db/migrate/20141110023518_create_course_exercises.rb | 2 +- db/migrate/20150111180500_create_base_exercises.rb | 2 +- db/migrate/20150111182242_add_base_exercise_id_to_exercise.rb | 2 +- db/migrate/20150111185855_add_version_to_exercise.rb | 2 +- .../20150111200649_remove_question_type_from_exercises.rb | 2 +- db/migrate/20150111204245_add_versions_to_base_exercises.rb | 2 +- db/migrate/20150111222945_remove_user_id_from_exercises.rb | 2 +- db/migrate/20150112141536_create_variation_groups.rb | 2 +- .../20150112143114_add_variation_group_id_to_base_exercise.rb | 2 +- db/migrate/20150117190855_create_workout_scores.rb | 2 +- db/migrate/20150201192746_remove_starter_code_from_exercise.rb | 2 +- db/migrate/20150201224732_create_coding_questions.rb | 2 +- .../20150201230342_remove_columns_from_coding_question.rb | 2 +- .../20150201230748_remove_columns_from_coding_question_2.rb | 2 +- db/migrate/20150201231050_add_columns_to_coding_questions.rb | 2 +- db/migrate/20150201234250_add_feedback_to_coding_question.rb | 2 +- .../20150201234351_remove_feedback_from_coding_question.rb | 2 +- db/migrate/20150201235152_create_test_case_results.rb | 2 +- db/migrate/20150205220518_rectify_test_case_result.rb | 2 +- db/migrate/20150205220837_rectify_test_case_result2.rb | 2 +- db/migrate/20150205221653_change_score_in_attempts.rb | 2 +- ...20150207221009_add_default_to_points_in_exercise_workouts.rb | 2 +- db/migrate/20150210001815_create_test_cases.rb | 2 +- .../20150210012044_add_test_method_to_coding_questions.rb | 2 +- db/migrate/20150218220059_fix_class_name_in_coding_questions.rb | 2 +- .../20150218220317_add_starter_code_to_coding_questions.rb | 2 +- .../20150219054901_fix_method_name_in_coding_questions.rb | 2 +- db/migrate/20150312175933_add_creator_id_to_courses.rb | 2 +- db/migrate/20150312183927_add_creator_id_to_exercises.rb | 2 +- db/migrate/20150312184040_add_creator_id_to_base_exercises.rb | 2 +- db/migrate/20150312184111_add_creator_id_to_workouts.rb | 2 +- .../20150312184659_remove_creator_id_from_base_exercises.rb | 2 +- db/migrate/20150320002742_add_omniauth_columns_to_users.rb | 2 +- db/migrate/20150324135215_remove_omniauth_columns_from_users.rb | 2 +- db/migrate/20150325143018_add_avatar_to_users.rb | 2 +- ...e_current_version_to_current_version_id_in_base_exercises.rb | 2 +- db/migrate/20150326123644_add_null_constraints_and_indices.rb | 2 +- db/migrate/20150326163802_add_foreign_key_constraints.rb | 2 +- .../20150326173027_rename_title_to_name_in_variation_group.rb | 2 +- ...50326193058_rename_ordering_to_order_in_exercise_workouts.rb | 2 +- db/migrate/20150326193301_rename_title_to_name_in_exercises.rb | 2 +- db/migrate/20150327213048_create_active_admin_comments.rb | 2 +- ...150328005919_rename_display_name_to_name_in_organizations.rb | 2 +- db/migrate/20150328011131_remove_name_from_users.rb | 2 +- db/migrate/20150328150855_remove_name_from_course_offering.rb | 2 +- db/migrate/20150330194627_create_friendly_id_slugs.rb | 2 +- db/migrate/20150331032124_add_slug_to_term.rb | 2 +- db/migrate/20150331121739_add_slug_to_organization.rb | 2 +- db/migrate/20150402033335_add_slug_to_course.rb | 2 +- db/migrate/20150402043958_add_slug_to_user.rb | 2 +- .../20150429122658_rename_exercises_to_exercise_versions.rb | 2 +- db/migrate/20150429132614_rename_base_exercises_to_exercises.rb | 2 +- db/migrate/20150429135647_create_exercise_owners.rb | 2 +- .../20150429160335_rename_variation_group_to_exercise_family.rb | 2 +- db/migrate/20150429170252_remove_user_id_from_base_exercises.rb | 2 +- db/migrate/20150430165328_rename_for_acts_as_list.rb | 2 +- db/migrate/20150501192055_rename_prompt_features.rb | 2 +- .../20150502155416_rename_coding_question_in_test_cases.rb | 2 +- ...0502155552_move_choices_from_exercise_versions_to_prompts.rb | 2 +- ...2181403_move_features_from_exercise_versions_to_exercises.rb | 2 +- ...50502203324_move_tags_from_exercise_versions_to_exercises.rb | 2 +- db/migrate/20150502210227_add_stats_to_exercises.rb | 2 +- .../20150502210606_remove_question_from_exercise_versions.rb | 2 +- .../20150503003408_add_workout_offering_to_workout_score.rb | 2 +- db/migrate/20150503004546_add_to_attempt.rb | 2 +- db/migrate/20150503153952_create_workout_owners.rb | 2 +- db/migrate/20150503190401_fix_choice_answer.rb | 2 +- db/migrate/20150503232731_create_prompt_answer.rb | 2 +- db/migrate/20150504000235_create_coding_prompt_answer.rb | 2 +- .../20150504002111_create_multiple_choice_prompt_answer.rb | 2 +- db/migrate/20150504004247_create_irt_data.rb | 2 +- .../20150504051530_add_cutoff_date_to_course_offerings.rb | 2 +- db/migrate/20150506122722_create_attempts_tag_user_scores.rb | 2 +- db/migrate/20150506124650_remove_target_group_from_workouts.rb | 2 +- db/migrate/20150506161404_add.rb | 2 +- db/migrate/20150507004949_remove_is_scrambled_from_prompt.rb | 2 +- ...0447_remove_null_constraint_from_exercise_current_version.rb | 2 +- .../20150507143058_rename_prompt_to_question_in_prompts.rb | 2 +- db/migrate/20150507163037_remove_tags.rb | 2 +- ..._acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb | 2 +- ...240_add_missing_unique_indices.acts_as_taggable_on_engine.rb | 2 +- ...taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb | 2 +- ...242_add_missing_taggable_index.acts_as_taggable_on_engine.rb | 2 +- ...change_collation_for_tag_names.acts_as_taggable_on_engine.rb | 2 +- ...510144703_rename_position_to_version_in_exercise_versions.rb | 2 +- db/migrate/20150511225412_actable_ids_are_not_unique.rb | 2 +- db/migrate/20150515205710_revise_test_case_structure.rb | 2 +- ...150522205618_add_answer_to_multiple_choice_prompt_answers.rb | 2 +- ...5184958_remove_answer_from_multiple_choice_prompt_answers.rb | 2 +- db/migrate/20150528120715_add_error_to_coding_prompt_answer.rb | 2 +- .../20150531004056_change_class_name_to_string_in_errors.rb | 2 +- db/migrate/20150604011650_add_published_to_workout_offerings.rb | 2 +- ...0608001534_change_dates_to_datetimes_in_workout_offerings.rb | 2 +- db/migrate/20150912035415_create_time_zones.rb | 2 +- db/migrate/20150912200222_add_time_zone_id_to_users.rb | 2 +- db/migrate/20150916031620_add_current_workout_score_to_users.rb | 2 +- db/migrate/20150921113747_add_is_public_to_workouts.rb | 2 +- db/migrate/20150923144539_add_feedback_ready_to_attempts.rb | 2 +- db/migrate/20150927020504_create_student_extensions.rb | 2 +- .../20151015033713_add_time_limit_to_workout_offerings.rb | 2 +- db/migrate/20151015035823_create_workout_policy.rb | 2 +- .../20151015044326_add_time_limit_to_student_extensions.rb | 2 +- ...217060925_add_invisible_before_review_to_workout_policies.rb | 2 +- .../20160224064748_add_opening_date_to_student_extensions.rb | 2 +- ...5005739_add_continue_from_workout_id_to_workout_offerings.rb | 2 +- db/migrate/20160512005525_add_keys.rb | 2 +- db/migrate/20160525210736_add_lis_info_to_workout_score.rb | 2 +- db/migrate/20160602163822_create_lms_instances.rb | 2 +- db/migrate/20160602181823_add_url_to_lms_instance.rb | 2 +- db/migrate/20160602182001_create_lms_types.rb | 2 +- db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb | 2 +- .../20160602185259_add_lms_instance_to_course_offering.rb | 2 +- .../20160614153123_add_organization_ref_to_lms_instances.rb | 2 +- ...20160706143029_add_lms_assignment_id_to_workout_offerings.rb | 2 +- ...0160707200651_make_workout_offerings_published_by_default.rb | 2 +- .../20160829140059_add_description_to_workout_policies.rb | 2 +- .../20161129200944_add_most_recent_to_workout_offerings.rb | 2 +- db/migrate/20170314235543_create_lti_identities.rb | 2 +- ...170411130659_add_example_hidden_static_ao_n_to_test_cases.rb | 2 +- ...0170420144021_add_text_representation_to_exercise_version.rb | 2 +- ...0170427154810_add_lms_assignment_url_to_workout_offerings.rb | 2 +- ...430212102_rename_all_or_nothing_to_screening_in_test_case.rb | 2 +- db/migrate/20170502163350_create_license_policies.rb | 2 +- db/migrate/20170502163602_create_licenses.rb | 2 +- db/migrate/20170502165648_create_user_groups.rb | 2 +- db/migrate/20170502165837_create_exercise_collections.rb | 2 +- .../20170502171412_add_exercise_collection_to_exercises.rb | 2 +- db/migrate/20170502204756_create_memberships.rb | 2 +- db/migrate/20170503154537_add_user_to_exercise_collection.rb | 2 +- db/migrate/20170509003343_add_hide_examples_to_coding_prompt.rb | 2 +- db/migrate/20170518161546_add_user_group_to_course.rb | 2 +- ...20170518161825_add_course_offering_to_exercise_collection.rb | 2 +- db/migrate/20170530210400_add_description_to_user_groups.rb | 2 +- db/migrate/20170531150805_create_group_access_requests.rb | 2 +- .../20170810205035_add_feedback_line_to_test_case_results.rb | 2 +- db/migrate/20170830160216_add_is_hidden_to_course.rb | 2 +- db/migrate/20170830164617_add_is_hidden_to_organization.rb | 2 +- ...837_remove_lms_assignment_id_index_from_workout_offerings.rb | 2 +- db/migrate/20180718193136_create_visualization_loggings.rb | 2 +- .../20190131151757_add_attempt_limit_to_workout_offerings.rb | 2 +- db/migrate/20190416161435_add_feedback_times_to_attempt.rb | 2 +- db/migrate/20190422155326_create_lti_workouts.rb | 2 +- .../20190712171251_tie_workout_scores_with_lms_instance.rb | 2 +- 188 files changed, 188 insertions(+), 188 deletions(-) diff --git a/db/migrate/20130912181453_create_organizations.rb b/db/migrate/20130912181453_create_organizations.rb index 2d996b8f..e3d360e9 100644 --- a/db/migrate/20130912181453_create_organizations.rb +++ b/db/migrate/20130912181453_create_organizations.rb @@ -1,4 +1,4 @@ -class CreateOrganizations < ActiveRecord::Migration +class CreateOrganizations < ActiveRecord::Migration[5.1] def change create_table :organizations do |t| t.string :display_name, null: false diff --git a/db/migrate/20130912182541_create_courses.rb b/db/migrate/20130912182541_create_courses.rb index 1ccdf7de..534d82ba 100644 --- a/db/migrate/20130912182541_create_courses.rb +++ b/db/migrate/20130912182541_create_courses.rb @@ -1,4 +1,4 @@ -class CreateCourses < ActiveRecord::Migration +class CreateCourses < ActiveRecord::Migration[5.1] def change create_table :courses do |t| t.string :name, null: false diff --git a/db/migrate/20130912200825_create_terms.rb b/db/migrate/20130912200825_create_terms.rb index 086e72e9..ab4e6c4b 100644 --- a/db/migrate/20130912200825_create_terms.rb +++ b/db/migrate/20130912200825_create_terms.rb @@ -1,4 +1,4 @@ -class CreateTerms < ActiveRecord::Migration +class CreateTerms < ActiveRecord::Migration[5.1] def change create_table :terms do |t| t.integer :season, null: false diff --git a/db/migrate/20130912202622_create_course_offerings.rb b/db/migrate/20130912202622_create_course_offerings.rb index a9426eb5..1d60bd5b 100644 --- a/db/migrate/20130912202622_create_course_offerings.rb +++ b/db/migrate/20130912202622_create_course_offerings.rb @@ -1,4 +1,4 @@ -class CreateCourseOfferings < ActiveRecord::Migration +class CreateCourseOfferings < ActiveRecord::Migration[5.1] def change create_table :course_offerings do |t| t.references :course, null: false, index: true diff --git a/db/migrate/20130913163500_devise_create_users.rb b/db/migrate/20130913163500_devise_create_users.rb index 79016fd7..bd168ece 100644 --- a/db/migrate/20130913163500_devise_create_users.rb +++ b/db/migrate/20130913163500_devise_create_users.rb @@ -1,4 +1,4 @@ -class DeviseCreateUsers < ActiveRecord::Migration +class DeviseCreateUsers < ActiveRecord::Migration[5.1] def change create_table(:users) do |t| ## Database authenticatable diff --git a/db/migrate/20130915012131_create_course_roles.rb b/db/migrate/20130915012131_create_course_roles.rb index 344e3089..06491e3c 100644 --- a/db/migrate/20130915012131_create_course_roles.rb +++ b/db/migrate/20130915012131_create_course_roles.rb @@ -1,4 +1,4 @@ -class CreateCourseRoles < ActiveRecord::Migration +class CreateCourseRoles < ActiveRecord::Migration[5.1] def change create_table :course_roles do |t| t.string :name, :unique => true, :null => false diff --git a/db/migrate/20130915030206_create_course_enrollments.rb b/db/migrate/20130915030206_create_course_enrollments.rb index 9e3d36e6..66a4ebf7 100644 --- a/db/migrate/20130915030206_create_course_enrollments.rb +++ b/db/migrate/20130915030206_create_course_enrollments.rb @@ -1,4 +1,4 @@ -class CreateCourseEnrollments < ActiveRecord::Migration +class CreateCourseEnrollments < ActiveRecord::Migration[5.1] def change create_table :course_enrollments do |t| t.references :user, index: true diff --git a/db/migrate/20130915044012_create_tags.rb b/db/migrate/20130915044012_create_tags.rb index c78a2612..02b039de 100644 --- a/db/migrate/20130915044012_create_tags.rb +++ b/db/migrate/20130915044012_create_tags.rb @@ -1,4 +1,4 @@ -class CreateTags < ActiveRecord::Migration +class CreateTags < ActiveRecord::Migration[5.1] def change create_table :tags do |t| t.string :name, null: false diff --git a/db/migrate/20130915050202_create_prompts.rb b/db/migrate/20130915050202_create_prompts.rb index e95edb36..dce6569a 100644 --- a/db/migrate/20130915050202_create_prompts.rb +++ b/db/migrate/20130915050202_create_prompts.rb @@ -1,4 +1,4 @@ -class CreatePrompts < ActiveRecord::Migration +class CreatePrompts < ActiveRecord::Migration[5.1] def change create_table :prompts do |t| t.belongs_to :exercise, index: true, null: false diff --git a/db/migrate/20130915055146_create_choices.rb b/db/migrate/20130915055146_create_choices.rb index f9040bac..2a7c879e 100644 --- a/db/migrate/20130915055146_create_choices.rb +++ b/db/migrate/20130915055146_create_choices.rb @@ -1,4 +1,4 @@ -class CreateChoices < ActiveRecord::Migration +class CreateChoices < ActiveRecord::Migration[5.1] def change create_table :choices do |t| t.belongs_to :exercise, index: true, null: false diff --git a/db/migrate/20130915203004_create_languages.rb b/db/migrate/20130915203004_create_languages.rb index a1c86bc3..5932a16e 100644 --- a/db/migrate/20130915203004_create_languages.rb +++ b/db/migrate/20130915203004_create_languages.rb @@ -1,4 +1,4 @@ -class CreateLanguages < ActiveRecord::Migration +class CreateLanguages < ActiveRecord::Migration[5.1] def change create_table :languages do |t| t.string :name diff --git a/db/migrate/20130916142010_create_global_roles.rb b/db/migrate/20130916142010_create_global_roles.rb index 40c1c048..bac93896 100644 --- a/db/migrate/20130916142010_create_global_roles.rb +++ b/db/migrate/20130916142010_create_global_roles.rb @@ -1,4 +1,4 @@ -class CreateGlobalRoles < ActiveRecord::Migration +class CreateGlobalRoles < ActiveRecord::Migration[5.1] def change create_table :global_roles do |t| t.string :name, :unique => true, :null => false diff --git a/db/migrate/20130916203844_add_initial_columns_to_users.rb b/db/migrate/20130916203844_add_initial_columns_to_users.rb index 2451bd65..75fba1e8 100644 --- a/db/migrate/20130916203844_add_initial_columns_to_users.rb +++ b/db/migrate/20130916203844_add_initial_columns_to_users.rb @@ -1,4 +1,4 @@ -class AddInitialColumnsToUsers < ActiveRecord::Migration +class AddInitialColumnsToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :first_name, :string, index: true add_column :users, :last_name, :string, index: true diff --git a/db/migrate/20130918174151_create_stems.rb b/db/migrate/20130918174151_create_stems.rb index 0cf1e531..d9960e27 100644 --- a/db/migrate/20130918174151_create_stems.rb +++ b/db/migrate/20130918174151_create_stems.rb @@ -1,4 +1,4 @@ -class CreateStems < ActiveRecord::Migration +class CreateStems < ActiveRecord::Migration[5.1] def change create_table :stems do |t| t.text :preamble diff --git a/db/migrate/20130918190714_create_exercises.rb b/db/migrate/20130918190714_create_exercises.rb index 61cc00df..eee1528b 100644 --- a/db/migrate/20130918190714_create_exercises.rb +++ b/db/migrate/20130918190714_create_exercises.rb @@ -1,4 +1,4 @@ -class CreateExercises < ActiveRecord::Migration +class CreateExercises < ActiveRecord::Migration[5.1] def change create_table :exercises do |t| t.belongs_to :user, index: true, null: false diff --git a/db/migrate/20130923164556_rename_exercises_type_to_question_type.rb b/db/migrate/20130923164556_rename_exercises_type_to_question_type.rb index 13c464b4..b23220b0 100644 --- a/db/migrate/20130923164556_rename_exercises_type_to_question_type.rb +++ b/db/migrate/20130923164556_rename_exercises_type_to_question_type.rb @@ -1,4 +1,4 @@ -class RenameExercisesTypeToQuestionType < ActiveRecord::Migration +class RenameExercisesTypeToQuestionType < ActiveRecord::Migration[5.1] def change #create_table :exercises do |t| # t.belongs_to :user, index: true, null: false diff --git a/db/migrate/20131001051705_create_experience_column.rb b/db/migrate/20131001051705_create_experience_column.rb index d9df1e22..499f1ee3 100644 --- a/db/migrate/20131001051705_create_experience_column.rb +++ b/db/migrate/20131001051705_create_experience_column.rb @@ -1,4 +1,4 @@ -class CreateExperienceColumn < ActiveRecord::Migration +class CreateExperienceColumn < ActiveRecord::Migration[5.1] def change add_column :exercises, :experience, :integer end diff --git a/db/migrate/20131003025313_create_attempts.rb b/db/migrate/20131003025313_create_attempts.rb index dd909f85..f8e17b47 100644 --- a/db/migrate/20131003025313_create_attempts.rb +++ b/db/migrate/20131003025313_create_attempts.rb @@ -1,4 +1,4 @@ -class CreateAttempts < ActiveRecord::Migration +class CreateAttempts < ActiveRecord::Migration[5.1] def change create_table :attempts do |t| t.belongs_to :user, index: true, null: false diff --git a/db/migrate/20131009161911_add_tagtype_to_tags.rb b/db/migrate/20131009161911_add_tagtype_to_tags.rb index f16e3195..a6989f4b 100644 --- a/db/migrate/20131009161911_add_tagtype_to_tags.rb +++ b/db/migrate/20131009161911_add_tagtype_to_tags.rb @@ -1,4 +1,4 @@ -class AddTagtypeToTags < ActiveRecord::Migration +class AddTagtypeToTags < ActiveRecord::Migration[5.1] def change add_column :tags, :tagtype, :integer, :default => 0 end diff --git a/db/migrate/20131010184442_change_tags_in_exercise.rb b/db/migrate/20131010184442_change_tags_in_exercise.rb index 9d18d55f..72d95623 100644 --- a/db/migrate/20131010184442_change_tags_in_exercise.rb +++ b/db/migrate/20131010184442_change_tags_in_exercise.rb @@ -1,4 +1,4 @@ -class ChangeTagsInExercise < ActiveRecord::Migration +class ChangeTagsInExercise < ActiveRecord::Migration[5.1] def change remove_column :exercises, :language, :belongs_to #language now represented as a tag with tagtype=2 end diff --git a/db/migrate/20131011170027_create_total_exercise_and_total_experience_in_tag.rb b/db/migrate/20131011170027_create_total_exercise_and_total_experience_in_tag.rb index 88b8677c..19be0288 100644 --- a/db/migrate/20131011170027_create_total_exercise_and_total_experience_in_tag.rb +++ b/db/migrate/20131011170027_create_total_exercise_and_total_experience_in_tag.rb @@ -1,4 +1,4 @@ -class CreateTotalExerciseAndTotalExperienceInTag < ActiveRecord::Migration +class CreateTotalExerciseAndTotalExperienceInTag < ActiveRecord::Migration[5.1] def change add_column :tags, :total_exercises, :integer, :default => 0 add_column :tags, :total_experience, :integer, :default => 0 diff --git a/db/migrate/20131015080408_create_tag_user_scores.rb b/db/migrate/20131015080408_create_tag_user_scores.rb index b4350a29..81a3fd87 100644 --- a/db/migrate/20131015080408_create_tag_user_scores.rb +++ b/db/migrate/20131015080408_create_tag_user_scores.rb @@ -1,4 +1,4 @@ -class CreateTagUserScores < ActiveRecord::Migration +class CreateTagUserScores < ActiveRecord::Migration[5.1] def change create_table :tag_user_scores do |t| t.belongs_to :user, index: true, null: false diff --git a/db/migrate/20131015083558_rename_tag_name.rb b/db/migrate/20131015083558_rename_tag_name.rb index 8d5ecd8a..86560ef7 100644 --- a/db/migrate/20131015083558_rename_tag_name.rb +++ b/db/migrate/20131015083558_rename_tag_name.rb @@ -1,4 +1,4 @@ -class RenameTagName < ActiveRecord::Migration +class RenameTagName < ActiveRecord::Migration[5.1] def change rename_column :tags, :name, :tag_name end diff --git a/db/migrate/20131015151339_add_completed_exercises_to_tag_user_scores.rb b/db/migrate/20131015151339_add_completed_exercises_to_tag_user_scores.rb index 394fa313..88713100 100644 --- a/db/migrate/20131015151339_add_completed_exercises_to_tag_user_scores.rb +++ b/db/migrate/20131015151339_add_completed_exercises_to_tag_user_scores.rb @@ -1,4 +1,4 @@ -class AddCompletedExercisesToTagUserScores < ActiveRecord::Migration +class AddCompletedExercisesToTagUserScores < ActiveRecord::Migration[5.1] def change add_column :tag_user_scores, :completed_exercises, :integer, :default => 0 end diff --git a/db/migrate/20131016023405_change_language_to_tags.rb b/db/migrate/20131016023405_change_language_to_tags.rb index 1ec72520..407fbeb8 100644 --- a/db/migrate/20131016023405_change_language_to_tags.rb +++ b/db/migrate/20131016023405_change_language_to_tags.rb @@ -1,4 +1,4 @@ -class ChangeLanguageToTags < ActiveRecord::Migration +class ChangeLanguageToTags < ActiveRecord::Migration[5.1] def change drop_table :languages remove_column :exercises, :language_id diff --git a/db/migrate/20131016154908_create_workouts.rb b/db/migrate/20131016154908_create_workouts.rb index 43477884..ab9af3c6 100644 --- a/db/migrate/20131016154908_create_workouts.rb +++ b/db/migrate/20131016154908_create_workouts.rb @@ -1,4 +1,4 @@ -class CreateWorkouts < ActiveRecord::Migration +class CreateWorkouts < ActiveRecord::Migration[5.1] def change create_table :workouts do |t| t.string :name, null: false diff --git a/db/migrate/20131016175805_create_tags_workouts.rb b/db/migrate/20131016175805_create_tags_workouts.rb index bce10d4d..ce7b5601 100644 --- a/db/migrate/20131016175805_create_tags_workouts.rb +++ b/db/migrate/20131016175805_create_tags_workouts.rb @@ -1,4 +1,4 @@ -class CreateTagsWorkouts < ActiveRecord::Migration +class CreateTagsWorkouts < ActiveRecord::Migration[5.1] def change create_table :tags_workouts do |t| t.belongs_to :tag, index: true diff --git a/db/migrate/20131119032619_rename_workouts_exercises_exercises_workouts.rb b/db/migrate/20131119032619_rename_workouts_exercises_exercises_workouts.rb index f4de4434..98709ae0 100644 --- a/db/migrate/20131119032619_rename_workouts_exercises_exercises_workouts.rb +++ b/db/migrate/20131119032619_rename_workouts_exercises_exercises_workouts.rb @@ -1,4 +1,4 @@ -class RenameWorkoutsExercisesExercisesWorkouts < ActiveRecord::Migration +class RenameWorkoutsExercisesExercisesWorkouts < ActiveRecord::Migration[5.1] def change rename_table :workouts_exercises, :exercises_workouts #add_index "exercises_workouts", ["exercise_id"], name: "index_exercises_workouts_on_exercise_id" diff --git a/db/migrate/20131210061217_add_coding_exercise_columns.rb b/db/migrate/20131210061217_add_coding_exercise_columns.rb index 6da782fc..2cc33558 100644 --- a/db/migrate/20131210061217_add_coding_exercise_columns.rb +++ b/db/migrate/20131210061217_add_coding_exercise_columns.rb @@ -1,4 +1,4 @@ -class AddCodingExerciseColumns < ActiveRecord::Migration +class AddCodingExerciseColumns < ActiveRecord::Migration[5.1] def change add_column :exercises, :starter_code, :text diff --git a/db/migrate/20140422205525_update_exercise.rb b/db/migrate/20140422205525_update_exercise.rb index 70dc740c..e96c0841 100644 --- a/db/migrate/20140422205525_update_exercise.rb +++ b/db/migrate/20140422205525_update_exercise.rb @@ -1,4 +1,4 @@ -class UpdateExercise < ActiveRecord::Migration +class UpdateExercise < ActiveRecord::Migration[5.1] def change #allow empty titles in database change_column_null(:exercises, :title, true) diff --git a/db/migrate/20140428202518_create_resource_files.rb b/db/migrate/20140428202518_create_resource_files.rb index 40294876..2c9c8ceb 100644 --- a/db/migrate/20140428202518_create_resource_files.rb +++ b/db/migrate/20140428202518_create_resource_files.rb @@ -1,4 +1,4 @@ -class CreateResourceFiles < ActiveRecord::Migration +class CreateResourceFiles < ActiveRecord::Migration[5.1] def change create_table :resource_files do |t| t.column :filename, :string diff --git a/db/migrate/20141019133633_add_name_to_users.rb b/db/migrate/20141019133633_add_name_to_users.rb index bac750eb..3c0d3cb0 100644 --- a/db/migrate/20141019133633_add_name_to_users.rb +++ b/db/migrate/20141019133633_add_name_to_users.rb @@ -1,4 +1,4 @@ -class AddNameToUsers < ActiveRecord::Migration +class AddNameToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :name, :string end diff --git a/db/migrate/20141019134233_create_identities.rb b/db/migrate/20141019134233_create_identities.rb index ea0b8ba8..8155c6d6 100644 --- a/db/migrate/20141019134233_create_identities.rb +++ b/db/migrate/20141019134233_create_identities.rb @@ -1,4 +1,4 @@ -class CreateIdentities < ActiveRecord::Migration +class CreateIdentities < ActiveRecord::Migration[5.1] def change create_table :identities do |t| t.references :user, index: true diff --git a/db/migrate/20141019235246_add_name_to_workouts.rb b/db/migrate/20141019235246_add_name_to_workouts.rb index b2a4c368..cc22e0ed 100644 --- a/db/migrate/20141019235246_add_name_to_workouts.rb +++ b/db/migrate/20141019235246_add_name_to_workouts.rb @@ -1,4 +1,4 @@ -class AddNameToWorkouts < ActiveRecord::Migration +class AddNameToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :description, :text end diff --git a/db/migrate/20141019235449_add_target_group_to_workouts.rb b/db/migrate/20141019235449_add_target_group_to_workouts.rb index ecf3d9f1..4b3d4dc7 100644 --- a/db/migrate/20141019235449_add_target_group_to_workouts.rb +++ b/db/migrate/20141019235449_add_target_group_to_workouts.rb @@ -1,4 +1,4 @@ -class AddTargetGroupToWorkouts < ActiveRecord::Migration +class AddTargetGroupToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :target_group, :string end diff --git a/db/migrate/20141019235637_add_points_multiplier_to_workouts.rb b/db/migrate/20141019235637_add_points_multiplier_to_workouts.rb index 82f44d13..83153800 100644 --- a/db/migrate/20141019235637_add_points_multiplier_to_workouts.rb +++ b/db/migrate/20141019235637_add_points_multiplier_to_workouts.rb @@ -1,4 +1,4 @@ -class AddPointsMultiplierToWorkouts < ActiveRecord::Migration +class AddPointsMultiplierToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :points_multiplier, :integer end diff --git a/db/migrate/20141019235708_add_opening_date_to_workouts.rb b/db/migrate/20141019235708_add_opening_date_to_workouts.rb index 05a14f7e..9b5e4615 100644 --- a/db/migrate/20141019235708_add_opening_date_to_workouts.rb +++ b/db/migrate/20141019235708_add_opening_date_to_workouts.rb @@ -1,4 +1,4 @@ -class AddOpeningDateToWorkouts < ActiveRecord::Migration +class AddOpeningDateToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :opening_date, :date end diff --git a/db/migrate/20141019235750_add_soft_deadline_to_workouts.rb b/db/migrate/20141019235750_add_soft_deadline_to_workouts.rb index a28484fd..73d0ac64 100644 --- a/db/migrate/20141019235750_add_soft_deadline_to_workouts.rb +++ b/db/migrate/20141019235750_add_soft_deadline_to_workouts.rb @@ -1,4 +1,4 @@ -class AddSoftDeadlineToWorkouts < ActiveRecord::Migration +class AddSoftDeadlineToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :soft_deadline, :date end diff --git a/db/migrate/20141019235803_add_hard_deadline_to_workouts.rb b/db/migrate/20141019235803_add_hard_deadline_to_workouts.rb index 64009884..c2841eb6 100644 --- a/db/migrate/20141019235803_add_hard_deadline_to_workouts.rb +++ b/db/migrate/20141019235803_add_hard_deadline_to_workouts.rb @@ -1,4 +1,4 @@ -class AddHardDeadlineToWorkouts < ActiveRecord::Migration +class AddHardDeadlineToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :hard_deadline, :date end diff --git a/db/migrate/20141102232155_create_join_table_course_offerings_workouts.rb b/db/migrate/20141102232155_create_join_table_course_offerings_workouts.rb index d8c6c79e..4d287941 100644 --- a/db/migrate/20141102232155_create_join_table_course_offerings_workouts.rb +++ b/db/migrate/20141102232155_create_join_table_course_offerings_workouts.rb @@ -1,4 +1,4 @@ -class CreateJoinTableCourseOfferingsWorkouts < ActiveRecord::Migration +class CreateJoinTableCourseOfferingsWorkouts < ActiveRecord::Migration[5.1] def change create_join_table :course_offerings, :workouts do |t| # t.index [:course_offering_id, :workout_id] diff --git a/db/migrate/20141109193127_create_workout_offerings.rb b/db/migrate/20141109193127_create_workout_offerings.rb index 005ea829..d7ca7ea1 100644 --- a/db/migrate/20141109193127_create_workout_offerings.rb +++ b/db/migrate/20141109193127_create_workout_offerings.rb @@ -1,4 +1,4 @@ -class CreateWorkoutOfferings < ActiveRecord::Migration +class CreateWorkoutOfferings < ActiveRecord::Migration[5.1] def change create_table :workout_offerings do |t| t.integer :course_offering_id diff --git a/db/migrate/20141109193909_add_deadlines_to_workout_offering.rb b/db/migrate/20141109193909_add_deadlines_to_workout_offering.rb index 2991b112..12bd1ad3 100644 --- a/db/migrate/20141109193909_add_deadlines_to_workout_offering.rb +++ b/db/migrate/20141109193909_add_deadlines_to_workout_offering.rb @@ -1,4 +1,4 @@ -class AddDeadlinesToWorkoutOffering < ActiveRecord::Migration +class AddDeadlinesToWorkoutOffering < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :opening_date, :date add_column :workout_offerings, :soft_deadline, :date diff --git a/db/migrate/20141109202442_create_exercise_workouts.rb b/db/migrate/20141109202442_create_exercise_workouts.rb index c735439b..a3fbc36c 100644 --- a/db/migrate/20141109202442_create_exercise_workouts.rb +++ b/db/migrate/20141109202442_create_exercise_workouts.rb @@ -1,4 +1,4 @@ -class CreateExerciseWorkouts < ActiveRecord::Migration +class CreateExerciseWorkouts < ActiveRecord::Migration[5.1] def change create_table :exercise_workouts do |t| t.integer :exercise_id diff --git a/db/migrate/20141109204133_fix_order_column_in_exercise_workouts.rb b/db/migrate/20141109204133_fix_order_column_in_exercise_workouts.rb index 8eb75d03..4fb5dea2 100644 --- a/db/migrate/20141109204133_fix_order_column_in_exercise_workouts.rb +++ b/db/migrate/20141109204133_fix_order_column_in_exercise_workouts.rb @@ -1,4 +1,4 @@ -class FixOrderColumnInExerciseWorkouts < ActiveRecord::Migration +class FixOrderColumnInExerciseWorkouts < ActiveRecord::Migration[5.1] def change rename_column :exercise_workouts, :order, :ordering end diff --git a/db/migrate/20141110010213_remove_deadlines_from_workouts.rb b/db/migrate/20141110010213_remove_deadlines_from_workouts.rb index e2651250..ee437307 100644 --- a/db/migrate/20141110010213_remove_deadlines_from_workouts.rb +++ b/db/migrate/20141110010213_remove_deadlines_from_workouts.rb @@ -1,4 +1,4 @@ -class RemoveDeadlinesFromWorkouts < ActiveRecord::Migration +class RemoveDeadlinesFromWorkouts < ActiveRecord::Migration[5.1] def change remove_column :workouts, :opening_date, :date remove_column :workouts, :soft_deadline, :date diff --git a/db/migrate/20141110023518_create_course_exercises.rb b/db/migrate/20141110023518_create_course_exercises.rb index 01be995f..796af32d 100644 --- a/db/migrate/20141110023518_create_course_exercises.rb +++ b/db/migrate/20141110023518_create_course_exercises.rb @@ -1,4 +1,4 @@ -class CreateCourseExercises < ActiveRecord::Migration +class CreateCourseExercises < ActiveRecord::Migration[5.1] def change create_table :course_exercises do |t| t.integer :course_id diff --git a/db/migrate/20150111180500_create_base_exercises.rb b/db/migrate/20150111180500_create_base_exercises.rb index 36129d4c..21c59d17 100644 --- a/db/migrate/20150111180500_create_base_exercises.rb +++ b/db/migrate/20150111180500_create_base_exercises.rb @@ -1,4 +1,4 @@ -class CreateBaseExercises < ActiveRecord::Migration +class CreateBaseExercises < ActiveRecord::Migration[5.1] def change create_table :base_exercises do |t| t.integer :user_id diff --git a/db/migrate/20150111182242_add_base_exercise_id_to_exercise.rb b/db/migrate/20150111182242_add_base_exercise_id_to_exercise.rb index b7457629..f6595795 100644 --- a/db/migrate/20150111182242_add_base_exercise_id_to_exercise.rb +++ b/db/migrate/20150111182242_add_base_exercise_id_to_exercise.rb @@ -1,4 +1,4 @@ -class AddBaseExerciseIdToExercise < ActiveRecord::Migration +class AddBaseExerciseIdToExercise < ActiveRecord::Migration[5.1] def change add_column :exercises, :base_exercise_id, :integer add_index :exercises, :base_exercise_id diff --git a/db/migrate/20150111185855_add_version_to_exercise.rb b/db/migrate/20150111185855_add_version_to_exercise.rb index 5b70a209..1653ac7a 100644 --- a/db/migrate/20150111185855_add_version_to_exercise.rb +++ b/db/migrate/20150111185855_add_version_to_exercise.rb @@ -1,4 +1,4 @@ -class AddVersionToExercise < ActiveRecord::Migration +class AddVersionToExercise < ActiveRecord::Migration[5.1] def change add_column :exercises, :version, :integer end diff --git a/db/migrate/20150111200649_remove_question_type_from_exercises.rb b/db/migrate/20150111200649_remove_question_type_from_exercises.rb index 8dda4ea4..1dbfe17b 100644 --- a/db/migrate/20150111200649_remove_question_type_from_exercises.rb +++ b/db/migrate/20150111200649_remove_question_type_from_exercises.rb @@ -1,4 +1,4 @@ -class RemoveQuestionTypeFromExercises < ActiveRecord::Migration +class RemoveQuestionTypeFromExercises < ActiveRecord::Migration[5.1] def change remove_column :exercises, :question_type, :integer end diff --git a/db/migrate/20150111204245_add_versions_to_base_exercises.rb b/db/migrate/20150111204245_add_versions_to_base_exercises.rb index 05cd9bdc..d45652ef 100644 --- a/db/migrate/20150111204245_add_versions_to_base_exercises.rb +++ b/db/migrate/20150111204245_add_versions_to_base_exercises.rb @@ -1,4 +1,4 @@ -class AddVersionsToBaseExercises < ActiveRecord::Migration +class AddVersionsToBaseExercises < ActiveRecord::Migration[5.1] def change add_column :base_exercises, :versions, :integer end diff --git a/db/migrate/20150111222945_remove_user_id_from_exercises.rb b/db/migrate/20150111222945_remove_user_id_from_exercises.rb index 02b2c811..6183d73c 100644 --- a/db/migrate/20150111222945_remove_user_id_from_exercises.rb +++ b/db/migrate/20150111222945_remove_user_id_from_exercises.rb @@ -1,4 +1,4 @@ -class RemoveUserIdFromExercises < ActiveRecord::Migration +class RemoveUserIdFromExercises < ActiveRecord::Migration[5.1] def change remove_column :exercises, :user_id, :integer end diff --git a/db/migrate/20150112141536_create_variation_groups.rb b/db/migrate/20150112141536_create_variation_groups.rb index b778c679..f8d8f0ed 100644 --- a/db/migrate/20150112141536_create_variation_groups.rb +++ b/db/migrate/20150112141536_create_variation_groups.rb @@ -1,4 +1,4 @@ -class CreateVariationGroups < ActiveRecord::Migration +class CreateVariationGroups < ActiveRecord::Migration[5.1] def change create_table :variation_groups do |t| t.string :title diff --git a/db/migrate/20150112143114_add_variation_group_id_to_base_exercise.rb b/db/migrate/20150112143114_add_variation_group_id_to_base_exercise.rb index e6587e36..d6518d2c 100644 --- a/db/migrate/20150112143114_add_variation_group_id_to_base_exercise.rb +++ b/db/migrate/20150112143114_add_variation_group_id_to_base_exercise.rb @@ -1,4 +1,4 @@ -class AddVariationGroupIdToBaseExercise < ActiveRecord::Migration +class AddVariationGroupIdToBaseExercise < ActiveRecord::Migration[5.1] def change add_column :base_exercises, :variation_group_id, :integer end diff --git a/db/migrate/20150117190855_create_workout_scores.rb b/db/migrate/20150117190855_create_workout_scores.rb index 849320a8..4e365f83 100644 --- a/db/migrate/20150117190855_create_workout_scores.rb +++ b/db/migrate/20150117190855_create_workout_scores.rb @@ -1,4 +1,4 @@ -class CreateWorkoutScores < ActiveRecord::Migration +class CreateWorkoutScores < ActiveRecord::Migration[5.1] def change create_table :workout_scores do |t| t.belongs_to :workout, index: true diff --git a/db/migrate/20150201192746_remove_starter_code_from_exercise.rb b/db/migrate/20150201192746_remove_starter_code_from_exercise.rb index a597797e..9dcf3bb6 100644 --- a/db/migrate/20150201192746_remove_starter_code_from_exercise.rb +++ b/db/migrate/20150201192746_remove_starter_code_from_exercise.rb @@ -1,4 +1,4 @@ -class RemoveStarterCodeFromExercise < ActiveRecord::Migration +class RemoveStarterCodeFromExercise < ActiveRecord::Migration[5.1] def change remove_column :exercises, :starter_code, :text end diff --git a/db/migrate/20150201224732_create_coding_questions.rb b/db/migrate/20150201224732_create_coding_questions.rb index c687a736..2684e9b5 100644 --- a/db/migrate/20150201224732_create_coding_questions.rb +++ b/db/migrate/20150201224732_create_coding_questions.rb @@ -1,4 +1,4 @@ -class CreateCodingQuestions < ActiveRecord::Migration +class CreateCodingQuestions < ActiveRecord::Migration[5.1] def change create_table :coding_questions do |t| t.string :base_class diff --git a/db/migrate/20150201230342_remove_columns_from_coding_question.rb b/db/migrate/20150201230342_remove_columns_from_coding_question.rb index ebba4028..731d197c 100644 --- a/db/migrate/20150201230342_remove_columns_from_coding_question.rb +++ b/db/migrate/20150201230342_remove_columns_from_coding_question.rb @@ -1,4 +1,4 @@ -class RemoveColumnsFromCodingQuestion < ActiveRecord::Migration +class RemoveColumnsFromCodingQuestion < ActiveRecord::Migration[5.1] def change remove_column :coding_questions, :base_class, :wrapper_code, :test_script end diff --git a/db/migrate/20150201230748_remove_columns_from_coding_question_2.rb b/db/migrate/20150201230748_remove_columns_from_coding_question_2.rb index 133fd10a..a62603c3 100644 --- a/db/migrate/20150201230748_remove_columns_from_coding_question_2.rb +++ b/db/migrate/20150201230748_remove_columns_from_coding_question_2.rb @@ -1,4 +1,4 @@ -class RemoveColumnsFromCodingQuestion2 < ActiveRecord::Migration +class RemoveColumnsFromCodingQuestion2 < ActiveRecord::Migration[5.1] def change remove_column :coding_questions, :wrapper_code remove_column :coding_questions, :test_script diff --git a/db/migrate/20150201231050_add_columns_to_coding_questions.rb b/db/migrate/20150201231050_add_columns_to_coding_questions.rb index 60fd28d2..a5a9caa9 100644 --- a/db/migrate/20150201231050_add_columns_to_coding_questions.rb +++ b/db/migrate/20150201231050_add_columns_to_coding_questions.rb @@ -1,4 +1,4 @@ -class AddColumnsToCodingQuestions < ActiveRecord::Migration +class AddColumnsToCodingQuestions < ActiveRecord::Migration[5.1] def change add_column :coding_questions, :base_class, :string add_column :coding_questions, :wrapper_code, :text diff --git a/db/migrate/20150201234250_add_feedback_to_coding_question.rb b/db/migrate/20150201234250_add_feedback_to_coding_question.rb index 80d11619..df7f491b 100644 --- a/db/migrate/20150201234250_add_feedback_to_coding_question.rb +++ b/db/migrate/20150201234250_add_feedback_to_coding_question.rb @@ -1,4 +1,4 @@ -class AddFeedbackToCodingQuestion < ActiveRecord::Migration +class AddFeedbackToCodingQuestion < ActiveRecord::Migration[5.1] def change add_column :coding_questions, :feedback, :text end diff --git a/db/migrate/20150201234351_remove_feedback_from_coding_question.rb b/db/migrate/20150201234351_remove_feedback_from_coding_question.rb index 7e38c183..51690638 100644 --- a/db/migrate/20150201234351_remove_feedback_from_coding_question.rb +++ b/db/migrate/20150201234351_remove_feedback_from_coding_question.rb @@ -1,4 +1,4 @@ -class RemoveFeedbackFromCodingQuestion < ActiveRecord::Migration +class RemoveFeedbackFromCodingQuestion < ActiveRecord::Migration[5.1] def change remove_column :coding_questions, :feedback, :text end diff --git a/db/migrate/20150201235152_create_test_case_results.rb b/db/migrate/20150201235152_create_test_case_results.rb index 9d38b2ca..ec2876a0 100644 --- a/db/migrate/20150201235152_create_test_case_results.rb +++ b/db/migrate/20150201235152_create_test_case_results.rb @@ -1,4 +1,4 @@ -class CreateTestCaseResults < ActiveRecord::Migration +class CreateTestCaseResults < ActiveRecord::Migration[5.1] def change create_table :test_case_results do |t| t.integer :test_case_id, index: true diff --git a/db/migrate/20150205220518_rectify_test_case_result.rb b/db/migrate/20150205220518_rectify_test_case_result.rb index 68a5af10..976d2f2c 100644 --- a/db/migrate/20150205220518_rectify_test_case_result.rb +++ b/db/migrate/20150205220518_rectify_test_case_result.rb @@ -1,4 +1,4 @@ -class RectifyTestCaseResult < ActiveRecord::Migration +class RectifyTestCaseResult < ActiveRecord::Migration[5.1] def change remove_column :test_case_results, :score, :float add_column :test_case_results, :pass, :boolean diff --git a/db/migrate/20150205220837_rectify_test_case_result2.rb b/db/migrate/20150205220837_rectify_test_case_result2.rb index 4fb8f8fd..5ab7c23c 100644 --- a/db/migrate/20150205220837_rectify_test_case_result2.rb +++ b/db/migrate/20150205220837_rectify_test_case_result2.rb @@ -1,4 +1,4 @@ -class RectifyTestCaseResult2 < ActiveRecord::Migration +class RectifyTestCaseResult2 < ActiveRecord::Migration[5.1] def change remove_column :test_case_results, :max, :boolean end diff --git a/db/migrate/20150205221653_change_score_in_attempts.rb b/db/migrate/20150205221653_change_score_in_attempts.rb index 64d4662d..f3281a22 100644 --- a/db/migrate/20150205221653_change_score_in_attempts.rb +++ b/db/migrate/20150205221653_change_score_in_attempts.rb @@ -1,4 +1,4 @@ -class ChangeScoreInAttempts < ActiveRecord::Migration +class ChangeScoreInAttempts < ActiveRecord::Migration[5.1] def change change_column :attempts, :score, :number, default: 0.0 end diff --git a/db/migrate/20150207221009_add_default_to_points_in_exercise_workouts.rb b/db/migrate/20150207221009_add_default_to_points_in_exercise_workouts.rb index d361a7a7..39fc84c8 100644 --- a/db/migrate/20150207221009_add_default_to_points_in_exercise_workouts.rb +++ b/db/migrate/20150207221009_add_default_to_points_in_exercise_workouts.rb @@ -1,4 +1,4 @@ -class AddDefaultToPointsInExerciseWorkouts < ActiveRecord::Migration +class AddDefaultToPointsInExerciseWorkouts < ActiveRecord::Migration[5.1] def change change_column :exercise_workouts, :points, :float, default: 1.0 end diff --git a/db/migrate/20150210001815_create_test_cases.rb b/db/migrate/20150210001815_create_test_cases.rb index 66e1fedf..740d8067 100644 --- a/db/migrate/20150210001815_create_test_cases.rb +++ b/db/migrate/20150210001815_create_test_cases.rb @@ -1,4 +1,4 @@ -class CreateTestCases < ActiveRecord::Migration +class CreateTestCases < ActiveRecord::Migration[5.1] def change # created in 2131210061217_add_coding_exercise_columns, # but here we blast it and recreate a new structure. diff --git a/db/migrate/20150210012044_add_test_method_to_coding_questions.rb b/db/migrate/20150210012044_add_test_method_to_coding_questions.rb index 3ee0f9c8..4d98807e 100644 --- a/db/migrate/20150210012044_add_test_method_to_coding_questions.rb +++ b/db/migrate/20150210012044_add_test_method_to_coding_questions.rb @@ -1,4 +1,4 @@ -class AddTestMethodToCodingQuestions < ActiveRecord::Migration +class AddTestMethodToCodingQuestions < ActiveRecord::Migration[5.1] def change add_column :coding_questions, :test_method, :string end diff --git a/db/migrate/20150218220059_fix_class_name_in_coding_questions.rb b/db/migrate/20150218220059_fix_class_name_in_coding_questions.rb index af9790d9..4aa8bcff 100644 --- a/db/migrate/20150218220059_fix_class_name_in_coding_questions.rb +++ b/db/migrate/20150218220059_fix_class_name_in_coding_questions.rb @@ -1,4 +1,4 @@ -class FixClassNameInCodingQuestions < ActiveRecord::Migration +class FixClassNameInCodingQuestions < ActiveRecord::Migration[5.1] def change rename_column :coding_questions, :base_class, :class_name end diff --git a/db/migrate/20150218220317_add_starter_code_to_coding_questions.rb b/db/migrate/20150218220317_add_starter_code_to_coding_questions.rb index fbb8a99c..244c99a1 100644 --- a/db/migrate/20150218220317_add_starter_code_to_coding_questions.rb +++ b/db/migrate/20150218220317_add_starter_code_to_coding_questions.rb @@ -1,4 +1,4 @@ -class AddStarterCodeToCodingQuestions < ActiveRecord::Migration +class AddStarterCodeToCodingQuestions < ActiveRecord::Migration[5.1] def change add_column :coding_questions, :starter_code, :text end diff --git a/db/migrate/20150219054901_fix_method_name_in_coding_questions.rb b/db/migrate/20150219054901_fix_method_name_in_coding_questions.rb index df69ccec..7d6f420d 100644 --- a/db/migrate/20150219054901_fix_method_name_in_coding_questions.rb +++ b/db/migrate/20150219054901_fix_method_name_in_coding_questions.rb @@ -1,4 +1,4 @@ -class FixMethodNameInCodingQuestions < ActiveRecord::Migration +class FixMethodNameInCodingQuestions < ActiveRecord::Migration[5.1] def change rename_column :coding_questions, :test_method, :method_name end diff --git a/db/migrate/20150312175933_add_creator_id_to_courses.rb b/db/migrate/20150312175933_add_creator_id_to_courses.rb index 29099c9e..fcaf04a9 100644 --- a/db/migrate/20150312175933_add_creator_id_to_courses.rb +++ b/db/migrate/20150312175933_add_creator_id_to_courses.rb @@ -1,4 +1,4 @@ -class AddCreatorIdToCourses < ActiveRecord::Migration +class AddCreatorIdToCourses < ActiveRecord::Migration[5.1] def change add_column :courses, :creator_id, :integer end diff --git a/db/migrate/20150312183927_add_creator_id_to_exercises.rb b/db/migrate/20150312183927_add_creator_id_to_exercises.rb index 0b57dcd4..99371dcb 100644 --- a/db/migrate/20150312183927_add_creator_id_to_exercises.rb +++ b/db/migrate/20150312183927_add_creator_id_to_exercises.rb @@ -1,4 +1,4 @@ -class AddCreatorIdToExercises < ActiveRecord::Migration +class AddCreatorIdToExercises < ActiveRecord::Migration[5.1] def change add_column :exercises, :creator_id, :integer end diff --git a/db/migrate/20150312184040_add_creator_id_to_base_exercises.rb b/db/migrate/20150312184040_add_creator_id_to_base_exercises.rb index dcb30a9c..833bd0a1 100644 --- a/db/migrate/20150312184040_add_creator_id_to_base_exercises.rb +++ b/db/migrate/20150312184040_add_creator_id_to_base_exercises.rb @@ -1,4 +1,4 @@ -class AddCreatorIdToBaseExercises < ActiveRecord::Migration +class AddCreatorIdToBaseExercises < ActiveRecord::Migration[5.1] def change add_column :base_exercises, :creator_id, :integer end diff --git a/db/migrate/20150312184111_add_creator_id_to_workouts.rb b/db/migrate/20150312184111_add_creator_id_to_workouts.rb index ff50203f..1245fd3f 100644 --- a/db/migrate/20150312184111_add_creator_id_to_workouts.rb +++ b/db/migrate/20150312184111_add_creator_id_to_workouts.rb @@ -1,4 +1,4 @@ -class AddCreatorIdToWorkouts < ActiveRecord::Migration +class AddCreatorIdToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :creator_id, :integer end diff --git a/db/migrate/20150312184659_remove_creator_id_from_base_exercises.rb b/db/migrate/20150312184659_remove_creator_id_from_base_exercises.rb index 4b4762a1..708134ff 100644 --- a/db/migrate/20150312184659_remove_creator_id_from_base_exercises.rb +++ b/db/migrate/20150312184659_remove_creator_id_from_base_exercises.rb @@ -1,4 +1,4 @@ -class RemoveCreatorIdFromBaseExercises < ActiveRecord::Migration +class RemoveCreatorIdFromBaseExercises < ActiveRecord::Migration[5.1] def change remove_column :base_exercises, :creator_id, :integer end diff --git a/db/migrate/20150320002742_add_omniauth_columns_to_users.rb b/db/migrate/20150320002742_add_omniauth_columns_to_users.rb index 6814f682..3686ca46 100644 --- a/db/migrate/20150320002742_add_omniauth_columns_to_users.rb +++ b/db/migrate/20150320002742_add_omniauth_columns_to_users.rb @@ -1,4 +1,4 @@ -class AddOmniauthColumnsToUsers < ActiveRecord::Migration +class AddOmniauthColumnsToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :provider, :string add_column :users, :uid, :string diff --git a/db/migrate/20150324135215_remove_omniauth_columns_from_users.rb b/db/migrate/20150324135215_remove_omniauth_columns_from_users.rb index 5aba20c0..64ace5a5 100644 --- a/db/migrate/20150324135215_remove_omniauth_columns_from_users.rb +++ b/db/migrate/20150324135215_remove_omniauth_columns_from_users.rb @@ -1,4 +1,4 @@ -class RemoveOmniauthColumnsFromUsers < ActiveRecord::Migration +class RemoveOmniauthColumnsFromUsers < ActiveRecord::Migration[5.1] def change remove_column :users, :provider, :string remove_column :users, :uid, :string diff --git a/db/migrate/20150325143018_add_avatar_to_users.rb b/db/migrate/20150325143018_add_avatar_to_users.rb index 7b4de37a..efc120b1 100644 --- a/db/migrate/20150325143018_add_avatar_to_users.rb +++ b/db/migrate/20150325143018_add_avatar_to_users.rb @@ -1,4 +1,4 @@ -class AddAvatarToUsers < ActiveRecord::Migration +class AddAvatarToUsers < ActiveRecord::Migration[5.1] def change add_column :users, :avatar, :string end diff --git a/db/migrate/20150325235931_rename_current_version_to_current_version_id_in_base_exercises.rb b/db/migrate/20150325235931_rename_current_version_to_current_version_id_in_base_exercises.rb index 2ef0a374..06ab6fe6 100644 --- a/db/migrate/20150325235931_rename_current_version_to_current_version_id_in_base_exercises.rb +++ b/db/migrate/20150325235931_rename_current_version_to_current_version_id_in_base_exercises.rb @@ -1,4 +1,4 @@ -class RenameCurrentVersionToCurrentVersionIdInBaseExercises < ActiveRecord::Migration +class RenameCurrentVersionToCurrentVersionIdInBaseExercises < ActiveRecord::Migration[5.1] def change rename_column :base_exercises, :current_version, :current_version_id end diff --git a/db/migrate/20150326123644_add_null_constraints_and_indices.rb b/db/migrate/20150326123644_add_null_constraints_and_indices.rb index 2d5002d7..e5ab06cf 100644 --- a/db/migrate/20150326123644_add_null_constraints_and_indices.rb +++ b/db/migrate/20150326123644_add_null_constraints_and_indices.rb @@ -1,4 +1,4 @@ -class AddNullConstraintsAndIndices < ActiveRecord::Migration +class AddNullConstraintsAndIndices < ActiveRecord::Migration[5.1] def change # identities change_column_null :identities, :user_id, false diff --git a/db/migrate/20150326163802_add_foreign_key_constraints.rb b/db/migrate/20150326163802_add_foreign_key_constraints.rb index c038c9d7..0e57ca08 100644 --- a/db/migrate/20150326163802_add_foreign_key_constraints.rb +++ b/db/migrate/20150326163802_add_foreign_key_constraints.rb @@ -1,4 +1,4 @@ -class AddForeignKeyConstraints < ActiveRecord::Migration +class AddForeignKeyConstraints < ActiveRecord::Migration[5.1] def change add_foreign_key :attempts, :exercises, dependent: :delete add_foreign_key :attempts, :users, dependent: :delete diff --git a/db/migrate/20150326173027_rename_title_to_name_in_variation_group.rb b/db/migrate/20150326173027_rename_title_to_name_in_variation_group.rb index 5d06e319..8a28f552 100644 --- a/db/migrate/20150326173027_rename_title_to_name_in_variation_group.rb +++ b/db/migrate/20150326173027_rename_title_to_name_in_variation_group.rb @@ -1,4 +1,4 @@ -class RenameTitleToNameInVariationGroup < ActiveRecord::Migration +class RenameTitleToNameInVariationGroup < ActiveRecord::Migration[5.1] def change rename_column :variation_groups, :title, :name end diff --git a/db/migrate/20150326193058_rename_ordering_to_order_in_exercise_workouts.rb b/db/migrate/20150326193058_rename_ordering_to_order_in_exercise_workouts.rb index 287b5152..c8771bc8 100644 --- a/db/migrate/20150326193058_rename_ordering_to_order_in_exercise_workouts.rb +++ b/db/migrate/20150326193058_rename_ordering_to_order_in_exercise_workouts.rb @@ -1,4 +1,4 @@ -class RenameOrderingToOrderInExerciseWorkouts < ActiveRecord::Migration +class RenameOrderingToOrderInExerciseWorkouts < ActiveRecord::Migration[5.1] def change rename_column :exercise_workouts, :ordering, :order end diff --git a/db/migrate/20150326193301_rename_title_to_name_in_exercises.rb b/db/migrate/20150326193301_rename_title_to_name_in_exercises.rb index bf76d18f..616e2e07 100644 --- a/db/migrate/20150326193301_rename_title_to_name_in_exercises.rb +++ b/db/migrate/20150326193301_rename_title_to_name_in_exercises.rb @@ -1,4 +1,4 @@ -class RenameTitleToNameInExercises < ActiveRecord::Migration +class RenameTitleToNameInExercises < ActiveRecord::Migration[5.1] def change rename_column :exercises, :title, :name end diff --git a/db/migrate/20150327213048_create_active_admin_comments.rb b/db/migrate/20150327213048_create_active_admin_comments.rb index 9efd1478..fd0c5aee 100644 --- a/db/migrate/20150327213048_create_active_admin_comments.rb +++ b/db/migrate/20150327213048_create_active_admin_comments.rb @@ -1,4 +1,4 @@ -class CreateActiveAdminComments < ActiveRecord::Migration +class CreateActiveAdminComments < ActiveRecord::Migration[5.1] def self.up create_table :active_admin_comments do |t| t.string :namespace diff --git a/db/migrate/20150328005919_rename_display_name_to_name_in_organizations.rb b/db/migrate/20150328005919_rename_display_name_to_name_in_organizations.rb index a7978380..2cefce0c 100644 --- a/db/migrate/20150328005919_rename_display_name_to_name_in_organizations.rb +++ b/db/migrate/20150328005919_rename_display_name_to_name_in_organizations.rb @@ -1,4 +1,4 @@ -class RenameDisplayNameToNameInOrganizations < ActiveRecord::Migration +class RenameDisplayNameToNameInOrganizations < ActiveRecord::Migration[5.1] def change rename_column :organizations, :display_name, :name end diff --git a/db/migrate/20150328011131_remove_name_from_users.rb b/db/migrate/20150328011131_remove_name_from_users.rb index 623cc753..b6082677 100644 --- a/db/migrate/20150328011131_remove_name_from_users.rb +++ b/db/migrate/20150328011131_remove_name_from_users.rb @@ -1,4 +1,4 @@ -class RemoveNameFromUsers < ActiveRecord::Migration +class RemoveNameFromUsers < ActiveRecord::Migration[5.1] def change remove_column :users, :name, :string end diff --git a/db/migrate/20150328150855_remove_name_from_course_offering.rb b/db/migrate/20150328150855_remove_name_from_course_offering.rb index 58dffa86..083834c6 100644 --- a/db/migrate/20150328150855_remove_name_from_course_offering.rb +++ b/db/migrate/20150328150855_remove_name_from_course_offering.rb @@ -1,4 +1,4 @@ -class RemoveNameFromCourseOffering < ActiveRecord::Migration +class RemoveNameFromCourseOffering < ActiveRecord::Migration[5.1] def change remove_column :course_offerings, :name, :string change_column_null :course_offerings, :label, false diff --git a/db/migrate/20150330194627_create_friendly_id_slugs.rb b/db/migrate/20150330194627_create_friendly_id_slugs.rb index 770f6264..6e7389c0 100644 --- a/db/migrate/20150330194627_create_friendly_id_slugs.rb +++ b/db/migrate/20150330194627_create_friendly_id_slugs.rb @@ -1,4 +1,4 @@ -class CreateFriendlyIdSlugs < ActiveRecord::Migration +class CreateFriendlyIdSlugs < ActiveRecord::Migration[5.1] def change create_table :friendly_id_slugs do |t| t.string :slug, :null => false diff --git a/db/migrate/20150331032124_add_slug_to_term.rb b/db/migrate/20150331032124_add_slug_to_term.rb index 5260747e..25ec49b8 100644 --- a/db/migrate/20150331032124_add_slug_to_term.rb +++ b/db/migrate/20150331032124_add_slug_to_term.rb @@ -1,4 +1,4 @@ -class AddSlugToTerm < ActiveRecord::Migration +class AddSlugToTerm < ActiveRecord::Migration[5.1] def up add_column :terms, :slug, :string # Force generation of slug values for all entries diff --git a/db/migrate/20150331121739_add_slug_to_organization.rb b/db/migrate/20150331121739_add_slug_to_organization.rb index a095a1ad..49abb980 100644 --- a/db/migrate/20150331121739_add_slug_to_organization.rb +++ b/db/migrate/20150331121739_add_slug_to_organization.rb @@ -1,4 +1,4 @@ -class AddSlugToOrganization < ActiveRecord::Migration +class AddSlugToOrganization < ActiveRecord::Migration[5.1] def up remove_column :organizations, :url_part, :string diff --git a/db/migrate/20150402033335_add_slug_to_course.rb b/db/migrate/20150402033335_add_slug_to_course.rb index 42219394..1bb771e1 100644 --- a/db/migrate/20150402033335_add_slug_to_course.rb +++ b/db/migrate/20150402033335_add_slug_to_course.rb @@ -1,4 +1,4 @@ -class AddSlugToCourse < ActiveRecord::Migration +class AddSlugToCourse < ActiveRecord::Migration[5.1] def up remove_index :courses, :url_part diff --git a/db/migrate/20150402043958_add_slug_to_user.rb b/db/migrate/20150402043958_add_slug_to_user.rb index 83353b7a..cdeb142d 100644 --- a/db/migrate/20150402043958_add_slug_to_user.rb +++ b/db/migrate/20150402043958_add_slug_to_user.rb @@ -1,4 +1,4 @@ -class AddSlugToUser < ActiveRecord::Migration +class AddSlugToUser < ActiveRecord::Migration[5.1] def up add_column :users, :slug, :string diff --git a/db/migrate/20150429122658_rename_exercises_to_exercise_versions.rb b/db/migrate/20150429122658_rename_exercises_to_exercise_versions.rb index 7b0563b6..46c2873e 100644 --- a/db/migrate/20150429122658_rename_exercises_to_exercise_versions.rb +++ b/db/migrate/20150429122658_rename_exercises_to_exercise_versions.rb @@ -1,4 +1,4 @@ -class RenameExercisesToExerciseVersions < ActiveRecord::Migration +class RenameExercisesToExerciseVersions < ActiveRecord::Migration[5.1] def change rename_table :exercises, :exercise_versions rename_column :attempts, :exercise_id, :exercise_version_id diff --git a/db/migrate/20150429132614_rename_base_exercises_to_exercises.rb b/db/migrate/20150429132614_rename_base_exercises_to_exercises.rb index 12046180..9bd0794d 100644 --- a/db/migrate/20150429132614_rename_base_exercises_to_exercises.rb +++ b/db/migrate/20150429132614_rename_base_exercises_to_exercises.rb @@ -1,4 +1,4 @@ -class RenameBaseExercisesToExercises < ActiveRecord::Migration +class RenameBaseExercisesToExercises < ActiveRecord::Migration[5.1] def change rename_table :base_exercises, :exercises rename_column :exercise_versions, :base_exercise_id, :exercise_id diff --git a/db/migrate/20150429135647_create_exercise_owners.rb b/db/migrate/20150429135647_create_exercise_owners.rb index fc352688..14dcd8a2 100644 --- a/db/migrate/20150429135647_create_exercise_owners.rb +++ b/db/migrate/20150429135647_create_exercise_owners.rb @@ -1,4 +1,4 @@ -class CreateExerciseOwners < ActiveRecord::Migration +class CreateExerciseOwners < ActiveRecord::Migration[5.1] def change create_table :exercise_owners do |t| t.belongs_to :exercise, null: false diff --git a/db/migrate/20150429160335_rename_variation_group_to_exercise_family.rb b/db/migrate/20150429160335_rename_variation_group_to_exercise_family.rb index 88650c87..073533ab 100644 --- a/db/migrate/20150429160335_rename_variation_group_to_exercise_family.rb +++ b/db/migrate/20150429160335_rename_variation_group_to_exercise_family.rb @@ -1,4 +1,4 @@ -class RenameVariationGroupToExerciseFamily < ActiveRecord::Migration +class RenameVariationGroupToExerciseFamily < ActiveRecord::Migration[5.1] def change rename_table :variation_groups, :exercise_families rename_column :exercises, :variation_group_id, :exercise_family_id diff --git a/db/migrate/20150429170252_remove_user_id_from_base_exercises.rb b/db/migrate/20150429170252_remove_user_id_from_base_exercises.rb index 246b9d64..0aa1b106 100644 --- a/db/migrate/20150429170252_remove_user_id_from_base_exercises.rb +++ b/db/migrate/20150429170252_remove_user_id_from_base_exercises.rb @@ -1,4 +1,4 @@ -class RemoveUserIdFromBaseExercises < ActiveRecord::Migration +class RemoveUserIdFromBaseExercises < ActiveRecord::Migration[5.1] def change remove_column :exercises, :user_id, :integer end diff --git a/db/migrate/20150430165328_rename_for_acts_as_list.rb b/db/migrate/20150430165328_rename_for_acts_as_list.rb index d1fee669..12ce39b3 100644 --- a/db/migrate/20150430165328_rename_for_acts_as_list.rb +++ b/db/migrate/20150430165328_rename_for_acts_as_list.rb @@ -1,4 +1,4 @@ -class RenameForActsAsList < ActiveRecord::Migration +class RenameForActsAsList < ActiveRecord::Migration[5.1] def change rename_column :choices, :order, :position rename_column :exercise_versions, :version, :position diff --git a/db/migrate/20150501192055_rename_prompt_features.rb b/db/migrate/20150501192055_rename_prompt_features.rb index 430a5d46..3030de55 100644 --- a/db/migrate/20150501192055_rename_prompt_features.rb +++ b/db/migrate/20150501192055_rename_prompt_features.rb @@ -1,4 +1,4 @@ -class RenamePromptFeatures < ActiveRecord::Migration +class RenamePromptFeatures < ActiveRecord::Migration[5.1] def change change_table :prompts do |t| t.remove :language_id, :integer diff --git a/db/migrate/20150502155416_rename_coding_question_in_test_cases.rb b/db/migrate/20150502155416_rename_coding_question_in_test_cases.rb index e0353351..5626cd95 100644 --- a/db/migrate/20150502155416_rename_coding_question_in_test_cases.rb +++ b/db/migrate/20150502155416_rename_coding_question_in_test_cases.rb @@ -1,4 +1,4 @@ -class RenameCodingQuestionInTestCases < ActiveRecord::Migration +class RenameCodingQuestionInTestCases < ActiveRecord::Migration[5.1] def change rename_column :test_cases, :coding_question_id, :coding_prompt_id end diff --git a/db/migrate/20150502155552_move_choices_from_exercise_versions_to_prompts.rb b/db/migrate/20150502155552_move_choices_from_exercise_versions_to_prompts.rb index f2fd6d8d..ebfe9add 100644 --- a/db/migrate/20150502155552_move_choices_from_exercise_versions_to_prompts.rb +++ b/db/migrate/20150502155552_move_choices_from_exercise_versions_to_prompts.rb @@ -1,4 +1,4 @@ -class MoveChoicesFromExerciseVersionsToPrompts < ActiveRecord::Migration +class MoveChoicesFromExerciseVersionsToPrompts < ActiveRecord::Migration[5.1] def up remove_foreign_key :choices, :exercise_versions Choice.delete_all diff --git a/db/migrate/20150502181403_move_features_from_exercise_versions_to_exercises.rb b/db/migrate/20150502181403_move_features_from_exercise_versions_to_exercises.rb index 1cce96ce..40d0cda8 100644 --- a/db/migrate/20150502181403_move_features_from_exercise_versions_to_exercises.rb +++ b/db/migrate/20150502181403_move_features_from_exercise_versions_to_exercises.rb @@ -1,4 +1,4 @@ -class MoveFeaturesFromExerciseVersionsToExercises < ActiveRecord::Migration +class MoveFeaturesFromExerciseVersionsToExercises < ActiveRecord::Migration[5.1] def up add_column :exercises, :name, :string add_column :exercises, :is_public, :boolean, null: false, default: false diff --git a/db/migrate/20150502203324_move_tags_from_exercise_versions_to_exercises.rb b/db/migrate/20150502203324_move_tags_from_exercise_versions_to_exercises.rb index 62a0cadb..5b6d7eb9 100644 --- a/db/migrate/20150502203324_move_tags_from_exercise_versions_to_exercises.rb +++ b/db/migrate/20150502203324_move_tags_from_exercise_versions_to_exercises.rb @@ -1,4 +1,4 @@ -class MoveTagsFromExerciseVersionsToExercises < ActiveRecord::Migration +class MoveTagsFromExerciseVersionsToExercises < ActiveRecord::Migration[5.1] def up drop_table :exercise_versions_tags create_table :exercises_tags, id: false do |t| diff --git a/db/migrate/20150502210227_add_stats_to_exercises.rb b/db/migrate/20150502210227_add_stats_to_exercises.rb index 9d01f770..f56379de 100644 --- a/db/migrate/20150502210227_add_stats_to_exercises.rb +++ b/db/migrate/20150502210227_add_stats_to_exercises.rb @@ -1,4 +1,4 @@ -class AddStatsToExercises < ActiveRecord::Migration +class AddStatsToExercises < ActiveRecord::Migration[5.1] def change change_table :exercises do |t| t.integer :attempt_count, null: false, default: 0 diff --git a/db/migrate/20150502210606_remove_question_from_exercise_versions.rb b/db/migrate/20150502210606_remove_question_from_exercise_versions.rb index 92249f97..53ddacab 100644 --- a/db/migrate/20150502210606_remove_question_from_exercise_versions.rb +++ b/db/migrate/20150502210606_remove_question_from_exercise_versions.rb @@ -1,4 +1,4 @@ -class RemoveQuestionFromExerciseVersions < ActiveRecord::Migration +class RemoveQuestionFromExerciseVersions < ActiveRecord::Migration[5.1] def up remove_column :exercise_versions, :question, :text change_column :choices, :answer, :text diff --git a/db/migrate/20150503003408_add_workout_offering_to_workout_score.rb b/db/migrate/20150503003408_add_workout_offering_to_workout_score.rb index 81871628..3c267068 100644 --- a/db/migrate/20150503003408_add_workout_offering_to_workout_score.rb +++ b/db/migrate/20150503003408_add_workout_offering_to_workout_score.rb @@ -1,4 +1,4 @@ -class AddWorkoutOfferingToWorkoutScore < ActiveRecord::Migration +class AddWorkoutOfferingToWorkoutScore < ActiveRecord::Migration[5.1] def change change_table :workout_scores do |t| t.belongs_to :workout_offering diff --git a/db/migrate/20150503004546_add_to_attempt.rb b/db/migrate/20150503004546_add_to_attempt.rb index 8f11a753..7a03e20c 100644 --- a/db/migrate/20150503004546_add_to_attempt.rb +++ b/db/migrate/20150503004546_add_to_attempt.rb @@ -1,4 +1,4 @@ -class AddToAttempt < ActiveRecord::Migration +class AddToAttempt < ActiveRecord::Migration[5.1] def change change_table :attempts do |t| t.belongs_to :workout_score diff --git a/db/migrate/20150503153952_create_workout_owners.rb b/db/migrate/20150503153952_create_workout_owners.rb index 72f1a64f..dccd31c9 100644 --- a/db/migrate/20150503153952_create_workout_owners.rb +++ b/db/migrate/20150503153952_create_workout_owners.rb @@ -1,4 +1,4 @@ -class CreateWorkoutOwners < ActiveRecord::Migration +class CreateWorkoutOwners < ActiveRecord::Migration[5.1] def change create_table :workout_owners do |t| t.belongs_to :workout, null: false diff --git a/db/migrate/20150503190401_fix_choice_answer.rb b/db/migrate/20150503190401_fix_choice_answer.rb index ab3dd506..ca9bddc8 100644 --- a/db/migrate/20150503190401_fix_choice_answer.rb +++ b/db/migrate/20150503190401_fix_choice_answer.rb @@ -1,4 +1,4 @@ -class FixChoiceAnswer < ActiveRecord::Migration +class FixChoiceAnswer < ActiveRecord::Migration[5.1] def up remove_column :choices, :answer add_column :choices, :answer, :text diff --git a/db/migrate/20150503232731_create_prompt_answer.rb b/db/migrate/20150503232731_create_prompt_answer.rb index 21102ca0..df3c70f1 100644 --- a/db/migrate/20150503232731_create_prompt_answer.rb +++ b/db/migrate/20150503232731_create_prompt_answer.rb @@ -1,4 +1,4 @@ -class CreatePromptAnswer < ActiveRecord::Migration +class CreatePromptAnswer < ActiveRecord::Migration[5.1] def change create_table :prompt_answers do |t| t.belongs_to :attempt, required: true diff --git a/db/migrate/20150504000235_create_coding_prompt_answer.rb b/db/migrate/20150504000235_create_coding_prompt_answer.rb index 700a2618..1b9668ed 100644 --- a/db/migrate/20150504000235_create_coding_prompt_answer.rb +++ b/db/migrate/20150504000235_create_coding_prompt_answer.rb @@ -1,4 +1,4 @@ -class CreateCodingPromptAnswer < ActiveRecord::Migration +class CreateCodingPromptAnswer < ActiveRecord::Migration[5.1] def change remove_column :attempts, :answer, :text remove_column :attempts, :workout_offering_id, :integer diff --git a/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb b/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb index b45e3ec4..5b39cfa8 100644 --- a/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb +++ b/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb @@ -1,4 +1,4 @@ -class CreateMultipleChoicePromptAnswer < ActiveRecord::Migration +class CreateMultipleChoicePromptAnswer < ActiveRecord::Migration[5.1] def change create_table :multiple_choice_prompt_answers do |t| end diff --git a/db/migrate/20150504004247_create_irt_data.rb b/db/migrate/20150504004247_create_irt_data.rb index e3f45c68..42d5c4eb 100644 --- a/db/migrate/20150504004247_create_irt_data.rb +++ b/db/migrate/20150504004247_create_irt_data.rb @@ -1,4 +1,4 @@ -class CreateIRTData < ActiveRecord::Migration +class CreateIRTData < ActiveRecord::Migration[5.1] def change create_table :irt_data do |t| t.integer :attempt_count, null: false diff --git a/db/migrate/20150504051530_add_cutoff_date_to_course_offerings.rb b/db/migrate/20150504051530_add_cutoff_date_to_course_offerings.rb index 13d879e0..baf141e6 100644 --- a/db/migrate/20150504051530_add_cutoff_date_to_course_offerings.rb +++ b/db/migrate/20150504051530_add_cutoff_date_to_course_offerings.rb @@ -1,4 +1,4 @@ -class AddCutoffDateToCourseOfferings < ActiveRecord::Migration +class AddCutoffDateToCourseOfferings < ActiveRecord::Migration[5.1] def change add_column :course_offerings, :cutoff_date, :date end diff --git a/db/migrate/20150506122722_create_attempts_tag_user_scores.rb b/db/migrate/20150506122722_create_attempts_tag_user_scores.rb index ef696c64..dbcde3dc 100644 --- a/db/migrate/20150506122722_create_attempts_tag_user_scores.rb +++ b/db/migrate/20150506122722_create_attempts_tag_user_scores.rb @@ -1,4 +1,4 @@ -class CreateAttemptsTagUserScores < ActiveRecord::Migration +class CreateAttemptsTagUserScores < ActiveRecord::Migration[5.1] def change # HABTM table attempts <-> tag_user_scores (many to many) create_table :attempts_tag_user_scores, id: false do |t| diff --git a/db/migrate/20150506124650_remove_target_group_from_workouts.rb b/db/migrate/20150506124650_remove_target_group_from_workouts.rb index fe5e2bfb..f4b56107 100644 --- a/db/migrate/20150506124650_remove_target_group_from_workouts.rb +++ b/db/migrate/20150506124650_remove_target_group_from_workouts.rb @@ -1,4 +1,4 @@ -class RemoveTargetGroupFromWorkouts < ActiveRecord::Migration +class RemoveTargetGroupFromWorkouts < ActiveRecord::Migration[5.1] def change remove_column :workouts, :target_group, :string end diff --git a/db/migrate/20150506161404_add.rb b/db/migrate/20150506161404_add.rb index 828aaf95..3d383b6d 100644 --- a/db/migrate/20150506161404_add.rb +++ b/db/migrate/20150506161404_add.rb @@ -1,4 +1,4 @@ -class Add < ActiveRecord::Migration +class Add < ActiveRecord::Migration[5.1] def change add_column :exercises, :external_id, :string add_index :exercises, :external_id, unique: true diff --git a/db/migrate/20150507004949_remove_is_scrambled_from_prompt.rb b/db/migrate/20150507004949_remove_is_scrambled_from_prompt.rb index b475f286..8a1b0059 100644 --- a/db/migrate/20150507004949_remove_is_scrambled_from_prompt.rb +++ b/db/migrate/20150507004949_remove_is_scrambled_from_prompt.rb @@ -1,4 +1,4 @@ -class RemoveIsScrambledFromPrompt < ActiveRecord::Migration +class RemoveIsScrambledFromPrompt < ActiveRecord::Migration[5.1] def change remove_column :prompts, :is_scrambled, :boolean end diff --git a/db/migrate/20150507140447_remove_null_constraint_from_exercise_current_version.rb b/db/migrate/20150507140447_remove_null_constraint_from_exercise_current_version.rb index 618b5c92..4e013488 100644 --- a/db/migrate/20150507140447_remove_null_constraint_from_exercise_current_version.rb +++ b/db/migrate/20150507140447_remove_null_constraint_from_exercise_current_version.rb @@ -1,4 +1,4 @@ -class RemoveNullConstraintFromExerciseCurrentVersion < ActiveRecord::Migration +class RemoveNullConstraintFromExerciseCurrentVersion < ActiveRecord::Migration[5.1] def change change_column_null :exercises, :current_version_id, true end diff --git a/db/migrate/20150507143058_rename_prompt_to_question_in_prompts.rb b/db/migrate/20150507143058_rename_prompt_to_question_in_prompts.rb index 6d41330b..2088243d 100644 --- a/db/migrate/20150507143058_rename_prompt_to_question_in_prompts.rb +++ b/db/migrate/20150507143058_rename_prompt_to_question_in_prompts.rb @@ -1,4 +1,4 @@ -class RenamePromptToQuestionInPrompts < ActiveRecord::Migration +class RenamePromptToQuestionInPrompts < ActiveRecord::Migration[5.1] def change rename_column :prompts, :prompt, :question end diff --git a/db/migrate/20150507163037_remove_tags.rb b/db/migrate/20150507163037_remove_tags.rb index 66a199cc..ae34cd61 100644 --- a/db/migrate/20150507163037_remove_tags.rb +++ b/db/migrate/20150507163037_remove_tags.rb @@ -1,4 +1,4 @@ -class RemoveTags < ActiveRecord::Migration +class RemoveTags < ActiveRecord::Migration[5.1] def up drop_table :exercises_tags remove_column :tag_user_scores, :tag_id, :integer diff --git a/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb index 6bbd5594..27b770f7 100644 --- a/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb +++ b/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb @@ -1,5 +1,5 @@ # This migration comes from acts_as_taggable_on_engine (originally 1) -class ActsAsTaggableOnMigration < ActiveRecord::Migration +class ActsAsTaggableOnMigration < ActiveRecord::Migration[5.1] def self.up create_table :tags do |t| t.string :name diff --git a/db/migrate/20150507164240_add_missing_unique_indices.acts_as_taggable_on_engine.rb b/db/migrate/20150507164240_add_missing_unique_indices.acts_as_taggable_on_engine.rb index 4ca676f6..404230a5 100644 --- a/db/migrate/20150507164240_add_missing_unique_indices.acts_as_taggable_on_engine.rb +++ b/db/migrate/20150507164240_add_missing_unique_indices.acts_as_taggable_on_engine.rb @@ -1,5 +1,5 @@ # This migration comes from acts_as_taggable_on_engine (originally 2) -class AddMissingUniqueIndices < ActiveRecord::Migration +class AddMissingUniqueIndices < ActiveRecord::Migration[5.1] def self.up add_index :tags, :name, unique: true diff --git a/db/migrate/20150507164241_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb b/db/migrate/20150507164241_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb index 8edb5080..c54fffea 100644 --- a/db/migrate/20150507164241_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb +++ b/db/migrate/20150507164241_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb @@ -1,5 +1,5 @@ # This migration comes from acts_as_taggable_on_engine (originally 3) -class AddTaggingsCounterCacheToTags < ActiveRecord::Migration +class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[5.1] def self.up add_column :tags, :taggings_count, :integer, default: 0 diff --git a/db/migrate/20150507164242_add_missing_taggable_index.acts_as_taggable_on_engine.rb b/db/migrate/20150507164242_add_missing_taggable_index.acts_as_taggable_on_engine.rb index 71f2d7f4..a298379c 100644 --- a/db/migrate/20150507164242_add_missing_taggable_index.acts_as_taggable_on_engine.rb +++ b/db/migrate/20150507164242_add_missing_taggable_index.acts_as_taggable_on_engine.rb @@ -1,5 +1,5 @@ # This migration comes from acts_as_taggable_on_engine (originally 4) -class AddMissingTaggableIndex < ActiveRecord::Migration +class AddMissingTaggableIndex < ActiveRecord::Migration[5.1] def self.up add_index :taggings, [:taggable_id, :taggable_type, :context] end diff --git a/db/migrate/20150507164243_change_collation_for_tag_names.acts_as_taggable_on_engine.rb b/db/migrate/20150507164243_change_collation_for_tag_names.acts_as_taggable_on_engine.rb index bfb06bc7..980f110a 100644 --- a/db/migrate/20150507164243_change_collation_for_tag_names.acts_as_taggable_on_engine.rb +++ b/db/migrate/20150507164243_change_collation_for_tag_names.acts_as_taggable_on_engine.rb @@ -1,7 +1,7 @@ # This migration comes from acts_as_taggable_on_engine (originally 5) # This migration is added to circumvent issue #623 and have special characters # work properly -class ChangeCollationForTagNames < ActiveRecord::Migration +class ChangeCollationForTagNames < ActiveRecord::Migration[5.1] def up if ActsAsTaggableOn::Utils.using_mysql? execute("ALTER TABLE tags MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") diff --git a/db/migrate/20150510144703_rename_position_to_version_in_exercise_versions.rb b/db/migrate/20150510144703_rename_position_to_version_in_exercise_versions.rb index ffa9f6a5..cd4c668c 100644 --- a/db/migrate/20150510144703_rename_position_to_version_in_exercise_versions.rb +++ b/db/migrate/20150510144703_rename_position_to_version_in_exercise_versions.rb @@ -1,4 +1,4 @@ -class RenamePositionToVersionInExerciseVersions < ActiveRecord::Migration +class RenamePositionToVersionInExerciseVersions < ActiveRecord::Migration[5.1] def change rename_column :exercise_versions, :position, :version change_column_null :exercises, :versions, true diff --git a/db/migrate/20150511225412_actable_ids_are_not_unique.rb b/db/migrate/20150511225412_actable_ids_are_not_unique.rb index bd380e5a..339e0617 100644 --- a/db/migrate/20150511225412_actable_ids_are_not_unique.rb +++ b/db/migrate/20150511225412_actable_ids_are_not_unique.rb @@ -1,4 +1,4 @@ -class ActableIdsAreNotUnique < ActiveRecord::Migration +class ActableIdsAreNotUnique < ActiveRecord::Migration[5.1] def up # Remove unique: true from these two indices remove_index :prompts, :actable_id diff --git a/db/migrate/20150515205710_revise_test_case_structure.rb b/db/migrate/20150515205710_revise_test_case_structure.rb index a09085bb..b1ff1a11 100644 --- a/db/migrate/20150515205710_revise_test_case_structure.rb +++ b/db/migrate/20150515205710_revise_test_case_structure.rb @@ -1,4 +1,4 @@ -class ReviseTestCaseStructure < ActiveRecord::Migration +class ReviseTestCaseStructure < ActiveRecord::Migration[5.1] def up change_table :test_cases do |t| t.remove :test_script, :string diff --git a/db/migrate/20150522205618_add_answer_to_multiple_choice_prompt_answers.rb b/db/migrate/20150522205618_add_answer_to_multiple_choice_prompt_answers.rb index 460c7167..4e5c148e 100644 --- a/db/migrate/20150522205618_add_answer_to_multiple_choice_prompt_answers.rb +++ b/db/migrate/20150522205618_add_answer_to_multiple_choice_prompt_answers.rb @@ -1,4 +1,4 @@ -class AddAnswerToMultipleChoicePromptAnswers < ActiveRecord::Migration +class AddAnswerToMultipleChoicePromptAnswers < ActiveRecord::Migration[5.1] def change add_column :multiple_choice_prompt_answers, :answer, :string end diff --git a/db/migrate/20150525184958_remove_answer_from_multiple_choice_prompt_answers.rb b/db/migrate/20150525184958_remove_answer_from_multiple_choice_prompt_answers.rb index 67fc633d..7a99de60 100644 --- a/db/migrate/20150525184958_remove_answer_from_multiple_choice_prompt_answers.rb +++ b/db/migrate/20150525184958_remove_answer_from_multiple_choice_prompt_answers.rb @@ -1,4 +1,4 @@ -class RemoveAnswerFromMultipleChoicePromptAnswers < ActiveRecord::Migration +class RemoveAnswerFromMultipleChoicePromptAnswers < ActiveRecord::Migration[5.1] def change remove_column :multiple_choice_prompt_answers, :answer, :string end diff --git a/db/migrate/20150528120715_add_error_to_coding_prompt_answer.rb b/db/migrate/20150528120715_add_error_to_coding_prompt_answer.rb index adba7c7a..b5f117e0 100644 --- a/db/migrate/20150528120715_add_error_to_coding_prompt_answer.rb +++ b/db/migrate/20150528120715_add_error_to_coding_prompt_answer.rb @@ -1,4 +1,4 @@ -class AddErrorToCodingPromptAnswer < ActiveRecord::Migration +class AddErrorToCodingPromptAnswer < ActiveRecord::Migration[5.1] def change add_column :coding_prompt_answers, :error, :text end diff --git a/db/migrate/20150531004056_change_class_name_to_string_in_errors.rb b/db/migrate/20150531004056_change_class_name_to_string_in_errors.rb index 872a9d3c..71d3691c 100644 --- a/db/migrate/20150531004056_change_class_name_to_string_in_errors.rb +++ b/db/migrate/20150531004056_change_class_name_to_string_in_errors.rb @@ -1,4 +1,4 @@ -class ChangeClassNameToStringInErrors < ActiveRecord::Migration +class ChangeClassNameToStringInErrors < ActiveRecord::Migration[5.1] def change change_column :errors, :class_name, :string end diff --git a/db/migrate/20150604011650_add_published_to_workout_offerings.rb b/db/migrate/20150604011650_add_published_to_workout_offerings.rb index 59e4a5ca..cf56c5ef 100644 --- a/db/migrate/20150604011650_add_published_to_workout_offerings.rb +++ b/db/migrate/20150604011650_add_published_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddPublishedToWorkoutOfferings < ActiveRecord::Migration +class AddPublishedToWorkoutOfferings < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :published, :boolean, null: false, default: true diff --git a/db/migrate/20150608001534_change_dates_to_datetimes_in_workout_offerings.rb b/db/migrate/20150608001534_change_dates_to_datetimes_in_workout_offerings.rb index 6c00878d..79d7acb0 100644 --- a/db/migrate/20150608001534_change_dates_to_datetimes_in_workout_offerings.rb +++ b/db/migrate/20150608001534_change_dates_to_datetimes_in_workout_offerings.rb @@ -1,4 +1,4 @@ -class ChangeDatesToDatetimesInWorkoutOfferings < ActiveRecord::Migration +class ChangeDatesToDatetimesInWorkoutOfferings < ActiveRecord::Migration[5.1] def change change_column :workout_offerings, :opening_date, :datetime change_column :workout_offerings, :soft_deadline, :datetime diff --git a/db/migrate/20150912035415_create_time_zones.rb b/db/migrate/20150912035415_create_time_zones.rb index 7c0241a4..f5ecb2c2 100644 --- a/db/migrate/20150912035415_create_time_zones.rb +++ b/db/migrate/20150912035415_create_time_zones.rb @@ -1,4 +1,4 @@ -class CreateTimeZones < ActiveRecord::Migration +class CreateTimeZones < ActiveRecord::Migration[5.1] def change create_table :time_zones do |t| t.string :name diff --git a/db/migrate/20150912200222_add_time_zone_id_to_users.rb b/db/migrate/20150912200222_add_time_zone_id_to_users.rb index 5a3f6ae0..86553f82 100644 --- a/db/migrate/20150912200222_add_time_zone_id_to_users.rb +++ b/db/migrate/20150912200222_add_time_zone_id_to_users.rb @@ -1,4 +1,4 @@ -class AddTimeZoneIdToUsers < ActiveRecord::Migration +class AddTimeZoneIdToUsers < ActiveRecord::Migration[5.1] def change add_reference :users, :time_zone, index: true end diff --git a/db/migrate/20150916031620_add_current_workout_score_to_users.rb b/db/migrate/20150916031620_add_current_workout_score_to_users.rb index 858b7a41..4271cf23 100644 --- a/db/migrate/20150916031620_add_current_workout_score_to_users.rb +++ b/db/migrate/20150916031620_add_current_workout_score_to_users.rb @@ -1,4 +1,4 @@ -class AddCurrentWorkoutScoreToUsers < ActiveRecord::Migration +class AddCurrentWorkoutScoreToUsers < ActiveRecord::Migration[5.1] def change change_table :users do |t| t.integer :current_workout_score_id diff --git a/db/migrate/20150921113747_add_is_public_to_workouts.rb b/db/migrate/20150921113747_add_is_public_to_workouts.rb index 132650ad..3d71d5f8 100644 --- a/db/migrate/20150921113747_add_is_public_to_workouts.rb +++ b/db/migrate/20150921113747_add_is_public_to_workouts.rb @@ -1,4 +1,4 @@ -class AddIsPublicToWorkouts < ActiveRecord::Migration +class AddIsPublicToWorkouts < ActiveRecord::Migration[5.1] def change add_column :workouts, :is_public, :boolean add_index :workouts, :is_public diff --git a/db/migrate/20150923144539_add_feedback_ready_to_attempts.rb b/db/migrate/20150923144539_add_feedback_ready_to_attempts.rb index 32e3f89a..ff66bdb8 100644 --- a/db/migrate/20150923144539_add_feedback_ready_to_attempts.rb +++ b/db/migrate/20150923144539_add_feedback_ready_to_attempts.rb @@ -1,4 +1,4 @@ -class AddFeedbackReadyToAttempts < ActiveRecord::Migration +class AddFeedbackReadyToAttempts < ActiveRecord::Migration[5.1] def change add_column :attempts, :feedback_ready, :boolean end diff --git a/db/migrate/20150927020504_create_student_extensions.rb b/db/migrate/20150927020504_create_student_extensions.rb index f489f3cb..e63ffd64 100644 --- a/db/migrate/20150927020504_create_student_extensions.rb +++ b/db/migrate/20150927020504_create_student_extensions.rb @@ -1,4 +1,4 @@ -class CreateStudentExtensions < ActiveRecord::Migration +class CreateStudentExtensions < ActiveRecord::Migration[5.1] def change create_table :student_extensions do |t| t.belongs_to :user, index: true diff --git a/db/migrate/20151015033713_add_time_limit_to_workout_offerings.rb b/db/migrate/20151015033713_add_time_limit_to_workout_offerings.rb index b6ae97ed..fe778300 100644 --- a/db/migrate/20151015033713_add_time_limit_to_workout_offerings.rb +++ b/db/migrate/20151015033713_add_time_limit_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddTimeLimitToWorkoutOfferings < ActiveRecord::Migration +class AddTimeLimitToWorkoutOfferings < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :time_limit, :int end diff --git a/db/migrate/20151015035823_create_workout_policy.rb b/db/migrate/20151015035823_create_workout_policy.rb index 9a1e1279..ba2956a1 100644 --- a/db/migrate/20151015035823_create_workout_policy.rb +++ b/db/migrate/20151015035823_create_workout_policy.rb @@ -1,4 +1,4 @@ -class CreateWorkoutPolicy < ActiveRecord::Migration +class CreateWorkoutPolicy < ActiveRecord::Migration[5.1] def change create_table :workout_policies do |t| t.boolean :hide_thumbnails_before_start diff --git a/db/migrate/20151015044326_add_time_limit_to_student_extensions.rb b/db/migrate/20151015044326_add_time_limit_to_student_extensions.rb index e5ac608a..c45420a8 100644 --- a/db/migrate/20151015044326_add_time_limit_to_student_extensions.rb +++ b/db/migrate/20151015044326_add_time_limit_to_student_extensions.rb @@ -1,4 +1,4 @@ -class AddTimeLimitToStudentExtensions < ActiveRecord::Migration +class AddTimeLimitToStudentExtensions < ActiveRecord::Migration[5.1] def change add_column :student_extensions, :time_limit, :int end diff --git a/db/migrate/20160217060925_add_invisible_before_review_to_workout_policies.rb b/db/migrate/20160217060925_add_invisible_before_review_to_workout_policies.rb index e8e4b810..3af4327e 100644 --- a/db/migrate/20160217060925_add_invisible_before_review_to_workout_policies.rb +++ b/db/migrate/20160217060925_add_invisible_before_review_to_workout_policies.rb @@ -1,4 +1,4 @@ -class AddInvisibleBeforeReviewToWorkoutPolicies < ActiveRecord::Migration +class AddInvisibleBeforeReviewToWorkoutPolicies < ActiveRecord::Migration[5.1] def change add_column :workout_policies, :invisible_before_review, :boolean end diff --git a/db/migrate/20160224064748_add_opening_date_to_student_extensions.rb b/db/migrate/20160224064748_add_opening_date_to_student_extensions.rb index 8439845f..27f2bb02 100644 --- a/db/migrate/20160224064748_add_opening_date_to_student_extensions.rb +++ b/db/migrate/20160224064748_add_opening_date_to_student_extensions.rb @@ -1,4 +1,4 @@ -class AddOpeningDateToStudentExtensions < ActiveRecord::Migration +class AddOpeningDateToStudentExtensions < ActiveRecord::Migration[5.1] def change add_column :student_extensions, :opening_date, :datetime end diff --git a/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb b/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb index 9679d91a..08e26ea7 100644 --- a/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb +++ b/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddContinueFromWorkoutIdToWorkoutOfferings < ActiveRecord::Migration +class AddContinueFromWorkoutIdToWorkoutOfferings < ActiveRecord::Migration[5.1] def change change_table :workout_offerings do |t| t.integer :continue_from_workout_id diff --git a/db/migrate/20160512005525_add_keys.rb b/db/migrate/20160512005525_add_keys.rb index 9fba7eab..2cb120ee 100644 --- a/db/migrate/20160512005525_add_keys.rb +++ b/db/migrate/20160512005525_add_keys.rb @@ -1,4 +1,4 @@ -class AddKeys < ActiveRecord::Migration +class AddKeys < ActiveRecord::Migration[5.1] def change add_foreign_key "attempts", "workout_scores", name: "attempts_active_score_id_fk", column: "active_score_id" add_foreign_key "attempts", "exercise_versions", name: "attempts_exercise_version_id_fk" diff --git a/db/migrate/20160525210736_add_lis_info_to_workout_score.rb b/db/migrate/20160525210736_add_lis_info_to_workout_score.rb index a03d3cc0..2bc7ecfc 100644 --- a/db/migrate/20160525210736_add_lis_info_to_workout_score.rb +++ b/db/migrate/20160525210736_add_lis_info_to_workout_score.rb @@ -1,4 +1,4 @@ -class AddLisInfoToWorkoutScore < ActiveRecord::Migration +class AddLisInfoToWorkoutScore < ActiveRecord::Migration[5.1] def change add_column :workout_scores, :lis_outcome_service_url, :string add_column :workout_scores, :lis_result_sourcedid, :string diff --git a/db/migrate/20160602163822_create_lms_instances.rb b/db/migrate/20160602163822_create_lms_instances.rb index e2f70f00..bfe2366c 100644 --- a/db/migrate/20160602163822_create_lms_instances.rb +++ b/db/migrate/20160602163822_create_lms_instances.rb @@ -1,4 +1,4 @@ -class CreateLmsInstances < ActiveRecord::Migration +class CreateLmsInstances < ActiveRecord::Migration[5.1] def change create_table :lms_instances do |t| t.string :consumer_key diff --git a/db/migrate/20160602181823_add_url_to_lms_instance.rb b/db/migrate/20160602181823_add_url_to_lms_instance.rb index 9b3eefaf..808e20a3 100644 --- a/db/migrate/20160602181823_add_url_to_lms_instance.rb +++ b/db/migrate/20160602181823_add_url_to_lms_instance.rb @@ -1,4 +1,4 @@ -class AddUrlToLmsInstance < ActiveRecord::Migration +class AddUrlToLmsInstance < ActiveRecord::Migration[5.1] def change add_column :lms_instances, :url, :string diff --git a/db/migrate/20160602182001_create_lms_types.rb b/db/migrate/20160602182001_create_lms_types.rb index 76466e60..6c89d67c 100644 --- a/db/migrate/20160602182001_create_lms_types.rb +++ b/db/migrate/20160602182001_create_lms_types.rb @@ -1,4 +1,4 @@ -class CreateLmsTypes < ActiveRecord::Migration +class CreateLmsTypes < ActiveRecord::Migration[5.1] def change create_table :lms_types do |t| t.string :name, null: false diff --git a/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb b/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb index d707e7d7..33cecc60 100644 --- a/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb +++ b/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb @@ -1,4 +1,4 @@ -class AddLmsTypeIdToLmsInstance < ActiveRecord::Migration +class AddLmsTypeIdToLmsInstance < ActiveRecord::Migration[5.1] def change add_column :lms_instances, :lms_type_id, :integer add_foreign_key :lms_instances, :lms_types diff --git a/db/migrate/20160602185259_add_lms_instance_to_course_offering.rb b/db/migrate/20160602185259_add_lms_instance_to_course_offering.rb index a545eb18..3c286e74 100644 --- a/db/migrate/20160602185259_add_lms_instance_to_course_offering.rb +++ b/db/migrate/20160602185259_add_lms_instance_to_course_offering.rb @@ -1,4 +1,4 @@ -class AddLmsInstanceToCourseOffering < ActiveRecord::Migration +class AddLmsInstanceToCourseOffering < ActiveRecord::Migration[5.1] def change add_reference :course_offerings, :lms_instance, index: true end diff --git a/db/migrate/20160614153123_add_organization_ref_to_lms_instances.rb b/db/migrate/20160614153123_add_organization_ref_to_lms_instances.rb index 26dccce5..974df39a 100644 --- a/db/migrate/20160614153123_add_organization_ref_to_lms_instances.rb +++ b/db/migrate/20160614153123_add_organization_ref_to_lms_instances.rb @@ -1,4 +1,4 @@ -class AddOrganizationRefToLmsInstances < ActiveRecord::Migration +class AddOrganizationRefToLmsInstances < ActiveRecord::Migration[5.1] def change add_reference :lms_instances, :organization, index: true end diff --git a/db/migrate/20160706143029_add_lms_assignment_id_to_workout_offerings.rb b/db/migrate/20160706143029_add_lms_assignment_id_to_workout_offerings.rb index da283a71..32dc4c69 100644 --- a/db/migrate/20160706143029_add_lms_assignment_id_to_workout_offerings.rb +++ b/db/migrate/20160706143029_add_lms_assignment_id_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddLmsAssignmentIdToWorkoutOfferings < ActiveRecord::Migration +class AddLmsAssignmentIdToWorkoutOfferings < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :lms_assignment_id, :string diff --git a/db/migrate/20160707200651_make_workout_offerings_published_by_default.rb b/db/migrate/20160707200651_make_workout_offerings_published_by_default.rb index d7f72f31..2d41c75a 100644 --- a/db/migrate/20160707200651_make_workout_offerings_published_by_default.rb +++ b/db/migrate/20160707200651_make_workout_offerings_published_by_default.rb @@ -1,4 +1,4 @@ -class MakeWorkoutOfferingsPublishedByDefault < ActiveRecord::Migration +class MakeWorkoutOfferingsPublishedByDefault < ActiveRecord::Migration[5.1] def change change_column :workout_offerings, :published, :boolean, default: true end diff --git a/db/migrate/20160829140059_add_description_to_workout_policies.rb b/db/migrate/20160829140059_add_description_to_workout_policies.rb index 3d5bf37a..fa5defa1 100644 --- a/db/migrate/20160829140059_add_description_to_workout_policies.rb +++ b/db/migrate/20160829140059_add_description_to_workout_policies.rb @@ -1,4 +1,4 @@ -class AddDescriptionToWorkoutPolicies < ActiveRecord::Migration +class AddDescriptionToWorkoutPolicies < ActiveRecord::Migration[5.1] def change add_column :workout_policies, :description, :string end diff --git a/db/migrate/20161129200944_add_most_recent_to_workout_offerings.rb b/db/migrate/20161129200944_add_most_recent_to_workout_offerings.rb index 424efadd..d4787bdf 100644 --- a/db/migrate/20161129200944_add_most_recent_to_workout_offerings.rb +++ b/db/migrate/20161129200944_add_most_recent_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddMostRecentToWorkoutOfferings < ActiveRecord::Migration +class AddMostRecentToWorkoutOfferings < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :most_recent, :boolean, default: true end diff --git a/db/migrate/20170314235543_create_lti_identities.rb b/db/migrate/20170314235543_create_lti_identities.rb index 7e5ee5d3..e45ac145 100644 --- a/db/migrate/20170314235543_create_lti_identities.rb +++ b/db/migrate/20170314235543_create_lti_identities.rb @@ -1,4 +1,4 @@ -class CreateLtiIdentities < ActiveRecord::Migration +class CreateLtiIdentities < ActiveRecord::Migration[5.1] def change create_table :lti_identities do |t| t.string :lti_user_id diff --git a/db/migrate/20170411130659_add_example_hidden_static_ao_n_to_test_cases.rb b/db/migrate/20170411130659_add_example_hidden_static_ao_n_to_test_cases.rb index 08f54a8a..2e74d93c 100644 --- a/db/migrate/20170411130659_add_example_hidden_static_ao_n_to_test_cases.rb +++ b/db/migrate/20170411130659_add_example_hidden_static_ao_n_to_test_cases.rb @@ -1,4 +1,4 @@ -class AddExampleHiddenStaticAoNToTestCases < ActiveRecord::Migration +class AddExampleHiddenStaticAoNToTestCases < ActiveRecord::Migration[5.1] def change add_column :test_cases, :static, :boolean, null: false, default: false add_column :test_cases, :all_or_nothing, :boolean, null: false, default: false diff --git a/db/migrate/20170420144021_add_text_representation_to_exercise_version.rb b/db/migrate/20170420144021_add_text_representation_to_exercise_version.rb index 8dac6301..7cc5e81e 100644 --- a/db/migrate/20170420144021_add_text_representation_to_exercise_version.rb +++ b/db/migrate/20170420144021_add_text_representation_to_exercise_version.rb @@ -1,4 +1,4 @@ -class AddTextRepresentationToExerciseVersion < ActiveRecord::Migration +class AddTextRepresentationToExerciseVersion < ActiveRecord::Migration[5.1] def change add_column :exercise_versions, :text_representation, :text, limit: 16.megabytes - 1 end diff --git a/db/migrate/20170427154810_add_lms_assignment_url_to_workout_offerings.rb b/db/migrate/20170427154810_add_lms_assignment_url_to_workout_offerings.rb index e2c3baf7..cd6b7a8c 100644 --- a/db/migrate/20170427154810_add_lms_assignment_url_to_workout_offerings.rb +++ b/db/migrate/20170427154810_add_lms_assignment_url_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddLmsAssignmentUrlToWorkoutOfferings < ActiveRecord::Migration +class AddLmsAssignmentUrlToWorkoutOfferings < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :lms_assignment_url, :string end diff --git a/db/migrate/20170430212102_rename_all_or_nothing_to_screening_in_test_case.rb b/db/migrate/20170430212102_rename_all_or_nothing_to_screening_in_test_case.rb index 27c04873..5b337d99 100644 --- a/db/migrate/20170430212102_rename_all_or_nothing_to_screening_in_test_case.rb +++ b/db/migrate/20170430212102_rename_all_or_nothing_to_screening_in_test_case.rb @@ -1,4 +1,4 @@ -class RenameAllOrNothingToScreeningInTestCase < ActiveRecord::Migration +class RenameAllOrNothingToScreeningInTestCase < ActiveRecord::Migration[5.1] def change rename_column :test_cases, :all_or_nothing, :screening end diff --git a/db/migrate/20170502163350_create_license_policies.rb b/db/migrate/20170502163350_create_license_policies.rb index 75542c2d..1db45262 100644 --- a/db/migrate/20170502163350_create_license_policies.rb +++ b/db/migrate/20170502163350_create_license_policies.rb @@ -1,4 +1,4 @@ -class CreateLicensePolicies < ActiveRecord::Migration +class CreateLicensePolicies < ActiveRecord::Migration[5.1] def change create_table :license_policies do |t| t.string :name diff --git a/db/migrate/20170502163602_create_licenses.rb b/db/migrate/20170502163602_create_licenses.rb index 49ba84be..9670123c 100644 --- a/db/migrate/20170502163602_create_licenses.rb +++ b/db/migrate/20170502163602_create_licenses.rb @@ -1,4 +1,4 @@ -class CreateLicenses < ActiveRecord::Migration +class CreateLicenses < ActiveRecord::Migration[5.1] def change create_table :licenses do |t| t.string :name diff --git a/db/migrate/20170502165648_create_user_groups.rb b/db/migrate/20170502165648_create_user_groups.rb index b3d61cfd..809f28ce 100644 --- a/db/migrate/20170502165648_create_user_groups.rb +++ b/db/migrate/20170502165648_create_user_groups.rb @@ -1,4 +1,4 @@ -class CreateUserGroups < ActiveRecord::Migration +class CreateUserGroups < ActiveRecord::Migration[5.1] def change create_table :user_groups do |t| t.string :name diff --git a/db/migrate/20170502165837_create_exercise_collections.rb b/db/migrate/20170502165837_create_exercise_collections.rb index b7452439..e8d7c6aa 100644 --- a/db/migrate/20170502165837_create_exercise_collections.rb +++ b/db/migrate/20170502165837_create_exercise_collections.rb @@ -1,4 +1,4 @@ -class CreateExerciseCollections < ActiveRecord::Migration +class CreateExerciseCollections < ActiveRecord::Migration[5.1] def change create_table :exercise_collections do |t| t.string :name diff --git a/db/migrate/20170502171412_add_exercise_collection_to_exercises.rb b/db/migrate/20170502171412_add_exercise_collection_to_exercises.rb index cf30076f..289dc9d8 100644 --- a/db/migrate/20170502171412_add_exercise_collection_to_exercises.rb +++ b/db/migrate/20170502171412_add_exercise_collection_to_exercises.rb @@ -1,4 +1,4 @@ -class AddExerciseCollectionToExercises < ActiveRecord::Migration +class AddExerciseCollectionToExercises < ActiveRecord::Migration[5.1] def change add_reference :exercises, :exercise_collection, index: true, foreign_key: true end diff --git a/db/migrate/20170502204756_create_memberships.rb b/db/migrate/20170502204756_create_memberships.rb index 9e9d9814..56a067e5 100644 --- a/db/migrate/20170502204756_create_memberships.rb +++ b/db/migrate/20170502204756_create_memberships.rb @@ -1,4 +1,4 @@ -class CreateMemberships < ActiveRecord::Migration +class CreateMemberships < ActiveRecord::Migration[5.1] def change create_table :memberships do |t| t.integer :user_id diff --git a/db/migrate/20170503154537_add_user_to_exercise_collection.rb b/db/migrate/20170503154537_add_user_to_exercise_collection.rb index a3568bd9..0df94577 100644 --- a/db/migrate/20170503154537_add_user_to_exercise_collection.rb +++ b/db/migrate/20170503154537_add_user_to_exercise_collection.rb @@ -1,4 +1,4 @@ -class AddUserToExerciseCollection < ActiveRecord::Migration +class AddUserToExerciseCollection < ActiveRecord::Migration[5.1] def change add_reference :exercise_collections, :user, index: true, foreign_key: true end diff --git a/db/migrate/20170509003343_add_hide_examples_to_coding_prompt.rb b/db/migrate/20170509003343_add_hide_examples_to_coding_prompt.rb index 459e3034..d36eee5c 100644 --- a/db/migrate/20170509003343_add_hide_examples_to_coding_prompt.rb +++ b/db/migrate/20170509003343_add_hide_examples_to_coding_prompt.rb @@ -1,4 +1,4 @@ -class AddHideExamplesToCodingPrompt < ActiveRecord::Migration +class AddHideExamplesToCodingPrompt < ActiveRecord::Migration[5.1] def change add_column :coding_prompts, :hide_examples, :boolean, null: false, default: false end diff --git a/db/migrate/20170518161546_add_user_group_to_course.rb b/db/migrate/20170518161546_add_user_group_to_course.rb index 3d202fb5..42e67b33 100644 --- a/db/migrate/20170518161546_add_user_group_to_course.rb +++ b/db/migrate/20170518161546_add_user_group_to_course.rb @@ -1,4 +1,4 @@ -class AddUserGroupToCourse < ActiveRecord::Migration +class AddUserGroupToCourse < ActiveRecord::Migration[5.1] def change add_reference :courses, :user_group, index: true, foreign_key: true end diff --git a/db/migrate/20170518161825_add_course_offering_to_exercise_collection.rb b/db/migrate/20170518161825_add_course_offering_to_exercise_collection.rb index 5d6e3006..e2f92292 100644 --- a/db/migrate/20170518161825_add_course_offering_to_exercise_collection.rb +++ b/db/migrate/20170518161825_add_course_offering_to_exercise_collection.rb @@ -1,4 +1,4 @@ -class AddCourseOfferingToExerciseCollection < ActiveRecord::Migration +class AddCourseOfferingToExerciseCollection < ActiveRecord::Migration[5.1] def change add_reference :exercise_collections, :course_offering, index: true, foreign_key: true end diff --git a/db/migrate/20170530210400_add_description_to_user_groups.rb b/db/migrate/20170530210400_add_description_to_user_groups.rb index fcc69e43..1a71678e 100644 --- a/db/migrate/20170530210400_add_description_to_user_groups.rb +++ b/db/migrate/20170530210400_add_description_to_user_groups.rb @@ -1,4 +1,4 @@ -class AddDescriptionToUserGroups < ActiveRecord::Migration +class AddDescriptionToUserGroups < ActiveRecord::Migration[5.1] def change add_column :user_groups, :description, :text end diff --git a/db/migrate/20170531150805_create_group_access_requests.rb b/db/migrate/20170531150805_create_group_access_requests.rb index d9118bfa..b32e9ff8 100644 --- a/db/migrate/20170531150805_create_group_access_requests.rb +++ b/db/migrate/20170531150805_create_group_access_requests.rb @@ -1,4 +1,4 @@ -class CreateGroupAccessRequests < ActiveRecord::Migration +class CreateGroupAccessRequests < ActiveRecord::Migration[5.1] def change create_table :group_access_requests do |t| t.references :user, index: true, foreign_key: true diff --git a/db/migrate/20170810205035_add_feedback_line_to_test_case_results.rb b/db/migrate/20170810205035_add_feedback_line_to_test_case_results.rb index 187fb1bd..6088d47b 100644 --- a/db/migrate/20170810205035_add_feedback_line_to_test_case_results.rb +++ b/db/migrate/20170810205035_add_feedback_line_to_test_case_results.rb @@ -1,4 +1,4 @@ -class AddFeedbackLineToTestCaseResults < ActiveRecord::Migration +class AddFeedbackLineToTestCaseResults < ActiveRecord::Migration[5.1] def change add_column :test_case_results, :feedback_line_no, :integer, :after => :execution_feedback diff --git a/db/migrate/20170830160216_add_is_hidden_to_course.rb b/db/migrate/20170830160216_add_is_hidden_to_course.rb index 6fcec9ed..8c921836 100644 --- a/db/migrate/20170830160216_add_is_hidden_to_course.rb +++ b/db/migrate/20170830160216_add_is_hidden_to_course.rb @@ -1,4 +1,4 @@ -class AddIsHiddenToCourse < ActiveRecord::Migration +class AddIsHiddenToCourse < ActiveRecord::Migration[5.1] def change add_column :courses, :is_hidden, :boolean, default: false end diff --git a/db/migrate/20170830164617_add_is_hidden_to_organization.rb b/db/migrate/20170830164617_add_is_hidden_to_organization.rb index 48bdf035..1b39f9e7 100644 --- a/db/migrate/20170830164617_add_is_hidden_to_organization.rb +++ b/db/migrate/20170830164617_add_is_hidden_to_organization.rb @@ -1,4 +1,4 @@ -class AddIsHiddenToOrganization < ActiveRecord::Migration +class AddIsHiddenToOrganization < ActiveRecord::Migration[5.1] def change add_column :organizations, :is_hidden, :boolean, default: false end diff --git a/db/migrate/20170920191837_remove_lms_assignment_id_index_from_workout_offerings.rb b/db/migrate/20170920191837_remove_lms_assignment_id_index_from_workout_offerings.rb index 0d9f13ac..2e5e899e 100644 --- a/db/migrate/20170920191837_remove_lms_assignment_id_index_from_workout_offerings.rb +++ b/db/migrate/20170920191837_remove_lms_assignment_id_index_from_workout_offerings.rb @@ -1,4 +1,4 @@ -class RemoveLmsAssignmentIdIndexFromWorkoutOfferings < ActiveRecord::Migration +class RemoveLmsAssignmentIdIndexFromWorkoutOfferings < ActiveRecord::Migration[5.1] def change remove_index :workout_offerings, :lms_assignment_id # remove it to get rid of unique requirement diff --git a/db/migrate/20180718193136_create_visualization_loggings.rb b/db/migrate/20180718193136_create_visualization_loggings.rb index 58a5061c..4824e465 100644 --- a/db/migrate/20180718193136_create_visualization_loggings.rb +++ b/db/migrate/20180718193136_create_visualization_loggings.rb @@ -1,4 +1,4 @@ -class CreateVisualizationLoggings < ActiveRecord::Migration +class CreateVisualizationLoggings < ActiveRecord::Migration[5.1] def change create_table :visualization_loggings do |t| t.references :user, index: true diff --git a/db/migrate/20190131151757_add_attempt_limit_to_workout_offerings.rb b/db/migrate/20190131151757_add_attempt_limit_to_workout_offerings.rb index 4c2339b5..f3a1363a 100644 --- a/db/migrate/20190131151757_add_attempt_limit_to_workout_offerings.rb +++ b/db/migrate/20190131151757_add_attempt_limit_to_workout_offerings.rb @@ -1,4 +1,4 @@ -class AddAttemptLimitToWorkoutOfferings < ActiveRecord::Migration +class AddAttemptLimitToWorkoutOfferings < ActiveRecord::Migration[5.1] def change add_column :workout_offerings, :attempt_limit, :integer, default: nil end diff --git a/db/migrate/20190416161435_add_feedback_times_to_attempt.rb b/db/migrate/20190416161435_add_feedback_times_to_attempt.rb index dbfbc825..2133e42d 100644 --- a/db/migrate/20190416161435_add_feedback_times_to_attempt.rb +++ b/db/migrate/20190416161435_add_feedback_times_to_attempt.rb @@ -1,4 +1,4 @@ -class AddFeedbackTimesToAttempt < ActiveRecord::Migration +class AddFeedbackTimesToAttempt < ActiveRecord::Migration[5.1] def change add_column :attempts, :time_taken, :decimal add_column :attempts, :feedback_timeout, :decimal diff --git a/db/migrate/20190422155326_create_lti_workouts.rb b/db/migrate/20190422155326_create_lti_workouts.rb index 1aaaf6be..a81fd1c6 100644 --- a/db/migrate/20190422155326_create_lti_workouts.rb +++ b/db/migrate/20190422155326_create_lti_workouts.rb @@ -1,4 +1,4 @@ -class CreateLtiWorkouts < ActiveRecord::Migration +class CreateLtiWorkouts < ActiveRecord::Migration[5.1] def change create_table :lti_workouts do |t| t.references :workout, index: true diff --git a/db/migrate/20190712171251_tie_workout_scores_with_lms_instance.rb b/db/migrate/20190712171251_tie_workout_scores_with_lms_instance.rb index 5d964815..f926e721 100644 --- a/db/migrate/20190712171251_tie_workout_scores_with_lms_instance.rb +++ b/db/migrate/20190712171251_tie_workout_scores_with_lms_instance.rb @@ -1,4 +1,4 @@ -class TieWorkoutScoresWithLmsInstance < ActiveRecord::Migration +class TieWorkoutScoresWithLmsInstance < ActiveRecord::Migration[5.1] def change add_reference :lti_workouts, :lms_instance, index: true, foreign_key: true add_reference :workout_scores, :lti_workout, index: true, foreign_key: true From b35161cf38e59f9a346070ee97fefaee00244051 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 22 Oct 2020 03:42:30 -0400 Subject: [PATCH 021/117] update Gemfile.lock Signed-off-by: Alex Hicks --- Gemfile.lock | 183 ++++++++++++++++++++++++++------------------------- 1 file changed, 93 insertions(+), 90 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7225552c..fb81cae4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,7 +6,7 @@ GIT GIT remote: https://github.com/seuros/capistrano-puma.git - revision: 87bfd747bd43a19b5d214e0272b76fb723cf1f5f + revision: b148515f78476b68ab8e09bcc494e82ceb53eba0 specs: capistrano3-puma (4.0.0) capistrano (~> 3.7) @@ -26,66 +26,66 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.3) - actionpack (= 5.2.4.3) + actioncable (5.2.4.4) + actionpack (= 5.2.4.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.3) - actionpack (= 5.2.4.3) - actionview (= 5.2.4.3) - activejob (= 5.2.4.3) + actionmailer (5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.3) - actionview (= 5.2.4.3) - activesupport (= 5.2.4.3) + actionpack (5.2.4.4) + actionview (= 5.2.4.4) + activesupport (= 5.2.4.4) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.3) - activesupport (= 5.2.4.3) + actionview (5.2.4.4) + activesupport (= 5.2.4.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_record-acts_as (4.0.1) + active_record-acts_as (4.0.3) activerecord (>= 4.2) activesupport (>= 4.2) active_record_union (1.3.0) activerecord (>= 4.0) - activeadmin (2.7.0) + activeadmin (2.8.1) arbre (~> 1.2, >= 1.2.1) - formtastic (~> 3.1) + formtastic (>= 3.1, < 5.0) formtastic_i18n (~> 0.4) inherited_resources (~> 1.7) jquery-rails (~> 4.2) - kaminari (~> 1.0, >= 1.0.1) + kaminari (~> 1.0, >= 1.2.1) railties (>= 5.2, < 6.1) ransack (~> 2.1, >= 2.1.1) sassc-rails (~> 2.1) sprockets (>= 3.0, < 4.1) - activejob (5.2.4.3) - activesupport (= 5.2.4.3) + activejob (5.2.4.4) + activesupport (= 5.2.4.4) globalid (>= 0.3.6) - activemodel (5.2.4.3) - activesupport (= 5.2.4.3) - activerecord (5.2.4.3) - activemodel (= 5.2.4.3) - activesupport (= 5.2.4.3) + activemodel (5.2.4.4) + activesupport (= 5.2.4.4) + activerecord (5.2.4.4) + activemodel (= 5.2.4.4) + activesupport (= 5.2.4.4) arel (>= 9.0) - activestorage (5.2.4.3) - actionpack (= 5.2.4.3) - activerecord (= 5.2.4.3) + activestorage (5.2.4.4) + actionpack (= 5.2.4.4) + activerecord (= 5.2.4.4) marcel (~> 0.3.1) - activesupport (5.2.4.3) + activesupport (5.2.4.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) acts-as-taggable-on (6.5.0) activerecord (>= 5.0, < 6.1) - acts_as_list (1.0.1) + acts_as_list (1.0.2) activerecord (>= 4.2) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) @@ -94,14 +94,15 @@ GEM annotate (3.1.1) activerecord (>= 3.2, < 7.0) rake (>= 10.4, < 14.0) - arbre (1.2.1) - activesupport (>= 3.0.0) + arbre (1.3.0) + activesupport (>= 3.0.0, < 6.1) + ruby2_keywords (>= 0.0.2, < 1.0) arel (9.0.0) - autoprefixer-rails (9.7.6) + autoprefixer-rails (10.0.1.0) execjs awesome_print (1.8.0) - bcrypt (3.1.13) - bootsnap (1.4.6) + bcrypt (3.1.16) + bootsnap (1.4.8) msgpack (~> 1.0) bootstrap-editable-rails (0.0.9) railties (>= 3.1) @@ -115,16 +116,16 @@ GEM builder (3.2.4) byebug (11.1.3) cancancan (3.1.0) - capistrano (3.14.0) + capistrano (3.14.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.6.0) + capistrano-bundler (2.0.1) capistrano (~> 3.1) - capistrano-rails (1.5.0) + capistrano-rails (1.6.1) capistrano (~> 3.1) - capistrano-bundler (~> 1.1) + capistrano-bundler (>= 1.1, < 3) capistrano-rvm (0.1.2) capistrano (~> 3.0) sshkit (~> 1.2) @@ -154,7 +155,7 @@ GEM celluloid-supervision (0.20.6) timers (>= 4.1.1) choice (0.2.0) - cocoon (1.2.14) + cocoon (1.2.15) codemirror-rails (5.16.0) railties (>= 3.0, < 6.0) coderay (1.1.3) @@ -165,21 +166,21 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.6) + concurrent-ruby (1.1.7) crass (1.0.6) css3-progress-bar-rails (0.5.1) rails (>= 3.1) - csv_shaper (1.3.1) + csv_shaper (1.3.2) activesupport (>= 3.0.0) daemons (1.3.1) - declarative (0.0.10) - devise (4.7.1) + declarative (0.0.20) + devise (4.7.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.3) + diff-lcs (1.4.4) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) erubi (1.9.0) @@ -187,16 +188,17 @@ GEM eventmachine (1.2.7) exception_handler (0.3.45) execjs (2.7.0) - factory_bot (5.2.0) - activesupport (>= 4.2.0) - factory_bot_rails (5.2.0) - factory_bot (~> 5.2.0) - railties (>= 4.2.0) - faker (2.12.0) + factory_bot (6.1.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.1.0) + factory_bot (~> 6.1.0) + railties (>= 5.0.0) + faker (2.14.0) i18n (>= 1.6, < 2) - faraday (1.0.1) + faraday (1.1.0) multipart-post (>= 1.2, < 3) - ffi (1.13.0) + ruby2_keywords + ffi (1.13.1) font-awesome-rails (4.7.0.5) railties (>= 3.2, < 6.1) formtastic (3.1.5) @@ -204,12 +206,12 @@ GEM formtastic-bootstrap (3.1.1) formtastic (>= 3.0) formtastic_i18n (0.6.0) - friendly_id (5.3.0) + friendly_id (5.4.0) activerecord (>= 4.0.0) globalid (0.4.2) activesupport (>= 4.2.0) gravtastic (3.2.6) - haml (5.1.2) + haml (5.2.0) temple (>= 0.8.0) tilt haml-rails (2.0.1) @@ -230,7 +232,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (1.8.3) + i18n (1.8.5) concurrent-ruby (~> 1.0) immigrant (0.3.6) activerecord (>= 3.0) @@ -251,7 +253,7 @@ GEM js-routes (1.4.9) railties (>= 4) sprockets-rails - jwt (2.2.1) + jwt (2.2.2) kaminari (1.2.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.1) @@ -268,7 +270,7 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) log_file (0.0.3) - loofah (2.6.0) + loofah (2.7.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -282,21 +284,21 @@ GEM mimemagic (0.3.5) mini_mime (1.0.2) mini_portile2 (2.4.0) - minitest (5.14.1) + minitest (5.14.2) modernizr-rails (2.7.1) momentjs-rails (2.20.1) railties (>= 3.1) msgpack (1.3.3) - multi_json (1.14.1) + multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) mysql2 (0.5.3) net-scp (3.0.0) net-ssh (>= 2.6.5, < 7.0.0) - net-ssh (6.0.2) + net-ssh (6.1.0) netrc (0.11.0) - nio4r (2.5.2) - nokogiri (1.10.9) + nio4r (2.5.4) + nokogiri (1.10.10) mini_portile2 (~> 2.4.0) oauth (0.5.4) oauth2 (1.4.4) @@ -314,14 +316,14 @@ GEM addressable (~> 2.3) nokogiri (~> 1.5) omniauth (~> 1.2) - omniauth-facebook (6.0.0) + omniauth-facebook (8.0.0) omniauth-oauth2 (~> 1.2) omniauth-google-oauth2 (0.8.0) jwt (>= 2.0) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.6) - omniauth-oauth2 (1.6.0) - oauth2 (~> 1.1) + omniauth-oauth2 (1.7.0) + oauth2 (~> 1.4) omniauth (~> 1.9) orm_adapter (0.5.0) polyamorous (2.3.2) @@ -330,35 +332,35 @@ GEM pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (4.0.5) - puma (4.3.5) + public_suffix (4.0.6) + puma (4.3.6) nio4r (~> 2.0) rabl (0.14.3) activesupport (>= 2.3.14) - rack (2.2.2) + rack (2.2.3) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.4.3) - actioncable (= 5.2.4.3) - actionmailer (= 5.2.4.3) - actionpack (= 5.2.4.3) - actionview (= 5.2.4.3) - activejob (= 5.2.4.3) - activemodel (= 5.2.4.3) - activerecord (= 5.2.4.3) - activestorage (= 5.2.4.3) - activesupport (= 5.2.4.3) + rails (5.2.4.4) + actioncable (= 5.2.4.4) + actionmailer (= 5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) + activemodel (= 5.2.4.4) + activerecord (= 5.2.4.4) + activestorage (= 5.2.4.4) + activesupport (= 5.2.4.4) bundler (>= 1.3.0) - railties (= 5.2.4.3) + railties (= 5.2.4.4) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (5.2.4.3) - actionpack (= 5.2.4.3) - activesupport (= 5.2.4.3) + railties (5.2.4.4) + actionpack (= 5.2.4.4) + activesupport (= 5.2.4.4) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -373,7 +375,7 @@ GEM ffi (~> 1.0) rdoc (6.2.1) redcarpet (3.5.0) - regexp_parser (1.7.1) + regexp_parser (1.8.2) remotipart (1.4.4) representable (2.4.1) declarative (~> 0.0.5) @@ -388,7 +390,7 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.4) - rspec-core (3.9.2) + rspec-core (3.9.3) rspec-support (~> 3.9.3) rspec-expectations (3.9.2) diff-lcs (>= 1.2.0, < 2.0) @@ -407,7 +409,8 @@ GEM rspec-support (3.9.3) ruby-graphviz (1.2.5) rexml - ruby_parser (3.14.2) + ruby2_keywords (0.0.2) + ruby_parser (3.15.0) sexp_processor (~> 4.9) rubyzip (2.3.0) sass (3.7.4) @@ -427,11 +430,11 @@ GEM tilt sdoc (1.1.0) rdoc (>= 5.0) - sexp_processor (4.14.1) + sexp_processor (4.15.1) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -451,7 +454,7 @@ GEM thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) - timers (4.3.0) + timers (4.3.2) truncate_html (0.9.3) tzinfo (1.2.7) thread_safe (~> 0.1) @@ -462,9 +465,9 @@ GEM unf_ext unf_ext (0.0.7.7) wannabe_bool (0.7.1) - warden (1.2.8) - rack (>= 2.0.6) - websocket-driver (0.7.2) + warden (1.2.9) + rack (>= 2.0.9) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) From c2ac77107d6ccf818b242367cee1a886ab1543ee Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 22 Oct 2020 03:42:56 -0400 Subject: [PATCH 022/117] update Dockerfile Signed-off-by: Alex Hicks --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3785f28f..2edd4673 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,8 @@ RUN apt-get update -qq \ python-feedvalidator \ python-sphinx \ ant \ + default-jre \ + default-jdk \ && pip install --upgrade pip # install rubygems @@ -72,7 +74,4 @@ RUN find ./runservers.sh -type f -exec chmod +x {} \; EXPOSE 80 -## JAVA INSTALLATION -RUN apt-get install -y default-jre default-jdk - CMD ["bash", "./runservers.sh"] From 9afd3d33c61e7819c214e7c849a02e3f81d4d8e1 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 22 Oct 2020 03:43:17 -0400 Subject: [PATCH 023/117] change runservers for dev Signed-off-by: Alex Hicks --- runservers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runservers.sh b/runservers.sh index ad5ebf1b..0b584218 100755 --- a/runservers.sh +++ b/runservers.sh @@ -1 +1 @@ -RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start --ssl --ssl-key-file server.key --ssl-cert-file server.crt -p 9292 --debug +RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start -p 9292 --debug \ No newline at end of file From 01ed81a7a68eea75a8deb43d81dfbabfea8294f4 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 12 Nov 2020 02:29:12 -0500 Subject: [PATCH 024/117] updated exception_handler and rails 5.1 Signed-off-by: Alex Hicks --- Gemfile | 2 +- Gemfile.lock | 7 +++++-- config/application.rb | 3 ++- config/initializers/exception_handler.rb | 8 -------- 4 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 config/initializers/exception_handler.rb diff --git a/Gemfile b/Gemfile index 95768648..ea753c6e 100644 --- a/Gemfile +++ b/Gemfile @@ -82,7 +82,7 @@ gem 'omniauth-google-oauth2' gem 'omniauth-cas' gem 'cancancan' gem 'activeadmin' -gem 'exception_handler', '= 0.3.45' +gem 'exception_handler', '~> 0.8.0.0' gem 'kaminari', '~> 1.2.1' # Auto-paginated views gem 'remotipart' # Adds support for remote mulitpart forms (file uploads) diff --git a/Gemfile.lock b/Gemfile.lock index fb81cae4..c5dd81d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,7 +186,10 @@ GEM erubi (1.9.0) erubis (2.7.0) eventmachine (1.2.7) - exception_handler (0.3.45) + exception_handler (0.8.0.0) + bundler + rails (>= 4.2.0) + responders execjs (2.7.0) factory_bot (6.1.0) activesupport (>= 5.0.0) @@ -506,7 +509,7 @@ DEPENDENCIES css3-progress-bar-rails csv_shaper devise - exception_handler (= 0.3.45) + exception_handler (~> 0.8.0.0) factory_bot_rails faker font-awesome-rails diff --git a/config/application.rb b/config/application.rb index 2ff74cf1..9dad7b3a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -17,7 +17,8 @@ class Application < Rails::Application # exception_handler configuration (no longer relies on an initializer) config.exception_handler = { - db: true + db: true, + dev: true } # Timeout for feedback polls (milliseconds) diff --git a/config/initializers/exception_handler.rb b/config/initializers/exception_handler.rb deleted file mode 100644 index a3b7c540..00000000 --- a/config/initializers/exception_handler.rb +++ /dev/null @@ -1,8 +0,0 @@ -# Exception Handler -### -# You can add different settings using this block -# Use the docs at http://github.com/richpeck/exception_handler for info -### -ExceptionHandler.setup do |config| - config.db = true -end From 2e0f4899ec425ff6d187c984e848c31f8f143117 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 12 Nov 2020 05:59:49 -0500 Subject: [PATCH 025/117] added ssl for testing Signed-off-by: Alex Hicks --- runservers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runservers.sh b/runservers.sh index 0b584218..2a736353 100755 --- a/runservers.sh +++ b/runservers.sh @@ -1 +1 @@ -RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start -p 9292 --debug \ No newline at end of file +RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start --ssl --ssl-key-file server.key --ssl-cert-file server.crt -p 9292 --debug \ No newline at end of file From 9b71517089eb6dacca751bbbe097fde69f09a7bd Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 12 Nov 2020 11:22:26 -0500 Subject: [PATCH 026/117] removed ssl Signed-off-by: Alex Hicks --- runservers.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runservers.sh b/runservers.sh index 2a736353..0b584218 100755 --- a/runservers.sh +++ b/runservers.sh @@ -1 +1 @@ -RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start --ssl --ssl-key-file server.key --ssl-cert-file server.crt -p 9292 --debug \ No newline at end of file +RUBYOPT='-W:no-deprecated' RAILS_ENV=development bundle exec thin start -p 9292 --debug \ No newline at end of file From aeb7de45c3971dbf1185e31939dcaa1df99f4033 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Tue, 17 Nov 2020 02:24:29 -0500 Subject: [PATCH 027/117] update gemfile Signed-off-by: Alex Hicks --- Gemfile | 3 +-- Gemfile.lock | 46 ++++++++++++++++++++++------------------------ db/schema.rb | 37 ++++++++++++++++++++++--------------- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/Gemfile b/Gemfile index 94c1db03..d94ad4c8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' gem 'rails', '~> 5.1' -gem 'sprockets', '~> 3.7.2' # 4.0 requires ruby 2.5 +gem 'sprockets', '< 4.0.0' gem 'bootstrap-sass', '~> 3.2.0' gem 'jquery-rails' gem 'jquery-ui-rails' @@ -123,4 +123,3 @@ gem 'rest-client' # Gems for cookie updates gem 'user_agent_parser' # 2.6 requires ruby 2.4 gem 'rails_same_site_cookie' -gem 'sprockets', '< 4.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 46654a4e..434c7c3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,12 +6,12 @@ GIT GIT remote: https://github.com/seuros/capistrano-puma.git - revision: b148515f78476b68ab8e09bcc494e82ceb53eba0 + revision: f8093ad3e68e67390d0ffeb5f50830b1587973f9 specs: - capistrano3-puma (4.0.0) + capistrano3-puma (5.0.0.beta1) capistrano (~> 3.7) capistrano-bundler - puma (~> 4.0) + puma (>= 4.0, < 6.0) GIT remote: https://github.com/voormedia/rails-erd.git @@ -98,11 +98,11 @@ GEM activesupport (>= 3.0.0, < 6.1) ruby2_keywords (>= 0.0.2, < 1.0) arel (9.0.0) - autoprefixer-rails (10.0.1.0) + autoprefixer-rails (10.0.2.0) execjs awesome_print (1.8.0) bcrypt (3.1.16) - bootsnap (1.4.8) + bootsnap (1.5.1) msgpack (~> 1.0) bootstrap-editable-rails (0.0.9) railties (>= 3.1) @@ -209,7 +209,7 @@ GEM formtastic-bootstrap (3.1.1) formtastic (>= 3.0) formtastic_i18n (0.6.0) - friendly_id (5.4.0) + friendly_id (5.4.1) activerecord (>= 4.0.0) globalid (0.4.2) activesupport (>= 4.2.0) @@ -269,7 +269,7 @@ GEM activerecord kaminari-core (= 1.2.1) kaminari-core (1.2.1) - listen (3.2.1) + listen (3.3.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) log_file (0.0.3) @@ -283,7 +283,7 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) + mime-types-data (3.2020.1104) mimemagic (0.3.5) mini_mime (1.0.2) mini_portile2 (2.4.0) @@ -295,7 +295,7 @@ GEM multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) - mysql2 (0.5.3) + mysql2 (0.4.10) net-scp (3.0.0) net-ssh (>= 2.6.5, < 7.0.0) net-ssh (6.1.0) @@ -361,15 +361,15 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) + rails_same_site_cookie (0.1.8) + rack (>= 1.5) + user_agent_parser (~> 2.5) railties (5.2.4.4) actionpack (= 5.2.4.4) activesupport (= 5.2.4.4) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) - rails_same_site_cookie (0.1.8) - rack (>= 1.5) - user_agent_parser (~> 2.5) rake (13.0.1) ransack (2.3.2) activerecord (>= 5.2.1) @@ -396,14 +396,14 @@ GEM mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.2.4) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) + rspec-core (3.10.0) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) + rspec-support (~> 3.10.0) rspec-rails (4.0.1) actionpack (>= 4.2) activesupport (>= 4.2) @@ -412,7 +412,7 @@ GEM rspec-expectations (~> 3.9) rspec-mocks (~> 3.9) rspec-support (~> 3.9) - rspec-support (3.9.3) + rspec-support (3.10.0) ruby-graphviz (1.2.5) rexml ruby2_keywords (0.0.2) @@ -434,7 +434,7 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sdoc (1.1.0) + sdoc (2.0.2) rdoc (>= 5.0) sexp_processor (4.15.1) sprockets (3.7.2) @@ -462,7 +462,7 @@ GEM tilt (2.0.10) timers (4.3.2) truncate_html (0.9.3) - tzinfo (1.2.7) + tzinfo (1.2.8) thread_safe (~> 0.1) uber (0.0.15) uglifier (4.2.0) @@ -470,9 +470,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.7) - unicode-display_width (1.6.0) - unicode_utils (1.4.0) - user_agent_parser (2.5.1) + user_agent_parser (2.7.0) wannabe_bool (0.7.1) warden (1.2.9) rack (>= 2.0.9) diff --git a/db/schema.rb b/db/schema.rb index 1519d7f0..766f469b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_07_12_171251) do +ActiveRecord::Schema.define(version: 2020_09_29_144816) do create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "namespace" @@ -241,6 +241,14 @@ t.index ["is_public"], name: "index_exercises_on_is_public" end + create_table "exercises_tags", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t| + t.bigint "exercise_id" + t.bigint "tag_id" + t.index ["exercise_id", "tag_id"], name: "index_exercises_tags_on_exercise_id_and_tag_id", unique: true + t.index ["exercise_id"], name: "index_exercises_tags_on_exercise_id" + t.index ["tag_id"], name: "index_exercises_tags_on_tag_id" + end + create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "slug", default: "", null: false t.integer "sluggable_id", null: false @@ -288,6 +296,12 @@ t.float "discrimination", null: false end + create_table "languages", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t| + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" @@ -320,10 +334,6 @@ t.index ["url"], name: "index_lms_instances_on_url", unique: true end - add_index "lms_instances", ["lms_type_id"], name: "lms_instances_lms_type_id_fk", using: :btree - add_index "lms_instances", ["organization_id"], name: "index_lms_instances_on_organization_id", using: :btree - add_index "lms_instances", ["url"], name: "index_lms_instances_on_url", unique: true, using: :btree - create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at" @@ -459,20 +469,17 @@ t.string "tagger_type" t.string "context", limit: 128 t.datetime "created_at" + t.index ["context"], name: "index_taggings_on_context" t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true + t.index ["tag_id"], name: "index_taggings_on_tag_id" t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" + t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy" + t.index ["taggable_id"], name: "index_taggings_on_taggable_id" + t.index ["taggable_type"], name: "index_taggings_on_taggable_type" + t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type" + t.index ["tagger_id"], name: "index_taggings_on_tagger_id" end - add_index "taggings", ["context"], name: "index_taggings_on_context", using: :btree - add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree - add_index "taggings", ["tag_id"], name: "index_taggings_on_tag_id", using: :btree - add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree - add_index "taggings", ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy", using: :btree - add_index "taggings", ["taggable_id"], name: "index_taggings_on_taggable_id", using: :btree - add_index "taggings", ["taggable_type"], name: "index_taggings_on_taggable_type", using: :btree - add_index "taggings", ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type", using: :btree - add_index "taggings", ["tagger_id"], name: "index_taggings_on_tagger_id", using: :btree - create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.integer "taggings_count", default: 0 From 256ac4bc81852787644aaa1a24d63752140cc9ce Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Tue, 17 Nov 2020 02:39:27 -0500 Subject: [PATCH 028/117] update schema.rb --- db/schema.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 1cf86be3..766f469b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -336,7 +336,6 @@ create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false - t.datetime "created_at" t.datetime "updated_at" t.index ["name"], name: "index_lms_types_on_name", unique: true @@ -355,7 +354,6 @@ create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "workout_id" t.string "lms_assignment_id", null: false - t.datetime "created_at" t.datetime "updated_at" t.integer "lms_instance_id" From 6143893d9113dc245133a5b7082355e66f8044f9 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 29 Jul 2021 10:27:54 -0400 Subject: [PATCH 029/117] bundle updates for yanked gem Signed-off-by: Alex Hicks --- Gemfile.lock | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 434c7c3c..4fc688e1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -284,7 +284,9 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2020.1104) - mimemagic (0.3.5) + mimemagic (0.3.10) + nokogiri (~> 1) + rake mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.2) From bbb909f06380bd86591bfeceba8e93da27b92d9d Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Thu, 29 Jul 2021 15:09:47 -0400 Subject: [PATCH 030/117] dockerfile updates Signed-off-by: Alex Hicks --- Dockerfile | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2edd4673..9d655d27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ MAINTAINER Jihane Najdi # Default environment ARG RAILS_ENV='development' -# Ruby changed the way optional params are done, but Rails hasn't caught up +# Ruby changed the way optional params are done, but Rails hasn't caught up ARG RUBYOPT='-W:no-deprecated' ARG BASEDIR='/code-workout/' @@ -47,15 +47,13 @@ RUN apt-get update -qq \ && pip install --upgrade pip # install rubygems -ENV GEM_HOME /usr/local/bundle -ENV PATH $GEM_HOME/bin:$PATH ENV BUNDLER_VERSION 2.1.4 ENV RAILS_ENV=$RAILS_ENV -RUN gem install bundler -v $BUNDLER_VERSION \ - && bundle config --global path "$GEM_HOME" \ - && bundle config --global bin "$GEM_HOME/bin" \ - && bundle config git.allow_insecure true +RUN gem install bundler -v $BUNDLER_VERSION #\ +# && bundle config --global path "$GEM_HOME" \ +# && bundle config --global bin "$GEM_HOME/bin" \ +# && bundle config git.allow_insecure true VOLUME ${BASEDIR} WORKDIR ${BASEDIR} @@ -63,8 +61,9 @@ WORKDIR ${BASEDIR} COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock -RUN bundle update -RUN bundle check || bundle install +#RUN bundle update +#RUN bundle check || bundle install +RUN bundle install COPY runservers.sh runservers.sh From 36fda2eac26225aee5d5beeb0860d0c7d4bedd94 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Fri, 30 Jul 2021 09:38:43 -0400 Subject: [PATCH 031/117] fix merge issues Signed-off-by: Alex Hicks --- Dockerfile | 9 +++------ Gemfile.lock | 11 ++--------- config/application.rb | 2 -- db/schema.rb | 14 +------------- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index ad7fccd3..29bcd94e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ MAINTAINER Jihane Najdi ARG RAILS_ENV='development' # Ruby changed the way optional params are done, but Rails hasn't caught up ARG RUBYOPT='-W:no-deprecated' -ARG BASEDIR='/code-workout/' +ARG BASEDIR='/code-workout' ENV TZ=America/New_York RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone @@ -53,10 +53,7 @@ RUN apt-get install -y default-jre default-jdk ENV BUNDLER_VERSION 2.1.4 ENV RAILS_ENV=$RAILS_ENV -RUN gem install bundler -v $BUNDLER_VERSION #\ -# && bundle config --global path "$GEM_HOME" \ -# && bundle config --global bin "$GEM_HOME/bin" \ -# && bundle config git.allow_insecure true +RUN gem install bundler -v $BUNDLER_VERSION COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock @@ -65,7 +62,7 @@ COPY Gemfile.lock Gemfile.lock #RUN bundle check || bundle install RUN bundle install -VOLUME ${BASEDIR} +#VOLUME ${BASEDIR} WORKDIR ${BASEDIR} COPY runservers.sh runservers.sh diff --git a/Gemfile.lock b/Gemfile.lock index 66072746..12c5ccc6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -190,7 +190,6 @@ GEM domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) ed25519 (1.2.4) - equatable (0.6.1) erubi (1.9.0) erubis (2.7.0) eventmachine (1.2.7) @@ -297,6 +296,7 @@ GEM mimemagic (0.3.10) nokogiri (~> 1) rake + mini_magick (4.11.0) mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.14.2) @@ -461,11 +461,6 @@ GEM net-scp (>= 1.1.2) net-ssh (>= 2.8.0) ssrf_filter (1.0.7) - strings (0.1.5) - strings-ansi (~> 0.1) - unicode-display_width (~> 1.5) - unicode_utils (~> 1.4) - strings-ansi (0.1.0) sucker_punch (1.6.0) celluloid (~> 0.17.2) temple (0.8.2) @@ -489,8 +484,6 @@ GEM unf_ext unf_ext (0.0.7.7) user_agent_parser (2.7.0) - unicode-display_width (1.6.0) - unicode_utils (1.4.0) wannabe_bool (0.7.1) warden (1.2.9) rack (>= 2.0.9) @@ -535,8 +528,8 @@ DEPENDENCIES css3-progress-bar-rails csv_shaper devise - exception_handler (~> 0.8.0.0) ed25519 + exception_handler (~> 0.8.0.0) factory_bot_rails faker font-awesome-rails diff --git a/config/application.rb b/config/application.rb index ce5a41fb..4d058501 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,8 +26,6 @@ class Application < Rails::Application config.feedback_timeout = 1700 config.feedback_timeout_padding = 300 - config.active_record.raise_in_transactional_callbacks = true - # Allow iframe embedding? config.action_dispatch.default_headers.merge!( {'X-Frame-Options' => 'ALLOWALL'}) diff --git a/db/schema.rb b/db/schema.rb index e945124e..7e4b9404 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -334,10 +334,6 @@ t.index ["url"], name: "index_lms_instances_on_url", unique: true end - add_index "lms_instances", ["lms_type_id"], name: "lms_instances_lms_type_id_fk", using: :btree - add_index "lms_instances", ["organization_id"], name: "index_lms_instances_on_organization_id", using: :btree - add_index "lms_instances", ["url"], name: "index_lms_instances_on_url", unique: true, using: :btree - create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at" @@ -355,9 +351,6 @@ t.index ["user_id"], name: "index_lti_identities_on_user_id" end - add_index "lti_identities", ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id", using: :btree - add_index "lti_identities", ["user_id"], name: "index_lti_identities_on_user_id", using: :btree - create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "workout_id" t.string "lms_assignment_id", null: false @@ -393,8 +386,6 @@ t.index ["slug"], name: "index_organizations_on_slug", unique: true end - add_index "organizations", ["slug"], name: "index_organizations_on_slug", unique: true, using: :btree - create_table "ownerships", force: :cascade do |t| t.string "filename", limit: 255 t.integer "resource_file_id", limit: 4 @@ -443,12 +434,9 @@ t.string "hashval", limit: 255 t.index ["token"], name: "index_resource_files_on_token" t.index ["user_id"], name: "index_resource_files_on_user_id" + t.index ["hashval"], name: "index_resource_files_on_hashval" end - add_index "resource_files", ["hashval"], name: "index_resource_files_on_hashval", using: :btree - add_index "resource_files", ["token"], name: "index_resource_files_on_token", using: :btree - add_index "resource_files", ["user_id"], name: "index_resource_files_on_user_id", using: :btree - create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "first_name" t.string "last_name_name" From d8ce07af36fab7b8cb97629cb9b79886e1320a95 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Fri, 30 Jul 2021 15:35:15 -0400 Subject: [PATCH 032/117] update schema and remove broken parameter Signed-off-by: Alex Hicks --- app/views/sse/_ajax_feedback.html.haml | 4 ++-- app/views/sse/feedback_update.js.coffee | 2 +- db/schema.rb | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/sse/_ajax_feedback.html.haml b/app/views/sse/_ajax_feedback.html.haml index 44ca3973..54a15721 100644 --- a/app/views/sse/_ajax_feedback.html.haml +++ b/app/views/sse/_ajax_feedback.html.haml @@ -50,7 +50,7 @@ %tbody - has_hidden = false - hidden_pass = true - - answer.test_case_results(true).each do |tcr| + - answer.test_case_results().each do |tcr| - if tcr.test_case.hidden? - has_hidden = true - hidden_pass = hidden_pass && tcr.pass @@ -85,7 +85,7 @@ %th.text-center Result %tbody - if examples.any? - - answer.test_case_results(true).each do |tcr| + - answer.test_case_results().each do |tcr| - if tcr.test_case.is_example? %tr %td diff --git a/app/views/sse/feedback_update.js.coffee b/app/views/sse/feedback_update.js.coffee index 481933cf..d327cc77 100644 --- a/app/views/sse/feedback_update.js.coffee +++ b/app/views/sse/feedback_update.js.coffee @@ -27,7 +27,7 @@ if attempt_error_line_no addLineWidget(editor, '<%= @attempt.prompt_answers.first.specific.error %>', attempt_error_line_no, 'error') -tcrs = <%= raw @attempt.prompt_answers.first.specific.test_case_results(true). +tcrs = <%= raw @attempt.prompt_answers.first.specific.test_case_results(). where.not(execution_feedback: :null). select("test_case_id, execution_feedback, feedback_line_no").to_json %>; for tcr in tcrs diff --git a/db/schema.rb b/db/schema.rb index 7e4b9404..60e1b473 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -75,6 +75,7 @@ create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.text "answer" t.text "error" + t.integer "error_line_no" end create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| @@ -516,6 +517,7 @@ t.integer "test_case_id", null: false t.integer "user_id", null: false t.text "execution_feedback" + t.integer "feedback_line_no" t.datetime "created_at" t.datetime "updated_at" t.boolean "pass", null: false From fabdf505f4804e4f4d81c8327c241744565d4371 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Tue, 1 Feb 2022 10:23:30 -0800 Subject: [PATCH 033/117] Remove "try it here" link in notice after exercise is created or updated. The user is already redirected to the exercise practice page, so the link is redundant. --- app/controllers/exercises_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 767aea22..f19e9795 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -447,7 +447,7 @@ def upload_create # Notify user of success success_msgs << - "
  • X#{e.id}: #{e.name} saved, try it #{view_context.link_to 'here', exercise_practice_path(e)}.
  • " + "
  • X#{e.id}: #{e.name} saved
  • " end end From 7ad6d8d88ddf527270f6bc15a50edbadcc01b4e2 Mon Sep 17 00:00:00 2001 From: Ayaan Kazerouni Date: Thu, 3 Feb 2022 10:56:09 -0800 Subject: [PATCH 034/117] Update Dockerfile to place Gemfile at appropriate location Update some gems --- Dockerfile | 12 +- Gemfile.lock | 355 ++++++++++++++++++++++++++------------------------- 2 files changed, 188 insertions(+), 179 deletions(-) diff --git a/Dockerfile b/Dockerfile index eb9b6c79..52e145d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone #install 'development tools' build-essential dkms curl libxslt-dev libpq-dev python-dev python-pip python-feedvalidator python-software-properties python-sphinx libmariadbclient-dev libcurl4-gnutls-dev libevent-dev libffi-dev libssl-dev stunnel4 libsqlite3-dev # libmariadbclient-dev RUN apt-get update -qq \ - && apt-get install -y \ + && apt-get install -y --fix-missing \ apt-utils \ build-essential \ libpq-dev \ @@ -56,14 +56,14 @@ RUN gem install bundler -v $BUNDLER_VERSION \ && bundle config --global bin "$GEM_HOME/bin" \ && bundle config git.allow_insecure true -COPY Gemfile Gemfile -COPY Gemfile.lock Gemfile.lock - -RUN bundle install - VOLUME ${BASEDIR} WORKDIR ${BASEDIR} +COPY Gemfile ${BASEDIR}/Gemfile +COPY Gemfile.lock ${BASEDIR}/Gemfile.lock + +RUN bundle install + COPY runservers.sh runservers.sh RUN find /code-workout -type d -exec chmod 2775 {} \; diff --git a/Gemfile.lock b/Gemfile.lock index 6d7ffafa..380752ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,18 +6,18 @@ GIT GIT remote: git://github.com/seuros/capistrano-puma.git - revision: 6112323390cff15539d947882d72d937622cfdf4 + revision: f9801f6762d152c5d9da18ce726c7561cc4f9a8b specs: - capistrano3-puma (4.0.0) + capistrano3-puma (5.2.0) capistrano (~> 3.7) capistrano-bundler - puma (~> 4.0) + puma (>= 4.0, < 6.0) GIT remote: git://github.com/voormedia/rails-erd.git - revision: 0fbb1cdf2c84b06afd12974baace8d512bb798da + revision: e41243f6d33303bfc9bdc1cff159bb1986cbee22 specs: - rails-erd (1.6.0) + rails-erd (1.6.1) activerecord (>= 4.2) activesupport (>= 4.2) choice (~> 0.2.0) @@ -45,9 +45,9 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.3) - active_record-acts_as (3.1.0) - activerecord (>= 4.2, < 5.2.2.rc1) - activesupport (>= 4.2, < 5.2.2.rc1) + active_record-acts_as (4.0.3) + activerecord (>= 4.2) + activesupport (>= 4.2) active_record_union (1.3.0) activerecord (>= 4.0) activeadmin (1.4.3) @@ -79,22 +79,23 @@ GEM tzinfo (~> 1.1) acts-as-taggable-on (5.0.0) activerecord (>= 4.2.8) - acts_as_list (0.9.19) - activerecord (>= 3.0) - addressable (2.6.0) - public_suffix (>= 2.0.2, < 4.0) - airbrussh (1.3.2) + acts_as_list (1.0.4) + activerecord (>= 4.2) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + airbrussh (1.4.0) sshkit (>= 1.6.1, != 1.7.0) - annotate (2.7.5) + annotate (3.1.1) activerecord (>= 3.2, < 7.0) - rake (>= 10.4, < 13.0) - arbre (1.2.1) - activesupport (>= 3.0.0) + rake (>= 10.4, < 14.0) + arbre (1.4.0) + activesupport (>= 3.0.0, < 6.2) + ruby2_keywords (>= 0.0.2, < 1.0) arel (6.0.4) - autoprefixer-rails (9.6.0) - execjs - awesome_print (1.8.0) - bcrypt (3.1.13) + autoprefixer-rails (10.4.2.0) + execjs (~> 2) + awesome_print (1.9.2) + bcrypt (3.1.16) bcrypt_pbkdf (1.1.0) bootstrap-editable-rails (0.0.9) railties (>= 3.1) @@ -106,28 +107,29 @@ GEM bootstrap3-datetimepicker-rails (4.17.47) momentjs-rails (>= 2.8.1) builder (3.2.4) - byebug (11.0.1) - cancancan (3.0.1) - capistrano (3.11.0) + byebug (11.1.3) + cancancan (3.3.0) + capistrano (3.16.0) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) sshkit (>= 1.9.0) - capistrano-bundler (1.6.0) + capistrano-bundler (2.0.1) capistrano (~> 3.1) - capistrano-rails (1.4.0) + capistrano-rails (1.6.1) capistrano (~> 3.1) - capistrano-bundler (~> 1.1) + capistrano-bundler (>= 1.1, < 3) capistrano-rvm (0.1.2) capistrano (~> 3.0) sshkit (~> 1.2) - capybara (3.15.1) + capybara (3.36.0) addressable + matrix mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.2) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) carrierwave (1.3.2) activemodel (>= 4.0.0) @@ -152,10 +154,10 @@ GEM celluloid-supervision (0.20.6) timers (>= 4.1.1) choice (0.2.0) - cocoon (1.2.14) + cocoon (1.2.15) codemirror-rails (5.16.0) railties (>= 3.0, < 6.0) - coderay (1.1.2) + coderay (1.1.3) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -163,57 +165,72 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.6) + concurrent-ruby (1.1.9) crass (1.0.6) css3-progress-bar-rails (0.5.1) rails (>= 3.1) - csv_shaper (1.3.0) + csv_shaper (1.3.2) activesupport (>= 3.0.0) - daemons (1.3.1) - declarative (0.0.10) - devise (4.7.1) + daemons (1.4.1) + declarative (0.0.20) + devise (4.8.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - diff-lcs (1.3) - domain_name (0.5.20180417) + diff-lcs (1.5.0) + domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - ed25519 (1.2.4) - equatable (0.6.1) + ed25519 (1.3.0) erubis (2.7.0) eventmachine (1.2.7) exception_handler (0.3.45) - execjs (2.7.0) - factory_bot (5.0.2) + execjs (2.8.1) + factory_bot (5.2.0) activesupport (>= 4.2.0) - factory_bot_rails (5.0.2) - factory_bot (~> 5.0.2) + factory_bot_rails (5.2.0) + factory_bot (~> 5.2.0) railties (>= 4.2.0) - faker (1.9.4) - i18n (>= 0.7) - pastel (~> 0.7.2) - thor (~> 0.20.0) - tty-pager (~> 0.12.0) - tty-screen (~> 0.6.5) - tty-tree (~> 0.3.0) - faraday (0.15.4) + faker (2.2.1) + i18n (>= 0.8) + faraday (1.9.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.3) multipart-post (>= 1.2, < 3) - ffi (1.11.1) - font-awesome-rails (4.7.0.5) - railties (>= 3.2, < 6.1) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + ffi (1.15.5) + font-awesome-rails (4.7.0.8) + railties (>= 3.2, < 8.0) formtastic (3.1.5) actionpack (>= 3.2.13) formtastic-bootstrap (3.1.1) formtastic (>= 3.0) - formtastic_i18n (0.6.0) - friendly_id (5.2.5) + formtastic_i18n (0.7.0) + friendly_id (5.4.2) activerecord (>= 4.0.0) globalid (0.4.2) activesupport (>= 4.2.0) gravtastic (3.2.6) - haml (5.1.1) + haml (5.2.2) temple (>= 0.8.0) tilt haml-rails (1.0.0) @@ -225,13 +242,14 @@ GEM has_scope (0.7.2) actionpack (>= 4.1) activesupport (>= 4.1) - hashie (3.6.0) + hashie (5.0.0) html2haml (2.2.0) erubis (~> 2.7.0) haml (>= 4.0, < 6) nokogiri (>= 1.6.0) ruby_parser (~> 3.5) - http-cookie (1.0.3) + http-accept (1.7.0) + http-cookie (1.0.4) domain_name (~> 0.5) i18n (0.9.5) concurrent-ruby (~> 1.0) @@ -247,95 +265,93 @@ GEM has_scope (~> 0.6) railties (>= 4.2, < 5.3) responders - jquery-rails (4.3.5) + jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) jquery-ui-rails (6.0.1) railties (>= 3.2.16) - js-routes (1.4.7) + js-routes (2.2.2) railties (>= 4) - sprockets-rails - jwt (2.2.1) - kaminari (1.2.1) + jwt (2.3.0) + kaminari (1.2.2) activesupport (>= 4.1.0) - kaminari-actionview (= 1.2.1) - kaminari-activerecord (= 1.2.1) - kaminari-core (= 1.2.1) - kaminari-actionview (1.2.1) + kaminari-actionview (= 1.2.2) + kaminari-activerecord (= 1.2.2) + kaminari-core (= 1.2.2) + kaminari-actionview (1.2.2) actionview - kaminari-core (= 1.2.1) - kaminari-activerecord (1.2.1) + kaminari-core (= 1.2.2) + kaminari-activerecord (1.2.2) activerecord - kaminari-core (= 1.2.1) - kaminari-core (1.2.1) + kaminari-core (= 1.2.2) + kaminari-core (1.2.2) log_file (0.0.3) - loofah (2.6.0) + loofah (2.13.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - method_source (0.9.2) - mime-types (3.2.2) + matrix (0.4.2) + method_source (1.0.0) + mime-types (3.4.1) mime-types-data (~> 3.2015) - mime-types-data (3.2019.0331) + mime-types-data (3.2022.0105) mini_magick (4.11.0) - mini_mime (1.0.2) + mini_mime (1.1.2) mini_portile2 (2.4.0) - minitest (5.14.1) + minitest (5.15.0) modernizr-rails (2.7.1) - momentjs-rails (2.20.1) + momentjs-rails (2.29.1.1) railties (>= 3.1) - multi_json (1.13.1) + multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) mysql2 (0.4.10) - net-scp (2.0.0) - net-ssh (>= 2.6.5, < 6.0.0) - net-ssh (5.2.0) + net-scp (3.0.0) + net-ssh (>= 2.6.5, < 7.0.0) + net-ssh (6.1.0) netrc (0.11.0) - nio4r (2.5.2) - nokogiri (1.10.9) + nio4r (2.5.8) + nokogiri (1.10.10) mini_portile2 (~> 2.4.0) - oauth (0.5.4) - oauth2 (1.4.1) - faraday (>= 0.8, < 0.16.0) + oauth (0.5.8) + oauth2 (1.4.7) + faraday (>= 0.8, < 2.0) jwt (>= 1.0, < 3.0) multi_json (~> 1.3) multi_xml (~> 0.5) rack (>= 1.2, < 3) oj (2.18.5) oj_mimic_json (1.0.1) - omniauth (1.9.0) - hashie (>= 3.4.6, < 3.7.0) + omniauth (1.9.1) + hashie (>= 3.4.6) rack (>= 1.6.2, < 3) - omniauth-cas (1.1.1) + omniauth-cas (2.0.0) addressable (~> 2.3) nokogiri (~> 1.5) omniauth (~> 1.2) - omniauth-facebook (5.0.0) + omniauth-facebook (9.0.0) omniauth-oauth2 (~> 1.2) - omniauth-google-oauth2 (0.7.0) + omniauth-google-oauth2 (0.8.2) jwt (>= 2.0) - omniauth (>= 1.1.1) - omniauth-oauth2 (>= 1.5) - omniauth-oauth2 (1.6.0) oauth2 (~> 1.1) - omniauth (~> 1.9) + omniauth (~> 1.1) + omniauth-oauth2 (>= 1.6) + omniauth-oauth2 (1.7.2) + oauth2 (~> 1.4) + omniauth (>= 1.9, < 3) orm_adapter (0.5.0) - pastel (0.7.3) - equatable (~> 0.6) - tty-color (~> 0.5) polyamorous (1.1.0) activerecord (>= 3.0) - power_assert (1.1.4) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - public_suffix (3.1.1) - puma (4.3.5) + power_assert (2.0.1) + pry (0.14.1) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) + puma (4.3.10) nio4r (~> 2.0) - rabl (0.14.1) + rabl (0.15.0) activesupport (>= 2.3.14) rack (1.6.13) rack-test (0.6.3) @@ -351,35 +367,35 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.2.11.3) sprockets-rails - rails-deprecated_sanitizer (1.0.3) + rails-deprecated_sanitizer (1.0.4) activesupport (>= 4.2.0.alpha) rails-dom-testing (1.0.9) activesupport (>= 4.2.0, < 5.0) nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.2) loofah (~> 2.3) - rails_same_site_cookie (0.1.8) + rails_same_site_cookie (0.1.9) rack (>= 1.5) - user_agent_parser (~> 2.5) + user_agent_parser (~> 2.6) railties (4.2.11.3) actionpack (= 4.2.11.3) activesupport (= 4.2.11.3) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (12.3.3) + rake (13.0.6) ransack (1.8.10) actionpack (>= 3.0, < 5.2) activerecord (>= 3.0, < 5.2) activesupport (>= 3.0, < 5.2) i18n - rb-fsevent (0.10.3) - rb-inotify (0.10.0) + rb-fsevent (0.11.0) + rb-inotify (0.10.1) ffi (~> 1.0) - rdoc (6.1.1) - redcarpet (3.4.0) - regexp_parser (1.5.1) - remotipart (1.4.3) + rdoc (6.3.3) + redcarpet (3.5.1) + regexp_parser (2.2.0) + remotipart (1.4.4) representable (2.4.1) declarative (~> 0.0.5) uber (~> 0.0.15) @@ -387,49 +403,57 @@ GEM responders (2.4.1) actionpack (>= 4.2.0, < 6.0) railties (>= 4.2.0, < 6.0) - rest-client (2.0.2) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rspec-core (3.8.2) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.4) + rexml (3.2.5) + rspec-core (3.10.2) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-rails (3.8.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.2) - ruby-graphviz (1.2.4) - ruby_parser (3.13.1) - sexp_processor (~> 4.9) - rubyzip (1.3.0) + rspec-support (~> 3.10.0) + rspec-rails (4.1.2) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.3) + ruby-graphviz (1.2.5) + rexml + ruby2_keywords (0.0.5) + ruby_parser (3.18.1) + sexp_processor (~> 4.16) + rubyzip (2.3.2) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sdoc (1.0.0) - rdoc (>= 5.0) - sexp_processor (4.12.1) + sass-rails (6.0.0) + sassc-rails (~> 2.1, >= 2.1.1) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt + sdoc (2.3.0) + rdoc (>= 5.0, < 6.4.0) + sexp_processor (4.16.0) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -438,47 +462,32 @@ GEM activerecord (>= 3.0) activesupport (>= 3.0) polyamorous (~> 1.1.0) - sshkit (1.19.1) + sshkit (1.21.2) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) ssrf_filter (1.0.7) - strings (0.1.5) - strings-ansi (~> 0.1) - unicode-display_width (~> 1.5) - unicode_utils (~> 1.4) - strings-ansi (0.1.0) sucker_punch (1.6.0) celluloid (~> 0.17.2) - temple (0.8.1) + temple (0.8.2) test-unit (3.0.9) power_assert - thin (1.7.2) + thin (1.8.1) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (0.20.3) + thor (1.2.1) thread_safe (0.3.6) - tilt (2.0.9) - timers (4.3.0) + tilt (2.0.10) + timers (4.3.3) truncate_html (0.9.3) - tty-color (0.5.0) - tty-pager (0.12.1) - strings (~> 0.1.4) - tty-screen (~> 0.6) - tty-which (~> 0.4) - tty-screen (0.6.5) - tty-tree (0.3.0) - tty-which (0.4.1) - tzinfo (1.2.7) + tzinfo (1.2.9) thread_safe (~> 0.1) uber (0.0.15) - uglifier (4.1.20) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.7.6) - unicode-display_width (1.6.0) - unicode_utils (1.4.0) + unf_ext (0.0.8) user_agent_parser (2.7.0) wannabe_bool (0.7.1) warden (1.2.7) From e61f88f8da394af601f3829a357e4d372edcb05e Mon Sep 17 00:00:00 2001 From: i-m-karanagarwal <77572300+i-m-karanagarwal@users.noreply.github.com> Date: Thu, 7 Apr 2022 21:34:56 +0530 Subject: [PATCH 035/117] Fixed Gemfile according to new github policies --- Gemfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 43766f40..3e7065b1 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'coffee-script-source' gem 'test-unit', '~> 3.0.9' gem 'nokogiri', '~> 1.10.4' gem 'csv_shaper' -gem 'andand', github: 'raganwald/andand' +gem 'andand', git: 'https://github.com/raganwald/andand.git' gem 'responders' # Can't move above 1.1 until migrating to rails 4.2+ gem 'friendly_id', '~> 5' gem 'active_record-acts_as' @@ -50,7 +50,7 @@ group :development, :test do gem 'sqlite3', '~> 1.3.0' gem 'rspec-rails' gem 'annotate' - gem 'rails-erd', github: 'voormedia/rails-erd' + gem 'rails-erd', git: 'https://github.com/voormedia/rails-erd' gem 'faker' # Needed for debugging support in Aptana Studio. Disabled, since these # two gems do not support Ruby 2.0 yet :-(. @@ -113,7 +113,7 @@ group :deploy do gem 'capistrano-bundler' gem 'capistrano-rails' gem 'capistrano-rvm' - gem 'capistrano3-puma', github: 'seuros/capistrano-puma' + gem 'capistrano3-puma', git: 'https://github.com/seuros/capistrano-puma' end #for multi-color progress bar From d3eaa7b4380398cbce51f3d0507f9f61d2e15754 Mon Sep 17 00:00:00 2001 From: Divyansh Mishra Date: Thu, 17 Nov 2022 12:31:55 -0500 Subject: [PATCH 036/117] Peml integration basic --- Dockerfile | 4 +- Gemfile | 1 + Gemfile.lock | 30 +++++++++++++++ app/controllers/exercises_controller.rb | 51 ++++++++++++++++++++++++- docker-compose.yml | 6 +-- 5 files changed, 85 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 29bcd94e..51e8d176 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,11 +57,11 @@ RUN gem install bundler -v $BUNDLER_VERSION COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock - +RUN echo Gemfile.lock #RUN bundle update #RUN bundle check || bundle install RUN bundle install - +RUN bundle exec peml #VOLUME ${BASEDIR} WORKDIR ${BASEDIR} diff --git a/Gemfile b/Gemfile index e5d7380e..381031e7 100644 --- a/Gemfile +++ b/Gemfile @@ -27,6 +27,7 @@ gem 'acts-as-taggable-on' gem 'representable', '~> 2.1' gem 'redcarpet' gem 'loofah', '>= 2.3.1' +gem 'peml', github: 'CSSPLICE/peml' gem 'truncate_html' gem 'tzinfo' # For timezone support gem 'active_record_union' diff --git a/Gemfile.lock b/Gemfile.lock index 12c5ccc6..b0cf8d41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,16 @@ +GIT + remote: https://github.com/CSSPLICE/peml.git + revision: 91e6bcb3a6923dcb05c669eb6409383058bf1a7c + specs: + peml (0.1.1) + dottie (~> 0.0.1) + json_schemer (>= 0.2) + kramdown (~> 2.3.1) + kramdown-parser-gfm (~> 1.1.0) + liquid (>= 3.0.6) + parslet (>= 1.8) + redcarpet (>= 3.5) + GIT remote: https://github.com/raganwald/andand.git revision: d6c4545b6649c70495c26e2038206c5fdb2d14d6 @@ -189,6 +202,9 @@ GEM diff-lcs (1.4.4) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) + dottie (0.0.2) + ecma-re-validator (0.2.1) + regexp_parser (~> 1.2) ed25519 (1.2.4) erubi (1.9.0) erubis (2.7.0) @@ -230,6 +246,7 @@ GEM haml (>= 4.0.6, < 6.0) html2haml (>= 1.0.1) railties (>= 5.1) + hana (1.3.7) has_scope (0.7.2) actionpack (>= 4.1) activesupport (>= 4.1) @@ -265,6 +282,11 @@ GEM js-routes (1.4.9) railties (>= 4) sprockets-rails + json_schemer (0.2.16) + ecma-re-validator (~> 0.2) + hana (~> 1.3) + regexp_parser (~> 1.5) + uri_template (~> 0.7) jwt (2.2.2) kaminari (1.2.1) activesupport (>= 4.1.0) @@ -278,6 +300,11 @@ GEM activerecord kaminari-core (= 1.2.1) kaminari-core (1.2.1) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (5.4.0) listen (3.3.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) @@ -341,6 +368,7 @@ GEM oauth2 (~> 1.4) omniauth (~> 1.9) orm_adapter (0.5.0) + parslet (2.0.0) polyamorous (2.3.2) activerecord (>= 5.2.1) power_assert (1.2.0) @@ -483,6 +511,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.7) + uri_template (0.7.0) user_agent_parser (2.7.0) wannabe_bool (0.7.1) warden (1.2.9) @@ -559,6 +588,7 @@ DEPENDENCIES omniauth-cas omniauth-facebook omniauth-google-oauth2 + peml! pry puma (~> 4.3.5) rabl diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index ef7e715e..ea53db6e 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -3,6 +3,7 @@ class ExercisesController < ApplicationController require 'oauth/request_proxy/rack_request' require 'zip' require 'tempfile' + require 'peml' load_and_authorize_resource @@ -326,11 +327,17 @@ def cleanFile(files,fileList) # POST /exercises/upload_create def upload_create if params[:exercise] + peml=false exercise_params = params[:exercise] exercise_version_params = exercise_params[:exercise_version] edit_rights = exercise_params[:exercise_collection_id].to_i text_representation = exercise_version_params['text_representation'] - hash = YAML.load(text_representation) + if text_representation.to_s.include? ('exercise_id') + hash = Peml::Loader.new.load(text_representation) + peml=true + else + hash = YAML.load(text_representation) + end else text_representation = File.read(params[:form][:file].path) hash = YAML.load(text_representation) @@ -341,10 +348,14 @@ def upload_create if fileList != "" && !files.nil? files = cleanFile(files,fileList) end + if(peml) + hash = convert_peml(hash) + end if !hash.kind_of?(Array) hash = [hash] end - + + puts(hash) # figure out if we need to add this to an exercise collection exercise_collection = nil if edit_rights == 0 @@ -977,6 +988,42 @@ def create_new_version return newexercise end + # -------------------------------------------------------------- + # Convert the parsed peml hash into a + # hash corresponding to exercise data model + def convert_peml(hash) + #starting with three compulsory peml keys + new_hash = {"external_id" => hash["exercise_id"], "name", hash["title"]} + new_hash["current_version"] = {} + new_hash["current_version"]["version"] = 1 + if(hash["author"].is_a? String) + new_hash["current_version"]["creator"] = hash["author"] + else + new_hash["current_version"]["creator"] = hash["author"].key?("name") ? hash["author"].key?("name") : hash["author"].key?("email") + end + new_hash["experience"] = hash["difficulty"] + new_hash["tag_list"] = hash["tag"] + new_hash["style_list"] = "code writing" #dynamically handle which coding style it is + new_hash["language_list"] = "" + hash["systems"].each do |system| + new_hash["language_list"]+=system["language"] + end + new_hash["is_public"] = hash["is_public"] + new_hash["current_version"]["prompts"] = [] + prompt = {"position" => 1, "question" => hash["instructions"], "class_name" => "", "methos_name" => ""} + + #------------------------------------------------------------------------------------------- + # This part also needs to be dynamically handled to check if these files are in src instead + prompt["starter_code"] = hash["assets"]["code"]["starter"]["files"][0]["content"] + prompt["wrapper_code"] = hash["assets"]["code"]["wrapper"]["files"][0]["content"] + prompt["tests"] = hash["assets"]["test"]["files"][0]["content"] + #------------------------------------------------------------------------------------------- + + #dynamically handle which type of prompt it is + new_hash["current_version"]["prompts"]<<{"coding_prompt" => prompt} + new_hash + end + # ------------------------------------------------------------- # Only allow a trusted parameter "white list" through. diff --git a/docker-compose.yml b/docker-compose.yml index f2115c6f..dedb1323 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.8' services: db_dev: - image: mysql:5.7 + image: mariadb environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: codeworkout @@ -10,9 +10,9 @@ services: ports: - "3307:3306" volumes: - - dbdata:/var/lib/mysql:delegated + - dbdata:/var/lib/mariadb:delegated db_test: - image: mysql:5.7 + image: mariadb environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: codeworkout From 24aa0557d3cb69bed750f68e1cb7fa4269a329fc Mon Sep 17 00:00:00 2001 From: Divyansh Mishra Date: Mon, 28 Nov 2022 16:07:34 -0500 Subject: [PATCH 037/117] Peml integration refactoring --- Gemfile | 1 + Gemfile.lock | 1 + app/controllers/exercises_controller.rb | 58 ++--------------- app/utils/peml_parsing_util.rb | 83 +++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 52 deletions(-) create mode 100644 app/utils/peml_parsing_util.rb diff --git a/Gemfile b/Gemfile index 381031e7..4fa46919 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,7 @@ gem 'peml', github: 'CSSPLICE/peml' gem 'truncate_html' gem 'tzinfo' # For timezone support gem 'active_record_union' +gem 'dottie', '~> 0.0.1' gem 'mysql2', '~> 0.4.0' gem 'modernizr-rails' gem 'rubyzip', '>= 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index b0cf8d41..21fd95c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -557,6 +557,7 @@ DEPENDENCIES css3-progress-bar-rails csv_shaper devise + dottie (~> 0.0.1) ed25519 exception_handler (~> 0.8.0.0) factory_bot_rails diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index ea53db6e..cdccaaec 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -3,8 +3,6 @@ class ExercisesController < ApplicationController require 'oauth/request_proxy/rack_request' require 'zip' require 'tempfile' - require 'peml' - load_and_authorize_resource skip_authorize_resource only: [:practice, :call_open_pop] @@ -327,35 +325,28 @@ def cleanFile(files,fileList) # POST /exercises/upload_create def upload_create if params[:exercise] - peml=false exercise_params = params[:exercise] exercise_version_params = exercise_params[:exercise_version] edit_rights = exercise_params[:exercise_collection_id].to_i text_representation = exercise_version_params['text_representation'] - if text_representation.to_s.include? ('exercise_id') - hash = Peml::Loader.new.load(text_representation) - peml=true - else - hash = YAML.load(text_representation) - end else text_representation = File.read(params[:form][:file].path) - hash = YAML.load(text_representation) edit_rights = 0 # Personal exercise end + if text_representation.to_s.include? ('exercise_id') + hash = PemlParsingUtil.new.parse(text_representation) + else + hash = YAML.load(text_representation) + end files = exercise_params[:files] fileList = exercise_params[:fileList] if fileList != "" && !files.nil? files = cleanFile(files,fileList) end - if(peml) - hash = convert_peml(hash) - end if !hash.kind_of?(Array) hash = [hash] end - - puts(hash) + # figure out if we need to add this to an exercise collection exercise_collection = nil if edit_rights == 0 @@ -988,43 +979,6 @@ def create_new_version return newexercise end - # -------------------------------------------------------------- - # Convert the parsed peml hash into a - # hash corresponding to exercise data model - def convert_peml(hash) - #starting with three compulsory peml keys - new_hash = {"external_id" => hash["exercise_id"], "name", hash["title"]} - new_hash["current_version"] = {} - new_hash["current_version"]["version"] = 1 - if(hash["author"].is_a? String) - new_hash["current_version"]["creator"] = hash["author"] - else - new_hash["current_version"]["creator"] = hash["author"].key?("name") ? hash["author"].key?("name") : hash["author"].key?("email") - end - new_hash["experience"] = hash["difficulty"] - new_hash["tag_list"] = hash["tag"] - new_hash["style_list"] = "code writing" #dynamically handle which coding style it is - new_hash["language_list"] = "" - hash["systems"].each do |system| - new_hash["language_list"]+=system["language"] - end - new_hash["is_public"] = hash["is_public"] - new_hash["current_version"]["prompts"] = [] - prompt = {"position" => 1, "question" => hash["instructions"], "class_name" => "", "methos_name" => ""} - - #------------------------------------------------------------------------------------------- - # This part also needs to be dynamically handled to check if these files are in src instead - prompt["starter_code"] = hash["assets"]["code"]["starter"]["files"][0]["content"] - prompt["wrapper_code"] = hash["assets"]["code"]["wrapper"]["files"][0]["content"] - prompt["tests"] = hash["assets"]["test"]["files"][0]["content"] - #------------------------------------------------------------------------------------------- - - #dynamically handle which type of prompt it is - new_hash["current_version"]["prompts"]<<{"coding_prompt" => prompt} - new_hash - end - - # ------------------------------------------------------------- # Only allow a trusted parameter "white list" through. def exercise_params diff --git a/app/utils/peml_parsing_util.rb b/app/utils/peml_parsing_util.rb new file mode 100644 index 00000000..5d14e23e --- /dev/null +++ b/app/utils/peml_parsing_util.rb @@ -0,0 +1,83 @@ +require 'peml' +require 'dottie/ext' +require 'net/http' +require 'uri' + +class PemlParsingUtil + def parse (text_representation) + convert_peml(Peml::Loader.new.load(text_representation).dottie!) + end + + # Convert the parsed peml hash into a hash corresponding to exercise data model + def convert_peml(hash) + #starting with three compulsory peml keys + new_hash = {"external_id" => hash["exercise_id"], "name" => hash["title"]}.dottie! + new_hash["experience"] = hash["difficulty"] + new_hash["tag_list"] = hash["tag"].to_s + + # PEML does not have an is_public equivalent so we put this value under a key of the same name + new_hash["is_public"] = hash["is_public"] + + # PEML is designed to handle programming assignments and 'code writing' is the equivalent in code-workout + new_hash["style_list"] = "code writing" + + new_hash["language_list"] = "" + hash["systems"].each do |system| + new_hash["language_list"]+=system["language"] + end + + new_hash["current_version"] = {} + new_hash["current_version.version"] = hash["version.id"] + new_hash["current_version.creator"] = get_author(hash) + new_hash["current_version.prompts"] = [] + prompt = {"position" => 1, "question" => hash["instructions"], "class_name" => "", "method_name" => ""} + + #------------------------------------------------------------------------------------------- + # PEML assets are either remote URLs or array of files which have different keys. + # We try to fetch content from the files. + prompt["starter_code"] = get_content(hash["assets.code.starter"]) + prompt["wrapper_code"] = get_content(hash["assets.code.wrapper"]) + prompt["tests"] = get_content(hash["assets.tests"]) + #------------------------------------------------------------------------------------------- + + #Again, PEML is designed for coding problems and thus, 'coding_prompt' + new_hash["current_version"]["prompts"]<<{"coding_prompt" => prompt} + new_hash + end + + def get_author(hash) + creator = "" + if hash.key?("authors") + hash["authors"].each do |author| + if author.is_a? String + creator += author + else + creator += author.key?("email") ? author["email"] : author["name"] + end + creator += "," + end + creator.delete_suffix!(",") + elsif(hash.key?("author")) + if hash["author"].is_a? String + creator = hash["author"] + else + creator = hash["author"].key?("email") ? hash["author.email"] : hash["author.name"] + end + elsif(hash.key?("license")) + creator = hash["license.owner"] + end + creator + end + + def get_content(asset_child) + asset_collection = [] + if asset_child.is_a? String && asset_child =~ URI::regexp + asset_collection << Net::HTTP.get(URI.parse(asset_child)) + else + asset_child.each do |asset_file| + asset_collection << asset_file["files.content"] + end + end + asset_collection.join(',') + end +end \ No newline at end of file From 9b74718030566c4f6f20f4e5e28a351e4abb63cc Mon Sep 17 00:00:00 2001 From: Divyansh Mishra Date: Mon, 28 Nov 2022 18:58:23 -0500 Subject: [PATCH 038/117] Bug fixed and added help page --- app/controllers/help_controller.rb | 3 +++ app/utils/peml_parsing_util.rb | 19 +++++++++++++------ app/views/help/exercise_peml_format.haml | 11 +++++++++++ app/views/help/index.html.haml | 3 +++ config/routes.rb | 1 + 5 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 app/views/help/exercise_peml_format.haml diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 2dd68eb9..41bc3450 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -10,4 +10,7 @@ def exercise_format def lti_configuration end + def exercise_peml_format + end + end diff --git a/app/utils/peml_parsing_util.rb b/app/utils/peml_parsing_util.rb index 5d14e23e..924072e4 100644 --- a/app/utils/peml_parsing_util.rb +++ b/app/utils/peml_parsing_util.rb @@ -37,11 +37,11 @@ def convert_peml(hash) # We try to fetch content from the files. prompt["starter_code"] = get_content(hash["assets.code.starter"]) prompt["wrapper_code"] = get_content(hash["assets.code.wrapper"]) - prompt["tests"] = get_content(hash["assets.tests"]) + prompt["tests"] = get_content(hash["assets.test"]) #------------------------------------------------------------------------------------------- #Again, PEML is designed for coding problems and thus, 'coding_prompt' - new_hash["current_version"]["prompts"]<<{"coding_prompt" => prompt} + new_hash["current_version.prompts"]<<{"coding_prompt" => prompt} new_hash end @@ -71,11 +71,18 @@ def get_author(hash) def get_content(asset_child) asset_collection = [] - if asset_child.is_a? String && asset_child =~ URI::regexp - asset_collection << Net::HTTP.get(URI.parse(asset_child)) - else + if asset_child.is_a? String + begin + uri = URI.parse(asset_child) + if uri.is_a?(URI::HTTP) && !uri.host.nil? + asset_collection << Net::HTTP.get(uri) + end + rescue URI::InvalidURIError + asset_collection << "" + end + elsif asset_child.is_a? Array asset_child.each do |asset_file| - asset_collection << asset_file["files.content"] + asset_collection << asset_file["files"]["content"] end end asset_collection.join(',') diff --git a/app/views/help/exercise_peml_format.haml b/app/views/help/exercise_peml_format.haml new file mode 100644 index 00000000..b1ba4ac3 --- /dev/null +++ b/app/views/help/exercise_peml_format.haml @@ -0,0 +1,11 @@ +%h1 PEML Format for Exercises + +%p + This page describes how to use PEML's + %a{ href: 'https://cssplice.github.io/peml/schemas/exercise.html#src' } data model + to describe assignments in CodeWorkout. PEML stands for Programming Exercise Markup + Language, and, as the name suggests, can be used to describe programming assignments + (PEML does not have support for multiple choice questions). PEML follows a key-value + structure with support for nested collections and dictionaries. The PEML data model + describes a set of required keys, a set of recommended keys, and certain optional keys. + Some of the keys are discussed in the following sections. \ No newline at end of file diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 0f2c874c..bfc31590 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -4,6 +4,9 @@ %li %a{ href: '/help/exercise_format' } Format for defining exercises + %li + %a{ href: '/help/exercise_peml_format' } + [New] Format Exercises in PEML %li %a{ href: '/help/lti_configuration' } Configuration/setup for LTI connectivity in LMSes diff --git a/config/routes.rb b/config/routes.rb index 55355324..ed3e33f9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -188,6 +188,7 @@ get 'help' => 'help#index' get 'help/exercise_format' => 'help#exercise_format' get 'help/lti_configuration' => 'help/lti_configuration' + get 'help/exercise_peml_format' => 'help#exercise_peml_format' get 'static_pages/mockup1' => 'static_pages#mockup1' get 'static_pages/mockup2' => 'static_pages#mockup2' From cab8fff57e453be59c1ca0c5f351c30116e7b6d0 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Wed, 22 Nov 2023 16:23:33 -0500 Subject: [PATCH 039/117] creates new exercise collection for SPLICE along with a route to export data for catalog (/gym/exercises/embed_collection and /gym/exercises/export Signed-off-by: Alex Hicks --- app/assets/stylesheets/custom.scss | 30 +++++++++ app/controllers/exercises_controller.rb | 38 ++++++++++- app/models/exercise.rb | 19 +++++- .../exercises/embed_collection.html.haml | 63 +++++++++++++++++++ config/routes.rb | 4 +- docker-compose.yml | 4 +- 6 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 app/views/exercises/embed_collection.html.haml diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index 21c9fa4c..ae662ad9 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -569,3 +569,33 @@ ul.ui-autocomplete { max-height: 5em; height: auto; } + +// syling for embed collection page for SPLICE +.exercise-container.card { + width: 200%; // Adjust width as needed + // padding-left: 0 // +} + +.iframe-label-container { + clear: both; // Clear floats to ensure this container stays below preceding content + margin-top: 8px; // Add margin at the top to create space between this container and the preceding content + margin-bottom: 8px; // Adjust spacing below the label + // padding-left: 0 +} + +.iframe-label-container label { + display: block; + font-size: 11px; + margin-bottom: 5px; + +} + +.exercise-details { + margin-bottom: 10px; // Adds space before the button + padding-left: 20px; + + input.form-control { + width: 40%; // text field the same width as the thumbnail + font-size: 9px; + } +} diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index f19e9795..371d1b62 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -6,7 +6,7 @@ class ExercisesController < ApplicationController load_and_authorize_resource - skip_authorize_resource only: [:practice, :call_open_pop] + skip_authorize_resource only: [:practice, :call_open_pop, :embed_collection, :export] #~ Action methods ........................................................... after_action :allow_iframe, only: [:practice, :embed] @@ -27,6 +27,42 @@ def index end + # This embed_collection fetches all exercises for the embed_collections page and provides a simplified iframe urls of exercises for SPLICE + def embed_collection + if current_user + @exercises = Exercise.visible_to_user(current_user) + else + @exercises = Exercise.publicly_visible + end + @exercises = @exercises.page(params[:page]) + end + + # ------------------------------------------------------------- + # The export function gets all exercises metadata for SPLICE + # GET /gym/exercises/export + def export + @exercises = Exercise.all + export_data = @exercises.map do |exercise| + workout_names = exercise.exercise_workouts.map { |ew| ew.workout.name }.uniq + { + "Platform_name": "Code-Workout", + "URL": "https://codeworkout.cs.vt.edu", + "LTI_Instructions_URL": " https://opendsa-server.cs.vt.edu/guides/opendsa-canvas", + "Exercise_type": Exercise::TYPE_NAMES[exercise.question_type], + "license": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)", + "Description": exercise.exercise_collection&.description, # Safely fetching description + "Author": "Stephen Edwards", + "Institution": "VT", + "Keywords": workout_names.join(', '), + "Exercise_Name": exercise.name, + "Iframe_URL": exercise.iframe_url, + "LTI_URL": exercise.lti_launch_url + } + end + render json: export_data + end + + # ------------------------------------------------------------- # GET /exercises/download.csv def download diff --git a/app/models/exercise.rb b/app/models/exercise.rb index ee202140..c2c6008e 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -109,7 +109,8 @@ class Exercise < ActiveRecord::Base scope :visible_through_user, -> (u) { joins{exercise_owners.outer}.joins{exercise_collection.outer}. where{ (exercise_owners.owner == u) | (exercise_collection.user == u) } } - + attr_accessor :iframe_url # to get values to define in iframe url function for SPLICE + #~ Class methods ............................................................ # ------------------------------------------------------------- @@ -194,6 +195,22 @@ def self.publicly_visible end + # the iframeurl, iframe-embedcode and ltilaunch is to display and export each exercises information for SPLICE + def iframe_url + base_url = "https://code-workout.cs.vt.edu" # to be dynamically fetched maybe from a config file + "#{base_url}/gym/exercises/#{self.id}/practice" + end + + def iframe_embed_code + "" + end + + def lti_launch_url + base_url = "https://code-workout.cs.vt.edu" # to be fetched dynamically + "#{base_url}/lti/launch/gym/exercises/#{self.id}/practice" + end + + # ------------------------------------------------------------- def self.visible_through_user_group(user) Exercise.joins(exercise_collection: [ user_group: :memberships ]) diff --git a/app/views/exercises/embed_collection.html.haml b/app/views/exercises/embed_collection.html.haml new file mode 100644 index 00000000..f719d6fb --- /dev/null +++ b/app/views/exercises/embed_collection.html.haml @@ -0,0 +1,63 @@ +.container + %ol.breadcrumb.mb-4 + %li= link_to 'Home', root_path + %li= link_to 'Gym', gym_url + %li.active Exercises + + %h1.mb-3 Exercises + + - if can? :create, Exercise + %p.mb-4= link_to 'Create New', new_exercise_path, class: 'btn btn-primary' + + - if @exercises.size > 0 + .row + - @exercises.in_groups_of(2, false) do |group| + - group.each do |exercise| + .col-md-6.mb-4 + .exercise-container.card + .card-body + = render partial: 'exercise', locals: { exercise: exercise, user: current_user } + + - if exercise.iframe_url.present? + .exercise-details.mt-3 + .iframe-label-container + %label.mb-1 IFrame Embed Code: + %input.form-control.mb-2{ id: "iframe-code-#{exercise.id}", type: 'text', value: exercise.iframe_embed_code, readonly: true } + .buttons.mt-2 + %button.btn.btn-primary.mr-2{ 'data-toggle' => 'modal', 'data-target' => "#infoModal-#{exercise.id}" } + Generate LTI Launch Info + %button.btn.btn-primary{ 'data-toggle' => 'modal', 'data-target' => "#previewModal-#{exercise.id}" } + Preview + .modal.fade{ id: "infoModal-#{exercise.id}", tabindex: '-1', role: 'dialog', 'aria-labelledby' => "infoModalLabel-#{exercise.id}", 'aria-hidden' => 'true' } + .modal-dialog{ role: 'document' } + .modal-content + .modal-header + %h5.modal-title + Iframe and LTI Launch Information + %button.close{ type: 'button', 'data-dismiss' => 'modal', 'aria-label' => 'Close' } + %span{ 'aria-hidden' => 'true' } × + .modal-body + %p + %strong IFrame URL: + %input.form-control{ type: 'text', value: exercise.iframe_url, readonly: true } + %p + %strong LTI Launch URL: + %input.form-control{ type: 'text', value: exercise.lti_launch_url, readonly: true, disabled: true } + .modal.fade{ id: "previewModal-#{exercise.id}", tabindex: '-1', role: 'dialog', 'aria-labelledby' => "previewModalLabel-#{exercise.id}", 'aria-hidden' => 'true' } + .modal-dialog{ role: 'document' } + .modal-content + .modal-header + %h5.modal-title Preview + %button.close{ type: 'button', 'data-dismiss' => 'modal', 'aria-label' => 'Close' } + %span{ 'aria-hidden' => 'true' } × + .modal-body + %iframe{ src: exercise.iframe_url, width: '100%', height: '500', frameborder: '0', allowfullscreen: true } + .clearfix + = paginate @exercises + - else + %p.mt-4 + No public exercises are available to view right now. Please wait for contributors to add more. + +-# this page is to display the catalog of code workout's exercises for SPLICE + + diff --git a/config/routes.rb b/config/routes.rb index 4cdf4df6..4e801f9f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,7 +66,9 @@ as: :exercises_query_data get 'exercises/download_attempt_data' => 'exercises#download_attempt_data', as: :download_exercise_attempt_data - # At the bottom, so the routes above take precedence over existing ids + get 'exercises/embed_collection' => + 'exercises#embed_collection', as: :exercises_embed_collection + get 'exercises/export' => 'exercises#export', as: :exercises_export resources :exercises # /gym/workouts ... diff --git a/docker-compose.yml b/docker-compose.yml index 91facf81..d66cfe9e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.8' services: db_dev: - image: mysql:5.7 + image: mariadb:10.2 environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: codeworkout @@ -12,7 +12,7 @@ services: volumes: - dbdata:/var/lib/mysql:delegated db_test: - image: mysql:5.7 + image: mariadb:10.2 environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: codeworkout From 7fa0b75055870823e5627ff1c56c938cf81c95ea Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Tue, 5 Dec 2023 02:03:40 -0500 Subject: [PATCH 040/117] remove collection and update urls Signed-off-by: Alex Hicks --- app/assets/stylesheets/custom.scss | 30 --------- app/controllers/exercises_controller.rb | 14 +---- app/models/exercise.rb | 4 +- .../exercises/embed_collection.html.haml | 63 ------------------- config/routes.rb | 3 +- 5 files changed, 5 insertions(+), 109 deletions(-) delete mode 100644 app/views/exercises/embed_collection.html.haml diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index ae662ad9..21c9fa4c 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -569,33 +569,3 @@ ul.ui-autocomplete { max-height: 5em; height: auto; } - -// syling for embed collection page for SPLICE -.exercise-container.card { - width: 200%; // Adjust width as needed - // padding-left: 0 // -} - -.iframe-label-container { - clear: both; // Clear floats to ensure this container stays below preceding content - margin-top: 8px; // Add margin at the top to create space between this container and the preceding content - margin-bottom: 8px; // Adjust spacing below the label - // padding-left: 0 -} - -.iframe-label-container label { - display: block; - font-size: 11px; - margin-bottom: 5px; - -} - -.exercise-details { - margin-bottom: 10px; // Adds space before the button - padding-left: 20px; - - input.form-control { - width: 40%; // text field the same width as the thumbnail - font-size: 9px; - } -} diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 371d1b62..93898e90 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -6,7 +6,7 @@ class ExercisesController < ApplicationController load_and_authorize_resource - skip_authorize_resource only: [:practice, :call_open_pop, :embed_collection, :export] + skip_authorize_resource only: [:practice, :call_open_pop, :export] #~ Action methods ........................................................... after_action :allow_iframe, only: [:practice, :embed] @@ -27,16 +27,6 @@ def index end - # This embed_collection fetches all exercises for the embed_collections page and provides a simplified iframe urls of exercises for SPLICE - def embed_collection - if current_user - @exercises = Exercise.visible_to_user(current_user) - else - @exercises = Exercise.publicly_visible - end - @exercises = @exercises.page(params[:page]) - end - # ------------------------------------------------------------- # The export function gets all exercises metadata for SPLICE # GET /gym/exercises/export @@ -45,7 +35,7 @@ def export export_data = @exercises.map do |exercise| workout_names = exercise.exercise_workouts.map { |ew| ew.workout.name }.uniq { - "Platform_name": "Code-Workout", + "Platform_name": "CodeWorkout", "URL": "https://codeworkout.cs.vt.edu", "LTI_Instructions_URL": " https://opendsa-server.cs.vt.edu/guides/opendsa-canvas", "Exercise_type": Exercise::TYPE_NAMES[exercise.question_type], diff --git a/app/models/exercise.rb b/app/models/exercise.rb index c2c6008e..628a5d60 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -197,7 +197,7 @@ def self.publicly_visible # the iframeurl, iframe-embedcode and ltilaunch is to display and export each exercises information for SPLICE def iframe_url - base_url = "https://code-workout.cs.vt.edu" # to be dynamically fetched maybe from a config file + base_url = "https://codeworkoutdev.cs.vt.edu" # to be dynamically fetched maybe from a config file "#{base_url}/gym/exercises/#{self.id}/practice" end @@ -206,7 +206,7 @@ def iframe_embed_code end def lti_launch_url - base_url = "https://code-workout.cs.vt.edu" # to be fetched dynamically + base_url = "https://codeworkoutdev.cs.vt.edu" # to be fetched dynamically "#{base_url}/lti/launch/gym/exercises/#{self.id}/practice" end diff --git a/app/views/exercises/embed_collection.html.haml b/app/views/exercises/embed_collection.html.haml deleted file mode 100644 index f719d6fb..00000000 --- a/app/views/exercises/embed_collection.html.haml +++ /dev/null @@ -1,63 +0,0 @@ -.container - %ol.breadcrumb.mb-4 - %li= link_to 'Home', root_path - %li= link_to 'Gym', gym_url - %li.active Exercises - - %h1.mb-3 Exercises - - - if can? :create, Exercise - %p.mb-4= link_to 'Create New', new_exercise_path, class: 'btn btn-primary' - - - if @exercises.size > 0 - .row - - @exercises.in_groups_of(2, false) do |group| - - group.each do |exercise| - .col-md-6.mb-4 - .exercise-container.card - .card-body - = render partial: 'exercise', locals: { exercise: exercise, user: current_user } - - - if exercise.iframe_url.present? - .exercise-details.mt-3 - .iframe-label-container - %label.mb-1 IFrame Embed Code: - %input.form-control.mb-2{ id: "iframe-code-#{exercise.id}", type: 'text', value: exercise.iframe_embed_code, readonly: true } - .buttons.mt-2 - %button.btn.btn-primary.mr-2{ 'data-toggle' => 'modal', 'data-target' => "#infoModal-#{exercise.id}" } - Generate LTI Launch Info - %button.btn.btn-primary{ 'data-toggle' => 'modal', 'data-target' => "#previewModal-#{exercise.id}" } - Preview - .modal.fade{ id: "infoModal-#{exercise.id}", tabindex: '-1', role: 'dialog', 'aria-labelledby' => "infoModalLabel-#{exercise.id}", 'aria-hidden' => 'true' } - .modal-dialog{ role: 'document' } - .modal-content - .modal-header - %h5.modal-title - Iframe and LTI Launch Information - %button.close{ type: 'button', 'data-dismiss' => 'modal', 'aria-label' => 'Close' } - %span{ 'aria-hidden' => 'true' } × - .modal-body - %p - %strong IFrame URL: - %input.form-control{ type: 'text', value: exercise.iframe_url, readonly: true } - %p - %strong LTI Launch URL: - %input.form-control{ type: 'text', value: exercise.lti_launch_url, readonly: true, disabled: true } - .modal.fade{ id: "previewModal-#{exercise.id}", tabindex: '-1', role: 'dialog', 'aria-labelledby' => "previewModalLabel-#{exercise.id}", 'aria-hidden' => 'true' } - .modal-dialog{ role: 'document' } - .modal-content - .modal-header - %h5.modal-title Preview - %button.close{ type: 'button', 'data-dismiss' => 'modal', 'aria-label' => 'Close' } - %span{ 'aria-hidden' => 'true' } × - .modal-body - %iframe{ src: exercise.iframe_url, width: '100%', height: '500', frameborder: '0', allowfullscreen: true } - .clearfix - = paginate @exercises - - else - %p.mt-4 - No public exercises are available to view right now. Please wait for contributors to add more. - --# this page is to display the catalog of code workout's exercises for SPLICE - - diff --git a/config/routes.rb b/config/routes.rb index 4e801f9f..61a3050d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -66,9 +66,8 @@ as: :exercises_query_data get 'exercises/download_attempt_data' => 'exercises#download_attempt_data', as: :download_exercise_attempt_data - get 'exercises/embed_collection' => - 'exercises#embed_collection', as: :exercises_embed_collection get 'exercises/export' => 'exercises#export', as: :exercises_export + # At the bottom, so the routes above take precedence over existing ids resources :exercises # /gym/workouts ... From da0e63bed62abcd9d559804cb55fa78c12edf138 Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Mon, 11 Dec 2023 20:04:51 -0500 Subject: [PATCH 041/117] tweaks to SPLICE API export endpoint --- app/controllers/exercises_controller.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 93898e90..41373dd3 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -35,18 +35,19 @@ def export export_data = @exercises.map do |exercise| workout_names = exercise.exercise_workouts.map { |ew| ew.workout.name }.uniq { - "Platform_name": "CodeWorkout", - "URL": "https://codeworkout.cs.vt.edu", - "LTI_Instructions_URL": " https://opendsa-server.cs.vt.edu/guides/opendsa-canvas", - "Exercise_type": Exercise::TYPE_NAMES[exercise.question_type], + "catalog_type": "SLCItemCatalog", + "platform_name": "CodeWorkout", + "url": "https://codeworkout.cs.vt.edu", + "lti_instructions_url": "https://opendsa-server.cs.vt.edu/guides/opendsa-canvas", + "exercise_type": Exercise::TYPE_NAMES[exercise.question_type], "license": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)", - "Description": exercise.exercise_collection&.description, # Safely fetching description - "Author": "Stephen Edwards", - "Institution": "VT", - "Keywords": workout_names.join(', '), - "Exercise_Name": exercise.name, - "Iframe_URL": exercise.iframe_url, - "LTI_URL": exercise.lti_launch_url + "description": exercise.exercise_collection&.description, # Safely fetching description + "author": "Stephen Edwards", + "institution": "Virginia Tech", + "keywords": workout_names.join(', '), + "exercise_Name": exercise.name, + "iframe_url": exercise.iframe_url, + "lti_url": exercise.lti_launch_url } end render json: export_data From 2d2b4f1cc613a37b9d033a6bcaed92c1dd6321dd Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Mon, 11 Dec 2023 20:21:24 -0500 Subject: [PATCH 042/117] typo in exercise_name --- app/controllers/exercises_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 41373dd3..63b8a305 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -45,7 +45,7 @@ def export "author": "Stephen Edwards", "institution": "Virginia Tech", "keywords": workout_names.join(', '), - "exercise_Name": exercise.name, + "exercise_name": exercise.name, "iframe_url": exercise.iframe_url, "lti_url": exercise.lti_launch_url } From f1fd0615e3466296c42ba700fed3d22809a50f7e Mon Sep 17 00:00:00 2001 From: Yemi Aina Date: Thu, 21 Mar 2024 17:27:57 -0400 Subject: [PATCH 043/117] updating keywords as array and split exercise name phrases into words --- app/controllers/exercises_controller.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 63b8a305..b63679a8 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -31,11 +31,16 @@ def index # The export function gets all exercises metadata for SPLICE # GET /gym/exercises/export def export + # filter out stop/connector words for keywords from workout phrases or names + stop_words = ['the', 'and', 'a', 'to', 'of', 'in', 'for', 'on', 'with', 'as', 'by', 'at', 'from', 'is', 'that', 'which', 'it', 'an', 'be', 'this', 'are', 'we', 'can', 'if', 'has', 'but'] @exercises = Exercise.all export_data = @exercises.map do |exercise| - workout_names = exercise.exercise_workouts.map { |ew| ew.workout.name }.uniq + workout_names = exercise.exercise_workouts.map { |ew| ew.workout.name }.uniq.push(exercise.name) + # split phrases and remove any stop/connector words + keywords_array = workout_names.map { |phrase| phrase.downcase.split(/\W+/) }.flatten.uniq.reject { |word| stop_words.include?(word) || word.empty? } + { - "catalog_type": "SLCItemCatalog", + "catalog_type": "SLCItemCatalog", "platform_name": "CodeWorkout", "url": "https://codeworkout.cs.vt.edu", "lti_instructions_url": "https://opendsa-server.cs.vt.edu/guides/opendsa-canvas", @@ -44,7 +49,7 @@ def export "description": exercise.exercise_collection&.description, # Safely fetching description "author": "Stephen Edwards", "institution": "Virginia Tech", - "keywords": workout_names.join(', '), + "keywords": keywords_array, "exercise_name": exercise.name, "iframe_url": exercise.iframe_url, "lti_url": exercise.lti_launch_url From f70514feae3d04b140fad0186aab56081d668de4 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Fri, 15 Nov 2024 11:18:20 -0500 Subject: [PATCH 044/117] removed before_filter references, post-merge cleanup --- Gemfile | 1 - Gemfile.lock | 11 ++++++----- app/controllers/course_offerings_controller.rb | 8 ++++---- app/controllers/upload_roster_controller.rb | 2 +- app/controllers/workout_offerings_controller.rb | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 36fe34cb..7836afae 100644 --- a/Gemfile +++ b/Gemfile @@ -126,7 +126,6 @@ gem 'rest-client' # Gems for cookie updates gem 'user_agent_parser', '~> 2.7.0' gem 'rails_same_site_cookie' -gem 'sprockets', '< 4.0.0' gem 'image_hash' # Gems for resource uploder diff --git a/Gemfile.lock b/Gemfile.lock index a36fd03f..123e5b98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,10 +22,10 @@ GIT revision: b148515f78476b68ab8e09bcc494e82ceb53eba0 branch: v4.x specs: - capistrano3-puma (5.0.0.beta1) + capistrano3-puma (4.0.0) capistrano (~> 3.7) capistrano-bundler - puma (>= 4.0, < 6.0) + puma (~> 4.0) GIT remote: https://github.com/voormedia/rails-erd @@ -441,8 +441,8 @@ GEM rspec-support (~> 3.8.0) rspec-expectations (3.8.4) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-rails (3.8.2) @@ -456,6 +456,7 @@ GEM rspec-support (3.8.2) ruby-graphviz (1.2.5) rexml + ruby2_keywords (0.0.5) ruby_parser (3.13.1) sexp_processor (~> 4.9) rubyzip (2.3.0) @@ -548,7 +549,7 @@ DEPENDENCIES capistrano-rails capistrano-rvm capistrano3-puma (~> 4.0.0)! - capybara + capybara (~> 3.12.0) carrierwave (= 1.3.2) cocoon codemirror-rails diff --git a/app/controllers/course_offerings_controller.rb b/app/controllers/course_offerings_controller.rb index 5376e87c..150ce761 100644 --- a/app/controllers/course_offerings_controller.rb +++ b/app/controllers/course_offerings_controller.rb @@ -1,5 +1,5 @@ class CourseOfferingsController < ApplicationController - before_filter :rename_course_offering_id_param + before_action :rename_course_offering_id_param load_and_authorize_resource @@ -154,7 +154,7 @@ def enroll if @course_offering && !already_enrolled && (can_enroll || enrollee_manages_course || current_user_manages_course) - + co = CourseEnrollment.new( course_offering: @course_offering, @@ -187,8 +187,8 @@ def enroll end else message = "Could not enroll #{@user.display_name} in #{@course_offering.display_name_with_term} because" - reasons = "#{errors.to_sentence}." - message = "#{message} #{reasons}" + reasons = "#{errors.to_sentence}." + message = "#{message} #{reasons}" respond_to do |format| format.html { flash[:warning] = 'Unable to enroll in that course.' diff --git a/app/controllers/upload_roster_controller.rb b/app/controllers/upload_roster_controller.rb index 63c4d248..0b454d08 100644 --- a/app/controllers/upload_roster_controller.rb +++ b/app/controllers/upload_roster_controller.rb @@ -1,5 +1,5 @@ class UploadRosterController < ApplicationController - before_filter :authenticate_user! + before_action :authenticate_user! load_and_authorize_resource :course_offering diff --git a/app/controllers/workout_offerings_controller.rb b/app/controllers/workout_offerings_controller.rb index 901f5424..3522ad16 100644 --- a/app/controllers/workout_offerings_controller.rb +++ b/app/controllers/workout_offerings_controller.rb @@ -1,5 +1,5 @@ class WorkoutOfferingsController < ApplicationController - skip_before_filter :authenticate_user!, :only => :practice + skip_before_action :authenticate_user!, :only => :practice load_and_authorize_resource From db126474ba0b5260d8a3668d994923adb82db8b3 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Fri, 15 Nov 2024 11:52:57 -0500 Subject: [PATCH 045/117] fixed errant skip_before_action clause (?) --- app/controllers/workout_offerings_controller.rb | 4 ++-- config/deploy/staging.rb | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/workout_offerings_controller.rb b/app/controllers/workout_offerings_controller.rb index 3522ad16..173110d3 100644 --- a/app/controllers/workout_offerings_controller.rb +++ b/app/controllers/workout_offerings_controller.rb @@ -1,8 +1,8 @@ class WorkoutOfferingsController < ApplicationController - skip_before_action :authenticate_user!, :only => :practice + # Isn't this handled by authorize_resource already? + # skip_before_action :authenticate_user!, :only => :practice load_and_authorize_resource - skip_authorize_resource :only => :practice #~ Action methods ........................................................... diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index a094a694..815c836e 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -1,5 +1,6 @@ set :stage, :staging -set :branch, 'rails-5-upgrade' +# set :branch, 'rails-5-upgrade' +set :branch, 'staging' set :rails_env, 'staging' # Simple Role Syntax From 06b7bf8b678757742c7cb7521345632b681395c9 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Mon, 18 Nov 2024 13:31:44 -0500 Subject: [PATCH 046/117] updated db/schema.rb after successful deployment --- db/schema.rb | 289 +++++++++++++++++++++++++-------------------------- 1 file changed, 139 insertions(+), 150 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index e2fb7443..08510747 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,9 +10,9 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20211101005101) do +ActiveRecord::Schema.define(version: 2021_11_01_005101) do - create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "namespace" t.text "body" t.string "resource_id", default: "", null: false @@ -26,7 +26,7 @@ t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id" end - create_table "attempts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "attempts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.integer "exercise_version_id", null: false t.datetime "submit_time", null: false @@ -47,14 +47,14 @@ t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id" end - create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "tag_user_score_id" t.index ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk" end - create_table "choices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "choices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "multiple_choice_prompt_id", null: false t.integer "position", null: false t.text "feedback" @@ -65,20 +65,20 @@ t.index ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id" end - create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "choice_id" t.integer "multiple_choice_prompt_answer_id" t.index ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk" end - create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.text "answer" t.text "error" t.integer "error_line_no" end - create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.datetime "created_at" t.datetime "updated_at" t.string "class_name" @@ -89,7 +89,7 @@ t.boolean "hide_examples", default: false, null: false end - create_table "course_enrollments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "course_enrollments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.integer "course_offering_id", null: false t.integer "course_role_id", null: false @@ -99,7 +99,7 @@ t.index ["user_id"], name: "index_course_enrollments_on_user_id" end - create_table "course_exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "course_exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "course_id", null: false t.integer "exercise_id", null: false t.datetime "created_at" @@ -108,7 +108,7 @@ t.index ["exercise_id"], name: "course_exercises_exercise_id_fk" end - create_table "course_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "course_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "course_id", null: false t.integer "term_id", null: false t.string "label", default: "", null: false @@ -123,7 +123,7 @@ t.index ["term_id"], name: "index_course_offerings_on_term_id" end - create_table "course_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "course_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "can_manage_course", default: false, null: false t.boolean "can_manage_assignments", default: false, null: false @@ -132,7 +132,7 @@ t.boolean "builtin", default: false, null: false end - create_table "courses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "courses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.string "number", default: "", null: false t.integer "organization_id", null: false @@ -147,20 +147,23 @@ t.index ["user_group_id"], name: "index_courses_on_user_group_id" end - create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| - t.text "class_name" - t.text "status" + create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.string "usable_type" + t.integer "usable_id" + t.string "class_name" t.text "message" t.text "trace" - t.text "target" - t.text "referrer" + t.text "target_url" + t.text "referer_url" t.text "params" t.text "user_agent" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index ["class_name"], name: "index_errors_on_class_name" + t.index ["created_at"], name: "index_errors_on_created_at" end - create_table "exercise_collections", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercise_collections", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" t.integer "user_group_id" @@ -175,20 +178,20 @@ t.index ["user_id"], name: "index_exercise_collections_on_user_id" end - create_table "exercise_families", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercise_families", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" end - create_table "exercise_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercise_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "exercise_id", null: false t.integer "owner_id", null: false t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true t.index ["owner_id"], name: "exercise_owners_owner_id_fk" end - create_table "exercise_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercise_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "stem_id" t.datetime "created_at" t.datetime "updated_at" @@ -203,16 +206,14 @@ t.index ["stem_id"], name: "index_exercise_versions_on_stem_id" end - add_index "exercise_versions", ["creator_id"], name: "exercise_versions_creator_id_fk", using: :btree - add_index "exercise_versions", ["irt_data_id"], name: "exercise_versions_irt_data_id_fk", using: :btree - add_index "exercise_versions", ["stem_id"], name: "index_exercise_versions_on_stem_id", using: :btree - - create_table "exercise_versions_resource_files", id: false, force: :cascade do |t| - t.integer "exercise_version_id", limit: 4, null: false - t.integer "resource_file_id", limit: 4, null: false + create_table "exercise_versions_resource_files", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.integer "exercise_version_id", null: false + t.integer "resource_file_id", null: false + t.index ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id" + t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id" end - create_table "exercise_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercise_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "exercise_id", null: false t.integer "workout_id", null: false t.integer "position", null: false @@ -223,7 +224,7 @@ t.index ["workout_id"], name: "exercise_workouts_workout_id_fk" end - create_table "exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "question_type", null: false t.integer "current_version_id" t.datetime "created_at" @@ -244,13 +245,7 @@ t.index ["is_public"], name: "index_exercises_on_is_public" end - add_index "exercises", ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id", using: :btree - add_index "exercises", ["exercise_family_id"], name: "index_exercises_on_exercise_family_id", using: :btree - add_index "exercises", ["external_id"], name: "index_exercises_on_external_id", unique: true, using: :btree - add_index "exercises", ["irt_data_id"], name: "exercises_irt_data_id_fk", using: :btree - add_index "exercises", ["is_public"], name: "index_exercises_on_is_public", using: :btree - - create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "slug", default: "", null: false t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 @@ -262,14 +257,14 @@ t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" end - create_table "global_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "global_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "can_manage_all_courses", default: false, null: false t.boolean "can_edit_system_configuration", default: false, null: false t.boolean "builtin", default: false, null: false end - create_table "group_access_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "group_access_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "user_group_id" t.boolean "pending", default: true @@ -280,7 +275,7 @@ t.index ["user_id"], name: "index_group_access_requests_on_user_id" end - create_table "identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.string "provider", default: "", null: false t.string "uid", default: "", null: false @@ -290,20 +285,14 @@ t.index ["user_id"], name: "index_identities_on_user_id" end - create_table "irt_data", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "irt_data", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "attempt_count", null: false t.float "sum_of_scores", null: false t.float "difficulty", null: false t.float "discrimination", null: false end - create_table "languages", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t| - t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" t.boolean "can_fork" @@ -312,7 +301,7 @@ t.datetime "updated_at" end - create_table "licenses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "licenses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" t.string "url" @@ -322,7 +311,7 @@ t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id" end - create_table "lms_instances", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "lms_instances", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "consumer_key" t.string "consumer_secret" t.datetime "created_at" @@ -335,14 +324,14 @@ t.index ["url"], name: "index_lms_instances_on_url", unique: true end - create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false + create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.index ["name"], name: "index_lms_types_on_name", unique: true end - create_table "lti_identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "lti_identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "lti_user_id" t.integer "user_id" t.integer "lms_instance_id" @@ -352,7 +341,7 @@ t.index ["user_id"], name: "index_lti_identities_on_user_id" end - create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "workout_id" t.string "lms_assignment_id", null: false t.datetime "created_at" @@ -362,22 +351,22 @@ t.index ["workout_id"], name: "index_lti_workouts_on_workout_id" end - create_table "memberships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "memberships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "user_group_id" t.datetime "created_at" t.datetime "updated_at" end - create_table "multiple_choice_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "multiple_choice_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| end - create_table "multiple_choice_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "multiple_choice_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.boolean "allow_multiple", default: false, null: false t.boolean "is_scrambled", default: true, null: false end - create_table "organizations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "organizations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" @@ -387,18 +376,18 @@ t.index ["slug"], name: "index_organizations_on_slug", unique: true end - create_table "ownerships", force: :cascade do |t| - t.string "filename", limit: 255 - t.integer "resource_file_id", limit: 4 - t.integer "exercise_version_id", limit: 4 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + create_table "ownerships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.string "filename" + t.integer "resource_file_id" + t.integer "exercise_version_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["exercise_version_id"], name: "index_ownerships_on_exercise_version_id" + t.index ["filename"], name: "index_ownerships_on_filename" + t.index ["resource_file_id"], name: "index_ownerships_on_resource_file_id" end - add_index "ownerships", ["exercise_version_id"], name: "index_ownerships_on_exercise_version_id", using: :btree - add_index "ownerships", ["filename"], name: "index_ownerships_on_filename", using: :btree - - create_table "prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "prompt_id" t.integer "actable_id" @@ -409,7 +398,7 @@ t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id" end - create_table "prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "exercise_version_id", null: false t.text "question", null: false t.integer "position", null: false @@ -424,23 +413,20 @@ t.index ["irt_data_id"], name: "prompts_irt_data_id_fk" end - add_index "prompts", ["actable_id"], name: "index_prompts_on_actable_id", using: :btree - add_index "prompts", ["irt_data_id"], name: "prompts_irt_data_id_fk", using: :btree - - create_table "resource_files", force: :cascade do |t| - t.string "filename", limit: 255 - t.string "token", limit: 255, default: "", null: false - t.integer "user_id", limit: 4, null: false - t.boolean "public", default: true + create_table "resource_files", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.string "filename" + t.string "token", default: "", null: false + t.integer "user_id", null: false + t.boolean "public", default: true t.datetime "created_at" t.datetime "updated_at" - t.string "hashval", limit: 255 + t.string "hashval" + t.index ["hashval"], name: "index_resource_files_on_hashval" t.index ["token"], name: "index_resource_files_on_token" t.index ["user_id"], name: "index_resource_files_on_user_id" - t.index ["hashval"], name: "index_resource_files_on_hashval" end - create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "first_name" t.string "last_name_name" t.string "email" @@ -450,13 +436,13 @@ t.datetime "updated_at" end - create_table "stems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "stems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.text "preamble" t.datetime "created_at" t.datetime "updated_at" end - create_table "student_extensions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "student_extensions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "workout_offering_id" t.datetime "soft_deadline" @@ -469,7 +455,7 @@ t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id" end - create_table "tag_user_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "tag_user_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.integer "experience", default: 0 t.datetime "created_at" @@ -478,7 +464,7 @@ t.index ["user_id"], name: "index_tag_user_scores_on_user_id" end - create_table "taggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "taggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -497,13 +483,13 @@ t.index ["tagger_id"], name: "index_taggings_on_tagger_id" end - create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name" t.integer "taggings_count", default: 0 t.index ["name"], name: "index_tags_on_name", unique: true end - create_table "terms", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "terms", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "season", null: false t.date "starts_on", null: false t.date "ends_on", null: false @@ -515,11 +501,11 @@ t.index ["year", "season"], name: "index_terms_on_year_and_season" end - create_table "test_case_results", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "test_case_results", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "test_case_id", null: false t.integer "user_id", null: false t.text "execution_feedback" - t.integer "feedback_line_no" + t.integer "feedback_line_no" t.datetime "created_at" t.datetime "updated_at" t.boolean "pass", null: false @@ -529,7 +515,7 @@ t.index ["user_id"], name: "index_test_case_results_on_user_id" end - create_table "test_cases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "test_cases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.text "negative_feedback" t.float "weight", null: false t.text "description" @@ -545,22 +531,22 @@ t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id" end - create_table "time_zones", force: :cascade do |t| - t.string "name", limit: 255 - t.string "zone", limit: 255 - t.string "display_as", limit: 255 + create_table "time_zones", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.string "name" + t.string "zone" + t.string "display_as" t.datetime "created_at" t.datetime "updated_at" end - create_table "user_groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "user_groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" t.text "description" end - create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -592,7 +578,7 @@ t.index ["time_zone_id"], name: "index_users_on_time_zone_id" end - create_table "visualization_loggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| + create_table "visualization_loggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "exercise_id" t.integer "workout_id" @@ -605,7 +591,7 @@ t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id" end - create_table "workout_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "workout_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "course_offering_id", null: false t.integer "workout_id", null: false t.datetime "created_at" @@ -613,53 +599,51 @@ t.datetime "opening_date" t.datetime "soft_deadline" t.datetime "hard_deadline" - t.boolean "published", default: true, null: false - t.integer "time_limit", limit: 4 - t.integer "workout_policy_id", limit: 4 - t.integer "continue_from_workout_id", limit: 4 - t.string "lms_assignment_id", limit: 255 - t.boolean "most_recent", default: true - t.string "lms_assignment_url", limit: 255 - t.integer "attempt_limit", limit: 4 - end - - add_index "workout_offerings", ["continue_from_workout_id"], name: "workout_offerings_continue_from_workout_id_fk", using: :btree - add_index "workout_offerings", ["course_offering_id"], name: "index_workout_offerings_on_course_offering_id", using: :btree - add_index "workout_offerings", ["lms_assignment_id"], name: "index_workout_offerings_on_lms_assignment_id", using: :btree - add_index "workout_offerings", ["workout_id"], name: "index_workout_offerings_on_workout_id", using: :btree - add_index "workout_offerings", ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id", using: :btree - - create_table "workout_owners", force: :cascade do |t| - t.integer "workout_id", limit: 4, null: false - t.integer "owner_id", limit: 4, null: false - end - - add_index "workout_owners", ["owner_id"], name: "workout_owners_owner_id_fk", using: :btree - add_index "workout_owners", ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true, using: :btree - - create_table "workout_policies", force: :cascade do |t| - t.boolean "hide_thumbnails_before_start" - t.boolean "hide_feedback_before_finish" - t.boolean "hide_compilation_feedback_before_finish" - t.boolean "no_review_before_close" - t.boolean "hide_feedback_in_review_before_close" - t.boolean "hide_thumbnails_in_review_before_close" - t.boolean "no_hints" - t.boolean "no_faq" - t.string "name", limit: 255 - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "invisible_before_review" - t.string "description", limit: 255 - t.boolean "hide_score_before_finish" - t.boolean "hide_score_in_review_before_close" - end - - create_table "workout_scores", force: :cascade do |t| - t.integer "workout_id", limit: 4, null: false - t.integer "user_id", limit: 4, null: false - t.float "score", limit: 24 - t.boolean "completed" + t.boolean "published", default: true, null: false + t.integer "time_limit" + t.integer "workout_policy_id" + t.integer "continue_from_workout_id" + t.string "lms_assignment_id" + t.boolean "most_recent", default: true + t.string "lms_assignment_url" + t.integer "attempt_limit" + t.index ["continue_from_workout_id"], name: "workout_offerings_continue_from_workout_id_fk" + t.index ["course_offering_id"], name: "index_workout_offerings_on_course_offering_id" + t.index ["lms_assignment_id"], name: "index_workout_offerings_on_lms_assignment_id" + t.index ["workout_id"], name: "index_workout_offerings_on_workout_id" + t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id" + end + + create_table "workout_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.integer "workout_id", null: false + t.integer "owner_id", null: false + t.index ["owner_id"], name: "workout_owners_owner_id_fk" + t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true + end + + create_table "workout_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.boolean "hide_thumbnails_before_start" + t.boolean "hide_feedback_before_finish" + t.boolean "hide_compilation_feedback_before_finish" + t.boolean "no_review_before_close" + t.boolean "hide_feedback_in_review_before_close" + t.boolean "hide_thumbnails_in_review_before_close" + t.boolean "no_hints" + t.boolean "no_faq" + t.string "name" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "invisible_before_review" + t.string "description" + t.boolean "hide_score_before_finish" + t.boolean "hide_score_in_review_before_close" + end + + create_table "workout_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + t.integer "workout_id", null: false + t.integer "user_id", null: false + t.float "score" + t.boolean "completed" t.datetime "completed_at" t.datetime "last_attempted_at" t.integer "exercises_completed" @@ -677,7 +661,7 @@ t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk" end - create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "scrambled", default: false t.datetime "created_at" @@ -711,6 +695,7 @@ add_foreign_key "courses", "organizations", name: "courses_organization_id_fk" add_foreign_key "exercise_owners", "exercises", name: "exercise_owners_exercise_id_fk" add_foreign_key "exercise_owners", "users", column: "owner_id", name: "exercise_owners_owner_id_fk" + add_foreign_key "exercise_versions", "exercises", name: "exercise_versions_exercise_id_fk" add_foreign_key "exercise_versions", "irt_data", column: "irt_data_id", name: "exercise_versions_irt_data_id_fk" add_foreign_key "exercise_versions", "stems", name: "exercise_versions_stem_id_fk" add_foreign_key "exercise_versions", "users", column: "creator_id", name: "exercise_versions_creator_id_fk" @@ -719,13 +704,16 @@ add_foreign_key "exercise_workouts", "exercises", name: "exercise_workouts_exercise_id_fk" add_foreign_key "exercise_workouts", "workouts", name: "exercise_workouts_workout_id_fk" add_foreign_key "exercises", "exercise_families", name: "exercises_exercise_family_id_fk" + add_foreign_key "exercises", "exercise_versions", column: "current_version_id", name: "exercises_current_version_id_fk" add_foreign_key "exercises", "irt_data", column: "irt_data_id", name: "exercises_irt_data_id_fk" add_foreign_key "identities", "users", name: "identities_user_id_fk" add_foreign_key "lms_instances", "lms_types", name: "lms_instances_lms_type_id_fk" - add_foreign_key "lti_workouts", "lms_instances", name: "lti_workouts_lms_instance_id_fk" + add_foreign_key "lti_workouts", "lms_instances" add_foreign_key "ownerships", "exercise_versions" + add_foreign_key "ownerships", "resource_files" add_foreign_key "prompt_answers", "attempts", name: "prompt_answers_attempt_id_fk" add_foreign_key "prompt_answers", "prompts", name: "prompt_answers_prompt_id_fk" + add_foreign_key "prompts", "exercise_versions", name: "prompts_exercise_version_id_fk" add_foreign_key "prompts", "irt_data", column: "irt_data_id", name: "prompts_irt_data_id_fk" add_foreign_key "resource_files", "users", name: "resource_files_user_id_fk" add_foreign_key "student_extensions", "users", name: "student_extensions_user_id_fk" @@ -734,6 +722,7 @@ add_foreign_key "test_case_results", "coding_prompt_answers", name: "test_case_results_coding_prompt_answer_id_fk" add_foreign_key "test_case_results", "test_cases", name: "test_case_results_test_case_id_fk" add_foreign_key "test_case_results", "users", name: "test_case_results_user_id_fk" + add_foreign_key "test_cases", "coding_prompts", name: "test_cases_coding_prompt_id_fk" add_foreign_key "users", "global_roles", name: "users_global_role_id_fk" add_foreign_key "users", "time_zones", name: "users_time_zone_id_fk" add_foreign_key "users", "workout_scores", column: "current_workout_score_id", name: "users_current_workout_score_id_fk" @@ -743,7 +732,7 @@ add_foreign_key "workout_offerings", "workouts", name: "workout_offerings_workout_id_fk" add_foreign_key "workout_owners", "users", column: "owner_id", name: "workout_owners_owner_id_fk" add_foreign_key "workout_owners", "workouts", name: "workout_owners_workout_id_fk" - add_foreign_key "workout_scores", "lti_workouts", name: "workout_scores_lti_workout_id_fk" + add_foreign_key "workout_scores", "lti_workouts" add_foreign_key "workout_scores", "users", name: "workout_scores_user_id_fk" add_foreign_key "workout_scores", "workout_offerings", name: "workout_scores_workout_offering_id_fk" add_foreign_key "workout_scores", "workouts", name: "workout_scores_workout_id_fk" From e2afc7b634f4fdd501415a7c39718b2c0931bdf5 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 10 Dec 2024 10:38:19 -0500 Subject: [PATCH 047/117] added rails 4.2 version constraint to 2024 migrations --- .../20240207035240_add_compound_index_to_workout_scores.rb | 2 +- db/migrate/20240207040304_add_compound_index_to_attempts.rb | 2 +- .../20240207161742_add_lti_user_id_index_to_lti_identities.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/migrate/20240207035240_add_compound_index_to_workout_scores.rb b/db/migrate/20240207035240_add_compound_index_to_workout_scores.rb index 1be6f06f..52ca4a00 100644 --- a/db/migrate/20240207035240_add_compound_index_to_workout_scores.rb +++ b/db/migrate/20240207035240_add_compound_index_to_workout_scores.rb @@ -1,4 +1,4 @@ -class AddCompoundIndexToWorkoutScores < ActiveRecord::Migration +class AddCompoundIndexToWorkoutScores < ActiveRecord::Migration[4.2] def change add_index :workout_scores, [:user_id, :workout_id, :workout_offering_id], name: 'idx_ws_on_user_workout_workout_offering' diff --git a/db/migrate/20240207040304_add_compound_index_to_attempts.rb b/db/migrate/20240207040304_add_compound_index_to_attempts.rb index 6c3d48b6..cbda48c9 100644 --- a/db/migrate/20240207040304_add_compound_index_to_attempts.rb +++ b/db/migrate/20240207040304_add_compound_index_to_attempts.rb @@ -1,4 +1,4 @@ -class AddCompoundIndexToAttempts < ActiveRecord::Migration +class AddCompoundIndexToAttempts < ActiveRecord::Migration[4.2] def change add_index :attempts, [:user_id, :exercise_version_id], name: 'idx_attempts_on_user_exercise_version' diff --git a/db/migrate/20240207161742_add_lti_user_id_index_to_lti_identities.rb b/db/migrate/20240207161742_add_lti_user_id_index_to_lti_identities.rb index 6a94b856..f04b9f7c 100644 --- a/db/migrate/20240207161742_add_lti_user_id_index_to_lti_identities.rb +++ b/db/migrate/20240207161742_add_lti_user_id_index_to_lti_identities.rb @@ -1,4 +1,4 @@ -class AddLtiUserIdIndexToLtiIdentities < ActiveRecord::Migration +class AddLtiUserIdIndexToLtiIdentities < ActiveRecord::Migration[4.2] def change add_index :lti_identities, :lti_user_id end From cb9070a3896124e44d5f1412710735077f594d2f Mon Sep 17 00:00:00 2001 From: s-edwards Date: Fri, 7 Feb 2025 10:59:41 -0500 Subject: [PATCH 048/117] updated from live staging server --- db/schema.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 8cb24948..81f27878 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20240207161742) do +ActiveRecord::Schema.define(version: 2024_02_07_161742) do create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "namespace" @@ -43,13 +43,12 @@ t.decimal "worker_time", precision: 10 t.index ["active_score_id"], name: "index_attempts_on_active_score_id" t.index ["exercise_version_id"], name: "index_attempts_on_exercise_version_id" + t.index ["user_id", "exercise_version_id"], name: "idx_attempts_on_user_exercise_version" t.index ["user_id"], name: "index_attempts_on_user_id" + t.index ["workout_score_id", "exercise_version_id"], name: "idx_attempts_on_workout_score_exercise_version" t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id" end - add_index "attempts", ["user_id", "exercise_version_id"], name: "idx_attempts_on_user_exercise_version", using: :btree - add_index "attempts", ["workout_score_id", "exercise_version_id"], name: "idx_attempts_on_workout_score_exercise_version", using: :btree - create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "tag_user_score_id" @@ -341,11 +340,10 @@ t.datetime "created_at" t.datetime "updated_at" t.index ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id" + t.index ["lti_user_id"], name: "index_lti_identities_on_lti_user_id" t.index ["user_id"], name: "index_lti_identities_on_user_id" end - add_index "lti_identities", ["lti_user_id"], name: "index_lti_identities_on_lti_user_id", using: :btree - create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.integer "workout_id" t.string "lms_assignment_id", null: false @@ -661,13 +659,12 @@ t.integer "lti_workout_id" t.datetime "started_at" t.index ["lti_workout_id"], name: "index_workout_scores_on_lti_workout_id" + t.index ["user_id", "workout_id", "workout_offering_id"], name: "idx_ws_on_user_workout_workout_offering" t.index ["user_id"], name: "index_workout_scores_on_user_id" t.index ["workout_id"], name: "index_workout_scores_on_workout_id" t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk" end - add_index "workout_scores", ["user_id", "workout_id", "workout_offering_id"], name: "idx_ws_on_user_workout_workout_offering", using: :btree - create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "scrambled", default: false @@ -700,6 +697,7 @@ add_foreign_key "course_offerings", "courses", name: "course_offerings_course_id_fk" add_foreign_key "course_offerings", "terms", name: "course_offerings_term_id_fk" add_foreign_key "courses", "organizations", name: "courses_organization_id_fk" + add_foreign_key "exercise_owners", "exercises", name: "exercise_owners_exercise_id_fk" add_foreign_key "exercise_owners", "users", column: "owner_id", name: "exercise_owners_owner_id_fk" add_foreign_key "exercise_versions", "exercises", name: "exercise_versions_exercise_id_fk" add_foreign_key "exercise_versions", "irt_data", column: "irt_data_id", name: "exercise_versions_irt_data_id_fk" From c2af174eb595bacff0f0338d94656b55abca45af Mon Sep 17 00:00:00 2001 From: s-edwards Date: Fri, 7 Feb 2025 11:20:45 -0500 Subject: [PATCH 049/117] fixed publicly_visible() error --- app/models/exercise.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 6249e1af..b6dff0a9 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -187,16 +187,16 @@ def self.visible_to_user(user) def self.publicly_visible public_license = Exercise.joins( exercise_collection: [ license: :license_policy ]) - .where(is_public: nil, exercise_collection: - { license: - { license_policy: - { is_public: true } } } - ) + .where('exercises.is_public is null and license_policies.is_public = true') + # .where(is_public: nil, exercise_collection: + # { license: + # { license_policy: + # { is_public: true } } } + # ) - public_exercise = Exercise.where(is_public: true) + public_exercises = Exercise.where(is_public: true) - return Exercise.joins(exercise_collection: [ license: :license_policy ]) - .where('(exercises.is_public is null and license_policies.is_public = true) or exercises.is_public = true') + return public_exercises.union(public_license) end From fcf881da54ec8731571e9e23022d77fea3c41b51 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 18 Feb 2025 11:38:55 -0500 Subject: [PATCH 050/117] upgraded errors table for v0.8.0.0 of exception_handler gem --- .../20250218154318_upgrade_errors_table.rb | 21 +++++++++++++++++++ db/schema.rb | 11 +++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20250218154318_upgrade_errors_table.rb diff --git a/db/migrate/20250218154318_upgrade_errors_table.rb b/db/migrate/20250218154318_upgrade_errors_table.rb new file mode 100644 index 00000000..2cdf61ca --- /dev/null +++ b/db/migrate/20250218154318_upgrade_errors_table.rb @@ -0,0 +1,21 @@ +class UpgradeErrorsTable < ActiveRecord::Migration[5.2] + + # Upgrades the structure of the "errors" table used to store exception + # data using the exception_handler gem. The gem changed the table structure + # without including a corresponding migration of its own, so this migration + # is intended to change the structure to match the gem's 0.8.0.0 version. + + # The list of attributes from the exception model class in the gem: + # ATTRS = %i(class_name status message trace target referrer params user_agent) + + # The old structure: (class_name message trace target_url referer_url params user_agent) + + def change + change_table :errors do |t| + t.rename :target_url, :target + t.rename :referer_url, :referrer + t.change :class_name, :text + t.text :status + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 81f27878..a73a45ac 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_02_07_161742) do +ActiveRecord::Schema.define(version: 2025_02_18_154318) do create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "namespace" @@ -152,16 +152,17 @@ create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| t.string "usable_type" t.integer "usable_id" - t.string "class_name" + t.text "class_name" t.text "message" t.text "trace" - t.text "target_url" - t.text "referer_url" + t.text "target" + t.text "referrer" t.text "params" t.text "user_agent" t.datetime "created_at" t.datetime "updated_at" - t.index ["class_name"], name: "index_errors_on_class_name" + t.text "status" + t.index ["class_name"], name: "index_errors_on_class_name", length: 1024 t.index ["created_at"], name: "index_errors_on_created_at" end From f2498269b8ff67050c0121ab71e917748c841e73 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Thu, 20 Feb 2025 22:23:48 -0500 Subject: [PATCH 051/117] PEML translation now working, but still some data model mismatches (method name, class name, and csv-unquoted) --- app/controllers/exercises_controller.rb | 56 ++++--- app/utils/peml_parsing_util.rb | 200 +++++++++++++++--------- app/views/layouts/_flash.html.haml | 5 +- config/routes.rb | 12 +- 4 files changed, 172 insertions(+), 101 deletions(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index d4efbf16..ddf6431d 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -165,7 +165,7 @@ def new # ------------------------------------------------------------- # GET /exercises/1/edit def edit - puts "ResourceFile.UPLOAD_PATH = #{ResourceFile::UPLOAD_PATH}" + logger.debug "ResourceFile.UPLOAD_PATH = #{ResourceFile::UPLOAD_PATH}" @exercise_version = @exercise.current_version @attached_files = [] @exercise_version.ownerships.each do |e| @@ -201,6 +201,7 @@ def edit # ------------------------------------------------------------- # POST /exercises def create + # REMOVE? ex = Exercise.new exercise_version = ExerciseVersion.new(exercise: ex) msg = params[:exercise] || params[:coding_question] @@ -275,6 +276,7 @@ def random_exercise # ------------------------------------------------------------- # POST exercises/create_mcqs def create_mcqs + # REMOVE CSV.foreach(params[:form].fetch(:mcqfile).path, {headers: true}) do |row| if row['Question'].include?('Python') next @@ -289,22 +291,26 @@ def create_mcqs # ------------------------------------------------------------- # GET exercises/upload_mcqs def upload_mcqs + # REMOVE end # ------------------------------------------------------------- # GET exercises/upload_exercises def upload + # REMOVE end # ------------------------------------------------------------- def upload_yaml + # REMOVE end # ------------------------------------------------------------- def yaml_create + # REMOVE @yaml_exers = YAML.load_file(params[:form].fetch(:yamlfile).path) @yaml_exers.each do |exercise| @ex = Exercise.new @@ -371,22 +377,30 @@ def upload_create text_representation = File.read(params[:form][:file].path) edit_rights = 0 # Personal exercise end - if text_representation.to_s.include? ('exercise_id') - hash = PemlParsingUtil.new.parse(text_representation) - else + if text_representation.start_with?('---') hash = YAML.load(text_representation) - end - files = exercise_params[:files] - fileList = exercise_params[:fileList] - if fileList != "" && !files.nil? - files = cleanFile(files,fileList) + else + logger.debug '==========' + logger.debug 'PEML Input' + logger.debug '==========' + logger.debug text_representation + hash = PemlParsingUtil.new.parse(text_representation) + logger.debug '==========' + logger.debug 'PEML Hash' + logger.debug '==========' + logger.debug hash.to_yaml + logger.debug '==========' end if !hash.kind_of?(Array) hash = [hash] end files = exercise_params[:files] - puts "files = #{files.inspect}" + fileList = exercise_params[:fileList] + if fileList != "" && !files.nil? + files = cleanFile(files,fileList) + end + logger.debug "files = #{files.inspect}" @attached_files = exercise_params[:attached_files] if @attached_files == "null" @attached_files = nil @@ -394,7 +408,7 @@ def upload_create if @attached_files @attached_files = JSON.parse(@attached_files) end - puts "attached files = #{@attached_files.inspect}" + logger.debug "attached files = #{@attached_files.inspect}" # figure out if we need to add this to an exercise collection exercise_collection = nil @@ -430,7 +444,7 @@ def upload_create if !e.save success_all = false # put together an error message - error_msgs << "Errors while saving exercise #{e.andand.name}:
      " + error_msgs << "

      Errors while saving exercise #{e.andand.name}:

        " e.errors.full_messages.each do |msg| error_msgs << "
      • #{msg}
      • " end @@ -444,16 +458,16 @@ def upload_create # copy all retained resource files, skipping any to be removed prev_version = e.exercise_versions.offset(1).first if prev_version - puts "processing ownerships from prev version #{prev_version.id}" + logger.debug "processing ownerships from prev version #{prev_version.id}" prev_version.ownerships.each do |o| - puts "checking ownership #{o.inspect}" + logger.debug "checking ownership #{o.inspect}" # Double-loop isn't the greatest design, but both lists are short @attached_files.each do |a| - puts "checking against attachment #{a.inspect}" + logger.debug "checking against attachment #{a.inspect}" # uploaded flag is true if it was previously uploaded # deleted flag is true if it is to be pruned/removed from exercise if a['name'] == o.filename && a['uploaded'] && !a['deleted'] - puts "adding ownership record" + logger.debug "adding ownership record" ownertable = ex_ver.ownerships.create!( filename: o.filename, resource_file: o.resource_file) @@ -464,9 +478,9 @@ def upload_create # Now add all newly uploaded attached files if files files.each do |file| - puts "processing new upload #{file.inspect}" + logger.debug "processing new upload #{file.inspect}" @attached_files.each do |a| - puts "checking against attachment #{a.inspect}" + logger.debug "checking against attachment #{a.inspect}" if a['name'] == file.original_filename && !a['uploaded'] && !a['deleted'] Ownership.create!( filename: file.original_filename, @@ -495,10 +509,10 @@ def upload_create redirect_to @return_to, flash: { success: success_msgs.html_safe } and return else if !success_msgs.blank? - error_msgs << 'Some exercises were successfully saved.' + error_msgs << '

        Some exercises were successfully saved.

        ' error_msgs << '
          ' + success_msgs.join('') + '
        ' end - redirect_to @return_to, flash: { error: error_msgs.join("").html_safe } and return + redirect_back fallback_location: @return_to, flash: { error: error_msgs.join("").html_safe } and return end end @@ -934,7 +948,7 @@ def evaluate if exercise_prompt_answer.save CodeWorker.new.async.perform(@attempt.id) else - puts 'IMPROPER PROMPT', + logger.error 'IMPROPER PROMPT', 'unable to save prompt_answer: ' \ "#{prompt_answer.errors.full_messages.to_s}", 'IMPROPER PROMPT' diff --git a/app/utils/peml_parsing_util.rb b/app/utils/peml_parsing_util.rb index 924072e4..0018549e 100644 --- a/app/utils/peml_parsing_util.rb +++ b/app/utils/peml_parsing_util.rb @@ -4,87 +4,141 @@ require 'uri' class PemlParsingUtil - def parse (text_representation) - convert_peml(Peml::Loader.new.load(text_representation).dottie!) - end + def parse (text_representation) + convert_peml(Peml::Loader.new.load(text_representation).dottie!) + end # Convert the parsed peml hash into a hash corresponding to exercise data model - def convert_peml(hash) + def convert_peml(hash) + hash = Dottie(hash) #starting with three compulsory peml keys - new_hash = {"external_id" => hash["exercise_id"], "name" => hash["title"]}.dottie! - new_hash["experience"] = hash["difficulty"] - new_hash["tag_list"] = hash["tag"].to_s + new_hash = { + 'external_id' => hash['exercise_id'], + 'name' => hash['title'] + }.dottie! + new_hash['experience'] = hash['difficulty'] if hash['difficulty'] + new_hash['tag_list'] = hash['tags.topics'].to_s if hash['tags.topics'] - # PEML does not have an is_public equivalent so we put this value under a key of the same name - new_hash["is_public"] = hash["is_public"] + # PEML does not have an is_public equivalent so we put this value + # under a key of the same name + # new_hash["is_public"] = hash["is_public"] - # PEML is designed to handle programming assignments and 'code writing' is the equivalent in code-workout - new_hash["style_list"] = "code writing" + # PEML is designed to handle programming assignments and 'code writing' + # is the equivalent in code-workout + new_hash['style_list'] = hash['tags.style'].to_s || 'code writing' - new_hash["language_list"] = "" - hash["systems"].each do |system| - new_hash["language_list"]+=system["language"] - end + starter_code = nil + wrapper_code = nil + tests = nil + systems = hash['systems'] + if systems + system = systems.first + new_hash['language_list'] = system['language'] + # assets should be under the system, so try to grab them here first + Rails.logger.debug 'get_file_content(system[\'assets.code.starter\'])' + starter_code = get_file_content(system['assets.code.starter']) + Rails.logger.debug 'get_file_content(system[\'assets.code.wrapper\'])' + wrapper_code = get_file_content(system['assets.code.wrapper']) + Rails.logger.debug 'get_file_content(system[\'assets.test\'])' + tests = get_file_content(system['assets.test']) + end - new_hash["current_version"] = {} - new_hash["current_version.version"] = hash["version.id"] - new_hash["current_version.creator"] = get_author(hash) - new_hash["current_version.prompts"] = [] - prompt = {"position" => 1, "question" => hash["instructions"], "class_name" => "", "method_name" => ""} - - #------------------------------------------------------------------------------------------- - # PEML assets are either remote URLs or array of files which have different keys. - # We try to fetch content from the files. - prompt["starter_code"] = get_content(hash["assets.code.starter"]) - prompt["wrapper_code"] = get_content(hash["assets.code.wrapper"]) - prompt["tests"] = get_content(hash["assets.test"]) - #------------------------------------------------------------------------------------------- - - #Again, PEML is designed for coding problems and thus, 'coding_prompt' - new_hash["current_version.prompts"]<<{"coding_prompt" => prompt} - new_hash - end + new_hash['current_version'] = {} + new_hash['current_version.version'] = hash['version.id'] if hash['version.id'] + new_hash['current_version.creator'] = get_author_email(hash) + new_hash['current_version.prompts'] = [] + prompt = { + 'position' => 1, + 'question' => hash['instructions'], + 'class_name' => 'Answer', + 'method_name' => 'answer' + } - def get_author(hash) - creator = "" - if hash.key?("authors") - hash["authors"].each do |author| - if author.is_a? String - creator += author - else - creator += author.key?("email") ? author["email"] : author["name"] - end - creator += "," - end - creator.delete_suffix!(",") - elsif(hash.key?("author")) - if hash["author"].is_a? String - creator = hash["author"] - else - creator = hash["author"].key?("email") ? hash["author.email"] : hash["author.name"] - end - elsif(hash.key?("license")) - creator = hash["license.owner"] - end - creator - end + #----------------------------------------------------------------------- + # PEML assets might be at the global level, if they apply to all + # systems, so fill in missing assets here + Rails.logger.debug 'assets:' + Rails.logger.debug hash['assets'].to_yaml + Rails.logger.debug 'get_file_content(hash[\'assets.code.starter\'])' + starter_code ||= get_file_content(hash['assets.code.starter']) + prompt['starter_code'] = starter_code if starter_code + Rails.logger.debug 'get_file_content(hash[\'assets.code.wrapper\'])' + wrapper_code ||= get_file_content(hash['assets.code.wrapper']) + prompt['wrapper_code'] = wrapper_code if wrapper_code + Rails.logger.debug 'get_file_content(hash[\'assets.test\'])' + tests ||= get_file_content(hash['assets.test']) + # FIXME: give error if missing tests + prompt['tests'] = tests if tests + #----------------------------------------------------------------------- + + # Again, PEML is designed for coding problems and thus, 'coding_prompt' + new_hash['current_version.prompts'] << { 'coding_prompt' => prompt } + new_hash + end - def get_content(asset_child) - asset_collection = [] - if asset_child.is_a? String - begin - uri = URI.parse(asset_child) - if uri.is_a?(URI::HTTP) && !uri.host.nil? - asset_collection << Net::HTTP.get(uri) - end - rescue URI::InvalidURIError - asset_collection << "" - end - elsif asset_child.is_a? Array - asset_child.each do |asset_file| - asset_collection << asset_file["files"]["content"] - end + def get_author_email(hash) + creator = hash['author.email'] || hash['license.owner.email'] + if !creator and hash.key?('authors') + creator = hash['authors[0].email'] + end + Rails.logger.debug "get_author_email() = '#{creator}'" + creator + end + + def get_file_content(files) + content = nil + file = nil + Rails.logger.debug '==========' + Rails.logger.debug 'get_file_content(files), files:' + Rails.logger.debug '==========' + Rails.logger.debug files.to_yaml + Rails.logger.debug files.inspect + if files + file = files['files'][0] + Rails.logger.debug '==========' + Rails.logger.debug 'get_file_content(files), file:' + Rails.logger.debug '==========' + Rails.logger.debug file.to_yaml + Rails.logger.debug file.inspect + end + if file.is_a? String + file.strip! + if file.sub!(/^url\((.*)\)$/, '\1') + uri = URI.parse(file) + if uri.is_a?(URI::HTTP) && !uri.host.nil? + content = Net::HTTP.get(uri) end - asset_collection.join(',') + else + # FIXME: is this an error? + content = file end -end \ No newline at end of file + + # FIXME: add error checking if URL not parsable/readable + elsif file.is_a? Hash + content = file['content'] + else + # FIXME: add error checking if not a hash + end + content + end + +# def get_content(asset_child) +# asset_collection = [] +# if asset_child.is_a? String +# begin +# uri = URI.parse(asset_child) +# if uri.is_a?(URI::HTTP) && !uri.host.nil? +# asset_collection << Net::HTTP.get(uri) +# end +# rescue URI::InvalidURIError +# # asset_collection << "" +# # FIXME: Needs error message generation +# end +# elsif asset_child.is_a? Array +# asset_child.each do |asset_file| +# asset_collection << asset_file["files"]["content"] +# end +# end +# asset_collection.join(',') +# end +end diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index ecd80db3..4d6fa16b 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -3,4 +3,7 @@ %div{ class: "alert #{flash_class_for(type)} fade in", role: 'alert' } %button.close{ data: { dismiss: 'alert'} } × = icon_tag_for(type) - = message + - if message && message.start_with?('<') + = raw message + - else + = message diff --git a/config/routes.rb b/config/routes.rb index ae1c50c9..3e7f25b5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,14 +46,14 @@ # /gym/exercises ... get 'exercises/call_open_pop' => 'exercises#call_open_pop' - get 'exercises_import' => 'exercises#upload_yaml' - post 'exercises_yaml_create' => 'exercises#yaml_create' + get 'exercises_import' => 'exercises#upload_yaml' # REMOVE + post 'exercises_yaml_create' => 'exercises#yaml_create' # REMOVE get 'exercises/upload' => 'exercises#upload', as: :exercises_upload get 'exercises/download' => 'exercises#download', as: :exercises_download post 'exercises/upload_create' => 'exercises#upload_create' get 'exercises/upload_mcqs' => 'exercises#upload_mcqs', - as: :exercises_upload_mcqs - post 'exercises/create_mcqs' => 'exercises#create_mcqs' + as: :exercises_upload_mcqs # REMOVE + post 'exercises/create_mcqs' => 'exercises#create_mcqs' # REMOVE get '/exercises/any' => 'exercises#random_exercise', as: :random_exercise get 'exercises/:id/practice' => 'exercises#practice', @@ -87,8 +87,8 @@ as: :practice_workout get 'workouts/:id/evaluate' => 'workouts#evaluate', as: :workout_evaluate get 'workouts_dummy' => 'workouts#dummy' - get 'workouts_import' => 'workouts#upload_yaml' - post 'workouts_yaml_create' => 'workouts#yaml_create' + get 'workouts_import' => 'workouts#upload_yaml' # REMOVE? + post 'workouts_yaml_create' => 'workouts#yaml_create' # REMOVE? post 'workouts/search' => 'workouts#search', as: :workouts_search get 'workouts/:id/download_attempt_data' => 'workouts#download_attempt_data', as: :download_workout_attempt_data From d89df52facb20367395281656fc034383b3cc0e2 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Sun, 23 Feb 2025 20:06:50 -0500 Subject: [PATCH 052/117] Updates to PEML data mapping --- app/controllers/exercises_controller.rb | 130 ++++++++++--------- app/utils/peml_parsing_util.rb | 158 +++++++++++++++++++----- 2 files changed, 198 insertions(+), 90 deletions(-) diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index ddf6431d..7a7f6543 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -377,6 +377,8 @@ def upload_create text_representation = File.read(params[:form][:file].path) edit_rights = 0 # Personal exercise end + error_msgs = [] + # FIXME: add support for JSON here as well if text_representation.start_with?('---') hash = YAML.load(text_representation) else @@ -384,12 +386,23 @@ def upload_create logger.debug 'PEML Input' logger.debug '==========' logger.debug text_representation - hash = PemlParsingUtil.new.parse(text_representation) + hash = PemlParsingUtil.new.parse(text_representation, error_msgs) logger.debug '==========' logger.debug 'PEML Hash' logger.debug '==========' logger.debug hash.to_yaml logger.debug '==========' + + if !error_msgs.empty? + raw_msgs = error_msgs + error_msgs = [] + error_msgs << "Errors while parsing PEML for exercise #{hash['name']}:
          " + raw_msgs.each do |msg| + error_msgs << "
        • #{msg}
        • " + end + error_msgs << "
        " + end + end if !hash.kind_of?(Array) hash = [hash] @@ -437,79 +450,82 @@ def upload_create # parse the text_representation exercises = ExerciseRepresenter.for_collection.new([]).from_hash(hash) - success_all = true - error_msgs = [] + success_all = error_msgs.empty? success_msgs = [] - exercises.each do |e| - if !e.save - success_all = false - # put together an error message - error_msgs << "

        Errors while saving exercise #{e.andand.name}:

          " - e.errors.full_messages.each do |msg| - error_msgs << "
        • #{msg}
        • " - end - error_msgs << "
        " - else # successfully created the exercise - ex_ver = e.current_version - - # make the current user an exercise owner if they aren't already - e.add_owner!(current_user) - - # copy all retained resource files, skipping any to be removed - prev_version = e.exercise_versions.offset(1).first - if prev_version - logger.debug "processing ownerships from prev version #{prev_version.id}" - prev_version.ownerships.each do |o| - logger.debug "checking ownership #{o.inspect}" - # Double-loop isn't the greatest design, but both lists are short - @attached_files.each do |a| - logger.debug "checking against attachment #{a.inspect}" - # uploaded flag is true if it was previously uploaded - # deleted flag is true if it is to be pruned/removed from exercise - if a['name'] == o.filename && a['uploaded'] && !a['deleted'] - logger.debug "adding ownership record" - ownertable = ex_ver.ownerships.create!( - filename: o.filename, - resource_file: o.resource_file) + successful_exercise = nil + if success_all + exercises.each do |e| + if !e.save + success_all = false + # put together an error message + error_msgs << "Errors while saving exercise #{e.andand.name}:
          " + e.errors.full_messages.each do |msg| + error_msgs << "
        • #{msg}
        • " + end + error_msgs << "
        " + else # successfully created the exercise + successful_exercise ||= e + ex_ver = e.current_version + + # make the current user an exercise owner if they aren't already + e.add_owner!(current_user) + + # copy all retained resource files, skipping any to be removed + prev_version = e.exercise_versions.offset(1).first + if prev_version + logger.debug "processing ownerships from prev version #{prev_version.id}" + prev_version.ownerships.each do |o| + logger.debug "checking ownership #{o.inspect}" + # Double-loop isn't the greatest design, but both lists are short + @attached_files.each do |a| + logger.debug "checking against attachment #{a.inspect}" + # uploaded flag is true if it was previously uploaded + # deleted flag is true if it is to be pruned/removed from exercise + if a['name'] == o.filename && a['uploaded'] && !a['deleted'] + logger.debug "adding ownership record" + ownertable = ex_ver.ownerships.create!( + filename: o.filename, + resource_file: o.resource_file) + end end end end - end - # Now add all newly uploaded attached files - if files - files.each do |file| - logger.debug "processing new upload #{file.inspect}" - @attached_files.each do |a| - logger.debug "checking against attachment #{a.inspect}" - if a['name'] == file.original_filename && !a['uploaded'] && !a['deleted'] - Ownership.create!( - filename: file.original_filename, - exercise_version: ex_ver, - resource_file: ResourceFile.for_upload(file, current_user) - ) + # Now add all newly uploaded attached files + if files + files.each do |file| + logger.debug "processing new upload #{file.inspect}" + @attached_files.each do |a| + logger.debug "checking against attachment #{a.inspect}" + if a['name'] == file.original_filename && !a['uploaded'] && !a['deleted'] + Ownership.create!( + filename: file.original_filename, + exercise_version: ex_ver, + resource_file: ResourceFile.for_upload(file, current_user) + ) + end end end end - end - # Add exercise to collection - exercise_collection.andand.add(e, override: true) + # Add exercise to collection + exercise_collection.andand.add(e, override: true) - # Update the text representation - e.current_version.update(text_representation: text_representation) + # Update the text representation + e.current_version.update(text_representation: text_representation) - # Notify user of success - success_msgs << - "
      • X#{e.id}: #{e.name} saved
      • " + # Notify user of success + success_msgs << + "
      • X#{e.id}: #{e.name} saved
      • " + end end end if success_all success_msgs = '
          ' + success_msgs.join("") + '
        ' - redirect_to @return_to, flash: { success: success_msgs.html_safe } and return + redirect_to exercise_practice_path(successful_exercise), flash: { success: success_msgs.html_safe } and return else if !success_msgs.blank? - error_msgs << '

        Some exercises were successfully saved.

        ' + error_msgs << 'Some exercises were successfully saved.' error_msgs << '
          ' + success_msgs.join('') + '
        ' end redirect_back fallback_location: @return_to, flash: { error: error_msgs.join("").html_safe } and return diff --git a/app/utils/peml_parsing_util.rb b/app/utils/peml_parsing_util.rb index 0018549e..9a58bd08 100644 --- a/app/utils/peml_parsing_util.rb +++ b/app/utils/peml_parsing_util.rb @@ -4,20 +4,24 @@ require 'uri' class PemlParsingUtil - def parse (text_representation) - convert_peml(Peml::Loader.new.load(text_representation).dottie!) + def parse (text_representation, error_msgs) + peml = Peml::Loader.new.load(text_representation).dottie! + error_msgs.concat(Peml::validate(peml)) + convert_peml(peml, error_msgs) end # Convert the parsed peml hash into a hash corresponding to exercise data model - def convert_peml(hash) + def convert_peml(hash, error_msgs) hash = Dottie(hash) #starting with three compulsory peml keys new_hash = { 'external_id' => hash['exercise_id'], 'name' => hash['title'] }.dottie! - new_hash['experience'] = hash['difficulty'] if hash['difficulty'] - new_hash['tag_list'] = hash['tags.topics'].to_s if hash['tags.topics'] + content = hash['difficulty'] + new_hash['experience'] = content if content + content = hash['tags.topics'] + new_hash['tag_list'] = content.to_s if content # PEML does not have an is_public equivalent so we put this value # under a key of the same name @@ -27,55 +31,69 @@ def convert_peml(hash) # is the equivalent in code-workout new_hash['style_list'] = hash['tags.style'].to_s || 'code writing' - starter_code = nil - wrapper_code = nil - tests = nil + prompt = { + 'position' => 1, + 'question' => hash['instructions'] + } systems = hash['systems'] if systems system = systems.first new_hash['language_list'] = system['language'] # assets should be under the system, so try to grab them here first Rails.logger.debug 'get_file_content(system[\'assets.code.starter\'])' - starter_code = get_file_content(system['assets.code.starter']) + content = get_file_content(system['assets.code.starter']) + prompt['starter_code'] = content if content Rails.logger.debug 'get_file_content(system[\'assets.code.wrapper\'])' - wrapper_code = get_file_content(system['assets.code.wrapper']) - Rails.logger.debug 'get_file_content(system[\'assets.test\'])' - tests = get_file_content(system['assets.test']) + content = get_file_content(system['assets.code.wrapper']) + prompt['wrapper_code'] = content if content + Rails.logger.debug 'get_test_file_content(prompt, system[\'assets.test\'])' + get_test_file_content(prompt, system['assets.test']) end new_hash['current_version'] = {} new_hash['current_version.version'] = hash['version.id'] if hash['version.id'] new_hash['current_version.creator'] = get_author_email(hash) new_hash['current_version.prompts'] = [] - prompt = { - 'position' => 1, - 'question' => hash['instructions'], - 'class_name' => 'Answer', - 'method_name' => 'answer' - } #----------------------------------------------------------------------- # PEML assets might be at the global level, if they apply to all # systems, so fill in missing assets here Rails.logger.debug 'assets:' Rails.logger.debug hash['assets'].to_yaml - Rails.logger.debug 'get_file_content(hash[\'assets.code.starter\'])' - starter_code ||= get_file_content(hash['assets.code.starter']) - prompt['starter_code'] = starter_code if starter_code - Rails.logger.debug 'get_file_content(hash[\'assets.code.wrapper\'])' - wrapper_code ||= get_file_content(hash['assets.code.wrapper']) - prompt['wrapper_code'] = wrapper_code if wrapper_code - Rails.logger.debug 'get_file_content(hash[\'assets.test\'])' - tests ||= get_file_content(hash['assets.test']) + if !prompt['starter_code'] + Rails.logger.debug 'get_file_content(hash[\'assets.code.starter\'])' + prompt['starter_code'] = get_file_content(hash['assets.code.starter']) + end + if !prompt['wrapper_code'] + Rails.logger.debug 'get_file_content(hash[\'assets.code.wrapper\'])' + prompt['wrapper_code'] = get_file_content(hash['assets.code.wrapper']) + end + Rails.logger.debug 'get_test_file_content(prompt, hash[\'assets.test\'])' + get_test_file_content(prompt, hash['assets.test']) # FIXME: give error if missing tests - prompt['tests'] = tests if tests #----------------------------------------------------------------------- + if !prompt['class_name'] + prompt['class_name'] = 'Answer' + end + if !prompt['method_name'] + prompt['method_name'] = 'answer' + end # Again, PEML is designed for coding problems and thus, 'coding_prompt' new_hash['current_version.prompts'] << { 'coding_prompt' => prompt } new_hash end + + #----------------------------------------------------------------------- + # Tries to find the email of the author from the given hash. + # + # First, it tries to find it in the 'author.email' key, then in the + # 'license.owner.email' key. If neither works and the 'authors' key + # exists, it takes the email from the first author. + # + # @param hash [Hash] the hash to search for the author's email + # @return [String, nil] the author's email if found, otherwise nil def get_author_email(hash) creator = hash['author.email'] || hash['license.owner.email'] if !creator and hash.key?('authors') @@ -85,22 +103,96 @@ def get_author_email(hash) creator end - def get_file_content(files) - content = nil - file = nil + + #----------------------------------------------------------------------- + def get_test_file_content(prompt, files) Rails.logger.debug '==========' - Rails.logger.debug 'get_file_content(files), files:' + Rails.logger.debug 'get_test_file_content(prompt, files), prompt:' + Rails.logger.debug '==========' + Rails.logger.debug prompt.to_yaml + Rails.logger.debug '==========' + Rails.logger.debug 'get_test_file_content(prompt, files), files:' Rails.logger.debug '==========' Rails.logger.debug files.to_yaml Rails.logger.debug files.inspect - if files + if files and !prompt['tests'] file = files['files'][0] Rails.logger.debug '==========' - Rails.logger.debug 'get_file_content(files), file:' + Rails.logger.debug 'get_test_file_content(prompt, files), file:' Rails.logger.debug '==========' Rails.logger.debug file.to_yaml Rails.logger.debug file.inspect + file.dottie! + + # copy class and method names from file pattern properties, if present + class_name = file['pattern.class_name'] + prompt['class_name'] = class_name if class_name + method_name = file['pattern.method_name'] + prompt['method_name'] = method_name if method_name + + pattern_actual = file['pattern.actual'] || file['pattern_actual'] + + if !prompt['method_name'] && pattern_actual + Rails.logger.debug "pattern_actual = '#{pattern_actual}'" + # pattern.actual: subject.oneFinder({{nums}}) + method_name = pattern_actual.sub(/^.*subject\.(\w+)\(.*$/, '\1') + Rails.logger.debug "extracted method name = '#{method_name}'" + prompt['method_name'] = method_name if method_name + end + + # Extract file content + content = get_file_content(file) + + if content + # format: text/csv-unquoted + # Need to handle this + + # format: text/csv + # Need to handle this + # Drop first line of colum headers + content = content.lines[1..-1].join if content + + # Need to re-assemble straight CSV from either format + prompt['tests'] = content + end + end + Rails.logger.debug '==========' + Rails.logger.debug 'get_test_file_content(prompt, files) => prompt:' + Rails.logger.debug '==========' + Rails.logger.debug prompt.to_yaml + end + + #----------------------------------------------------------------------- + # Retrieves the content from the first file description in a PEML files + # array. + # + # - If the file content is a string and matches a URL pattern, it fetches + # the content from the URL. + # - If the file is a hash, it retrieves the 'content' key's value. + # + # Logs detailed information about the files and content retrieval process + # for debugging. + # + # @param files [Hash] A hash containing file information, expected to have + # a 'files' key. + # @return [String, nil] The content of the file, or nil if content cannot + # be retrieved. + def get_file_content(files) + content = nil + Rails.logger.debug '==========' + Rails.logger.debug 'get_file_content(files), files:' + Rails.logger.debug '==========' + Rails.logger.debug files.to_yaml + Rails.logger.debug files.inspect + file = files + if file and files['files'] + file = files['files'][0] end + Rails.logger.debug '==========' + Rails.logger.debug 'get_file_content(files), file:' + Rails.logger.debug '==========' + Rails.logger.debug file.to_yaml + Rails.logger.debug file.inspect if file.is_a? String file.strip! if file.sub!(/^url\((.*)\)$/, '\1') From 61bbf0616d89a9a7d86da7d01f0d944700ea2fcf Mon Sep 17 00:00:00 2001 From: Alex Hicks Date: Wed, 8 Oct 2025 14:56:07 -0400 Subject: [PATCH 053/117] fix package sources for deprecation in base of image --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dockerfile b/Dockerfile index 1f8a8716..561d7767 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,11 @@ ARG BASEDIR='/code-workout' ENV TZ=America/New_York RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone +RUN sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list && \ + sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list && \ + apt-get -o Acquire::Check-Valid-Until=false update && \ + apt-get install -y --no-install-recommends build-essential + #install 'development tools' build-essential dkms curl libxslt-dev libpq-dev python-dev python-pip python-feedvalidator python-software-properties python-sphinx libmariadbclient-dev libcurl4-gnutls-dev libevent-dev libffi-dev libssl-dev stunnel4 libsqlite3-dev # libmariadbclient-dev RUN apt-get update -qq \ From 05fd5591857d569e7b48ac92122346012e6c3b0d Mon Sep 17 00:00:00 2001 From: Kwasi Biritwum-Nyarko Date: Tue, 10 Feb 2026 11:50:31 -0500 Subject: [PATCH 054/117] created Dockerfile-dev to use Debian Archive repositories --- Dockerfile-dev | 79 +++ docker-compose.override.yml | 5 + .../parsons-ll.571d24fc4f2ccb55e801.svg | 8 + .../parsons-lr.c330dba0b18fa52d2f87.svg | 9 + .../parsons-rl.5befc9e953d826239b72.svg | 9 + .../parsons-rr.fb87504766e29faad579.svg | 8 + vendor/assets/javascripts/parsons.js | 261 ++++++++++ .../stylesheets/main.a0fed8a97bb9e31be37e.css | 488 ++++++++++++++++++ 8 files changed, 867 insertions(+) create mode 100644 Dockerfile-dev create mode 100644 docker-compose.override.yml create mode 100644 vendor/assets/images/parsons-ll.571d24fc4f2ccb55e801.svg create mode 100644 vendor/assets/images/parsons-lr.c330dba0b18fa52d2f87.svg create mode 100644 vendor/assets/images/parsons-rl.5befc9e953d826239b72.svg create mode 100644 vendor/assets/images/parsons-rr.fb87504766e29faad579.svg create mode 100644 vendor/assets/javascripts/parsons.js create mode 100644 vendor/assets/stylesheets/main.a0fed8a97bb9e31be37e.css diff --git a/Dockerfile-dev b/Dockerfile-dev new file mode 100644 index 00000000..1c1df277 --- /dev/null +++ b/Dockerfile-dev @@ -0,0 +1,79 @@ +FROM ruby:2.7.1 + +MAINTAINER Jihane Najdi + +# Default environment +ARG RAILS_ENV='development' +# Ruby changed the way optional params are done, but Rails hasn't caught up +ARG RUBYOPT='-W:no-deprecated' +ARG BASEDIR='/code-workout' + +ENV TZ=America/New_York +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Switch to the Debian Archive repositories +RUN sed -i 's/deb.debian.org/archive.debian.org/g' /etc/apt/sources.list && \ + sed -i 's/security.debian.org/archive.debian.org/g' /etc/apt/sources.list && \ + sed -i '/stretch-updates/d' /etc/apt/sources.list + +#install 'development tools' build-essential dkms curl libxslt-dev libpq-dev python-dev python-pip python-feedvalidator python-software-properties python-sphinx libmariadbclient-dev libcurl4-gnutls-dev libevent-dev libffi-dev libssl-dev stunnel4 libsqlite3-dev +# libmariadbclient-dev +RUN apt-get update -qq \ + && apt-get install -y --fix-missing \ + apt-utils \ + build-essential \ + libpq-dev \ + vim \ + cron \ + curl \ + nodejs \ + python-pip \ + git-core \ + zlib1g-dev \ + libssl-dev \ + libreadline-dev \ + libyaml-dev \ + libevent-dev \ + libsqlite3-dev \ + libsqlite3-dev \ + libxml2-dev \ + libxml2 \ + libxslt1-dev \ + libffi-dev \ + libxslt-dev \ + sqlite3 \ + dkms \ + python-dev \ + python-feedvalidator \ + python-sphinx \ + ant \ + default-jre \ + default-jdk \ + && pip install --upgrade pip + +## JAVA INSTALLATION +RUN apt-get install -y default-jre default-jdk + +# install rubygems +ENV BUNDLER_VERSION 2.1.4 +ENV RAILS_ENV=$RAILS_ENV + +RUN gem install bundler -v $BUNDLER_VERSION + +VOLUME ${BASEDIR} +WORKDIR ${BASEDIR} + +COPY Gemfile ${BASEDIR}/Gemfile +COPY Gemfile.lock ${BASEDIR}/Gemfile.lock + +RUN bundle install + +COPY runservers.sh runservers.sh + +RUN find /code-workout -type d -exec chmod 2775 {} \; +RUN find /code-workout -type f -exec chmod 0644 {} \; +RUN find ./runservers.sh -type f -exec chmod +x {} \; + +EXPOSE 80 + +CMD ["bash", "./runservers.sh"] diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..49bec42e --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,5 @@ +services: + web: + build: + context: . + dockerfile: Dockerfile-dev \ No newline at end of file diff --git a/vendor/assets/images/parsons-ll.571d24fc4f2ccb55e801.svg b/vendor/assets/images/parsons-ll.571d24fc4f2ccb55e801.svg new file mode 100644 index 00000000..34bff285 --- /dev/null +++ b/vendor/assets/images/parsons-ll.571d24fc4f2ccb55e801.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/vendor/assets/images/parsons-lr.c330dba0b18fa52d2f87.svg b/vendor/assets/images/parsons-lr.c330dba0b18fa52d2f87.svg new file mode 100644 index 00000000..24a8af6a --- /dev/null +++ b/vendor/assets/images/parsons-lr.c330dba0b18fa52d2f87.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/vendor/assets/images/parsons-rl.5befc9e953d826239b72.svg b/vendor/assets/images/parsons-rl.5befc9e953d826239b72.svg new file mode 100644 index 00000000..3fa10e7f --- /dev/null +++ b/vendor/assets/images/parsons-rl.5befc9e953d826239b72.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/vendor/assets/images/parsons-rr.fb87504766e29faad579.svg b/vendor/assets/images/parsons-rr.fb87504766e29faad579.svg new file mode 100644 index 00000000..6551524b --- /dev/null +++ b/vendor/assets/images/parsons-rr.fb87504766e29faad579.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/vendor/assets/javascripts/parsons.js b/vendor/assets/javascripts/parsons.js new file mode 100644 index 00000000..8fbd604d --- /dev/null +++ b/vendor/assets/javascripts/parsons.js @@ -0,0 +1,261 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["Parsons"] = factory(); + else + root["Parsons"] = factory(); +})(globalThis, () => { +return /******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./src/css/index.css": +/*!***************************!*\ + !*** ./src/css/index.css ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://Parsons/./src/css/index.css?"); + +/***/ }), + +/***/ "./src/css/parsons.css": +/*!*****************************!*\ + !*** ./src/css/parsons.css ***! + \*****************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://Parsons/./src/css/parsons.css?"); + +/***/ }), + +/***/ "./src/css/prettify.css": +/*!******************************!*\ + !*** ./src/css/prettify.css ***! + \******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://Parsons/./src/css/prettify.css?"); + +/***/ }), + +/***/ "./src/dagGrader.js": +/*!**************************!*\ + !*** ./src/dagGrader.js ***! + \**************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ DAGGrader)\n/* harmony export */ });\n/* harmony import */ var _lineGrader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lineGrader */ \"./src/lineGrader.js\");\n/* harmony import */ var _dagHelpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./dagHelpers */ \"./src/dagHelpers.js\");\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _iterableToArray(r) { if (\"undefined\" != typeof Symbol && null != r[Symbol.iterator] || null != r[\"@@iterator\"]) return Array.from(r); }\nfunction _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _createForOfIteratorHelper(r, e) { var t = \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && \"number\" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t[\"return\"] || t[\"return\"](); } finally { if (u) throw o; } } }; }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\n// import LineBasedGrader from \"./lineGrader\";\n\n\nfunction graphToNX(answerLines) {\n var graph = new _dagHelpers__WEBPACK_IMPORTED_MODULE_1__.DiGraph();\n var _iterator = _createForOfIteratorHelper(answerLines),\n _step;\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var line1 = _step.value;\n graph.addNode(line1.tag);\n var _iterator2 = _createForOfIteratorHelper(line1.depends),\n _step2;\n try {\n for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n var line2tag = _step2.value;\n // the depends graph lists the *incoming* edges of a node\n graph.addEdge(line2tag, line1.tag);\n }\n } catch (err) {\n _iterator2.e(err);\n } finally {\n _iterator2.f();\n }\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n return graph;\n}\nfunction isVertexCover(graph, vertexCover) {\n var _iterator3 = _createForOfIteratorHelper(graph.edges()),\n _step3;\n try {\n for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {\n var edge = _step3.value;\n if (!(vertexCover.has(edge[0]) || vertexCover.has(edge[1]))) {\n return false;\n }\n }\n } catch (err) {\n _iterator3.e(err);\n } finally {\n _iterator3.f();\n }\n return true;\n}\n\n// Find all subsets of the set using the correspondence of subsets of\n// a set to binary string whose length are the size of the set\nfunction allSubsets(arr) {\n var subsets = {};\n for (var i = 0; i <= arr.length; i++) {\n subsets[i] = [];\n }\n for (var _i = 0; _i < Math.pow(2, arr.length); _i++) {\n var bin = _i.toString(2);\n while (bin.length < arr.length) {\n bin = \"0\" + bin;\n }\n var subset = new Set();\n for (var j = 0; j < bin.length; j++) {\n if (bin[j] == \"1\") {\n subset.add(arr[j]);\n }\n }\n subsets[subset.size].push(subset);\n }\n return subsets;\n}\nvar DAGGrader = /*#__PURE__*/function (_LineBasedGrader) {\n function DAGGrader() {\n _classCallCheck(this, DAGGrader);\n return _callSuper(this, DAGGrader, arguments);\n }\n _inherits(DAGGrader, _LineBasedGrader);\n return _createClass(DAGGrader, [{\n key: \"inverseLISIndices\",\n value: function inverseLISIndices(arr, inSolution) {\n // For more details and a proof of the correctness of the algorithm, see the paper: https://arxiv.org/abs/2204.04196\n\n var solution = this.problem.solution;\n var answerLines = inSolution.map(function (block) {\n return block.lines[0];\n }); // assume NOT adaptive for DAG grading (for now)\n\n var graph = graphToNX(solution);\n var seen = new Set();\n var problematicSubgraph = new _dagHelpers__WEBPACK_IMPORTED_MODULE_1__.DiGraph();\n var _iterator4 = _createForOfIteratorHelper(answerLines),\n _step4;\n try {\n for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {\n var line1 = _step4.value;\n var _iterator6 = _createForOfIteratorHelper(seen),\n _step6;\n try {\n for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {\n var line2 = _step6.value;\n var problematic = (0,_dagHelpers__WEBPACK_IMPORTED_MODULE_1__.hasPath)(graph, {\n source: line1.tag,\n target: line2.tag\n });\n if (problematic) {\n problematicSubgraph.addEdge(line1.tag, line2.tag);\n }\n }\n } catch (err) {\n _iterator6.e(err);\n } finally {\n _iterator6.f();\n }\n seen.add(line1);\n }\n } catch (err) {\n _iterator4.e(err);\n } finally {\n _iterator4.f();\n }\n var mvc = null;\n var subsets = allSubsets(problematicSubgraph.nodes());\n for (var i = 0; i <= problematicSubgraph.numberOfNodes(); i++) {\n var _iterator5 = _createForOfIteratorHelper(subsets[i]),\n _step5;\n try {\n for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {\n var subset = _step5.value;\n if (isVertexCover(problematicSubgraph, subset)) {\n mvc = subset;\n break;\n }\n }\n } catch (err) {\n _iterator5.e(err);\n } finally {\n _iterator5.f();\n }\n if (mvc != null) {\n break;\n }\n }\n var indices = _toConsumableArray(mvc).map(function (tag) {\n for (var _i2 = 0; _i2 < answerLines.length; _i2++) {\n if (answerLines[_i2].tag === tag) return _i2;\n }\n });\n return indices;\n }\n }, {\n key: \"checkCorrectIndentation\",\n value: function checkCorrectIndentation(solutionLines, answerLines) {\n this.indentLeft = [];\n this.indentRight = [];\n var indentationByTag = {};\n for (var i = 0; i < solutionLines.length; i++) {\n var line = solutionLines[i];\n indentationByTag[line.tag] = line.indent;\n }\n var loopLimit = Math.min(solutionLines.length, answerLines.length);\n for (var _i3 = 0; _i3 < loopLimit; _i3++) {\n var solutionIndent = indentationByTag[answerLines[_i3].tag];\n if (answerLines[_i3].viewIndent() < solutionIndent) {\n this.indentRight.push(answerLines[_i3]);\n } else if (answerLines[_i3].viewIndent() > solutionIndent) {\n this.indentLeft.push(answerLines[_i3]);\n }\n }\n this.incorrectIndents = this.indentLeft.length + this.indentRight.length;\n return this.incorrectIndents == 0;\n }\n\n // Helper function to check if a dependency is satisfied\n // Supports prefix matching: depends \"myblocklist\" matches tag \"myblocklist-onea\"\n }, {\n key: \"dependencySatisfied\",\n value: function dependencySatisfied(seen, dependency) {\n // Exact match\n if (seen.has(dependency)) {\n return true;\n }\n // Prefix match: check if any tag in seen starts with the dependency\n var _iterator7 = _createForOfIteratorHelper(seen),\n _step7;\n try {\n for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {\n var tag = _step7.value;\n if (tag.startsWith(dependency + \"-\")) {\n return true;\n }\n }\n } catch (err) {\n _iterator7.e(err);\n } finally {\n _iterator7.f();\n }\n return false;\n }\n }, {\n key: \"checkCorrectOrdering\",\n value: function checkCorrectOrdering(solutionLines, answerLines) {\n if (!(0,_dagHelpers__WEBPACK_IMPORTED_MODULE_1__.isDirectedAcyclicGraph)(graphToNX(solutionLines))) {\n throw \"Dependency between blocks does not form a Directed Acyclic Graph;\" + \" Problem unsolvable.\";\n }\n var seen = new Set();\n var isCorrectOrder = true;\n this.correctLines = 0;\n this.solutionLength = solutionLines.length;\n var loopLimit = Math.min(solutionLines.length, answerLines.length);\n for (var i = 0; i < loopLimit; i++) {\n var line = answerLines[i];\n if (line.distractor) {\n isCorrectOrder = false;\n } else {\n for (var j = 0; j < line.depends.length; j++) {\n if (!this.dependencySatisfied(seen, line.depends[j])) {\n isCorrectOrder = false;\n }\n }\n }\n if (isCorrectOrder) {\n this.correctLines += 1;\n }\n seen.add(line.tag);\n }\n return isCorrectOrder;\n }\n }]);\n}(_lineGrader__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n\n\n//# sourceURL=webpack://Parsons/./src/dagGrader.js?"); + +/***/ }), + +/***/ "./src/dagHelpers.js": +/*!***************************!*\ + !*** ./src/dagHelpers.js ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ DiGraph: () => (/* binding */ DiGraph),\n/* harmony export */ hasPath: () => (/* binding */ hasPath),\n/* harmony export */ isDirectedAcyclicGraph: () => (/* binding */ isDirectedAcyclicGraph)\n/* harmony export */ });\n\n\n/**\n * This file adapted from JSNetworkX: https://github.com/fkling/JSNetworkX\n * Copyright (C) 2012 Felix Kling \n * JSNetworkX is distributed with the BSD license\n */\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _regeneratorValues(e) { if (null != e) { var t = e[\"function\" == typeof Symbol && Symbol.iterator || \"@@iterator\"], r = 0; if (t) return t.call(e); if (\"function\" == typeof e.next) return e; if (!isNaN(e.length)) return { next: function next() { return e && r >= e.length && (e = void 0), { value: e && e[r++], done: !e }; } }; } throw new TypeError(_typeof(e) + \" is not iterable\"); }\nfunction _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = \"function\" == typeof Symbol ? Symbol : {}, n = r.iterator || \"@@iterator\", o = r.toStringTag || \"@@toStringTag\"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, \"_invoke\", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError(\"Generator is already running\"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = \"next\"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError(\"iterator result is not an object\"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i[\"return\"]) && t.call(i), c < 2 && (u = TypeError(\"The iterator does not provide a '\" + o + \"' method\"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, \"GeneratorFunction\")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, \"constructor\", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, \"constructor\", GeneratorFunction), GeneratorFunction.displayName = \"GeneratorFunction\", _regeneratorDefine2(GeneratorFunctionPrototype, o, \"GeneratorFunction\"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, \"Generator\"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, \"toString\", function () { return \"[object Generator]\"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }\nfunction _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, \"\", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o(\"next\", 0), o(\"throw\", 1), o(\"return\", 2)); }, _regeneratorDefine2(e, r, n, t); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\nfunction _createForOfIteratorHelper(r, e) { var t = \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && \"number\" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t[\"return\"] || t[\"return\"](); } finally { if (u) throw o; } } }; }\nfunction _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _iterableToArrayLimit(r, l) { var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }\nfunction _arrayWithHoles(r) { if (Array.isArray(r)) return r; }\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction hasPath(G, _ref) {\n var source = _ref.source,\n target = _ref.target;\n try {\n bidirectionalShortestPath(G, source, target);\n } catch (error) {\n if (error instanceof JSNetworkXNoPath) {\n return false;\n }\n throw error;\n }\n return true;\n}\nfunction nodesAreEqual(a, b) {\n return a === b || _typeof(a) === \"object\" && a.toString() === b.toString();\n}\nfunction bidirectionalShortestPath(G, source, target) {\n // call helper to do the real work\n var _bidirectionalPredSuc = bidirectionalPredSucc(G, source, target),\n _bidirectionalPredSuc2 = _slicedToArray(_bidirectionalPredSuc, 3),\n pred = _bidirectionalPredSuc2[0],\n succ = _bidirectionalPredSuc2[1],\n w = _bidirectionalPredSuc2[2];\n\n // build path from pred+w+succ\n var path = [];\n // from source to w\n while (w != null) {\n path.push(w);\n w = pred.get(w);\n }\n w = succ.get(path[0]);\n path.reverse();\n // from w to target\n while (w != null) {\n path.push(w);\n w = succ.get(w);\n }\n return path;\n}\nfunction bidirectionalPredSucc(G, source, target) {\n // does BFS from both source and target and meets in the middle\n if (nodesAreEqual(source, target)) {\n return [new Map([[source, null]]), new Map([[target, null]]), source];\n }\n\n // handle either directed or undirected\n var gpred, gsucc;\n gpred = G.predecessorsIter.bind(G);\n gsucc = G.successorsIter.bind(G);\n\n // predecesssor and successors in search\n var pred = new Map([[source, null]]);\n var succ = new Map([[target, null]]);\n //\n // initialize fringes, start with forward\n var forwardFringe = [source];\n var reverseFringe = [target];\n var thisLevel;\n\n /*jshint newcap:false*/\n while (forwardFringe.length > 0 && reverseFringe.length > 0) {\n if (forwardFringe.length <= reverseFringe.length) {\n thisLevel = forwardFringe;\n forwardFringe = [];\n var _iterator = _createForOfIteratorHelper(thisLevel),\n _step;\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var v = _step.value;\n var _iterator2 = _createForOfIteratorHelper(gsucc(v)),\n _step2;\n try {\n for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n var w = _step2.value;\n if (!pred.has(w)) {\n forwardFringe.push(w);\n pred.set(w, v);\n }\n if (succ.has(w)) {\n return [pred, succ, w]; // found path\n }\n }\n } catch (err) {\n _iterator2.e(err);\n } finally {\n _iterator2.f();\n }\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n } else {\n thisLevel = reverseFringe;\n reverseFringe = [];\n var _iterator3 = _createForOfIteratorHelper(thisLevel),\n _step3;\n try {\n for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {\n var _v = _step3.value;\n var _iterator4 = _createForOfIteratorHelper(gpred(_v)),\n _step4;\n try {\n for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {\n var _w = _step4.value;\n if (!succ.has(_w)) {\n reverseFringe.push(_w);\n succ.set(_w, _v);\n }\n if (pred.has(_w)) {\n return [pred, succ, _w]; // found path\n }\n }\n } catch (err) {\n _iterator4.e(err);\n } finally {\n _iterator4.f();\n }\n }\n } catch (err) {\n _iterator3.e(err);\n } finally {\n _iterator3.f();\n }\n }\n }\n throw new JSNetworkXNoPath(\"No path between \" + source.toString() + \" and \" + target.toString() + \".\");\n}\nfunction topologicalSort(G, optNbunch) {\n // nonrecursive version\n var seen = new Set();\n var orderExplored = []; // provide order and\n // fast search without more general priorityDictionary\n var explored = new Set();\n if (optNbunch == null) {\n optNbunch = G.nodesIter();\n }\n var _iterator5 = _createForOfIteratorHelper(optNbunch),\n _step5;\n try {\n for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {\n var v = _step5.value;\n // process all vertices in G\n if (explored.has(v)) {\n return; // continue\n }\n var fringe = [v]; // nodes yet to look at\n while (fringe.length > 0) {\n var w = fringe[fringe.length - 1]; // depth first search\n if (explored.has(w)) {\n // already looked down this branch\n fringe.pop();\n continue;\n }\n seen.add(w); // mark as seen\n // Check successors for cycles for new nodes\n var newNodes = [];\n /*eslint-disable no-loop-func*/\n G.get(w).forEach(function (_, n) {\n if (!explored.has(n)) {\n if (seen.has(n)) {\n // CYCLE !!\n throw new JSNetworkXUnfeasible(\"Graph contains a cycle.\");\n }\n newNodes.push(n);\n }\n });\n /*eslint-enable no-loop-func*/\n if (newNodes.length > 0) {\n // add new nodes to fringe\n fringe.push.apply(fringe, newNodes);\n } else {\n explored.add(w);\n orderExplored.unshift(w);\n }\n }\n }\n } catch (err) {\n _iterator5.e(err);\n } finally {\n _iterator5.f();\n }\n return orderExplored;\n}\nfunction isDirectedAcyclicGraph(G) {\n try {\n topologicalSort(G);\n return true;\n } catch (ex) {\n if (ex instanceof JSNetworkXUnfeasible) {\n return false;\n }\n throw ex;\n }\n}\nvar DiGraph = /*#__PURE__*/function () {\n function DiGraph() {\n _classCallCheck(this, DiGraph);\n this.graph = {}; // dictionary for graph attributes\n this.node = new Map(); // dictionary for node attributes\n // We store two adjacency lists:\n // the predecessors of node n are stored in the dict self.pred\n // the successors of node n are stored in the dict self.succ=self.adj\n this.adj = new Map(); // empty adjacency dictionary\n this.pred = new Map(); // predecessor\n this.succ = this.adj; // successor\n\n this.edge = this.adj;\n }\n return _createClass(DiGraph, [{\n key: \"addNode\",\n value: function addNode(n) {\n if (!this.succ.has(n)) {\n this.succ.set(n, new Map());\n this.pred.set(n, new Map());\n this.node.set(n);\n }\n }\n }, {\n key: \"addEdge\",\n value: function addEdge(u, v) {\n // add nodes\n if (!this.succ.has(u)) {\n this.succ.set(u, new Map());\n this.pred.set(u, new Map());\n this.node.set(u, {});\n }\n if (!this.succ.has(v)) {\n this.succ.set(v, new Map());\n this.pred.set(v, new Map());\n this.node.set(v, {});\n }\n\n // add the edge\n var datadict = this.adj.get(u).get(v) || {};\n this.succ.get(u).set(v, datadict);\n this.pred.get(v).set(u, datadict);\n }\n }, {\n key: \"nodes\",\n value: function nodes() {\n var optData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n return Array.from(optData ? this.node.entries() : this.node.keys());\n }\n }, {\n key: \"edges\",\n value: function edges(optNbunch) {\n var optData = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n return Array.from(this.edgesIter(optNbunch, optData));\n }\n }, {\n key: \"nodesIter\",\n value: function nodesIter() {\n var optData = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;\n if (optData) {\n return toIterator(this.node);\n }\n return this.node.keys();\n }\n }, {\n key: \"get\",\n value: function get(n) {\n var value = this.adj.get(n);\n if (typeof value === \"undefined\") {\n throw new KeyError(\"Graph does not contain node \" + n + \".\");\n }\n return value;\n }\n }, {\n key: \"numberOfNodes\",\n value: function numberOfNodes() {\n return this.node.size;\n }\n }, {\n key: \"nbunchIter\",\n value: /*#__PURE__*/_regenerator().m(function nbunchIter(optNbunch) {\n var adj, _iterator6, _step6, n, _t, _t2;\n return _regenerator().w(function (_context) {\n while (1) switch (_context.p = _context.n) {\n case 0:\n if (!(optNbunch == null)) {\n _context.n = 2;\n break;\n }\n return _context.d(_regeneratorValues(this.adj.keys()), 1);\n case 1:\n _context.n = 14;\n break;\n case 2:\n if (!this.hasNode(optNbunch)) {\n _context.n = 4;\n break;\n }\n _context.n = 3;\n return optNbunch;\n case 3:\n _context.n = 14;\n break;\n case 4:\n // if nbunch is a sequence of nodes\n adj = this.adj;\n _context.p = 5;\n _iterator6 = _createForOfIteratorHelper(toIterator(optNbunch));\n _context.p = 6;\n _iterator6.s();\n case 7:\n if ((_step6 = _iterator6.n()).done) {\n _context.n = 9;\n break;\n }\n n = _step6.value;\n if (!adj.has(n)) {\n _context.n = 8;\n break;\n }\n _context.n = 8;\n return n;\n case 8:\n _context.n = 7;\n break;\n case 9:\n _context.n = 11;\n break;\n case 10:\n _context.p = 10;\n _t = _context.v;\n _iterator6.e(_t);\n case 11:\n _context.p = 11;\n _iterator6.f();\n return _context.f(11);\n case 12:\n _context.n = 14;\n break;\n case 13:\n _context.p = 13;\n _t2 = _context.v;\n if (!(_t2 instanceof TypeError)) {\n _context.n = 14;\n break;\n }\n throw new JSNetworkXError(\"nbunch is not a node or a sequence of nodes\");\n case 14:\n return _context.a(2);\n }\n }, nbunchIter, this, [[6, 10, 11, 12], [5, 13]]);\n })\n }, {\n key: \"edgesIter\",\n value: function edgesIter(optNbunch) {\n var _this = this;\n var optData = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n return /*#__PURE__*/_regenerator().m(function _callee() {\n var nodesNbrs, _iterator7, _step7, nodeNbrs, _iterator8, _step8, nbrData, result, _t3, _t4;\n return _regenerator().w(function (_context2) {\n while (1) switch (_context2.p = _context2.n) {\n case 0:\n // handle calls with opt_data being the only argument\n if (isBoolean(optNbunch)) {\n optData = optNbunch;\n optNbunch = undefined;\n }\n if (optNbunch === undefined) {\n nodesNbrs = _this.adj;\n } else {\n nodesNbrs = mapIterator(_this.nbunchIter(optNbunch), function (n) {\n return tuple2(n, _this.adj.get(n));\n });\n }\n _iterator7 = _createForOfIteratorHelper(nodesNbrs);\n _context2.p = 1;\n _iterator7.s();\n case 2:\n if ((_step7 = _iterator7.n()).done) {\n _context2.n = 10;\n break;\n }\n nodeNbrs = _step7.value;\n _iterator8 = _createForOfIteratorHelper(nodeNbrs[1]);\n _context2.p = 3;\n _iterator8.s();\n case 4:\n if ((_step8 = _iterator8.n()).done) {\n _context2.n = 6;\n break;\n }\n nbrData = _step8.value;\n result = [nodeNbrs[0], nbrData[0]];\n if (optData) {\n result[2] = nbrData[1];\n }\n _context2.n = 5;\n return result;\n case 5:\n _context2.n = 4;\n break;\n case 6:\n _context2.n = 8;\n break;\n case 7:\n _context2.p = 7;\n _t3 = _context2.v;\n _iterator8.e(_t3);\n case 8:\n _context2.p = 8;\n _iterator8.f();\n return _context2.f(8);\n case 9:\n _context2.n = 2;\n break;\n case 10:\n _context2.n = 12;\n break;\n case 11:\n _context2.p = 11;\n _t4 = _context2.v;\n _iterator7.e(_t4);\n case 12:\n _context2.p = 12;\n _iterator7.f();\n return _context2.f(12);\n case 13:\n return _context2.a(2);\n }\n }, _callee, null, [[3, 7, 8, 9], [1, 11, 12, 13]]);\n })();\n }\n }, {\n key: \"reverse\",\n value: function reverse() {\n var optCopy = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;\n var H;\n if (optCopy) {\n H = new this.constructor(null, {\n name: \"Reverse of (\" + this.name + \")\"\n });\n H.addNodesFrom(this);\n H.addEdgesFrom(mapIterator(this.edgesIter(null, true), function (edge) {\n return tuple3c(edge[1], edge[0], deepcopy(edge[2]), edge);\n }));\n H.graph = deepcopy(this.graph);\n H.node = deepcopy(this.node);\n } else {\n var thisPred = this.pred;\n var thisSucc = this.succ;\n this.succ = thisPred;\n this.pred = thisSucc;\n this.adj = this.succ;\n H = this;\n }\n return H;\n }\n }, {\n key: \"successorsIter\",\n value: function successorsIter(n) {\n var nbrs = this.succ.get(n);\n if (nbrs !== undefined) {\n return nbrs.keys();\n }\n throw new JSNetworkXError(sprintf('The node \"%j\" is not in the digraph.', n));\n }\n }, {\n key: \"predecessorsIter\",\n value: function predecessorsIter(n) {\n var nbrs = this.pred.get(n);\n if (nbrs !== undefined) {\n return nbrs.keys();\n }\n throw new JSNetworkXError(sprintf('The node \"%j\" is not in the digraph.', n));\n }\n }, {\n key: \"successors\",\n value: function successors(n) {\n return Array.from(this.successorsIter(n));\n }\n }, {\n key: \"predecessors\",\n value: function predecessors(n) {\n return Array.from(this.predecessorsIter(n));\n }\n }]);\n}();\nvar JSNetworkXException = /*#__PURE__*/_createClass(function JSNetworkXException(message) {\n _classCallCheck(this, JSNetworkXException);\n this.name = \"JSNetworkXException\";\n this.message = message;\n});\nvar JSNetworkXAlgorithmError = /*#__PURE__*/function (_JSNetworkXException) {\n function JSNetworkXAlgorithmError(message) {\n var _this2;\n _classCallCheck(this, JSNetworkXAlgorithmError);\n _this2 = _callSuper(this, JSNetworkXAlgorithmError, [message]);\n _this2.name = \"JSNetworkXAlgorithmError\";\n return _this2;\n }\n _inherits(JSNetworkXAlgorithmError, _JSNetworkXException);\n return _createClass(JSNetworkXAlgorithmError);\n}(JSNetworkXException);\nvar JSNetworkXError = /*#__PURE__*/function (_JSNetworkXException2) {\n function JSNetworkXError(message) {\n var _this3;\n _classCallCheck(this, JSNetworkXError);\n _this3 = _callSuper(this, JSNetworkXError, [message]);\n _this3.name = \"JSNetworkXError\";\n return _this3;\n }\n _inherits(JSNetworkXError, _JSNetworkXException2);\n return _createClass(JSNetworkXError);\n}(JSNetworkXException);\nvar JSNetworkXUnfeasible = /*#__PURE__*/function (_JSNetworkXAlgorithmE) {\n function JSNetworkXUnfeasible(message) {\n var _this4;\n _classCallCheck(this, JSNetworkXUnfeasible);\n _this4 = _callSuper(this, JSNetworkXUnfeasible, [message]);\n _this4.name = \"JSNetworkXUnfeasible\";\n return _this4;\n }\n _inherits(JSNetworkXUnfeasible, _JSNetworkXAlgorithmE);\n return _createClass(JSNetworkXUnfeasible);\n}(JSNetworkXAlgorithmError);\nvar JSNetworkXNoPath = /*#__PURE__*/function (_JSNetworkXUnfeasible) {\n function JSNetworkXNoPath(message) {\n var _this5;\n _classCallCheck(this, JSNetworkXNoPath);\n _this5 = _callSuper(this, JSNetworkXNoPath, [message]);\n _this5.name = \"JSNetworkXNoPath\";\n return _this5;\n }\n _inherits(JSNetworkXNoPath, _JSNetworkXUnfeasible);\n return _createClass(JSNetworkXNoPath);\n}(JSNetworkXUnfeasible); // function from LoDash, needed by functions from JSNetworkX\nfunction isObjectLike(value) {\n return !!value && _typeof(value) == \"object\";\n}\n\n// function from LoDash, needed by functions from JSNetworkX\nfunction isBoolean(value) {\n var boolTag = \"[object Boolean]\";\n return value === true || value === false || isObjectLike(value) && Object.prototype.toString.call(value) == boolTag;\n}\n\n//# sourceURL=webpack://Parsons/./src/dagHelpers.js?"); + +/***/ }), + +/***/ "./src/hammer.min.js": +/*!***************************!*\ + !*** ./src/hammer.min.js ***! + \***************************/ +/***/ ((module, exports, __webpack_require__) => { + +eval("var __WEBPACK_AMD_DEFINE_RESULT__;function _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\n/*! Hammer.JS - v2.0.8 - 2016-04-23\n * http://hammerjs.github.io/\n *\n * Copyright (c) 2016 Jorik Tangelder;\n * Licensed under the MIT license */\n!function (a, b, c, d) {\n \"use strict\";\n\n function e(a, b, c) {\n return setTimeout(j(a, c), b);\n }\n function f(a, b, c) {\n return Array.isArray(a) ? (g(a, c[b], c), !0) : !1;\n }\n function g(a, b, c) {\n var e;\n if (a) if (a.forEach) a.forEach(b, c);else if (a.length !== d) for (e = 0; e < a.length;) b.call(c, a[e], e, a), e++;else for (e in a) a.hasOwnProperty(e) && b.call(c, a[e], e, a);\n }\n function h(b, c, d) {\n var e = \"DEPRECATED METHOD: \" + c + \"\\n\" + d + \" AT \\n\";\n return function () {\n var c = new Error(\"get-stack-trace\"),\n d = c && c.stack ? c.stack.replace(/^[^\\(]+?[\\n$]/gm, \"\").replace(/^\\s+at\\s+/gm, \"\").replace(/^Object.\\s*\\(/gm, \"{anonymous}()@\") : \"Unknown Stack Trace\",\n f = a.console && (a.console.warn || a.console.log);\n return f && f.call(a.console, e, d), b.apply(this, arguments);\n };\n }\n function i(a, b, c) {\n var d,\n e = b.prototype;\n d = a.prototype = Object.create(e), d.constructor = a, d._super = e, c && la(d, c);\n }\n function j(a, b) {\n return function () {\n return a.apply(b, arguments);\n };\n }\n function k(a, b) {\n return _typeof(a) == oa ? a.apply(b ? b[0] || d : d, b) : a;\n }\n function l(a, b) {\n return a === d ? b : a;\n }\n function m(a, b, c) {\n g(q(b), function (b) {\n a.addEventListener(b, c, !1);\n });\n }\n function n(a, b, c) {\n g(q(b), function (b) {\n a.removeEventListener(b, c, !1);\n });\n }\n function o(a, b) {\n for (; a;) {\n if (a == b) return !0;\n a = a.parentNode;\n }\n return !1;\n }\n function p(a, b) {\n return a.indexOf(b) > -1;\n }\n function q(a) {\n return a.trim().split(/\\s+/g);\n }\n function r(a, b, c) {\n if (a.indexOf && !c) return a.indexOf(b);\n for (var d = 0; d < a.length;) {\n if (c && a[d][c] == b || !c && a[d] === b) return d;\n d++;\n }\n return -1;\n }\n function s(a) {\n return Array.prototype.slice.call(a, 0);\n }\n function t(a, b, c) {\n for (var d = [], e = [], f = 0; f < a.length;) {\n var g = b ? a[f][b] : a[f];\n r(e, g) < 0 && d.push(a[f]), e[f] = g, f++;\n }\n return c && (d = b ? d.sort(function (a, c) {\n return a[b] > c[b];\n }) : d.sort()), d;\n }\n function u(a, b) {\n for (var c, e, f = b[0].toUpperCase() + b.slice(1), g = 0; g < ma.length;) {\n if (c = ma[g], e = c ? c + f : b, e in a) return e;\n g++;\n }\n return d;\n }\n function v() {\n return ua++;\n }\n function w(b) {\n var c = b.ownerDocument || b;\n return c.defaultView || c.parentWindow || a;\n }\n function x(a, b) {\n var c = this;\n this.manager = a, this.callback = b, this.element = a.element, this.target = a.options.inputTarget, this.domHandler = function (b) {\n k(a.options.enable, [a]) && c.handler(b);\n }, this.init();\n }\n function y(a) {\n var b,\n c = a.options.inputClass;\n return new (b = c ? c : xa ? M : ya ? P : wa ? R : L)(a, z);\n }\n function z(a, b, c) {\n var d = c.pointers.length,\n e = c.changedPointers.length,\n f = b & Ea && d - e === 0,\n g = b & (Ga | Ha) && d - e === 0;\n c.isFirst = !!f, c.isFinal = !!g, f && (a.session = {}), c.eventType = b, A(a, c), a.emit(\"hammer.input\", c), a.recognize(c), a.session.prevInput = c;\n }\n function A(a, b) {\n var c = a.session,\n d = b.pointers,\n e = d.length;\n c.firstInput || (c.firstInput = D(b)), e > 1 && !c.firstMultiple ? c.firstMultiple = D(b) : 1 === e && (c.firstMultiple = !1);\n var f = c.firstInput,\n g = c.firstMultiple,\n h = g ? g.center : f.center,\n i = b.center = E(d);\n b.timeStamp = ra(), b.deltaTime = b.timeStamp - f.timeStamp, b.angle = I(h, i), b.distance = H(h, i), B(c, b), b.offsetDirection = G(b.deltaX, b.deltaY);\n var j = F(b.deltaTime, b.deltaX, b.deltaY);\n b.overallVelocityX = j.x, b.overallVelocityY = j.y, b.overallVelocity = qa(j.x) > qa(j.y) ? j.x : j.y, b.scale = g ? K(g.pointers, d) : 1, b.rotation = g ? J(g.pointers, d) : 0, b.maxPointers = c.prevInput ? b.pointers.length > c.prevInput.maxPointers ? b.pointers.length : c.prevInput.maxPointers : b.pointers.length, C(c, b);\n var k = a.element;\n o(b.srcEvent.target, k) && (k = b.srcEvent.target), b.target = k;\n }\n function B(a, b) {\n var c = b.center,\n d = a.offsetDelta || {},\n e = a.prevDelta || {},\n f = a.prevInput || {};\n b.eventType !== Ea && f.eventType !== Ga || (e = a.prevDelta = {\n x: f.deltaX || 0,\n y: f.deltaY || 0\n }, d = a.offsetDelta = {\n x: c.x,\n y: c.y\n }), b.deltaX = e.x + (c.x - d.x), b.deltaY = e.y + (c.y - d.y);\n }\n function C(a, b) {\n var c,\n e,\n f,\n g,\n h = a.lastInterval || b,\n i = b.timeStamp - h.timeStamp;\n if (b.eventType != Ha && (i > Da || h.velocity === d)) {\n var j = b.deltaX - h.deltaX,\n k = b.deltaY - h.deltaY,\n l = F(i, j, k);\n e = l.x, f = l.y, c = qa(l.x) > qa(l.y) ? l.x : l.y, g = G(j, k), a.lastInterval = b;\n } else c = h.velocity, e = h.velocityX, f = h.velocityY, g = h.direction;\n b.velocity = c, b.velocityX = e, b.velocityY = f, b.direction = g;\n }\n function D(a) {\n for (var b = [], c = 0; c < a.pointers.length;) b[c] = {\n clientX: pa(a.pointers[c].clientX),\n clientY: pa(a.pointers[c].clientY)\n }, c++;\n return {\n timeStamp: ra(),\n pointers: b,\n center: E(b),\n deltaX: a.deltaX,\n deltaY: a.deltaY\n };\n }\n function E(a) {\n var b = a.length;\n if (1 === b) return {\n x: pa(a[0].clientX),\n y: pa(a[0].clientY)\n };\n for (var c = 0, d = 0, e = 0; b > e;) c += a[e].clientX, d += a[e].clientY, e++;\n return {\n x: pa(c / b),\n y: pa(d / b)\n };\n }\n function F(a, b, c) {\n return {\n x: b / a || 0,\n y: c / a || 0\n };\n }\n function G(a, b) {\n return a === b ? Ia : qa(a) >= qa(b) ? 0 > a ? Ja : Ka : 0 > b ? La : Ma;\n }\n function H(a, b, c) {\n c || (c = Qa);\n var d = b[c[0]] - a[c[0]],\n e = b[c[1]] - a[c[1]];\n return Math.sqrt(d * d + e * e);\n }\n function I(a, b, c) {\n c || (c = Qa);\n var d = b[c[0]] - a[c[0]],\n e = b[c[1]] - a[c[1]];\n return 180 * Math.atan2(e, d) / Math.PI;\n }\n function J(a, b) {\n return I(b[1], b[0], Ra) + I(a[1], a[0], Ra);\n }\n function K(a, b) {\n return H(b[0], b[1], Ra) / H(a[0], a[1], Ra);\n }\n function L() {\n this.evEl = Ta, this.evWin = Ua, this.pressed = !1, x.apply(this, arguments);\n }\n function M() {\n this.evEl = Xa, this.evWin = Ya, x.apply(this, arguments), this.store = this.manager.session.pointerEvents = [];\n }\n function N() {\n this.evTarget = $a, this.evWin = _a, this.started = !1, x.apply(this, arguments);\n }\n function O(a, b) {\n var c = s(a.touches),\n d = s(a.changedTouches);\n return b & (Ga | Ha) && (c = t(c.concat(d), \"identifier\", !0)), [c, d];\n }\n function P() {\n this.evTarget = bb, this.targetIds = {}, x.apply(this, arguments);\n }\n function Q(a, b) {\n var c = s(a.touches),\n d = this.targetIds;\n if (b & (Ea | Fa) && 1 === c.length) return d[c[0].identifier] = !0, [c, c];\n var e,\n f,\n g = s(a.changedTouches),\n h = [],\n i = this.target;\n if (f = c.filter(function (a) {\n return o(a.target, i);\n }), b === Ea) for (e = 0; e < f.length;) d[f[e].identifier] = !0, e++;\n for (e = 0; e < g.length;) d[g[e].identifier] && h.push(g[e]), b & (Ga | Ha) && delete d[g[e].identifier], e++;\n return h.length ? [t(f.concat(h), \"identifier\", !0), h] : void 0;\n }\n function R() {\n x.apply(this, arguments);\n var a = j(this.handler, this);\n this.touch = new P(this.manager, a), this.mouse = new L(this.manager, a), this.primaryTouch = null, this.lastTouches = [];\n }\n function S(a, b) {\n a & Ea ? (this.primaryTouch = b.changedPointers[0].identifier, T.call(this, b)) : a & (Ga | Ha) && T.call(this, b);\n }\n function T(a) {\n var b = a.changedPointers[0];\n if (b.identifier === this.primaryTouch) {\n var c = {\n x: b.clientX,\n y: b.clientY\n };\n this.lastTouches.push(c);\n var d = this.lastTouches,\n e = function e() {\n var a = d.indexOf(c);\n a > -1 && d.splice(a, 1);\n };\n setTimeout(e, cb);\n }\n }\n function U(a) {\n for (var b = a.srcEvent.clientX, c = a.srcEvent.clientY, d = 0; d < this.lastTouches.length; d++) {\n var e = this.lastTouches[d],\n f = Math.abs(b - e.x),\n g = Math.abs(c - e.y);\n if (db >= f && db >= g) return !0;\n }\n return !1;\n }\n function V(a, b) {\n this.manager = a, this.set(b);\n }\n function W(a) {\n if (p(a, jb)) return jb;\n var b = p(a, kb),\n c = p(a, lb);\n return b && c ? jb : b || c ? b ? kb : lb : p(a, ib) ? ib : hb;\n }\n function X() {\n if (!fb) return !1;\n var b = {},\n c = a.CSS && a.CSS.supports;\n return [\"auto\", \"manipulation\", \"pan-y\", \"pan-x\", \"pan-x pan-y\", \"none\"].forEach(function (d) {\n b[d] = c ? a.CSS.supports(\"touch-action\", d) : !0;\n }), b;\n }\n function Y(a) {\n this.options = la({}, this.defaults, a || {}), this.id = v(), this.manager = null, this.options.enable = l(this.options.enable, !0), this.state = nb, this.simultaneous = {}, this.requireFail = [];\n }\n function Z(a) {\n return a & sb ? \"cancel\" : a & qb ? \"end\" : a & pb ? \"move\" : a & ob ? \"start\" : \"\";\n }\n function $(a) {\n return a == Ma ? \"down\" : a == La ? \"up\" : a == Ja ? \"left\" : a == Ka ? \"right\" : \"\";\n }\n function _(a, b) {\n var c = b.manager;\n return c ? c.get(a) : a;\n }\n function aa() {\n Y.apply(this, arguments);\n }\n function ba() {\n aa.apply(this, arguments), this.pX = null, this.pY = null;\n }\n function ca() {\n aa.apply(this, arguments);\n }\n function da() {\n Y.apply(this, arguments), this._timer = null, this._input = null;\n }\n function ea() {\n aa.apply(this, arguments);\n }\n function fa() {\n aa.apply(this, arguments);\n }\n function ga() {\n Y.apply(this, arguments), this.pTime = !1, this.pCenter = !1, this._timer = null, this._input = null, this.count = 0;\n }\n function ha(a, b) {\n return b = b || {}, b.recognizers = l(b.recognizers, ha.defaults.preset), new ia(a, b);\n }\n function ia(a, b) {\n this.options = la({}, ha.defaults, b || {}), this.options.inputTarget = this.options.inputTarget || a, this.handlers = {}, this.session = {}, this.recognizers = [], this.oldCssProps = {}, this.element = a, this.input = y(this), this.touchAction = new V(this, this.options.touchAction), ja(this, !0), g(this.options.recognizers, function (a) {\n var b = this.add(new a[0](a[1]));\n a[2] && b.recognizeWith(a[2]), a[3] && b.requireFailure(a[3]);\n }, this);\n }\n function ja(a, b) {\n var c = a.element;\n if (c.style) {\n var d;\n g(a.options.cssProps, function (e, f) {\n d = u(c.style, f), b ? (a.oldCssProps[d] = c.style[d], c.style[d] = e) : c.style[d] = a.oldCssProps[d] || \"\";\n }), b || (a.oldCssProps = {});\n }\n }\n function ka(a, c) {\n var d = b.createEvent(\"Event\");\n d.initEvent(a, !0, !0), d.gesture = c, c.target.dispatchEvent(d);\n }\n var la,\n ma = [\"\", \"webkit\", \"Moz\", \"MS\", \"ms\", \"o\"],\n na = b.createElement(\"div\"),\n oa = \"function\",\n pa = Math.round,\n qa = Math.abs,\n ra = Date.now;\n la = \"function\" != typeof Object.assign ? function (a) {\n if (a === d || null === a) throw new TypeError(\"Cannot convert undefined or null to object\");\n for (var b = Object(a), c = 1; c < arguments.length; c++) {\n var e = arguments[c];\n if (e !== d && null !== e) for (var f in e) e.hasOwnProperty(f) && (b[f] = e[f]);\n }\n return b;\n } : Object.assign;\n var sa = h(function (a, b, c) {\n for (var e = Object.keys(b), f = 0; f < e.length;) (!c || c && a[e[f]] === d) && (a[e[f]] = b[e[f]]), f++;\n return a;\n }, \"extend\", \"Use `assign`.\"),\n ta = h(function (a, b) {\n return sa(a, b, !0);\n }, \"merge\", \"Use `assign`.\"),\n ua = 1,\n va = /mobile|tablet|ip(ad|hone|od)|android/i,\n wa = \"ontouchstart\" in a,\n xa = u(a, \"PointerEvent\") !== d,\n ya = wa && va.test(navigator.userAgent),\n za = \"touch\",\n Aa = \"pen\",\n Ba = \"mouse\",\n Ca = \"kinect\",\n Da = 25,\n Ea = 1,\n Fa = 2,\n Ga = 4,\n Ha = 8,\n Ia = 1,\n Ja = 2,\n Ka = 4,\n La = 8,\n Ma = 16,\n Na = Ja | Ka,\n Oa = La | Ma,\n Pa = Na | Oa,\n Qa = [\"x\", \"y\"],\n Ra = [\"clientX\", \"clientY\"];\n x.prototype = {\n handler: function handler() {},\n init: function init() {\n this.evEl && m(this.element, this.evEl, this.domHandler), this.evTarget && m(this.target, this.evTarget, this.domHandler), this.evWin && m(w(this.element), this.evWin, this.domHandler);\n },\n destroy: function destroy() {\n this.evEl && n(this.element, this.evEl, this.domHandler), this.evTarget && n(this.target, this.evTarget, this.domHandler), this.evWin && n(w(this.element), this.evWin, this.domHandler);\n }\n };\n var Sa = {\n mousedown: Ea,\n mousemove: Fa,\n mouseup: Ga\n },\n Ta = \"mousedown\",\n Ua = \"mousemove mouseup\";\n i(L, x, {\n handler: function handler(a) {\n var b = Sa[a.type];\n b & Ea && 0 === a.button && (this.pressed = !0), b & Fa && 1 !== a.which && (b = Ga), this.pressed && (b & Ga && (this.pressed = !1), this.callback(this.manager, b, {\n pointers: [a],\n changedPointers: [a],\n pointerType: Ba,\n srcEvent: a\n }));\n }\n });\n var Va = {\n pointerdown: Ea,\n pointermove: Fa,\n pointerup: Ga,\n pointercancel: Ha,\n pointerout: Ha\n },\n Wa = {\n 2: za,\n 3: Aa,\n 4: Ba,\n 5: Ca\n },\n Xa = \"pointerdown\",\n Ya = \"pointermove pointerup pointercancel\";\n a.MSPointerEvent && !a.PointerEvent && (Xa = \"MSPointerDown\", Ya = \"MSPointerMove MSPointerUp MSPointerCancel\"), i(M, x, {\n handler: function handler(a) {\n var b = this.store,\n c = !1,\n d = a.type.toLowerCase().replace(\"ms\", \"\"),\n e = Va[d],\n f = Wa[a.pointerType] || a.pointerType,\n g = f == za,\n h = r(b, a.pointerId, \"pointerId\");\n e & Ea && (0 === a.button || g) ? 0 > h && (b.push(a), h = b.length - 1) : e & (Ga | Ha) && (c = !0), 0 > h || (b[h] = a, this.callback(this.manager, e, {\n pointers: b,\n changedPointers: [a],\n pointerType: f,\n srcEvent: a\n }), c && b.splice(h, 1));\n }\n });\n var Za = {\n touchstart: Ea,\n touchmove: Fa,\n touchend: Ga,\n touchcancel: Ha\n },\n $a = \"touchstart\",\n _a = \"touchstart touchmove touchend touchcancel\";\n i(N, x, {\n handler: function handler(a) {\n var b = Za[a.type];\n if (b === Ea && (this.started = !0), this.started) {\n var c = O.call(this, a, b);\n b & (Ga | Ha) && c[0].length - c[1].length === 0 && (this.started = !1), this.callback(this.manager, b, {\n pointers: c[0],\n changedPointers: c[1],\n pointerType: za,\n srcEvent: a\n });\n }\n }\n });\n var ab = {\n touchstart: Ea,\n touchmove: Fa,\n touchend: Ga,\n touchcancel: Ha\n },\n bb = \"touchstart touchmove touchend touchcancel\";\n i(P, x, {\n handler: function handler(a) {\n var b = ab[a.type],\n c = Q.call(this, a, b);\n c && this.callback(this.manager, b, {\n pointers: c[0],\n changedPointers: c[1],\n pointerType: za,\n srcEvent: a\n });\n }\n });\n var cb = 2500,\n db = 25;\n i(R, x, {\n handler: function handler(a, b, c) {\n var d = c.pointerType == za,\n e = c.pointerType == Ba;\n if (!(e && c.sourceCapabilities && c.sourceCapabilities.firesTouchEvents)) {\n if (d) S.call(this, b, c);else if (e && U.call(this, c)) return;\n this.callback(a, b, c);\n }\n },\n destroy: function destroy() {\n this.touch.destroy(), this.mouse.destroy();\n }\n });\n var eb = u(na.style, \"touchAction\"),\n fb = eb !== d,\n gb = \"compute\",\n hb = \"auto\",\n ib = \"manipulation\",\n jb = \"none\",\n kb = \"pan-x\",\n lb = \"pan-y\",\n mb = X();\n V.prototype = {\n set: function set(a) {\n a == gb && (a = this.compute()), fb && this.manager.element.style && mb[a] && (this.manager.element.style[eb] = a), this.actions = a.toLowerCase().trim();\n },\n update: function update() {\n this.set(this.manager.options.touchAction);\n },\n compute: function compute() {\n var a = [];\n return g(this.manager.recognizers, function (b) {\n k(b.options.enable, [b]) && (a = a.concat(b.getTouchAction()));\n }), W(a.join(\" \"));\n },\n preventDefaults: function preventDefaults(a) {\n var b = a.srcEvent,\n c = a.offsetDirection;\n if (this.manager.session.prevented) return void b.preventDefault();\n var d = this.actions,\n e = p(d, jb) && !mb[jb],\n f = p(d, lb) && !mb[lb],\n g = p(d, kb) && !mb[kb];\n if (e) {\n var h = 1 === a.pointers.length,\n i = a.distance < 2,\n j = a.deltaTime < 250;\n if (h && i && j) return;\n }\n return g && f ? void 0 : e || f && c & Na || g && c & Oa ? this.preventSrc(b) : void 0;\n },\n preventSrc: function preventSrc(a) {\n this.manager.session.prevented = !0, a.preventDefault();\n }\n };\n var nb = 1,\n ob = 2,\n pb = 4,\n qb = 8,\n rb = qb,\n sb = 16,\n tb = 32;\n Y.prototype = {\n defaults: {},\n set: function set(a) {\n return la(this.options, a), this.manager && this.manager.touchAction.update(), this;\n },\n recognizeWith: function recognizeWith(a) {\n if (f(a, \"recognizeWith\", this)) return this;\n var b = this.simultaneous;\n return a = _(a, this), b[a.id] || (b[a.id] = a, a.recognizeWith(this)), this;\n },\n dropRecognizeWith: function dropRecognizeWith(a) {\n return f(a, \"dropRecognizeWith\", this) ? this : (a = _(a, this), delete this.simultaneous[a.id], this);\n },\n requireFailure: function requireFailure(a) {\n if (f(a, \"requireFailure\", this)) return this;\n var b = this.requireFail;\n return a = _(a, this), -1 === r(b, a) && (b.push(a), a.requireFailure(this)), this;\n },\n dropRequireFailure: function dropRequireFailure(a) {\n if (f(a, \"dropRequireFailure\", this)) return this;\n a = _(a, this);\n var b = r(this.requireFail, a);\n return b > -1 && this.requireFail.splice(b, 1), this;\n },\n hasRequireFailures: function hasRequireFailures() {\n return this.requireFail.length > 0;\n },\n canRecognizeWith: function canRecognizeWith(a) {\n return !!this.simultaneous[a.id];\n },\n emit: function emit(a) {\n function b(b) {\n c.manager.emit(b, a);\n }\n var c = this,\n d = this.state;\n qb > d && b(c.options.event + Z(d)), b(c.options.event), a.additionalEvent && b(a.additionalEvent), d >= qb && b(c.options.event + Z(d));\n },\n tryEmit: function tryEmit(a) {\n return this.canEmit() ? this.emit(a) : void (this.state = tb);\n },\n canEmit: function canEmit() {\n for (var a = 0; a < this.requireFail.length;) {\n if (!(this.requireFail[a].state & (tb | nb))) return !1;\n a++;\n }\n return !0;\n },\n recognize: function recognize(a) {\n var b = la({}, a);\n return k(this.options.enable, [this, b]) ? (this.state & (rb | sb | tb) && (this.state = nb), this.state = this.process(b), void (this.state & (ob | pb | qb | sb) && this.tryEmit(b))) : (this.reset(), void (this.state = tb));\n },\n process: function process(a) {},\n getTouchAction: function getTouchAction() {},\n reset: function reset() {}\n }, i(aa, Y, {\n defaults: {\n pointers: 1\n },\n attrTest: function attrTest(a) {\n var b = this.options.pointers;\n return 0 === b || a.pointers.length === b;\n },\n process: function process(a) {\n var b = this.state,\n c = a.eventType,\n d = b & (ob | pb),\n e = this.attrTest(a);\n return d && (c & Ha || !e) ? b | sb : d || e ? c & Ga ? b | qb : b & ob ? b | pb : ob : tb;\n }\n }), i(ba, aa, {\n defaults: {\n event: \"pan\",\n threshold: 10,\n pointers: 1,\n direction: Pa\n },\n getTouchAction: function getTouchAction() {\n var a = this.options.direction,\n b = [];\n return a & Na && b.push(lb), a & Oa && b.push(kb), b;\n },\n directionTest: function directionTest(a) {\n var b = this.options,\n c = !0,\n d = a.distance,\n e = a.direction,\n f = a.deltaX,\n g = a.deltaY;\n return e & b.direction || (b.direction & Na ? (e = 0 === f ? Ia : 0 > f ? Ja : Ka, c = f != this.pX, d = Math.abs(a.deltaX)) : (e = 0 === g ? Ia : 0 > g ? La : Ma, c = g != this.pY, d = Math.abs(a.deltaY))), a.direction = e, c && d > b.threshold && e & b.direction;\n },\n attrTest: function attrTest(a) {\n return aa.prototype.attrTest.call(this, a) && (this.state & ob || !(this.state & ob) && this.directionTest(a));\n },\n emit: function emit(a) {\n this.pX = a.deltaX, this.pY = a.deltaY;\n var b = $(a.direction);\n b && (a.additionalEvent = this.options.event + b), this._super.emit.call(this, a);\n }\n }), i(ca, aa, {\n defaults: {\n event: \"pinch\",\n threshold: 0,\n pointers: 2\n },\n getTouchAction: function getTouchAction() {\n return [jb];\n },\n attrTest: function attrTest(a) {\n return this._super.attrTest.call(this, a) && (Math.abs(a.scale - 1) > this.options.threshold || this.state & ob);\n },\n emit: function emit(a) {\n if (1 !== a.scale) {\n var b = a.scale < 1 ? \"in\" : \"out\";\n a.additionalEvent = this.options.event + b;\n }\n this._super.emit.call(this, a);\n }\n }), i(da, Y, {\n defaults: {\n event: \"press\",\n pointers: 1,\n time: 251,\n threshold: 9\n },\n getTouchAction: function getTouchAction() {\n return [hb];\n },\n process: function process(a) {\n var b = this.options,\n c = a.pointers.length === b.pointers,\n d = a.distance < b.threshold,\n f = a.deltaTime > b.time;\n if (this._input = a, !d || !c || a.eventType & (Ga | Ha) && !f) this.reset();else if (a.eventType & Ea) this.reset(), this._timer = e(function () {\n this.state = rb, this.tryEmit();\n }, b.time, this);else if (a.eventType & Ga) return rb;\n return tb;\n },\n reset: function reset() {\n clearTimeout(this._timer);\n },\n emit: function emit(a) {\n this.state === rb && (a && a.eventType & Ga ? this.manager.emit(this.options.event + \"up\", a) : (this._input.timeStamp = ra(), this.manager.emit(this.options.event, this._input)));\n }\n }), i(ea, aa, {\n defaults: {\n event: \"rotate\",\n threshold: 0,\n pointers: 2\n },\n getTouchAction: function getTouchAction() {\n return [jb];\n },\n attrTest: function attrTest(a) {\n return this._super.attrTest.call(this, a) && (Math.abs(a.rotation) > this.options.threshold || this.state & ob);\n }\n }), i(fa, aa, {\n defaults: {\n event: \"swipe\",\n threshold: 10,\n velocity: .3,\n direction: Na | Oa,\n pointers: 1\n },\n getTouchAction: function getTouchAction() {\n return ba.prototype.getTouchAction.call(this);\n },\n attrTest: function attrTest(a) {\n var b,\n c = this.options.direction;\n return c & (Na | Oa) ? b = a.overallVelocity : c & Na ? b = a.overallVelocityX : c & Oa && (b = a.overallVelocityY), this._super.attrTest.call(this, a) && c & a.offsetDirection && a.distance > this.options.threshold && a.maxPointers == this.options.pointers && qa(b) > this.options.velocity && a.eventType & Ga;\n },\n emit: function emit(a) {\n var b = $(a.offsetDirection);\n b && this.manager.emit(this.options.event + b, a), this.manager.emit(this.options.event, a);\n }\n }), i(ga, Y, {\n defaults: {\n event: \"tap\",\n pointers: 1,\n taps: 1,\n interval: 300,\n time: 250,\n threshold: 9,\n posThreshold: 10\n },\n getTouchAction: function getTouchAction() {\n return [ib];\n },\n process: function process(a) {\n var b = this.options,\n c = a.pointers.length === b.pointers,\n d = a.distance < b.threshold,\n f = a.deltaTime < b.time;\n if (this.reset(), a.eventType & Ea && 0 === this.count) return this.failTimeout();\n if (d && f && c) {\n if (a.eventType != Ga) return this.failTimeout();\n var g = this.pTime ? a.timeStamp - this.pTime < b.interval : !0,\n h = !this.pCenter || H(this.pCenter, a.center) < b.posThreshold;\n this.pTime = a.timeStamp, this.pCenter = a.center, h && g ? this.count += 1 : this.count = 1, this._input = a;\n var i = this.count % b.taps;\n if (0 === i) return this.hasRequireFailures() ? (this._timer = e(function () {\n this.state = rb, this.tryEmit();\n }, b.interval, this), ob) : rb;\n }\n return tb;\n },\n failTimeout: function failTimeout() {\n return this._timer = e(function () {\n this.state = tb;\n }, this.options.interval, this), tb;\n },\n reset: function reset() {\n clearTimeout(this._timer);\n },\n emit: function emit() {\n this.state == rb && (this._input.tapCount = this.count, this.manager.emit(this.options.event, this._input));\n }\n }), ha.VERSION = \"2.0.8\", ha.defaults = {\n domEvents: !1,\n touchAction: gb,\n enable: !0,\n inputTarget: null,\n inputClass: null,\n preset: [[ea, {\n enable: !1\n }], [ca, {\n enable: !1\n }, [\"rotate\"]], [fa, {\n direction: Na\n }], [ba, {\n direction: Na\n }, [\"swipe\"]], [ga], [ga, {\n event: \"doubletap\",\n taps: 2\n }, [\"tap\"]], [da]],\n cssProps: {\n userSelect: \"none\",\n touchSelect: \"none\",\n touchCallout: \"none\",\n contentZooming: \"none\",\n userDrag: \"none\",\n tapHighlightColor: \"rgba(0,0,0,0)\"\n }\n };\n var ub = 1,\n vb = 2;\n ia.prototype = {\n set: function set(a) {\n return la(this.options, a), a.touchAction && this.touchAction.update(), a.inputTarget && (this.input.destroy(), this.input.target = a.inputTarget, this.input.init()), this;\n },\n stop: function stop(a) {\n this.session.stopped = a ? vb : ub;\n },\n recognize: function recognize(a) {\n var b = this.session;\n if (!b.stopped) {\n this.touchAction.preventDefaults(a);\n var c,\n d = this.recognizers,\n e = b.curRecognizer;\n (!e || e && e.state & rb) && (e = b.curRecognizer = null);\n for (var f = 0; f < d.length;) c = d[f], b.stopped === vb || e && c != e && !c.canRecognizeWith(e) ? c.reset() : c.recognize(a), !e && c.state & (ob | pb | qb) && (e = b.curRecognizer = c), f++;\n }\n },\n get: function get(a) {\n if (a instanceof Y) return a;\n for (var b = this.recognizers, c = 0; c < b.length; c++) if (b[c].options.event == a) return b[c];\n return null;\n },\n add: function add(a) {\n if (f(a, \"add\", this)) return this;\n var b = this.get(a.options.event);\n return b && this.remove(b), this.recognizers.push(a), a.manager = this, this.touchAction.update(), a;\n },\n remove: function remove(a) {\n if (f(a, \"remove\", this)) return this;\n if (a = this.get(a)) {\n var b = this.recognizers,\n c = r(b, a);\n -1 !== c && (b.splice(c, 1), this.touchAction.update());\n }\n return this;\n },\n on: function on(a, b) {\n if (a !== d && b !== d) {\n var c = this.handlers;\n return g(q(a), function (a) {\n c[a] = c[a] || [], c[a].push(b);\n }), this;\n }\n },\n off: function off(a, b) {\n if (a !== d) {\n var c = this.handlers;\n return g(q(a), function (a) {\n b ? c[a] && c[a].splice(r(c[a], b), 1) : delete c[a];\n }), this;\n }\n },\n emit: function emit(a, b) {\n this.options.domEvents && ka(a, b);\n var c = this.handlers[a] && this.handlers[a].slice();\n if (c && c.length) {\n b.type = a, b.preventDefault = function () {\n b.srcEvent.preventDefault();\n };\n for (var d = 0; d < c.length;) c[d](b), d++;\n }\n },\n destroy: function destroy() {\n this.element && ja(this, !1), this.handlers = {}, this.session = {}, this.input.destroy(), this.element = null;\n }\n }, la(ha, {\n INPUT_START: Ea,\n INPUT_MOVE: Fa,\n INPUT_END: Ga,\n INPUT_CANCEL: Ha,\n STATE_POSSIBLE: nb,\n STATE_BEGAN: ob,\n STATE_CHANGED: pb,\n STATE_ENDED: qb,\n STATE_RECOGNIZED: rb,\n STATE_CANCELLED: sb,\n STATE_FAILED: tb,\n DIRECTION_NONE: Ia,\n DIRECTION_LEFT: Ja,\n DIRECTION_RIGHT: Ka,\n DIRECTION_UP: La,\n DIRECTION_DOWN: Ma,\n DIRECTION_HORIZONTAL: Na,\n DIRECTION_VERTICAL: Oa,\n DIRECTION_ALL: Pa,\n Manager: ia,\n Input: x,\n TouchAction: V,\n TouchInput: P,\n MouseInput: L,\n PointerEventInput: M,\n TouchMouseInput: R,\n SingleTouchInput: N,\n Recognizer: Y,\n AttrRecognizer: aa,\n Tap: ga,\n Pan: ba,\n Swipe: fa,\n Pinch: ca,\n Rotate: ea,\n Press: da,\n on: m,\n off: n,\n each: g,\n merge: ta,\n extend: sa,\n assign: la,\n inherit: i,\n bindFn: j,\n prefixed: u\n });\n var wb = \"undefined\" != typeof a ? a : \"undefined\" != typeof self ? self : {};\n wb.Hammer = ha, true ? !(__WEBPACK_AMD_DEFINE_RESULT__ = (function () {\n return ha;\n }).call(exports, __webpack_require__, exports, module),\n\t\t__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)) : 0;\n}(window, document, \"Hammer\");\n\n//# sourceURL=webpack://Parsons/./src/hammer.min.js?"); + +/***/ }), + +/***/ "./src/lineGrader.js": +/*!***************************!*\ + !*** ./src/lineGrader.js ***! + \***************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ LineBasedGrader)\n/* harmony export */ });\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\nvar LineBasedGrader = /*#__PURE__*/function () {\n function LineBasedGrader(problem) {\n _classCallCheck(this, LineBasedGrader);\n this.problem = problem;\n }\n // Use a LIS (Longest Increasing Subsequence) algorithm to return the indexes\n // that are not part of that subsequence.\n return _createClass(LineBasedGrader, [{\n key: \"inverseLISIndices\",\n value: function inverseLISIndices(arr) {\n // Get all subsequences\n var allSubsequences = [];\n for (var i = 0; i < arr.length; i++) {\n var subsequenceForCurrent = [arr[i]],\n current = arr[i],\n lastElementAdded = -1;\n for (var j = i; j < arr.length; j++) {\n var subsequent = arr[j];\n if (subsequent > current && lastElementAdded < subsequent) {\n subsequenceForCurrent.push(subsequent);\n lastElementAdded = subsequent;\n }\n }\n allSubsequences.push(subsequenceForCurrent);\n }\n // Figure out the longest one\n var longestSubsequenceLength = -1;\n var longestSubsequence;\n for (var _i in allSubsequences) {\n var subs = allSubsequences[_i];\n if (subs.length > longestSubsequenceLength) {\n longestSubsequenceLength = subs.length;\n longestSubsequence = subs;\n }\n }\n // Create the inverse indexes\n var indexes = [];\n var lIndex = 0;\n for (var _i2 = 0; _i2 < arr.length; _i2++) {\n if (lIndex > longestSubsequence.length) {\n indexes.push(_i2);\n } else {\n if (arr[_i2] == longestSubsequence[lIndex]) {\n lIndex += 1;\n } else {\n indexes.push(_i2);\n }\n }\n }\n return indexes;\n }\n // grade that element, returning the state\n }, {\n key: \"grade\",\n value: function grade() {\n var problem = this.problem;\n problem.clearFeedback();\n this.correctLines = 0;\n this.percentLines = 0;\n this.incorrectIndents = 0;\n var solutionLines = problem.solution;\n var answerLines = problem.answerLines();\n var i;\n var state;\n this.percentLines = Math.min(answerLines.length, solutionLines.length) / Math.max(answerLines.length, solutionLines.length);\n if (answerLines.length < solutionLines.length) {\n state = \"incorrectTooShort\";\n this.correctLength = false;\n } else if (answerLines.length == solutionLines.length) {\n this.correctLength = true;\n } else {\n state = \"incorrectMoveBlocks\";\n this.correctLength = false;\n }\n\n // Determine whether the code **that is there** is in the correct order\n // If there is too much or too little code this only matters for\n // calculating a percentage score.\n var isCorrectOrder = this.checkCorrectOrdering(solutionLines, answerLines);\n\n // Determine whether blocks are indented correctly\n var isCorrectIndents = this.checkCorrectIndentation(solutionLines, answerLines);\n if (isCorrectIndents && isCorrectOrder && this.correctLength) {\n // Perfect\n state = \"correct\";\n } else if (this.correctLength && isCorrectOrder) {\n state = \"incorrectIndent\";\n } else if (!isCorrectOrder && state != \"incorrectTooShort\") {\n state = \"incorrectMoveBlocks\";\n }\n this.calculatePercent();\n this.graderState = state;\n return state;\n }\n }, {\n key: \"checkCorrectIndentation\",\n value: function checkCorrectIndentation(solutionLines, answerLines) {\n this.indentLeft = [];\n this.indentRight = [];\n var loopLimit = Math.min(solutionLines.length, answerLines.length);\n for (var i = 0; i < loopLimit; i++) {\n if (answerLines[i].viewIndent() < answerLines[i].indent) {\n this.indentRight.push(answerLines[i]);\n } else if (answerLines[i].viewIndent() > solutionLines[i].indent) {\n this.indentLeft.push(answerLines[i]);\n }\n }\n this.incorrectIndents = this.indentLeft.length + this.indentRight.length;\n return this.incorrectIndents == 0;\n }\n }, {\n key: \"checkCorrectOrdering\",\n value: function checkCorrectOrdering(solutionLines, answerLines) {\n var isCorrectOrder = true;\n this.correctLines = 0;\n this.solutionLength = solutionLines.length;\n var loopLimit = Math.min(solutionLines.length, answerLines.length);\n for (var i = 0; i < loopLimit; i++) {\n if (answerLines[i].text !== solutionLines[i].text) {\n isCorrectOrder = false;\n } else {\n this.correctLines += 1;\n }\n }\n return isCorrectOrder;\n }\n }, {\n key: \"calculatePercent\",\n value: function calculatePercent() {\n var numLines = this.percentLines * 0.2;\n var lines = this.problem.answerLines().length;\n var numCorrectBlocks = this.correctLines / lines * 0.4;\n var numCorrectIndents = (this.correctLines - this.incorrectIndents) / lines * 0.4;\n var score = numLines + numCorrectBlocks + numCorrectIndents;\n score = Math.max(score, 0);\n this.problem.percent = score;\n }\n }]);\n}();\n\n\n//# sourceURL=webpack://Parsons/./src/lineGrader.js?"); + +/***/ }), + +/***/ "./src/parsons-i18n.en.js": +/*!********************************!*\ + !*** ./src/parsons-i18n.en.js ***! + \********************************/ +/***/ (() => { + +eval("// Only load i18n if the plugin is available\nif (typeof $ !== 'undefined' && $.i18n) {\n $.i18n().load({\n en: {\n msg_parson_check_me: \"Check\",\n msg_parson_reset: \"Reset\",\n msg_parson_help: \"Help me\",\n msg_parson_too_short: \"Your answer is too short. Add more blocks.\",\n msg_parson_drag_from_here: \"Drag from here\",\n msg_parson_drag_to_here: \"Drop blocks here\",\n msg_parson_correct_first_try: \"Perfect! It took you only one try to solve this. Great job!\",\n msg_parson_correct: \"Perfect! It took you $1 tries to solve this. Click Reset to try to solve it in one attempt.\",\n msg_parson_correct_runnable: \"Click Run below to test out your solution.\",\n msg_parson_wrong_indent: \"This block is not indented correctly. Either indent it more by dragging it right or reduce the indention by dragging it left.\",\n msg_parson_wrong_indents: \"These blocks are not indented correctly. To indent a block more, drag it to the right. To reduce the indention, drag it to the left.\",\n msg_parson_wrong_order: \"Highlighted blocks in your answer are wrong or are in the wrong order. This can be fixed by moving, removing, or replacing highlighted blocks.\",\n msg_parson_arrow_navigate: \"Arrow keys to navigate. Space to select / deselect block to move.\",\n msg_parson_help_info: \"Click on the Help Me button if you want to make the problem easier\",\n msg_parson_not_solution: \"Disabled an unneeded code block (one that is not part of the solution).\",\n msg_parson_provided_indent: \"Provided the indentation.\",\n msg_parson_combined_blocks: \"Combined two code blocks into one.\",\n msg_parson_remove_incorrect: \"Will remove an incorrect code block from answer area\",\n msg_parson_will_combine: \"Will combine two blocks\",\n msg_parson_atleast_three_attempts: \"You must make at least three distinct full attempts at a solution before you can get help\",\n msg_parson_three_blocks_left: \"There are only 3 correct blocks left. You should be able to put them in order\",\n msg_parson_will_provide_indent: \"Will provide indentation\"\n }\n });\n}\n\n//# sourceURL=webpack://Parsons/./src/parsons-i18n.en.js?"); + +/***/ }), + +/***/ "./src/parsons-i18n.pt-br.js": +/*!***********************************!*\ + !*** ./src/parsons-i18n.pt-br.js ***! + \***********************************/ +/***/ (() => { + +eval("// Only load i18n if the plugin is available\nif (typeof $ !== 'undefined' && $.i18n) {\n $.i18n().load({\n \"pt-br\": {\n msg_parson_check_me: \"Verificar\",\n msg_parson_reset: \"Resetar\",\n msg_parson_help: \"Ajuda\",\n msg_parson_too_short: \"Seu programa é muito curto. Adicione mais blocos.\",\n msg_parson_drag_from_here: \"Arraste daqui\",\n msg_parson_drag_to_here: \"Largue os blocos aqui\",\n msg_parson_correct_first_try: \"Perfeito! Você levou apenas uma tentativa para resolver. Bom trabalho!\",\n msg_parson_correct: \"Perfeito! Você levou $1 tentativas para resolver. Clique em Resetar para tentar resolver em uma tentativa.\",\n msg_parson_wrong_indent: \"Este bloco não está indentado corretamente. Indente mais arrastando-o para a direita ou reduza a indentação arrastando para a esquerda.\",\n msg_parson_wrong_indents: \"Estes blocos não estão indentados corretamente. Para indentar mais, arraste o bloco para a direita. Para reduzir a indentação, arraste para a esquerda.\",\n msg_parson_wrong_order: \"Blocos destacados no seu programa estão errados ou estão na ordem errada. Isso pode ser resolvido movendo, excluindo ou substituindo os blocos destacados.\",\n msg_parson_arrow_navigate: \"Use as teclas de setas para navegar. Espaço para selecionar/ desmarcar blocos para mover.\",\n msg_parson_help_info: \"Clique no botão Ajuda se você quiser facilitar o problema\",\n msg_parson_not_solution: \"Foi desabilitado um bloco de código desnecessário (que não faz parte da solução).\",\n msg_parson_provided_indent: \"Foi fornecida a indentação.\",\n msg_parson_combined_blocks: \"Dois blocos de códigos foram combinados em um.\",\n msg_parson_remove_incorrect: \"Será removido um bloco de código incorreto da área de resposta\",\n msg_parson_will_combine: \"Serão combinados dois blocos\",\n msg_parson_atleast_three_attempts: \"Você deve tentar pelo menos três vezes antes de pedir ajuda\",\n msg_parson_three_blocks_left: \"Restam apenas 3 blocos corretos. Você deve colocá-los em ordem\",\n msg_parson_will_provide_indent: \"Será fornecida a indentação\"\n }\n });\n}\n\n//# sourceURL=webpack://Parsons/./src/parsons-i18n.pt-br.js?"); + +/***/ }), + +/***/ "./src/parsons.js": +/*!************************!*\ + !*** ./src/parsons.js ***! + \************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Parsons)\n/* harmony export */ });\n/* harmony import */ var _runestonebase_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./runestonebase.js */ \"./src/runestonebase.js\");\n/* harmony import */ var _lineGrader_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lineGrader.js */ \"./src/lineGrader.js\");\n/* harmony import */ var _dagGrader_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./dagGrader.js */ \"./src/dagGrader.js\");\n/* harmony import */ var _parsonsLine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./parsonsLine.js */ \"./src/parsonsLine.js\");\n/* harmony import */ var _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./parsonsBlock.js */ \"./src/parsonsBlock.js\");\n/* =====================================================================\n==== Parsons Runestone Directive Javascript ============================\n======== Renders a Parsons problem based on the HTML created by the\n======== parsons.py script and the RST file.\n==== CONTRIBUTORS ======================================================\n======== Isaiah Mayerchak\n======== Jeff Rick\n======== Barbara Ericson\n======== Cole Bowers\n==== Adapted form the original JS Parsons by ===========================\n======== Ville Karavirta\n======== Petri Ihantola\n======== Juha Helminen\n======== Mike Hewner\n===================================================================== */\n/* =====================================================================\n==== LineBasedGrader Object ============================================\n======== Used for grading a Parsons problem.\n==== PROPERTIES ========================================================\n======== problem: the Parsons problem\n===================================================================== */\n\n\nfunction _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = \"function\" == typeof Symbol ? Symbol : {}, n = r.iterator || \"@@iterator\", o = r.toStringTag || \"@@toStringTag\"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, \"_invoke\", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError(\"Generator is already running\"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = \"next\"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError(\"iterator result is not an object\"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i[\"return\"]) && t.call(i), c < 2 && (u = TypeError(\"The iterator does not provide a '\" + o + \"' method\"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, \"GeneratorFunction\")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, \"constructor\", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, \"constructor\", GeneratorFunction), GeneratorFunction.displayName = \"GeneratorFunction\", _regeneratorDefine2(GeneratorFunctionPrototype, o, \"GeneratorFunction\"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, \"Generator\"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, \"toString\", function () { return \"[object Generator]\"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }\nfunction _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, \"\", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o(\"next\", 0), o(\"throw\", 1), o(\"return\", 2)); }, _regeneratorDefine2(e, r, n, t); }\nfunction asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }\nfunction _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, \"next\", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, \"throw\", n); } _next(void 0); }); }; }\nfunction _createForOfIteratorHelper(r, e) { var t = \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && \"number\" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t[\"return\"] || t[\"return\"](); } finally { if (u) throw o; } } }; }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\nfunction _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }\nfunction _possibleConstructorReturn(t, e) { if (e && (\"object\" == _typeof(e) || \"function\" == typeof e)) return e; if (void 0 !== e) throw new TypeError(\"Derived constructors may only return object or undefined\"); return _assertThisInitialized(t); }\nfunction _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); return e; }\nfunction _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }\nfunction _inherits(t, e) { if (\"function\" != typeof e && null !== e) throw new TypeError(\"Super expression must either be null or a function\"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, \"prototype\", { writable: !1 }), e && _setPrototypeOf(t, e); }\nfunction _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }\nfunction _superPropGet(t, o, e, r) { var p = _get(_getPrototypeOf(1 & r ? t.prototype : t), o, e); return 2 & r && \"function\" == typeof p ? function (t) { return p.apply(e, t); } : p; }\nfunction _get() { return _get = \"undefined\" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = _superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); }\nfunction _superPropBase(t, o) { for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t));); return t; }\nfunction _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }\n\n__webpack_require__(/*! ./parsons-i18n.en.js */ \"./src/parsons-i18n.en.js\");\n__webpack_require__(/*! ./parsons-i18n.pt-br.js */ \"./src/parsons-i18n.pt-br.js\");\n__webpack_require__(/*! ./prettify.js */ \"./src/prettify.js\");\n__webpack_require__(/*! ./css/parsons.css */ \"./src/css/parsons.css\");\n__webpack_require__(/*! ./css/prettify.css */ \"./src/css/prettify.css\");\n__webpack_require__(/*! ./css/index.css */ \"./src/css/index.css\");\n\n\n\n\n// import {injectHTML, loadFile, renderAll} from \"./helpers\";\n\n/* =====================================================================\n==== Parsons Object ====================================================\n======== The model and view of a Parsons problem based on what is\n======== specified in the HTML, which is based on what is specified\n======== in the RST file\n==== PROPERTIES ========================================================\n======== options: options largely specified from the HTML\n======== grader: a LineGrader for grading the problem\n======== lines: an array of all ParsonsLine as specified in the problem\n======== solution: an array of ParsonsLine in the solution\n======== blocks: the current blocks\n======== sourceArea: the element that contains the source blocks\n======== answerArea: the element that contains the answer blocks\n===================================================================== */\n\n/* =====================================================================\n==== INITIALIZATION ====================================================\n===================================================================== */\nvar Parsons = /*#__PURE__*/function (_RunestoneBase) {\n function Parsons(opts) {\n var _this;\n _classCallCheck(this, Parsons);\n _this = _callSuper(this, Parsons, [opts]);\n\n // Determine mode: PIF JSON vs HTML\n if (opts.pifJson) {\n // NEW: PIF JSON mode\n _this.pifMode = true;\n _this.pifData = opts.pifJson.value; // Note: accessing .value from your JSON structure\n _this.containerDiv = $(opts.orig || _this.createMinimalContainer());\n _this.origElem = null; // No HTML source\n _this.question = null; // Will create from JSON\n _this.divid = opts.divid || \"parsons-\" + Date.now();\n } else {\n // EXISTING: HTML mode\n _this.pifMode = false;\n var orig = opts.orig;\n _this.containerDiv = orig;\n _this.origElem = $(orig).find(\"pre.parsonsblocks\")[0];\n _this.question = $(orig).find(\".parsons_question\")[0];\n _this.divid = opts.orig.id;\n }\n\n // Common initialization\n _this.useRunestoneServices = opts.useRunestoneServices;\n var storageId = _superPropGet((_this, Parsons), \"localStorageKey\", _this, 3)([]);\n _this.storageId = storageId;\n _this.children = _this.pifMode ? [] : _this.origElem.childNodes;\n _this.contentArray = [];\n Parsons.counter++; // Unique identifier\n _this.counterId = \"parsons-\" + Parsons.counter;\n\n // Initialize options (mode-specific)\n if (_this.pifMode) {\n _this.initializeOptionsFromPIF();\n } else {\n _this.initializeOptions();\n }\n _this.grader = _this.options.grader === \"dag\" ? new _dagGrader_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"](_this) : new _lineGrader_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"](_this);\n _this.grader.showfeedback = _this.showfeedback;\n _this.blockIndex = 0;\n _this.checkCount = 0;\n _this.numDistinct = 0;\n _this.hasSolved = false;\n\n // Initialize lines (mode-specific)\n if (_this.pifMode) {\n _this.initializeLinesFromPIF();\n } else {\n var fulltext = $(_this.origElem).html();\n _this.initializeLines(fulltext.trim());\n }\n _this.initializeView();\n //Note: caption removed intentionally. Was previously set to \"Parsons\" in rs\n _this.caption = \"\";\n _this.addCaption(\"runestone\");\n\n // Initialize blocks for PIF mode or check server for regular mode\n if (_this.pifMode) {\n // Initialize blocks directly in PIF mode\n _this.initializeAreas(_this.blocksFromSource(), _this.fixedBlocks(), {});\n _this.initializeInteractivity();\n } else {\n // Check the server for an answer to complete things\n _this.checkServer(\"parsons\", true);\n }\n if (typeof Prism !== \"undefined\") {\n Prism.highlightAllUnder(_this.containerDiv);\n }\n _this.runnableDiv = null;\n return _this;\n }\n _inherits(Parsons, _RunestoneBase);\n return _createClass(Parsons, [{\n key: \"initializeOptionsFromPIF\",\n value: function initializeOptionsFromPIF() {\n var options = {\n pixelsPerIndent: 30\n };\n\n // Parse the JSON and extract relevant options\n if (!this.pifData || !this.pifData.options) {\n console.warn(\"Invalid PIF JSON data, using defaults\");\n this.options = options; // Set default options if JSON is invalid\n return;\n }\n var pifOptions = this.pifData.options;\n if (pifOptions.grader) {\n if (_typeof(pifOptions.grader) === 'object') {\n options.grader = pifOptions.grader.type || \"line\";\n // Note: showFeedback is handled elsewhere in the system\n } else {\n options.grader = pifOptions.grader;\n }\n } else {\n options.grader = \"line\"; // default\n }\n if (Object.prototype.hasOwnProperty.call(pifOptions, \"maxdist\")) {\n var parsedMaxdist = Number(pifOptions.maxdist);\n // Treat non-positive as \"show all distractors\" by leaving undefined\n if (Number.isFinite(parsedMaxdist) && parsedMaxdist > 0) {\n options.maxdist = parsedMaxdist;\n }\n }\n options.order = pifOptions.order || undefined;\n options.noindent = !pifOptions.indent; // Note: PIF uses 'indent', Parsons uses 'noindent'\n options.adaptive = pifOptions.adaptive || false;\n options.numbered = pifOptions.numbered || false;\n options.language = pifOptions.language || \"none\";\n options.runnable = pifOptions.runnable || false;\n\n // Extract blocks\n if (options.order && typeof options.order === 'string' && options.order.trim()) {\n var orderArray = options.order.match(/\\d+/g);\n if (orderArray) {\n options.order = orderArray.map(function (x) {\n return parseInt(x);\n });\n }\n }\n var prettifyLanguage = {\n python: \"prettyprint lang-py\",\n java: \"prettyprint lang-java\",\n javascript: \"prettyprint lang-js\",\n html: \"prettyprint lang-html\",\n c: \"prettyprint lang-c\",\n \"c++\": \"prettyprint lang-cpp\",\n cpp: \"prettyprint lang-cpp\",\n ruby: \"prettyprint lang-rb\",\n math: \"\",\n // No prettify for math\n natural: \"\" // No prettify for natural language\n }[options.language] || \"\";\n options.prettifyLanguage = prettifyLanguage;\n options.locale = \"en\"; // Default since no eBookConfig in PIF mode\n\n // Set instance properties\n this.noindent = options.noindent;\n if (options.adaptive) {\n this.initializeAdaptive();\n }\n this.options = options;\n }\n\n // Based on the data-fields in the original HTML, initialize options\n }, {\n key: \"initializeOptions\",\n value: function initializeOptions() {\n var options = {\n pixelsPerIndent: 30\n };\n // add maxdist and order if present\n var maxdist = $(this.origElem).data(\"maxdist\");\n var order = $(this.origElem).data(\"order\");\n var noindent = $(this.origElem).data(\"noindent\");\n var adaptive = $(this.origElem).data(\"adaptive\");\n var numbered = $(this.origElem).data(\"numbered\");\n var grader = $(this.origElem).data(\"grader\");\n options[\"numbered\"] = numbered;\n options[\"grader\"] = grader;\n if (maxdist !== undefined) {\n options[\"maxdist\"] = maxdist;\n }\n if (order !== undefined) {\n // convert order string to array of numbers\n order = order.match(/\\d+/g);\n for (var i = 0; i < order.length; i++) {\n order[i] = parseInt(order[i]);\n }\n options[\"order\"] = order;\n }\n if (noindent == undefined) {\n noindent = false;\n }\n options[\"noindent\"] = noindent;\n this.noindent = noindent;\n if (adaptive == undefined) {\n adaptive = false;\n } else if (adaptive) {\n this.initializeAdaptive();\n }\n options[\"adaptive\"] = adaptive;\n // add locale and language\n var locale = eBookConfig.locale;\n if (locale == undefined) {\n locale = \"en\";\n }\n options[\"locale\"] = locale;\n var language = $(this.origElem).data(\"language\");\n if (language == undefined) {\n language = eBookConfig.language;\n if (language == undefined) {\n language = \"python\";\n }\n }\n options[\"language\"] = language;\n var prettifyLanguage = {\n python: \"prettyprint lang-py\",\n java: \"prettyprint lang-java\",\n javascript: \"prettyprint lang-js\",\n html: \"prettyprint lang-html\",\n c: \"prettyprint lang-c\",\n \"c++\": \"prettyprint lang-cpp\",\n cpp: \"prettyprint lang-cpp\",\n ruby: \"prettyprint lang-rb\"\n }[language];\n if (prettifyLanguage == undefined) {\n prettifyLanguage = \"\";\n }\n options[\"prettifyLanguage\"] = prettifyLanguage;\n //runnable if the parent has a parsons-runnable attr\n options[\"runnable\"] = $(this.origElem).data(\"runnable\");\n this.options = options;\n }\n }, {\n key: \"initializeLinesFromPIF\",\n value: function initializeLinesFromPIF() {\n var _this$pifData, _this$pifData2;\n this.lines = [];\n\n // Get blocks from PIF data - handle both direct and nested structure\n var pifBlocks = ((_this$pifData = this.pifData) === null || _this$pifData === void 0 ? void 0 : _this$pifData.blocks) || ((_this$pifData2 = this.pifData) === null || _this$pifData2 === void 0 || (_this$pifData2 = _this$pifData2.value) === null || _this$pifData2 === void 0 ? void 0 : _this$pifData2.blocks) || [];\n if (!Array.isArray(pifBlocks) || pifBlocks.length === 0) {\n console.warn('No valid blocks found in PIF data');\n return;\n }\n var solution = [];\n var indents = [];\n for (var i = 0; i < pifBlocks.length; i++) {\n var pifBlock = pifBlocks[i];\n\n // Validate pifBlock structure\n if (!pifBlock || _typeof(pifBlock) !== 'object') {\n console.warn(\"PIF block at index \".concat(i, \" is invalid:\"), pifBlock);\n continue;\n }\n\n // Determine if it's a distractor - handle empty strings and various formats\n var blockType = (pifBlock.type || \"\").trim();\n var blockDepends = (pifBlock.depends || \"\").trim();\n var isDistractor = blockType === \"distractor\" || blockDepends === \"-1\";\n var isFixed = blockType === \"fixed\" || typeof pifBlock.tag === \"string\" && pifBlock.tag.trim() === \"fixed\";\n\n // Create ParsonsLine from PIF block with safe defaults\n var blockText = (pifBlock.text || \"\").toString().trim();\n\n // Skip empty blocks\n if (!blockText) {\n console.warn(\"Skipping empty block at index \".concat(i, \":\"), pifBlock);\n continue;\n }\n var displayMath = Boolean(pifBlock.displaymath);\n\n //make togglesArray work with backend later\n var togglesArray = [];\n var line = new _parsonsLine_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"](this, blockText, displayMath, togglesArray);\n\n // Set properties - handle various indent formats\n var indentValue = pifBlock.indent;\n if (typeof indentValue === 'number') {\n line.indent = indentValue;\n } else if (typeof indentValue === 'string') {\n if (indentValue.trim() === '') {\n line.indent = 0;\n } else {\n line.indent = parseInt(indentValue) || 0;\n }\n } else {\n line.indent = 0;\n }\n line.distractor = isDistractor;\n line.distractHelpText = line.paired = Boolean(pifBlock.paired); // Respect paired flag if present\n line.groupWithNext = false; // Each PIF block is typically a separate draggable unit\n line.fixed = isFixed;\n if (pifBlock.feedback && pifBlock.feedback.length !== 0) {\n line.distractHelptext = pifBlock.feedback;\n }\n if (pifBlock.reusable) {\n line.reusable = true;\n this.hasReusable = true;\n }\n\n // Store tag for all lines (needed for grouping)\n line.tag = pifBlock.tag || \"\";\n\n // Parse tag for grouping: tags like \"myblocklist-onea\" have group \"myblocklist\" and variant \"onea\"\n if (line.tag && line.tag.includes('-')) {\n var lastHyphenIndex = line.tag.lastIndexOf('-');\n line.groupTag = line.tag.substring(0, lastHyphenIndex);\n line.groupVariant = line.tag.substring(lastHyphenIndex + 1);\n } else {\n line.groupTag = null;\n line.groupVariant = null;\n }\n\n // Handle DAG grading\n if (this.options.grader === \"dag\" && !line.distractor) {\n // Handle depends field\n if (pifBlock.depends && pifBlock.depends.trim()) {\n if (Array.isArray(pifBlock.depends)) {\n line.depends = pifBlock.depends;\n } else if (typeof pifBlock.depends === 'string') {\n line.depends = pifBlock.depends.split(',').map(function (s) {\n return s.trim();\n }).filter(function (s) {\n return s;\n });\n } else {\n line.depends = [];\n }\n } else {\n line.depends = [];\n }\n }\n\n // Add to solution if not distractor and not fixed\n if (!line.distractor && !line.fixed) {\n solution.push(line);\n }\n\n // Note: line is automatically added to this.lines by ParsonsLine constructor\n // No need to manually push it here\n\n // Track indents for normalization\n if (indents.indexOf(line.indent) === -1) {\n indents.push(line.indent);\n }\n }\n\n // Normalize indents (same logic as original)\n indents = indents.sort(function (a, b) {\n return a - b;\n });\n for (var _i = 0; _i < this.lines.length; _i++) {\n this.lines[_i].indent = indents.indexOf(this.lines[_i].indent);\n }\n\n // Throws an error if reusable blocks are used without execute grading\n if (this.hasReusable && this.options.grader !== \"exec\") {\n throw new Error(\"Reusable blocks are only supported with execute grading.\");\n }\n this.solution = solution;\n }\n\n // Based on what is specified in the original HTML, create the HTML view\n }, {\n key: \"initializeView\",\n value: function initializeView() {\n this.outerDiv = document.createElement(\"div\");\n $(this.outerDiv).addClass(\"parsons\");\n this.outerDiv.id = this.counterId;\n this.parsTextDiv = document.createElement(\"div\");\n $(this.parsTextDiv).addClass(\"parsons-text\");\n this.keyboardTip = document.createElement(\"div\");\n $(this.keyboardTip).attr(\"role\", \"tooltip\");\n this.keyboardTip.id = this.counterId + \"-tip\";\n this.keyboardTip.innerHTML = $.i18n(\"msg_parson_arrow_navigate\");\n this.outerDiv.appendChild(this.keyboardTip);\n $(this.keyboardTip).hide();\n this.sortContainerDiv = document.createElement(\"div\");\n $(this.sortContainerDiv).addClass(\"sortable-code-container\");\n $(this.sortContainerDiv).attr(\"aria-describedby\", this.counterId + \"-tip\");\n this.outerDiv.appendChild(this.sortContainerDiv);\n this.sourceRegionDiv = document.createElement(\"div\");\n this.sourceRegionDiv.id = this.counterId + \"-sourceRegion\";\n $(this.sourceRegionDiv).addClass(\"sortable-code\");\n this.sourceLabel = document.createElement(\"div\");\n $(this.sourceLabel).attr(\"role\", \"tooltip\");\n this.sourceLabel.id = this.counterId + \"-sourceTip\";\n this.sourceLabel.innerHTML = $.i18n(\"msg_parson_drag_from_here\");\n this.sourceRegionDiv.appendChild(this.sourceLabel);\n this.sortContainerDiv.appendChild(this.sourceRegionDiv);\n this.sourceArea = document.createElement(\"div\");\n this.sourceArea.id = this.counterId + \"-source\";\n $(this.sourceArea).addClass(\"source\");\n $(this.sourceArea).attr(\"aria-describedby\", this.counterId + \"-sourceTip\");\n // set the source width to its max value. This allows the blocks to be created\n // at their \"natural\" size. As long as that is smaller than the max.\n // This allows us to use sensible functions to determine the correct heights\n // and widths for the drag and drop areas.\n this.sourceArea.style.width = \"425px\"; // The max it will be resized later.\n this.sourceRegionDiv.appendChild(this.sourceArea);\n this.answerRegionDiv = document.createElement(\"div\");\n this.answerRegionDiv.id = this.counterId + \"-answerRegion\";\n $(this.answerRegionDiv).addClass(\"sortable-code\");\n this.answerLabel = document.createElement(\"div\");\n $(this.answerLabel).attr(\"role\", \"tooltip\");\n this.answerLabel.id = this.counterId + \"-answerTip\";\n this.answerLabel.innerHTML = $.i18n(\"msg_parson_drag_to_here\");\n this.answerRegionDiv.appendChild(this.answerLabel);\n this.sortContainerDiv.appendChild(this.answerRegionDiv);\n this.answerArea = document.createElement(\"div\");\n this.answerArea.id = this.counterId + \"-answer\";\n $(this.answerArea).attr(\"aria-describedby\", this.counterId + \"-answerTip\");\n this.answerRegionDiv.appendChild(this.answerArea);\n this.parsonsControlDiv = document.createElement(\"div\");\n $(this.parsonsControlDiv).addClass(\"parsons-controls\");\n this.outerDiv.appendChild(this.parsonsControlDiv);\n var that = this;\n this.checkButton = document.createElement(\"button\");\n $(this.checkButton).attr(\"class\", \"btn btn-success\");\n this.checkButton.textContent = $.i18n && $.i18n(\"msg_parson_check_me\") !== \"msg_parson_check_me\" ? $.i18n(\"msg_parson_check_me\") : \"Check Me\";\n this.checkButton.id = this.counterId + \"-check\";\n this.parsonsControlDiv.appendChild(this.checkButton);\n this.checkButton.type = \"button\";\n this.checkButton.addEventListener(\"click\", function (event) {\n event.preventDefault();\n if (that.options.grader === \"exec\" || that.hasReusable) {\n //TODO: Implement executable grading and uncomment the line below\n var extractedCode = that.extractCode();\n console.log(\"EXTRACTED CODE = \".concat(extractedCode));\n var errorMessage = \"Executable grading not yet implemented.\";\n $('body').append(\"\\n
        \\n

        \".concat(errorMessage, \"

        \\n
        \\n \"));\n throw new Error(\"Executable grading not yet implemented.\");\n }\n that.checkCurrentAnswer();\n that.logCurrentAnswer();\n that.renderFeedback();\n });\n this.resetButton = document.createElement(\"button\");\n $(this.resetButton).attr(\"class\", \"btn btn-default\");\n this.resetButton.textContent = $.i18n && $.i18n(\"msg_parson_reset\") !== \"msg_parson_reset\" ? $.i18n(\"msg_parson_reset\") : \"Reset\";\n this.resetButton.id = this.counterId + \"-reset\";\n this.resetButton.type = \"button\";\n this.parsonsControlDiv.appendChild(this.resetButton);\n this.resetButton.addEventListener(\"click\", function (event) {\n event.preventDefault();\n that.clearFeedback();\n $(that.checkButton).prop(\"disabled\", false);\n that.resetView();\n that.checkCount = 0;\n that.logMove(\"reset\");\n that.setLocalStorage();\n });\n if (this.options.adaptive) {\n this.helpButton = document.createElement(\"button\");\n $(this.helpButton).attr(\"class\", \"btn btn-primary\");\n this.helpButton.textContent = $.i18n && $.i18n(\"msg_parson_help\") !== \"msg_parson_help\" ? $.i18n(\"msg_parson_help\") : \"Help Me\";\n this.helpButton.id = this.counterId + \"-help\";\n this.helpButton.disabled = false; // bje\n this.parsonsControlDiv.appendChild(this.helpButton);\n this.helpButton.addEventListener(\"click\", function (event) {\n event.preventDefault();\n that.helpMe();\n });\n }\n this.messageDiv = document.createElement(\"div\");\n this.messageDiv.id = this.counterId + \"-message\";\n this.parsonsControlDiv.appendChild(this.messageDiv);\n $(this.messageDiv).hide();\n if (!this.pifMode) {\n $(this.origElem).replaceWith(this.outerDiv);\n } else {\n // In PIF mode, append outerDiv to containerDiv\n this.containerDiv[0].appendChild(this.outerDiv);\n }\n $(this.outerDiv).closest(\".sqcontainer\").css(\"max-width\", \"none\");\n if (this.outerDiv && this.question) {\n if ($(this.question).html().match(/^\\s+$/)) {\n $(this.question).remove();\n } else {\n $(this.outerDiv).prepend(this.question);\n }\n }\n }\n\n // Initialize lines and solution properties\n }, {\n key: \"initializeLines\",\n value: function initializeLines(text) {\n this.lines = [];\n // Create the initial blocks\n var textBlocks = text.split(\"---\");\n if (textBlocks.length === 1) {\n // If there are no ---, then every line is its own block\n textBlocks = text.split(\"\\n\");\n }\n var solution = [];\n var indents = [];\n for (var i = 0; i < textBlocks.length; i++) {\n var textBlock = textBlocks[i];\n // Figure out options based on the #option\n // Remove the options from the code\n // only options are #paired or #distractor\n var options = {};\n var distractIndex;\n var distractHelptext = \"\";\n var tagIndex;\n var tag;\n var dependsIndex;\n var togglesArray = [];\n // uncomment to test toggle functionality\n // var togglesArray = [\n // {\n // pos: 5,\n // values: [\"1\",\"2\",\"3\"]\n // },\n // {\n // pos: 1,\n // values: [\"true\",\"false\"]\n // }\n // ];\n var depends = [];\n if (textBlock.includes(\"#paired:\")) {\n distractIndex = textBlock.indexOf(\"#paired:\");\n distractHelptext = textBlock.substring(distractIndex + 8, textBlock.length).trim();\n textBlock = textBlock.substring(0, distractIndex + 7);\n } else if (textBlock.includes(\"#distractor:\")) {\n distractIndex = textBlock.indexOf(\"#distractor:\");\n distractHelptext = textBlock.substring(distractIndex + 12, textBlock.length).trim();\n textBlock = textBlock.substring(0, distractIndex + 11);\n } else if (textBlock.includes(\"#tag:\")) {\n textBlock = textBlock.replace(/#tag:.*;.*;/, function (s) {\n return s.replace(/\\s+/g, \"\");\n }); // remove whitespace in tag and depends list\n tagIndex = textBlock.indexOf(\"#tag:\");\n tag = textBlock.substring(tagIndex + 5, textBlock.indexOf(\";\", tagIndex + 5));\n if (tag == \"\") tag = \"block-\" + i;\n dependsIndex = textBlock.indexOf(\"depends:\");\n var dependsString = textBlock.substring(dependsIndex + 9, textBlock.indexOf(\";\", dependsIndex + 9));\n depends = dependsString.length > 0 ? dependsString.split(\",\") : [];\n }\n if (textBlock.includes('class=\"displaymath')) {\n options[\"displaymath\"] = true;\n } else {\n options[\"displaymath\"] = false;\n }\n textBlock = textBlock.replace(/\\s*#(paired|distractor|reusable|tag:.*;.*;)\\s*/g, function (mystring, arg1) {\n options[arg1] = true;\n return \"\";\n });\n // Create lines\n var lines = [];\n if (!options[\"displaymath\"]) {\n var split = textBlock.split(\"\\n\");\n } else {\n var split = [textBlock];\n }\n for (var j = 0; j < split.length; j++) {\n var code = split[j];\n // discard blank rows\n if (!/^\\s*$/.test(code)) {\n var line = new _parsonsLine_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"](this, code, options[\"displaymath\"], togglesArray);\n lines.push(line);\n if (options[\"reusable\"]) {\n line.reusable = true;\n }\n if (options[\"paired\"]) {\n line.distractor = true;\n line.paired = true;\n line.distractHelptext = distractHelptext;\n } else if (options[\"distractor\"]) {\n line.distractor = true;\n line.paired = false;\n line.distractHelptext = distractHelptext;\n } else {\n line.distractor = false;\n line.paired = false;\n if (this.options.grader === \"dag\") {\n line.tag = tag;\n line.depends = depends;\n }\n solution.push(line);\n }\n if ($.inArray(line.indent, indents) == -1) {\n indents.push(line.indent);\n }\n }\n }\n if (lines.length > 0) {\n // Add groupWithNext\n for (j = 0; j < lines.length - 1; j++) {\n lines[j].groupWithNext = true;\n }\n lines[lines.length - 1].groupWithNext = false;\n }\n }\n // Normalize the indents\n indents = indents.sort(function (a, b) {\n return a - b;\n });\n for (i = 0; i < this.lines.length; i++) {\n line = this.lines[i];\n line.indent = indents.indexOf(line.indent);\n }\n this.solution = solution;\n }\n\n // Extracts code for execute grading\n }, {\n key: \"extractCode\",\n value: function extractCode() {\n var _this$pifData3;\n var code = \"\";\n var _iterator = _createForOfIteratorHelper(this.answerBlocks()),\n _step;\n try {\n for (_iterator.s(); !(_step = _iterator.n()).done;) {\n var block = _step.value;\n var _iterator2 = _createForOfIteratorHelper(block.lines),\n _step2;\n try {\n for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {\n var line = _step2.value;\n for (var i = 0; i < line.indent; i++) {\n code += \" \";\n }\n code += line.text + \"\\n\";\n }\n } catch (err) {\n _iterator2.e(err);\n } finally {\n _iterator2.f();\n }\n }\n } catch (err) {\n _iterator.e(err);\n } finally {\n _iterator.f();\n }\n var wrapper = (_this$pifData3 = this.pifData) === null || _this$pifData3 === void 0 ? void 0 : _this$pifData3.wrapper;\n if (wrapper) {\n code = wrapper.replace(\"___\", code);\n }\n var extraction = new Object();\n extraction.id = this.divid;\n extraction.code = code;\n extraction.language = this.options.language;\n return extraction;\n }\n\n // Based on the blocks, create the source and answer areas\n }, {\n key: \"initializeAreas\",\n value: function () {\n var _initializeAreas = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(sourceBlocks, answerBlocks, options) {\n var blocks, i, block, fixedBlocksList, idx, dropZone, currentFixed, nextFixed, disabled, indent, isHidden, replaceElement, _i2, areaWidth, areaHeight, item, maxFunction, height_add, self, bins, bin, line, pairedBins, lineNumbers, pairedDivs, j, gTag, groupBlocks, groupDiv, orIndicator, orText, curlyBrace;\n return _regenerator().w(function (_context3) {\n while (1) switch (_context3.n) {\n case 0:\n // Create blocks property as the sum of the two\n blocks = [];\n // Process source blocks - append all blocks directly (grouping handled via overlay)\n for (i = 0; i < sourceBlocks.length; i++) {\n block = sourceBlocks[i];\n blocks.push(block);\n this.sourceArea.appendChild(block.view);\n if (block.fixed) {\n console.log(\"[INIT AREAS] WARNING fixed block arrived in source:\", block.lines[0].text);\n }\n }\n for (i = 0; i < answerBlocks.length; i++) {\n block = answerBlocks[i];\n blocks.push(block);\n this.answerArea.appendChild(block.view);\n }\n this.blocks = blocks;\n // Track first and last fixed blocks for placement constraints\n this.firstFixedBlock = null;\n this.lastFixedBlock = null;\n fixedBlocksList = [];\n for (i = 0; i < answerBlocks.length; i++) {\n if (answerBlocks[i].fixed) {\n fixedBlocksList.push(answerBlocks[i]);\n }\n }\n if (fixedBlocksList.length > 0) {\n this.firstFixedBlock = fixedBlocksList[0];\n this.lastFixedBlock = fixedBlocksList[fixedBlocksList.length - 1];\n // Mark them for reference\n this.firstFixedBlock.isFirstFixed = true;\n this.lastFixedBlock.isLastFixed = true;\n }\n // Create drop zones between fixed blocks\n this.dropZones = [];\n if (fixedBlocksList.length >= 2) {\n for (idx = 0; idx < fixedBlocksList.length - 1; idx++) {\n dropZone = document.createElement(\"div\");\n $(dropZone).addClass(\"drop-zone\");\n dropZone.id = this.counterId + \"-dropzone-\" + idx;\n // Insert drop zone after the current fixed block\n currentFixed = fixedBlocksList[idx];\n nextFixed = fixedBlocksList[idx + 1]; // Insert the drop zone into the answer area - position will be set in updateView\n this.answerArea.insertBefore(dropZone, nextFixed.view);\n this.dropZones.push({\n element: dropZone,\n afterBlock: currentFixed,\n beforeBlock: nextFixed\n });\n }\n }\n // Make fixed blocks non-interactive\n for (i = 0; i < blocks.length; i++) {\n block = blocks[i];\n if (block.fixed) {\n $(block.view).addClass(\"fixed-block\");\n if (block.disable) {\n block.disable();\n }\n }\n // Store distractor feedback for later display if available\n if (block.isDistractor()) {\n if (block.lines[0].distractHelptext) {\n block.distractorFeedback = block.lines[0].distractHelptext;\n }\n }\n }\n // If present, disable some blocks\n disabled = options.disabled;\n if (disabled !== undefined) {\n for (i = 0; i < blocks.length; i++) {\n block = blocks[i];\n if (disabled.includes(block.lines[0].index)) {\n $(block.view).addClass(\"disabled\");\n }\n }\n }\n // Determine how much indent should be possible in the answer area\n indent = 0;\n if (!this.noindent) {\n if (this.options.language == \"natural\") {\n indent = this.solutionIndent();\n } else {\n indent = Math.max(0, this.solutionIndent());\n }\n }\n this.indent = indent;\n // For rendering, place in an onscreen position\n isHidden = this.outerDiv.offsetParent == null;\n if (isHidden) {\n replaceElement = document.createElement(\"div\");\n replaceElement.classList.add(\"runestone-sphinx\");\n $(this.outerDiv).replaceWith(replaceElement);\n // add runestone-sphinx class so the css rules for parsons will apply\n $(this.outerDiv).addClass(\"runestone-sphinx\");\n document.body.appendChild(this.outerDiv);\n }\n if (this.options.prettifyLanguage !== \"\") {\n prettyPrint();\n }\n for (_i2 = 0; _i2 < this.lines.length; _i2++) {\n this.lines[_i2].initializeWidth();\n }\n // Layout the areas\n\n // Establish the width and height of the droppable areas\n\n areaHeight = 20;\n height_add = 0;\n if (this.options.numbered != undefined) {\n height_add = 1;\n }\n // Warning -- all of this is just a bit of pixie dust discovered by trial\n // and error to try to get the height of the drag and drop boxes.\n // item is a jQuery object\n // outerHeight can be unreliable if elements are not yet visible\n // outerHeight will return bad results if MathJax has not rendered the math\n areaWidth = 300;\n self = this;\n maxFunction = (/*#__PURE__*/function () {\n var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(item) {\n var addition, outerH;\n return _regenerator().w(function (_context2) {\n while (1) switch (_context2.n) {\n case 0:\n if (!(this.options.language == \"natural\" || this.options.language == \"math\")) {\n _context2.n = 3;\n break;\n }\n if (!(typeof runestoneMathReady !== \"undefined\")) {\n _context2.n = 2;\n break;\n }\n _context2.n = 1;\n return runestoneMathReady.then(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {\n return _regenerator().w(function (_context) {\n while (1) switch (_context.n) {\n case 0:\n _context.n = 1;\n return self.queueMathJax(item[0]);\n case 1:\n return _context.a(2, _context.v);\n }\n }, _callee);\n })));\n case 1:\n _context2.n = 3;\n break;\n case 2:\n if (!(typeof MathJax !== \"undefined\" && typeof MathJax.startup !== \"undefined\")) {\n _context2.n = 3;\n break;\n }\n _context2.n = 3;\n return self.queueMathJax(item[0]);\n case 3:\n areaWidth = Math.max(areaWidth, item.outerWidth(true));\n item.width(areaWidth - 22);\n addition = 3.8;\n outerH = item.outerHeight(true);\n if (outerH != 38) {\n addition = 3.1 * (outerH - 38) / 21;\n }\n areaHeight += outerH + height_add * addition;\n case 4:\n return _context2.a(2);\n }\n }, _callee2, this);\n }));\n return function (_x4) {\n return _ref.apply(this, arguments);\n };\n }()).bind(this);\n i = 0;\n case 1:\n if (!(i < blocks.length)) {\n _context3.n = 3;\n break;\n }\n _context3.n = 2;\n return maxFunction($(blocks[i].view));\n case 2:\n i++;\n _context3.n = 1;\n break;\n case 3:\n // sometimes we have a problem with hidden elements not getting the right height\n // just make sure that we have a reasonable height. There must be a better way to\n // do this.\n if (this.isTimed && areaHeight < blocks.length * 38) {\n areaHeight = blocks.length * 50;\n }\n this.areaWidth = areaWidth;\n if (this.options.numbered != undefined) {\n this.areaWidth += 25;\n //areaHeight += (blocks.length);\n }\n // + 40 to areaHeight to provide some additional buffer in case any text overflow still happens - Vincent Qiu (September 2020)\n if (indent > 0 && indent <= 4) {\n $(this.answerArea).addClass(\"answer\" + indent);\n } else {\n $(this.answerArea).addClass(\"answer\");\n }\n // Initialize paired distractor decoration\n bins = [];\n bin = [];\n for (i = 0; i < this.lines.length; i++) {\n line = this.lines[i];\n if (line.block() == undefined) {\n if (bin.length > 0) {\n bins.push(bin);\n bin = [];\n }\n } else {\n bin.push(line);\n if (!line.groupWithNext) {\n bins.push(bin);\n bin = [];\n }\n }\n }\n pairedBins = [];\n lineNumbers = [];\n pairedDivs = [];\n if (this.pairDistractors || !this.options.adaptive) {\n for (i = bins.length - 1; i > -1; i--) {\n bin = bins[i];\n if (bin[0].paired) {\n // Add all in bin to line numbers\n for (j = bin.length - 1; j > -1; j--) {\n lineNumbers.unshift(bin[j].index);\n }\n } else {\n if (lineNumbers.length > 0) {\n // Add all in bin to line numbers\n for (j = bin.length - 1; j > -1; j--) {\n lineNumbers.unshift(bin[j].index);\n }\n pairedBins.unshift(lineNumbers);\n lineNumbers = [];\n }\n }\n }\n // for (i = 0; i < pairedBins.length; i++) {\n // var pairedDiv = document.createElement(\"div\");\n // $(pairedDiv).addClass(\"paired\");\n // $(pairedDiv).html(\n // \"or{\"\n // );\n // pairedDivs.push(pairedDiv);\n // this.sourceArea.appendChild(pairedDiv);\n // }\n } else {\n pairedBins = [];\n }\n areaHeight += pairedBins.length * 10; // the paired bins take up extra space which can\n // cause the blocks to spill out. This\n // corrects that by adding a little extra\n this.areaHeight = areaHeight + 40;\n $(this.sourceArea).css({\n width: this.areaWidth + 2,\n height: areaHeight\n });\n $(this.answerArea).css({\n width: this.options.pixelsPerIndent * indent + this.areaWidth + 2,\n height: areaHeight\n });\n this.pairedBins = pairedBins;\n this.pairedDivs = pairedDivs;\n\n // Create grouped blocks tracking and overlay divs\n this.groupedBlocksMap = {}; // Map groupTag -> array of blocks\n this.groupedDivs = []; // Array of overlay div elements\n\n // Find all grouped blocks\n for (i = 0; i < this.blocks.length; i++) {\n block = this.blocks[i];\n if (block.isGroupedBlock && block.groupTag) {\n if (!this.groupedBlocksMap[block.groupTag]) {\n this.groupedBlocksMap[block.groupTag] = [];\n }\n this.groupedBlocksMap[block.groupTag].push(block);\n }\n }\n\n // Create overlay div for each group\n for (gTag in this.groupedBlocksMap) {\n if (this.groupedBlocksMap.hasOwnProperty(gTag)) {\n groupBlocks = this.groupedBlocksMap[gTag];\n if (groupBlocks.length > 1) {\n groupDiv = document.createElement(\"div\");\n groupDiv.className = \"grouped-blocks-overlay\";\n groupDiv.setAttribute(\"data-group-tag\", gTag);\n\n // Create the \"or\" indicator\n orIndicator = document.createElement(\"div\");\n orIndicator.className = \"or-indicator\";\n orText = document.createElement(\"span\");\n orText.className = \"or-text\";\n orText.textContent = \"or\";\n orIndicator.appendChild(orText);\n curlyBrace = document.createElement(\"div\");\n curlyBrace.className = \"curly-brace\";\n orIndicator.appendChild(curlyBrace);\n groupDiv.appendChild(orIndicator);\n this.sourceArea.appendChild(groupDiv);\n this.groupedDivs.push({\n element: groupDiv,\n groupTag: gTag,\n blocks: groupBlocks\n });\n }\n }\n }\n if (this.options.numbered && this.options.numbered !== false) {\n this.addBlockLabels(sourceBlocks.concat(answerBlocks));\n }\n // Update the view\n this.state = undefined; // needs to be here for loading from storage\n this.updateView();\n // Put back into the offscreen position\n if (isHidden) {\n $(replaceElement).replaceWith(this.outerDiv);\n }\n case 4:\n return _context3.a(2);\n }\n }, _callee3, this);\n }));\n function initializeAreas(_x, _x2, _x3) {\n return _initializeAreas.apply(this, arguments);\n }\n return initializeAreas;\n }() // Make blocks interactive (both drag-and-drop and keyboard)\n }, {\n key: \"initializeInteractivity\",\n value: function initializeInteractivity() {\n for (var i = 0; i < this.blocks.length; i++) {\n this.blocks[i].initializeInteractivity();\n }\n for (var i = 0; i < this.lines.length; i++) {\n var _iterator3 = _createForOfIteratorHelper(this.lines[i].toggles),\n _step3;\n try {\n for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {\n var toggle = _step3.value;\n toggle.attachListeners();\n }\n } catch (err) {\n _iterator3.e(err);\n } finally {\n _iterator3.f();\n }\n }\n this.initializeTabIndex();\n var self = this;\n if (this.options.language == \"natural\" || this.options.language == \"math\") {\n if (typeof MathJax !== \"undefined\" && typeof MathJax.startup !== \"undefined\") {\n self.queueMathJax(self.outerDiv);\n }\n }\n }\n\n // Make one block be keyboard accessible\n }, {\n key: \"initializeTabIndex\",\n value: function initializeTabIndex() {\n for (var i = 0; i < this.blocks.length; i++) {\n var block = this.blocks[i];\n if (block.enabled()) {\n block.makeTabIndex();\n return this;\n }\n }\n }\n\n /* =====================================================================\n ==== SERVER COMMUNICATION ==============================================\n ===================================================================== */\n\n // Return the argument that is newer based on the timestamp\n }, {\n key: \"newerData\",\n value: function newerData(dataA, dataB) {\n var dateA = dataA.timestamp;\n var dateB = dataB.timestamp;\n if (dateA == undefined) {\n return dataB;\n }\n if (dateB == undefined) {\n return dataA;\n }\n dateA = this.dateFromTimestamp(dateA);\n dateB = this.dateFromTimestamp(dateB);\n if (dateA > dateB) {\n return dataA;\n } else {\n return dataB;\n }\n }\n\n // Based on the data, load\n }, {\n key: \"loadData\",\n value: function () {\n var _loadData = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(data) {\n var sourceHash, answerHash, adaptiveHash, options;\n return _regenerator().w(function (_context4) {\n while (1) switch (_context4.n) {\n case 0:\n sourceHash = data.source;\n if (sourceHash == undefined) {\n // maintain backwards compatibility\n sourceHash = data.trash;\n }\n answerHash = data.answer;\n adaptiveHash = data.adaptive;\n if (adaptiveHash == undefined) {\n options = {};\n } else {\n options = this.optionsFromHash(adaptiveHash);\n }\n if (options.noindent !== undefined) {\n this.noindent = true;\n }\n if (options.checkCount !== undefined) {\n this.checkCount = options.checkCount;\n }\n if (options.hasSolved !== undefined) {\n this.hasSolved = options.hasSolved;\n }\n if (!(sourceHash == undefined || answerHash == undefined || answerHash.length == 1)) {\n _context4.n = 2;\n break;\n }\n _context4.n = 1;\n return this.initializeAreas(this.blocksFromSource(), this.fixedBlocks(), options);\n case 1:\n _context4.n = 3;\n break;\n case 2:\n this.initializeAreas(this.blocksFromHash(sourceHash), this.fixedBlocks().concat(this.blocksFromHash(answerHash)), options);\n this.grade = this.grader.grade();\n if (this.grade == \"correct\") {\n this.correct = true;\n } else if (this.answerLines().length == 0) {\n this.correct = null;\n } else {\n this.correct = false;\n }\n case 3:\n // Start the interface\n if (this.needsReinitialization !== true) {\n this.initializeInteractivity();\n // This is a bit of a hack to get the blocks to be in the right place\n // when the page loads. It is needed because the blocks are not\n // visible when the page loads, so the size is off. This forces\n // a realignment.\n if (this.isTimed && !this.assessmentTaken) {\n this.resetView();\n }\n }\n case 4:\n return _context4.a(2);\n }\n }, _callee4, this);\n }));\n function loadData(_x5) {\n return _loadData.apply(this, arguments);\n }\n return loadData;\n }() // Return what is stored in local storage\n }, {\n key: \"localData\",\n value: function localData() {\n //This ternary was introduced to prevent persistence in playground environments\n var data = eBookConfig.isPlaygroundEnv ? null : localStorage.getItem(this.storageId);\n if (data !== null) {\n if (data.charAt(0) == \"{\") {\n data = JSON.parse(data);\n } else {\n data = {};\n }\n } else {\n data = {};\n }\n return data;\n }\n\n // RunestoneBase: Sent when the server has data\n }, {\n key: \"restoreAnswers\",\n value: function restoreAnswers(serverData) {\n this.loadData(serverData);\n }\n\n // RunestoneBase: Load what is in local storage\n }, {\n key: \"checkLocalStorage\",\n value: function checkLocalStorage() {\n if (this.graderactive) {\n return;\n }\n this.loadData(this.localData());\n }\n\n // RunestoneBase: Set the state of the problem in local storage\n }, {\n key: \"setLocalStorage\",\n value: function setLocalStorage(data) {\n var toStore;\n if (data == undefined) {\n toStore = {\n source: this.sourceHash(),\n answer: this.answerHash(),\n timestamp: new Date()\n };\n var adaptiveHash = this.adaptiveHash();\n if (adaptiveHash.length > 0) {\n toStore.adaptive = adaptiveHash;\n }\n } else {\n toStore = data;\n }\n localStorage.setItem(this.storageId, JSON.stringify(toStore));\n }\n\n /* =====================================================================\n ==== LOGGING ===========================================================\n ===================================================================== */\n // Log the interaction with the problem to the server:\n // start: the user started interacting with this problem\n // move: the user moved a block to a new position\n // reset: the reset button was pressed\n // removeDistractor: \"Help Me\" removed a distractor\n // removeIndentation: \"Help Me\" removed indentation\n // combineBlocks: \"Help Me\" combined blocks\n }, {\n key: \"logMove\",\n value: function logMove(activity) {\n var event = {\n event: \"parsonsMove\",\n div_id: this.divid,\n storageid: _superPropGet(Parsons, \"localStorageKey\", this, 3)([])\n };\n var act = activity + \"|\" + this.sourceHash() + \"|\" + this.answerHash();\n var adaptiveHash = this.adaptiveHash();\n if (adaptiveHash !== \"\") {\n act = act + \"|\" + adaptiveHash;\n }\n event.act = act;\n this.logBookEvent(event);\n }\n\n // Log the answer to the problem\n // correct: The answer given matches the solution\n // incorrect*: The answer is wrong for various reasons\n }, {\n key: \"logCurrentAnswer\",\n value: function () {\n var _logCurrentAnswer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(sid) {\n var event, answerHash, sourceHash, act, adaptiveHash;\n return _regenerator().w(function (_context5) {\n while (1) switch (_context5.n) {\n case 0:\n event = {\n event: \"parsons\",\n div_id: this.divid\n };\n answerHash = this.answerHash();\n event.answer = answerHash;\n sourceHash = this.sourceHash();\n event.source = sourceHash;\n act = sourceHash + \"|\" + answerHash;\n adaptiveHash = this.adaptiveHash();\n if (adaptiveHash !== \"\") {\n event.adaptive = adaptiveHash;\n act = act + \"|\" + adaptiveHash;\n }\n if (this.grade == \"correct\") {\n act = \"correct|\" + act;\n event.correct = \"T\";\n } else {\n act = \"incorrect|\" + act;\n event.correct = \"F\";\n }\n event.act = act;\n if (typeof sid !== \"undefined\") {\n event.sid = sid;\n }\n _context5.n = 1;\n return this.logBookEvent(event);\n case 1:\n return _context5.a(2);\n }\n }, _callee5, this);\n }));\n function logCurrentAnswer(_x6) {\n return _logCurrentAnswer.apply(this, arguments);\n }\n return logCurrentAnswer;\n }()\n /* =====================================================================\n ==== ACCESSING =========================================================\n ===================================================================== */\n // Answer the hash of the adaptive state\n }, {\n key: \"adaptiveHash\",\n value: function adaptiveHash() {\n if (!this.options.adaptive) {\n return \"\";\n }\n var hash = [];\n for (var i = 0; i < this.blocks.length; i++) {\n var block = this.blocks[i];\n if (!block.enabled()) {\n hash.push(\"d\" + block.lines[0].index);\n }\n }\n if (this.noindent !== this.options.noindent) {\n hash.push(\"i\");\n }\n hash.push(\"c\" + this.checkCount);\n if (this.hasSolved) {\n hash.push(\"s\");\n }\n return hash.join(\"-\");\n }\n\n // Create options for creating blocks based on a hash\n }, {\n key: \"optionsFromHash\",\n value: function optionsFromHash(hash) {\n var split;\n if (hash === \"-\" || hash === \"\" || hash === null) {\n split = [];\n } else {\n split = hash.split(\"-\");\n }\n var options = {};\n var disabled = [];\n for (var i = 0; i < split.length; i++) {\n var key = split[i];\n if (key[0] == \"i\") {\n options.noindent = true;\n } else if (key[0] == \"d\") {\n disabled.push(parseInt(key.slice(1)));\n } else if (key[0] == \"s\") {\n options.hasSolved = true;\n } else if (key[0] == \"c\") {\n options.checkCount = parseInt(key.slice(1));\n }\n }\n if (disabled.length > 0) {\n options.disabled = disabled;\n }\n return options;\n }\n\n // Answer the hash of the answer area\n }, {\n key: \"answerHash\",\n value: function answerHash() {\n var hash = [];\n var blocks = this.answerBlocks();\n for (var i = 0; i < blocks.length; i++) {\n hash.push(blocks[i].hash());\n }\n if (hash.length === 0) {\n return \"-\";\n } else {\n return hash.join(\"-\");\n }\n }\n\n // Answer the hash of the source area\n }, {\n key: \"sourceHash\",\n value: function sourceHash() {\n var hash = [];\n var blocks = this.sourceBlocks();\n for (var i = 0; i < blocks.length; i++) {\n hash.push(blocks[i].hash());\n }\n if (hash.length === 0) {\n return \"-\";\n } else {\n return hash.join(\"-\");\n }\n }\n\n // Inter-problem adaptive changes\n // Based on the recentAttempts, remove distractors, add indent, combine blocks\n }, {\n key: \"adaptBlocks\",\n value: function adaptBlocks(input) {\n var blocks = [];\n var distractors = [];\n var block;\n for (var i = 0; i < input.length; i++) {\n block = input[i];\n if (block.isDistractor()) {\n distractors.push(block);\n } else {\n blocks.push(block);\n }\n }\n this.recentAttempts = localStorage.getItem(this.adaptiveId + \"recentAttempts\");\n if (this.recentAttempts == undefined || this.recentAttempts == \"NaN\") {\n this.recentAttempts = 3;\n }\n var lastestAttemptCount = this.recentAttempts;\n var nBlocks = blocks.length;\n var nBlocksToCombine = 0;\n var nDistractors = distractors.length;\n var nToRemove = 0;\n this.pairDistractors = true;\n var giveIndentation = false;\n if (lastestAttemptCount < 2) {\n // 1 Try\n this.pairDistractors = false;\n this.limitDistractors = false;\n } else if (lastestAttemptCount < 4) {\n // 2-3 Tries\n // Do nothing they are doing normal\n this.pairDistractors = true;\n } else if (lastestAttemptCount < 6) {\n // 4-5 Tries\n // pair distractors\n this.pairDistractors = true;\n } else if (lastestAttemptCount < 8) {\n // 6-7 Tries\n // Remove 50% of distractors\n nToRemove = 0.5 * nDistractors;\n this.pairDistractors = true;\n } else {\n // 8+ Tries\n // Remove all of distractors\n nToRemove = nDistractors;\n this.pairDistractors = true;\n }\n /*\n else if(lastestAttemptCount < 12) { //10-11\n // Remove all distractors and give indentation\n nToRemove = nDistractors;\n giveIndentation = true;\n this.pairDistractors = true;\n } else if(lastestAttemptCount < 14) { // 12-13 Tries\n // Remove all of distractors\n // give indentation\n // reduce problem to 3/4 size\n giveIndentation = true;\n nToRemove = nDistractors;\n nBlocksToCombine = .25 * nBlocks;\n this.pairDistractors = true;\n } else { // >= 14 Tries\n // Remove all of distractors\n // give indentation\n // reduce problem to 1/2 size\n giveIndentation = true;\n nToRemove = nDistractors;\n nBlocksToCombine = .5 * nBlocks;\n this.pairDistractors = true;\n }\n */\n nBlocksToCombine = Math.min(nBlocksToCombine, nBlocks - 3);\n // Never combine so where there are less than three blocks left\n // Remove distractors\n distractors = this.shuffled(distractors);\n distractors = distractors.slice(0, nToRemove);\n var output = [];\n for (i = 0; i < input.length; i++) {\n block = input[i];\n if (!block.isDistractor()) {\n output.push(block);\n } else if ($.inArray(block, distractors) == -1) {\n output.push(block);\n }\n }\n //var output = input;\n if (giveIndentation) {\n for (var _i3 = 0; _i3 < output.length; _i3++) {\n output[_i3].addIndent();\n }\n this.indent = 0;\n this.noindent = true;\n }\n // combine blocks\n var solution = [];\n for (i = 0; i < this.lines.length; i++) {\n for (var j = 0; j < output.length; j++) {\n if (output[j].lines[0].index == i) {\n solution.push(output[j]);\n }\n }\n }\n for (var _i4 = 0; _i4 < nBlocksToCombine; _i4++) {\n // combine one set of blocks\n var best = -10;\n var combineIndex = -10;\n for (j = 0; j < solution.length - 1; j++) {\n block = solution[j];\n var next = solution[j + 1];\n var rating = 10 - block.lines.length - next.lines.length;\n var blockIndent = block.minimumLineIndent();\n var nextIndent = next.minimumLineIndent();\n if (blockIndent == nextIndent) {\n rating += 2;\n } else if (blockIndent > nextIndent) {\n rating -= 1;\n }\n if (block.lines[block.lines.length - 1].indent == next.lines[0].indent) {\n rating += 1;\n }\n if (rating >= best) {\n best = rating;\n combineIndex = j;\n }\n }\n block = solution[combineIndex];\n next = solution[combineIndex + 1];\n for (j = 0; j < next.lines.length; j++) {\n block.addLine(next.lines[j]);\n }\n var newSolution = [];\n for (j = 0; j < solution.length; j++) {\n if (j !== combineIndex + 1) {\n newSolution.push(solution[j]);\n }\n }\n var solution = newSolution;\n }\n // reorder\n var combinedOutput = [];\n for (i = 0; i < output.length; i++) {\n for (j = 0; j < solution.length; j++) {\n if (output[i].lines[0].index == solution[j].lines[0].index) {\n combinedOutput.push(solution[j]);\n }\n }\n }\n return combinedOutput;\n }\n }, {\n key: \"fixedBlocks\",\n value: function fixedBlocks() {\n var blocks = [];\n var current = [];\n for (var i = 0; i < this.lines.length; i++) {\n var line = this.lines[i];\n if (!line.fixed) {\n continue;\n }\n current.push(line);\n if (!line.groupWithNext) {\n blocks.push(new _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"](this, current));\n current = [];\n }\n }\n if (current.length) {\n blocks.push(new _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"](this, current));\n }\n return blocks;\n }\n\n // Return an array of code blocks based on what is specified in the problem\n }, {\n key: \"blocksFromSource\",\n value: function blocksFromSource() {\n var unorderedBlocks = [];\n var originalBlocks = [];\n var blocks = [];\n var lines = [];\n var block, line, i;\n\n // Ensures that cloned blocks arent returned; skip fixed lines (handled separately)\n for (i = 0; i < this.lines.length; i++) {\n line = this.lines[i];\n if (line.fixed) {\n if (lines.length > 0) {\n block = new _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"](this, lines);\n if (!block.isClone()) {\n unorderedBlocks.push(block);\n }\n lines = [];\n }\n continue;\n }\n lines.push(line);\n if (!line.groupWithNext) {\n block = new _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"](this, lines);\n if (!block.isClone()) {\n unorderedBlocks.push(block);\n }\n lines = [];\n }\n }\n if (lines.length > 0) {\n block = new _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"](this, lines);\n if (!block.isClone()) {\n unorderedBlocks.push(block);\n }\n }\n originalBlocks = unorderedBlocks;\n // Trim the distractors (only if maxdist is set and greater than 0)\n var removedBlocks = [];\n if (this.options.maxdist !== undefined && this.options.maxdist > 0) {\n var maxdist = this.options.maxdist;\n var distractors = [];\n for (i = 0; i < unorderedBlocks.length; i++) {\n block = unorderedBlocks[i];\n if (block.lines[0].distractor) {\n distractors.push(block);\n }\n }\n if (maxdist < distractors.length) {\n distractors = this.shuffled(distractors);\n distractors = distractors.slice(0, maxdist);\n for (i = 0; i < unorderedBlocks.length; i++) {\n block = unorderedBlocks[i];\n if (block.lines[0].distractor) {\n if ($.inArray(block, distractors) > -1) {\n blocks.push(block);\n } else {\n removedBlocks.push(i);\n }\n } else {\n blocks.push(block);\n }\n }\n unorderedBlocks = blocks;\n blocks = [];\n }\n }\n\n // This is necessary, set the pairDistractors value before blocks get shuffled - William Li (August 2020)\n if (this.recentAttempts < 2) {\n // 1 Try\n this.pairDistractors = false;\n } else {\n this.pairDistractors = true;\n }\n if (this.options.order === undefined) {\n // Shuffle, respecting grouped blocks (same groupTag) and paired distractors\n var chunks = [],\n chunk = [];\n var groupedChunks = {}; // Map groupTag -> array of blocks\n\n // First pass: collect all blocks by groupTag to count group sizes\n for (i = 0; i < unorderedBlocks.length; i++) {\n block = unorderedBlocks[i];\n var groupTag = block.lines[0].groupTag;\n if (groupTag) {\n if (!groupedChunks[groupTag]) {\n groupedChunks[groupTag] = [];\n }\n groupedChunks[groupTag].push(block);\n }\n }\n\n // Second pass: process blocks, treating single-block \"groups\" as regular blocks\n for (i = 0; i < unorderedBlocks.length; i++) {\n block = unorderedBlocks[i];\n var groupTag = block.lines[0].groupTag;\n\n // Only treat as grouped if there are 2+ blocks with the same groupTag\n if (groupTag && groupedChunks[groupTag] && groupedChunks[groupTag].length > 1) {\n // Skip - will be handled when we process grouped chunks\n continue;\n } else if (block.lines[0].paired && this.pairDistractors) {\n // William Li (August 2020) - paired distractors\n chunk.push(block);\n } else {\n // Regular ungrouped block (including single-block \"groups\")\n chunk = [];\n chunk.push(block);\n chunks.push(chunk);\n }\n }\n\n // Add grouped blocks (2+ blocks) as chunks\n for (var gTag in groupedChunks) {\n if (groupedChunks.hasOwnProperty(gTag)) {\n var groupBlocks = groupedChunks[gTag];\n // Only create visual group if there are 2+ blocks\n if (groupBlocks.length > 1) {\n for (var gi = 0; gi < groupBlocks.length; gi++) {\n groupBlocks[gi].isGroupedBlock = true;\n groupBlocks[gi].groupTag = gTag;\n groupBlocks[gi].groupIndex = gi;\n groupBlocks[gi].groupSize = groupBlocks.length;\n }\n chunks.push(groupBlocks);\n }\n }\n }\n chunks = this.shuffled(chunks);\n for (i = 0; i < chunks.length; i++) {\n chunk = chunks[i];\n if (chunk.length > 1) {\n // Check if this is a grouped chunk (don't shuffle within groups)\n if (chunk[0].isGroupedBlock) {\n // Keep grouped blocks in original order\n for (var j = 0; j < chunk.length; j++) {\n blocks.push(chunk[j]);\n }\n } else {\n // shuffle paired distractors\n chunk = this.shuffled(chunk);\n for (var j = 0; j < chunk.length; j++) {\n blocks.push(chunk[j]);\n }\n }\n } else {\n blocks.push(chunk[0]);\n }\n }\n } else {\n // Order according to order specified\n for (i = 0; i < this.options.order.length; i++) {\n block = originalBlocks[this.options.order[i]];\n if (block !== undefined && $.inArray(this.options.order[i], removedBlocks) < 0) {\n blocks.push(block);\n }\n }\n }\n this.pairDistractors = true;\n if (this.options.adaptive) {\n this.limitDistractors = true;\n blocks = this.adaptBlocks(blocks);\n if (!this.limitDistractors) {\n for (i = 0; i < removedBlocks.length; i++) {\n var index = this.options.order == undefined ? Math.random(0, blocks.length) : $.inArray(removedBlocks[i], this.options.order);\n blocks.splice(index, 0, originalBlocks[removedBlocks[i]]);\n }\n }\n }\n if (this.pairDistractors && this.options.order != undefined) {\n //move pairs together\n //Go through array looking for ditractor and its pair\n for (i = 1; i < originalBlocks.length; i++) {\n if (originalBlocks[i].lines[0].paired && $.inArray(originalBlocks[i], blocks) >= 0) {\n var j = i;\n while ($.inArray(originalBlocks[j - 1], blocks) < 0) {\n // find the paired distractor or solution block it will be next to\n j--;\n }\n var indexTo = $.inArray(originalBlocks[j - 1], blocks);\n var indexFrom = $.inArray(originalBlocks[i], blocks);\n blocks.splice(indexFrom, 1);\n blocks.splice(indexTo, 0, originalBlocks[i]);\n }\n }\n }\n return blocks;\n }\n\n // Return a codeblock that corresponds to the hash\n }, {\n key: \"blockFromHash\",\n value: function blockFromHash(hash) {\n var split = hash.split(\"_\");\n var lines = [];\n for (var i = 0; i < split.length - 1; i++) {\n var lineIndex = parseInt(split[i]);\n if (lineIndex >= 0 && lineIndex < this.lines.length && this.lines[lineIndex]) {\n lines.push(this.lines[lineIndex]);\n } else {\n console.warn(\"Invalid line index \".concat(lineIndex, \" in hash \").concat(hash, \". Available lines: \").concat(this.lines.length));\n }\n }\n\n // Don't create a block if no valid lines were found\n if (lines.length === 0) {\n console.warn(\"No valid lines found for hash \".concat(hash));\n return null;\n }\n var block = new _parsonsBlock_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"](this, lines);\n if (this.noindent) {\n block.indent = 0;\n } else {\n block.indent = Number(split[split.length - 1]);\n }\n return block;\n }\n\n // Return an array of codeblocks that corresponds to the hash\n }, {\n key: \"blocksFromHash\",\n value: function blocksFromHash(hash) {\n var split;\n if (hash === \"-\" || hash === \"\" || hash === null) {\n split = [];\n } else {\n split = hash.split(\"-\");\n }\n var blocks = [];\n for (var i = 0; i < split.length; i++) {\n var block = this.blockFromHash(split[i]);\n if (block !== null) {\n blocks.push(block);\n }\n }\n if (this.options.adaptive) {\n return this.adaptBlocks(blocks);\n } else {\n return blocks;\n }\n }\n\n // Return a block object by the full id including id prefix\n }, {\n key: \"getBlockById\",\n value: function getBlockById(id) {\n for (var i = 0; i < this.blocks.length; i++) {\n var block = this.blocks[i];\n if (block.view.id == id) {\n return block;\n }\n }\n return undefined;\n }\n\n // Return array of codeblocks that are the solution\n }, {\n key: \"solutionBlocks\",\n value: function solutionBlocks() {\n var solutionBlocks = [];\n var solutionLines = [];\n for (var i = 0; i < this.lines.length; i++) {\n if (!this.lines[i].distractor) {\n solutionLines.push(this.lines[i]);\n }\n }\n var block = solutionLines[0].block();\n solutionBlocks.push(block);\n for (var _i5 = 1; _i5 < solutionLines.length; _i5++) {\n var nextBlock = solutionLines[_i5].block();\n if (block !== nextBlock) {\n block = nextBlock;\n solutionBlocks.push(block);\n }\n }\n return solutionBlocks;\n }\n\n // Return array of codeblocks based on what is in the source field\n }, {\n key: \"sourceBlocks\",\n value: function sourceBlocks() {\n var sourceBlocks = [];\n var children = this.sourceArea.childNodes;\n for (var i = 0; i < children.length; i++) {\n var child = children[i];\n if ($(child).hasClass(\"block\")) {\n sourceBlocks.push(this.getBlockById(child.id));\n }\n }\n return sourceBlocks;\n }\n\n // Return array of enabled codeblocks based on what is in the source field\n }, {\n key: \"enabledSourceBlocks\",\n value: function enabledSourceBlocks() {\n var all = this.sourceBlocks();\n var enabled = [];\n for (var i = 0; i < all.length; i++) {\n var block = all[i];\n if (block.enabled()) {\n enabled.push(block);\n }\n }\n return enabled;\n }\n\n // Return array of codeblocks based on what is in the answer field\n }, {\n key: \"answerBlocks\",\n value: function answerBlocks() {\n var answerBlocks = [];\n var children = this.answerArea.childNodes;\n for (var i = 0; i < children.length; i++) {\n var block = this.getBlockById(children[i].id);\n if (block !== undefined) {\n answerBlocks.push(block);\n }\n }\n return answerBlocks;\n }\n\n // Return array of enabled codeblocks based on what is in the answer field\n }, {\n key: \"enabledAnswerBlocks\",\n value: function enabledAnswerBlocks() {\n var all = this.answerBlocks();\n var enabled = [];\n for (var i = 0; i < all.length; i++) {\n var block = all[i];\n if (block.enabled()) {\n enabled.push(block);\n }\n }\n return enabled;\n }\n\n // Return array of codelines based on what is in the answer field\n // Excludes fixed blocks since they are not part of grading\n }, {\n key: \"answerLines\",\n value: function answerLines() {\n var answerLines = [];\n var blocks = this.answerBlocks();\n for (var i = 0; i < blocks.length; i++) {\n var block = blocks[i];\n // Skip fixed blocks - they are not graded\n if (block.fixed) {\n continue;\n }\n for (var j = 0; j < block.lines.length; j++) {\n answerLines.push(block.lines[j]);\n }\n }\n return answerLines;\n }\n\n // Go up the hierarchy until you get to a block; return that block element\n }, {\n key: \"getBlockFor\",\n value: function getBlockFor(element) {\n var check = element;\n while (!check.classList.contains(\"block\")) {\n check = check.parentElement;\n }\n return check;\n }\n\n // Return the maximum indent for the solution\n }, {\n key: \"solutionIndent\",\n value: function solutionIndent() {\n var indent = 0;\n for (var i = 0; i < this.blocks.length; i++) {\n var block = this.blocks[i];\n indent = Math.max(indent, block.solutionIndent());\n }\n return indent;\n }\n\n /* =====================================================================\n ==== ACTION ============================================================\n ===================================================================== */\n\n // The \"Check Me\" button was pressed.\n }, {\n key: \"checkCurrentAnswer\",\n value: function checkCurrentAnswer() {\n if (!this.hasSolved) {\n this.checkCount++;\n this.clearFeedback();\n if (this.adaptiveId == undefined) {\n this.adaptiveId = this.storageId;\n }\n // TODO - rendering feedback is buried in the grader.grade method.\n // to disable feedback set this.grader.showfeedback boolean\n this.grader.showfeedback = false;\n this.grade = this.grader.grade();\n if (this.grade == \"correct\") {\n this.hasSolved = true;\n this.correct = true;\n $(this.checkButton).prop(\"disabled\", true);\n localStorage.setItem(this.adaptiveId + \"Solved\", true);\n this.recentAttempts = this.checkCount;\n localStorage.setItem(this.adaptiveId + \"recentAttempts\", this.recentAttempts);\n }\n localStorage.setItem(this.adaptiveId + this.divid + \"Count\", this.checkCount);\n this.setLocalStorage();\n\n // if not solved and not too short then check if should provide help\n if (!this.hasSolved && this.grade !== \"incorrectTooShort\") {\n if (this.canHelp) {\n // only count the attempt if the answer is different (to prevent gaming)\n var answerHash = this.answerHash();\n if (this.lastAnswerHash !== answerHash) {\n this.numDistinct++;\n this.lastAnswerHash = answerHash;\n }\n // if time to offer help\n if (this.numDistinct == 3 && !this.gotHelp) {\n alert($.i18n(\"msg_parson_help_info\"));\n } // end if\n } // end if can help\n } // end if not solved\n } // end outer if not solved\n\n // if now or previous was correct, display runnable\n if (this.hasSolved && this.options.runnable) {\n if (!this.runnableDiv) this.generateRunableVersion();else\n //reveal \"reset\" runnable\n this.runnableDiv.style.display = null;\n }\n }\n\n // Conver the parsons-runnable into an activecode and display it\n }, {\n key: \"generateRunableVersion\",\n value: function generateRunableVersion() {\n this.runnableDiv = document.getElementById(this.divid + \"-runnable\");\n this.runnableDiv.style.display = null;\n var parsonsText = '';\n var _iterator4 = _createForOfIteratorHelper(this.answerBlocks()),\n _step4;\n try {\n for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {\n var b = _step4.value;\n var _iterator5 = _createForOfIteratorHelper(b.lines),\n _step5;\n try {\n for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {\n var l = _step5.value;\n parsonsText += ' '.repeat(l.indent) + l.text + '\\n';\n }\n } catch (err) {\n _iterator5.e(err);\n } finally {\n _iterator5.f();\n }\n }\n } catch (err) {\n _iterator4.e(err);\n } finally {\n _iterator4.f();\n }\n parsonsText = parsonsText.slice(0, -1); // remove last newline\n\n var textEl = this.runnableDiv.querySelector('textarea');\n textEl.innerHTML = textEl.innerHTML.replace('==PARSONSCODE==', parsonsText);\n\n // data-component=\"parsons-runnable\" marks it as waiting to be turned into an activecode\n var activeCodeToBe = this.runnableDiv.querySelector('[data-component=\"parsons-runnable\"]');\n activeCodeToBe.dataset.component = 'activecode';\n window.runestoneComponents.renderOneComponent(this.runnableDiv);\n }\n }, {\n key: \"renderFeedback\",\n value: function renderFeedback() {\n this.grader.showfeedback = true;\n this.grade = this.grader.graderState;\n var feedbackArea;\n var answerArea = $(this.answerArea);\n if (this.showfeedback === true) {\n feedbackArea = $(this.messageDiv);\n } else {\n feedbackArea = $(\"#doesnotexist\");\n }\n if (this.grade === \"correct\") {\n answerArea.addClass(\"correct\");\n feedbackArea.fadeIn(100);\n feedbackArea.attr(\"class\", \"alert alert-info\");\n var message = this.checkCount > 1 ? $.i18n(\"msg_parson_correct\", this.checkCount) : $.i18n(\"msg_parson_correct_first_try\");\n if (this.options.runnable) message += \" \" + $.i18n(\"msg_parson_correct_runnable\");\n feedbackArea.html(message);\n }\n if (this.grade === \"incorrectTooShort\") {\n // too little code\n answerArea.addClass(\"incorrect\");\n feedbackArea.fadeIn(500);\n feedbackArea.attr(\"class\", \"alert alert-danger\");\n feedbackArea.html($.i18n(\"msg_parson_too_short\"));\n }\n if (this.grade === \"incorrectIndent\") {\n var incorrectBlocks = [];\n for (var i = 0; i < this.grader.indentLeft.length; i++) {\n block = this.grader.indentLeft[i].block();\n if (incorrectBlocks.indexOf(block) == -1) {\n incorrectBlocks.push(block);\n $(block.view).addClass(\"indentLeft\");\n }\n }\n for (var _i6 = 0; _i6 < this.grader.indentRight.length; _i6++) {\n block = this.grader.indentRight[_i6].block();\n if (incorrectBlocks.indexOf(block) == -1) {\n incorrectBlocks.push(block);\n $(block.view).addClass(\"indentRight\");\n }\n }\n feedbackArea.fadeIn(500);\n feedbackArea.attr(\"class\", \"alert alert-danger\");\n if (incorrectBlocks.length == 1) {\n feedbackArea.html($.i18n(\"msg_parson_wrong_indent\"));\n } else {\n feedbackArea.html($.i18n(\"msg_parson_wrong_indents\"));\n }\n }\n if (this.grade === \"incorrectMoveBlocks\") {\n var answerBlocks = this.answerBlocks();\n var inSolution = [];\n var inSolutionIndexes = [];\n var notInSolution = [];\n for (var _i7 = 0; _i7 < answerBlocks.length; _i7++) {\n var block = answerBlocks[_i7];\n // Skip fixed blocks - they are not part of grading\n if (block.fixed) {\n continue;\n }\n var index = this.solution.indexOf(block.lines[0]);\n if (index == -1) {\n notInSolution.push(block);\n } else {\n inSolution.push(block);\n inSolutionIndexes.push(index);\n }\n }\n var lisIndexes = this.grader.inverseLISIndices(inSolutionIndexes, inSolution);\n for (var _i8 = 0; _i8 < lisIndexes.length; _i8++) {\n notInSolution.push(inSolution[lisIndexes[_i8]]);\n }\n answerArea.addClass(\"incorrect\");\n feedbackArea.fadeIn(500);\n feedbackArea.attr(\"class\", \"alert alert-danger\");\n if (this.showfeedback === true) {\n var distractorFeedbacks = [];\n for (var _i9 = 0; _i9 < notInSolution.length; _i9++) {\n $(notInSolution[_i9].view).addClass(\"incorrectPosition\");\n // Check if it's a distractor with feedback\n if (notInSolution[_i9].isDistractor() && notInSolution[_i9].lines[0].distractHelptext) {\n distractorFeedbacks.push(notInSolution[_i9].lines[0].distractHelptext);\n }\n }\n // Display distractor-specific feedback if available\n if (distractorFeedbacks.length > 0) {\n var feedbackHtml = $.i18n(\"msg_parson_wrong_order\") + \"

        Feedback:
          \";\n for (var _i0 = 0; _i0 < distractorFeedbacks.length; _i0++) {\n feedbackHtml += \"
        • \" + distractorFeedbacks[_i0] + \"
        • \";\n }\n feedbackHtml += \"
        \";\n feedbackArea.html(feedbackHtml);\n return;\n }\n }\n feedbackArea.html($.i18n(\"msg_parson_wrong_order\"));\n }\n }\n\n /* =====================================================================\n ==== ADAPTIVE ==========================================================\n ===================================================================== */\n // Initialize this problem as adaptive\n // helpCount = number of checks before help is given (negative)\n // canHelp = boolean as to whether help can be provided\n // checkCount = how many times it has been checked before correct\n // userRating = 0..100 how good the person is at solving problems\n }, {\n key: \"initializeAdaptive\",\n value: function initializeAdaptive() {\n this.adaptiveId = _superPropGet(Parsons, \"localStorageKey\", this, 3)([]);\n this.canHelp = true;\n //this.helpCount = -3; // Number of checks before help is offered\n this.checkCount = 0;\n this.numDistinct = 0; // number of distinct solution attempts (different from previous)\n this.gotHelp = false;\n // Initialize the userRating\n var storageProblem = localStorage.getItem(this.adaptiveId + \"Problem\");\n if (storageProblem == this.divid) {\n // Already in this problem\n this.checkCount = localStorage.getItem(this.adaptiveId + this.divid + \"Count\");\n if (this.checkCount == undefined) {\n this.checkCount = 0;\n }\n return this;\n }\n var count = localStorage.getItem(this.adaptiveId + this.divid + \"Count\");\n if (count == undefined || count == \"NaN\") {\n count = 0;\n }\n this.checkCount = count;\n this.recentAttempts = localStorage.getItem(this.adaptiveId + \"recentAttempts\");\n if (this.recentAttempts == undefined || this.recentAttempts == \"NaN\") {\n this.recentAttempts = 3;\n }\n localStorage.setItem(this.adaptiveId + \"recentAttempts\", this.recentAttempts);\n localStorage.setItem(this.adaptiveId + \"Problem\", this.divid);\n localStorage.setItem(this.adaptiveId + this.divid + \"Count\", this.checkCount);\n localStorage.setItem(this.adaptiveId + \"Solved\", false);\n }\n\n // Return a boolean of whether the user must deal with indentation\n }, {\n key: \"usesIndentation\",\n value: function usesIndentation() {\n if (this.noindent || this.solutionIndent() == 0) {\n // was $(this.answerArea).hasClass(\"answer\") - bje changed\n return false;\n } else {\n return true;\n }\n }\n\n // Find a distractor to remove to make the problem easier\n // * try first in the answer area\n // * if not, try the source area\n // * if not, return undefined\n }, {\n key: \"distractorToRemove\",\n value: function distractorToRemove() {\n var blocks = this.enabledAnswerBlocks();\n var block;\n for (var i = 0; i < blocks.length; i++) {\n block = blocks[i];\n console.log(\"within enabled Answer Blocks\");\n if (block.isDistractor()) {\n return block;\n }\n }\n blocks = this.enabledSourceBlocks();\n for (var _i1 = 0; _i1 < blocks.length; _i1++) {\n block = blocks[_i1];\n if (block.isDistractor()) {\n return block;\n }\n }\n return undefined;\n }\n\n // Return the number of blocks that exist\n }, {\n key: \"numberOfBlocks\",\n value: function numberOfBlocks() {\n var fIncludeDistractors = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;\n var numberOfBlocks = 0;\n for (var i = 0; i < this.blocks.length; i++) {\n if (this.blocks[i].enabled() && (fIncludeDistractors || !this.blocks[i].isDistractor())) {\n numberOfBlocks += 1;\n }\n }\n return numberOfBlocks;\n }\n\n // Remove this distractors to make the problem easier\n }, {\n key: \"removeDistractor\",\n value: function removeDistractor(block) {\n // Alert the user to what is happening\n var feedbackArea = $(this.messageDiv);\n feedbackArea.fadeIn(500);\n feedbackArea.attr(\"class\", \"alert alert-info\");\n feedbackArea.html($.i18n(\"msg_parson_not_solution\"));\n // Stop ability to select\n if (block.lines[0].distractHelptext) {\n block.view.setAttribute(\"data-toggle\", \"tooltip\");\n block.view.setAttribute(\"title\", block.lines[0].distractHelptext);\n }\n block.disable();\n // If in answer area, move to source area\n if (!block.inSourceArea()) {\n var sourceRect = this.sourceArea.getBoundingClientRect();\n var startX = block.pageXCenter() - 1;\n var startY = block.pageYCenter();\n var endX = sourceRect.left + window.pageXOffset + sourceRect.width / 2;\n var endY = sourceRect.top + window.pageYOffset + block.view.getBoundingClientRect().height / 2;\n var slideUnderBlock = block.slideUnderBlock();\n if (slideUnderBlock !== undefined) {\n endY += slideUnderBlock.view.getBoundingClientRect().height + 20;\n endY += parseInt($(slideUnderBlock.view).css(\"top\"));\n }\n var that = this;\n $(block.view).css({\n \"border-color\": \"#000\",\n \"background-color\": \"#fff\"\n });\n $(block.view).animate({\n opacity: 1.0\n }, {\n duration: Math.sqrt(Math.pow(endY - startY, 2) + Math.pow(endX - startX, 2)) * 4 + 500,\n start: function start() {\n that.moving = block;\n that.movingX = startX;\n that.movingY = startY;\n that.updateView();\n },\n progress: function progress(a, p, c) {\n that.movingX = startX * (1 - p) + endX * p;\n that.movingY = startY * (1 - p) + endY * p;\n that.updateView();\n },\n complete: function complete() {\n delete that.moving;\n delete that.movingX;\n delete that.movingY;\n that.updateView();\n $(block.view).animate({\n opacity: 0.3,\n \"border-color\": \"#d3d3d3\",\n \"background-color\": \"#efefef\"\n }, {\n duration: 1000,\n complete: function complete() {\n $(block.view).css({\n opacity: \"\",\n \"border-color\": \"\",\n \"background-color\": \"\"\n });\n $(block.view).addClass(\"disabled\");\n }\n });\n }\n });\n } else {\n $(block.view).css({\n \"border-color\": \"#000\",\n \"background-color\": \"#fff\"\n });\n $(block.view).animate({\n opacity: 0.3,\n \"border-color\": \"#d3d3d3\",\n \"background-color\": \"#efefef\"\n }, {\n duration: 2000,\n complete: function complete() {\n $(block.view).css({\n \"border-color\": \"\",\n \"background-color\": \"\"\n });\n }\n });\n }\n }\n\n // Give the user the indentation\n }, {\n key: \"removeIndentation\",\n value: function removeIndentation() {\n // Alert the user to what is happening\n var feedbackArea = $(this.messageDiv);\n feedbackArea.fadeIn(500);\n feedbackArea.attr(\"class\", \"alert alert-info\");\n feedbackArea.html($.i18n(\"msg_parson_provided_indent\"));\n // Move and resize blocks\n var blockWidth = 200;\n for (var i = 0; i < this.lines.length; i++) {\n var line = this.lines[i];\n var expandedWidth = line.width + line.indent * this.options.pixelsPerIndent + 30;\n blockWidth = Math.max(blockWidth, expandedWidth);\n }\n if (this.options.numbered != undefined) {\n blockWidth += 25;\n }\n this.areaWidth = blockWidth + 22;\n var block, indent;\n var sourceBlocks = this.sourceBlocks();\n for (var _i10 = 0; _i10 < sourceBlocks.length; _i10++) {\n block = sourceBlocks[_i10];\n indent = block.solutionIndent();\n if (indent == 0) {\n $(block.view).animate({\n width: blockWidth\n }, {\n duration: 1000\n });\n } else {\n $(block.view).animate({\n width: blockWidth - indent * this.options.pixelsPerIndent,\n \"padding-left\": indent * this.options.pixelsPerIndent + 10\n }, {\n duration: 1000\n });\n }\n }\n for (var _i11 = 0; _i11 < this.pairedDivs.length; _i11++) {\n $(this.pairedDivs[_i11]).animate({\n width: blockWidth + 34\n }, {\n duration: 1000\n });\n }\n var answerBlocks = this.answerBlocks();\n for (var _i12 = 0; _i12 < answerBlocks.length; _i12++) {\n block = answerBlocks[_i12];\n indent = block.solutionIndent();\n if (indent == 0) {\n $(block.view).animate({\n left: 0,\n width: blockWidth\n }, {\n duration: 1000\n });\n } else {\n $(block.view).animate({\n left: 0,\n width: blockWidth - indent * this.options.pixelsPerIndent,\n \"padding-left\": indent * this.options.pixelsPerIndent + 10\n }, {\n duration: 1000\n });\n }\n }\n // Resize answer and source area\n $(this.answerArea).removeClass(\"answer1 answer2 answer3 answer4\");\n $(this.answerArea).addClass(\"answer\");\n this.indent = 0;\n this.noindent = true;\n $(this.sourceArea).animate({\n width: this.areaWidth + 2\n }, {\n duration: 1000\n });\n $(this.answerArea).animate({\n width: this.areaWidth + 2\n }, {\n duration: 1000\n });\n // Change the model (with view)\n $(this.answerArea).animate({\n opacity: 1.0\n }, {\n duration: 1100,\n complete: function complete() {\n $(this.answerArea).css({\n opacity: \"\"\n });\n // Update the model\n for (var _i13 = 0; _i13 < sourceBlocks.length; _i13++) {\n sourceBlocks[_i13].addIndent();\n }\n for (var _i14 = 0; _i14 < answerBlocks.length; _i14++) {\n answerBlocks[_i14].addIndent();\n }\n }\n });\n }\n\n // first check if any solution blocks are in the source still (left side) and not\n // in the answer\n }, {\n key: \"getSolutionBlockInSource\",\n value: function getSolutionBlockInSource() {\n var solutionBlocks = this.solutionBlocks();\n var answerBlocks = this.answerBlocks();\n var sourceBlocks = this.sourceBlocks();\n var solBlock = null;\n var currBlock = null;\n\n // loop through sourceBlocks and return a block if it is not in the solution\n for (var i = 0; i < sourceBlocks.length; i++) {\n // get the current block from the source\n currBlock = sourceBlocks[i];\n\n // if currBlock is in the solution and isn't the first block and isn't in the answer\n if (solutionBlocks.indexOf(currBlock) > 0 && answerBlocks.indexOf(currBlock) < 0) {\n return currBlock;\n }\n }\n // didn't find any block in the source that is in the solution\n return null;\n }\n\n // Find a block2 that is furthest from block1 in the answer\n // don't use the very first block in the solution as block2\n }, {\n key: \"getFurthestBlock\",\n value: function getFurthestBlock() {\n var solutionBlocks = this.solutionBlocks();\n var answerBlocks = this.answerBlocks();\n var maxDist = 0;\n var dist = 0;\n var maxBlock = null;\n var currBlock = null;\n var indexSol = 0;\n var prevBlock = null;\n var indexPrev = 0;\n\n // loop through the blocks in the answer\n for (var i = 0; i < answerBlocks.length; i++) {\n currBlock = answerBlocks[i];\n indexSol = solutionBlocks.indexOf(currBlock);\n if (indexSol > 0) {\n prevBlock = solutionBlocks[indexSol - 1];\n indexPrev = answerBlocks.indexOf(prevBlock);\n //alert(\"my index \" + i + \" index prev \" + indexPrev);\n\n // calculate the distance in the answer\n dist = Math.abs(i - indexPrev);\n if (dist > maxDist) {\n maxDist = dist;\n maxBlock = currBlock;\n }\n }\n }\n return maxBlock;\n }\n\n // Combine blocks together\n }, {\n key: \"combineBlocks\",\n value: function combineBlocks() {\n var solutionBlocks = this.solutionBlocks();\n var answerBlocks = this.answerBlocks();\n var sourceBlocks = this.sourceBlocks();\n\n // Alert the user to what is happening\n var feedbackArea = $(this.messageDiv);\n feedbackArea.fadeIn(500);\n feedbackArea.attr(\"class\", \"alert alert-info\");\n feedbackArea.html($.i18n(\"msg_parson_combined_blocks\"));\n var block1 = null;\n var block2 = null;\n\n // get a solution block that is still in source (not answer), if any\n block2 = this.getSolutionBlockInSource();\n\n // if none in source get block that is furthest from block1\n if (block2 == null) {\n block2 = this.getFurthestBlock();\n }\n\n // get block1 (above block2) in solution\n var index = solutionBlocks.indexOf(block2);\n block1 = solutionBlocks[index - 1];\n\n // get index of each in answer\n var index1 = answerBlocks.indexOf(block1);\n var index2 = answerBlocks.indexOf(block2);\n var move = false;\n\n // if both in answer set move based on if directly above each other\n if (index1 >= 0 && index2 >= 0) {\n move = index1 + 1 !== index2;\n\n // else if both in source set move again based on if above each other\n } else if (index1 < 0 && index2 < 0) {\n index1 = sourceBlocks.indexOf(block1);\n index2 = sourceBlocks.indexOf(block2);\n move = index1 + 1 !== index2;\n\n // one in source and one in answer so must move\n } else {\n move = true;\n if (index1 < 0) {\n index1 = sourceBlocks.indexOf(block1);\n }\n if (index2 < 0) {\n index2 = sourceBlocks.indexOf(block2);\n }\n }\n var subtract = index2 < index1; // is block2 higher\n\n if (move) {\n // Move the block\n var startX = block2.pageXCenter() - 1;\n var startY = block2.pageYCenter();\n var endX = block1.pageXCenter() - 1;\n var endY = block1.pageYCenter() + block1.view.getBoundingClientRect().height / 2 + 5;\n if (subtract) {\n endY -= block2.view.getBoundingClientRect().height / 2;\n } else {\n endY += block2.view.getBoundingClientRect().height / 2;\n }\n var that = this;\n $(block2.view).animate({\n opacity: 1\n }, {\n duration: 1000,\n // 1 seccond\n start: function start() {\n $(block1.view).css({\n \"border-color\": \"#000\",\n \"background-color\": \"#fff\"\n });\n $(block2.view).css({\n \"border-color\": \"#000\",\n \"background-color\": \"#fff\"\n });\n block2.lines[0].index += 1000;\n that.moving = block2;\n that.movingX = startX;\n that.movingY = startY;\n that.updateView();\n },\n progress: function progress(a, p, c) {\n that.movingX = startX * (1 - p) + endX * p;\n that.movingY = startY * (1 - p) + endY * p;\n that.updateView();\n },\n complete: function complete() {\n delete that.moving;\n delete that.movingX;\n delete that.movingY;\n that.updateView();\n block2.lines[0].index -= 1000;\n block1.consumeBlock(block2);\n $(block1.view).animate({\n \"border-color\": \"#d3d3d3\",\n \"background-color\": \"#efefef\"\n }, {\n duration: 1000,\n complete: function complete() {\n $(block1.view).css({\n \"border-color\": \"\",\n \"background-color\": \"\"\n });\n }\n });\n }\n });\n } else {\n $(block2.view).animate({\n opacity: 1\n }, {\n duration: 1000,\n start: function start() {\n $(block1.view).css({\n \"border-color\": \"#000\",\n \"background-color\": \"#fff\"\n });\n $(block2.view).css({\n \"border-color\": \"#000\",\n \"background-color\": \"#fff\"\n });\n },\n complete: function complete() {\n block1.consumeBlock(block2);\n $(block1.view).animate({\n \"border-color\": \"#d3d3d3\",\n \"background-color\": \"#efefef\"\n }, {\n duration: 1000,\n complete: function complete() {\n $(block1.view).css({\n \"border-color\": \"\",\n \"background-color\": \"\"\n });\n }\n });\n }\n });\n }\n }\n\n // Adapt the problem to be easier\n // * remove a distractor until none are present\n // * combine blocks until 3 are left\n }, {\n key: \"makeEasier\",\n value: function makeEasier() {\n var distractorToRemove = this.distractorToRemove();\n if (distractorToRemove !== undefined && !distractorToRemove.inSourceArea()) {\n alert($.i18n(\"msg_parson_remove_incorrect\"));\n this.removeDistractor(distractorToRemove);\n this.logMove(\"removedDistractor-\" + distractorToRemove.hash());\n } else {\n var numberOfBlocks = this.numberOfBlocks(false);\n if (numberOfBlocks > 3) {\n alert($.i18n(\"msg_parson_will_combine\"));\n this.combineBlocks();\n this.logMove(\"combinedBlocks\");\n } else {\n /*else if(this.numberOfBlocks(true) > 3 && distractorToRemove !== undefined) {\n alert(\"Will remove an incorrect code block from source area\");\n this.removeDistractor(distractorToRemove);\n this.logMove(\"removedDistractor-\" + distractorToRemove.hash());\n } */\n alert($.i18n(\"msg_parson_three_blocks_left\"));\n this.canHelp = false;\n }\n //if (numberOfBlocks < 5) {\n //\tthis.canHelp = false;\n //\tthis.helpButton.disabled = true;\n //}\n }\n }\n\n // The \"Help Me\" button was pressed and the problem should be simplified\n }, {\n key: \"helpMe\",\n value: function helpMe() {\n this.clearFeedback();\n //this.helpCount = -1; // amount to allow for multiple helps in a row\n //if (this.helpCount < 0) {\n //\tthis.helpCount = Math.max(this.helpCount, -1); // min 1 attempt before more help\n //this.helpButton.disabled = true;\n //}\n // if less than 3 attempts\n if (this.numDistinct < 3) {\n alert($.i18n(\"msg_parson_atleast_three_attempts\"));\n }\n // otherwise give help\n else {\n this.gotHelp = true;\n this.makeEasier();\n }\n }\n\n /* =====================================================================\n ==== UTILITY ===========================================================\n ===================================================================== */\n\n // Return a date from a timestamp (either mySQL or JS format)\n }, {\n key: \"dateFromTimestamp\",\n value: function dateFromTimestamp(timestamp) {\n var date = new Date(timestamp);\n if (isNaN(date.getTime())) {\n var t = timestamp.split(/[- :]/);\n date = new Date(t[0], t[1] - 1, t[2], t[3], t[4], t[5]);\n }\n return date;\n }\n\n // A function for returning a shuffled version of an array\n }, {\n key: \"shuffled\",\n value: function shuffled(array) {\n var currentIndex = array.length;\n var returnArray = array.slice();\n var temporaryValue, randomIndex;\n // While there remain elements to shuffle...\n while (0 !== currentIndex) {\n // Pick a remaining element...\n randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex -= 1;\n // And swap it with the current element.\n temporaryValue = returnArray[currentIndex];\n returnArray[currentIndex] = returnArray[randomIndex];\n returnArray[randomIndex] = temporaryValue;\n }\n return returnArray;\n }\n\n /* =====================================================================\n ==== KEYBOARD INTERACTION ==============================================\n ===================================================================== */\n\n // When the user has entered the Parsons problem via keyboard mode\n }, {\n key: \"enterKeyboardMode\",\n value: function enterKeyboardMode() {\n $(this.keyboardTip).show();\n $(this.sourceLabel).hide();\n $(this.answerLabel).hide();\n this.clearFeedback();\n }\n\n // When the user leaves the Parsons problem via keyboard mode\n }, {\n key: \"exitKeyboardMode\",\n value: function exitKeyboardMode() {\n $(this.keyboardTip).hide();\n $(this.sourceLabel).show();\n $(this.answerLabel).show();\n }\n\n /* =====================================================================\n ==== VIEW ==============================================================\n ===================================================================== */\n\n // Clear any feedback from the answer area\n }, {\n key: \"clearFeedback\",\n value: function clearFeedback() {\n $(this.answerArea).removeClass(\"incorrect correct\");\n var children = this.answerArea.childNodes;\n for (var i = 0; i < children.length; i++) {\n $(children[i]).removeClass(\"correctPosition incorrectPosition indentLeft indentRight\");\n }\n $(this.messageDiv).hide();\n }\n\n // Disable the interface\n }, {\n key: \"disableInteraction\",\n value: function () {\n var _disableInteraction = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee6() {\n var i, block;\n return _regenerator().w(function (_context6) {\n while (1) switch (_context6.n) {\n case 0:\n _context6.n = 1;\n return this.checkServerComplete;\n case 1:\n if (this.blocks !== undefined) {\n for (i = 0; i < this.blocks.length; i++) {\n block = this.blocks[i];\n block.disable();\n }\n }\n // Hide buttons\n $(this.checkButton).hide();\n $(this.resetButton).hide();\n case 2:\n return _context6.a(2);\n }\n }, _callee6, this);\n }));\n function disableInteraction() {\n return _disableInteraction.apply(this, arguments);\n }\n return disableInteraction;\n }() // Based on the moving element, etc., establish the moving state\n // rest: not moving\n // source: moving inside source area\n // answer: moving inside answer area\n // moving: moving outside areas\n }, {\n key: \"movingState\",\n value: function movingState() {\n if (this.moving == undefined) {\n return \"rest\";\n }\n var x = this.movingX - window.pageXOffset;\n var y = this.movingY - window.pageYOffset;\n // Check if in answer area\n var bounds = this.answerArea.getBoundingClientRect();\n if (x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom) {\n return \"answer\";\n }\n // Check if in source area\n bounds = this.sourceArea.getBoundingClientRect();\n if (x >= bounds.left && x <= bounds.right && y >= bounds.top && y <= bounds.bottom) {\n return \"source\";\n }\n return \"moving\";\n }\n\n // Update the Parsons view\n // This gets called when dragging a block\n }, {\n key: \"updateView\",\n value: function updateView() {\n // Based on the new and the old state, figure out what to update\n var state = this.state;\n var newState = this.movingState();\n var updateSource = true;\n var updateAnswer = true;\n var updateMoving = newState == \"moving\";\n if (state == newState) {\n if (newState == \"rest\") {\n updateSource = false;\n updateAnswer = false;\n } else if (newState == \"source\") {\n updateAnswer = false;\n } else if (newState == \"answer\") {\n updateSource = false;\n } else if (newState == \"moving\") {\n updateAnswer = false;\n updateSource = false;\n }\n }\n var movingHeight;\n if (this.moving !== undefined) {\n // Must get height here as detached items don't have height\n movingHeight = $(this.moving.view).outerHeight(true);\n $(this.moving.view).detach();\n }\n var positionTop, width;\n var baseWidth = this.areaWidth - 22;\n // Update the Source Area\n if (updateSource) {\n positionTop = 0;\n var blocks = this.sourceBlocks();\n if (newState == \"source\") {\n var hasInserted = false;\n var movingBin = this.moving.pairedBin();\n var binForBlock = [];\n for (i = 0; i < blocks.length; i++) {\n binForBlock.push(blocks[i].pairedBin());\n }\n if (!binForBlock.includes(movingBin)) {\n movingBin = -1;\n }\n\n // Build a map of which blocks are in groups and their positions\n var groupTagForBlock = [];\n var groupIndexForBlock = [];\n for (i = 0; i < blocks.length; i++) {\n groupTagForBlock.push(blocks[i].isGroupedBlock ? blocks[i].groupTag : null);\n groupIndexForBlock.push(blocks[i].isGroupedBlock ? blocks[i].groupIndex : -1);\n }\n\n // Check if this is a grouped block - find its original position within its group\n var groupInsertIndex = -1;\n var isMovingGrouped = this.moving.isGroupedBlock && this.moving.groupTag;\n if (isMovingGrouped) {\n var movingGroupTag = this.moving.groupTag;\n var movingGroupIndex = this.moving.groupIndex;\n\n // Find where in the blocks array this block should be inserted\n // based on its groupIndex relative to other group members\n for (i = 0; i < blocks.length; i++) {\n if (blocks[i].isGroupedBlock && blocks[i].groupTag === movingGroupTag) {\n // Found a group member\n if (blocks[i].groupIndex > movingGroupIndex) {\n // Insert before this block (it has a higher groupIndex)\n groupInsertIndex = i;\n break;\n }\n }\n }\n // If we didn't find a spot (all existing members have lower groupIndex),\n // insert after the last group member\n if (groupInsertIndex === -1) {\n for (i = blocks.length - 1; i >= 0; i--) {\n if (blocks[i].isGroupedBlock && blocks[i].groupTag === movingGroupTag) {\n groupInsertIndex = i + 1;\n break;\n }\n }\n }\n }\n\n // Build insertPositions - exclude positions inside groups for non-grouped blocks\n var insertPositions = [];\n if (binForBlock.length == 0) {\n insertPositions.push(0);\n } else {\n // Check if position 0 is valid\n var pos0Valid = true;\n if (!isMovingGrouped && groupTagForBlock[0] !== null) {\n // Non-grouped block can't insert at start of a group unless it's the group start\n if (groupIndexForBlock[0] > 0) {\n pos0Valid = false;\n }\n }\n if (pos0Valid) {\n if (movingBin == -1) {\n insertPositions.push(0);\n } else if (binForBlock[0] == movingBin) {\n insertPositions.push(0);\n }\n }\n for (i = 1; i < blocks.length; i++) {\n // For non-grouped blocks, don't allow insertion between blocks of the same group\n if (!isMovingGrouped) {\n var prevGroupTag = groupTagForBlock[i - 1];\n var currGroupTag = groupTagForBlock[i];\n // If both belong to the same group, skip this position\n if (prevGroupTag !== null && prevGroupTag === currGroupTag) {\n continue;\n }\n }\n if (binForBlock[i - 1] == movingBin) {\n insertPositions.push(i);\n } else if (binForBlock[i] == movingBin) {\n insertPositions.push(i);\n } else if (movingBin == -1 && binForBlock[i - 1] != binForBlock[i]) {\n insertPositions.push(i);\n }\n }\n\n // Check if end position is valid\n var endValid = true;\n if (!isMovingGrouped && blocks.length > 0) {\n var lastGroupTag = groupTagForBlock[blocks.length - 1];\n // Non-grouped can insert at end unless last block is mid-group\n // (This is actually always ok since it's after the group)\n }\n if (endValid) {\n if (movingBin == -1) {\n insertPositions.push(binForBlock.length);\n } else if (binForBlock[binForBlock.length - 1] == movingBin) {\n insertPositions.push(binForBlock.length);\n }\n }\n }\n var x = this.movingX - this.sourceArea.getBoundingClientRect().left - window.pageXOffset - baseWidth / 2 - 11;\n var y = this.movingY - this.sourceArea.getBoundingClientRect().top - window.pageYOffset;\n\n // If grouped block, insert at its original position within the group\n if (groupInsertIndex >= 0) {\n for (i = 0; i < blocks.length; i++) {\n var item = blocks[i];\n if (!hasInserted && i === groupInsertIndex) {\n hasInserted = true;\n this.sourceArea.insertBefore(this.moving.view, item.view);\n $(this.moving.view).css({\n left: x,\n top: y - movingHeight / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n positionTop = positionTop + movingHeight;\n }\n $(item.view).css({\n left: 0,\n top: positionTop,\n width: baseWidth,\n \"z-index\": 2\n });\n positionTop = positionTop + $(item.view).outerHeight(true);\n }\n if (!hasInserted) {\n // Insert at the end (group is at the end)\n $(this.moving.view).appendTo(\"#\" + this.counterId + \"-source\");\n $(this.moving.view).css({\n left: x,\n top: y - movingHeight / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n }\n } else {\n // Normal insertion logic (non-grouped blocks)\n for (i = 0; i < blocks.length; i++) {\n var item = blocks[i];\n var j;\n if (!hasInserted && insertPositions.includes(i)) {\n var testHeight = $(item.view).outerHeight(true);\n for (j = i + 1; j < blocks.length; j++) {\n if (insertPositions.includes(j)) {\n break;\n }\n testHeight += $(blocks[j].view).outerHeight(true);\n }\n if (y - positionTop < movingHeight + testHeight / 2 || i == insertPositions[insertPositions.length - 1]) {\n hasInserted = true;\n this.sourceArea.insertBefore(this.moving.view, item.view);\n $(this.moving.view).css({\n left: x,\n top: y - movingHeight / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n positionTop = positionTop + movingHeight;\n }\n }\n $(item.view).css({\n left: 0,\n top: positionTop,\n width: baseWidth,\n \"z-index\": 2\n });\n positionTop = positionTop + $(item.view).outerHeight(true);\n }\n if (!hasInserted) {\n $(this.moving.view).appendTo(\"#\" + this.counterId + \"-source\");\n $(this.moving.view).css({\n left: x,\n top: y - $(this.moving.view).outerHeight(true) / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n }\n }\n } else {\n for (var i = 0; i < blocks.length; i++) {\n item = blocks[i];\n $(item.view).css({\n left: 0,\n top: positionTop,\n width: baseWidth,\n \"z-index\": 2\n });\n positionTop = positionTop + $(item.view).outerHeight(true);\n }\n }\n // Update the Paired Distractor Indicators\n for (i = 0; i < this.pairedBins.length; i++) {\n var bin = this.pairedBins[i];\n var matching = [];\n for (j = 0; j < blocks.length; j++) {\n block = blocks[j];\n if (block.matchesBin(bin)) {\n matching.push(block);\n }\n }\n var div = this.pairedDivs[i];\n if (matching.length == 0) {\n $(div).hide();\n } else {\n $(div).show();\n var height = -5;\n height += parseInt($(matching[matching.length - 1].view).css(\"top\"));\n height -= parseInt($(matching[0].view).css(\"top\"));\n height += $(matching[matching.length - 1].view).outerHeight(true);\n $(div).css({\n left: -6,\n top: $(matching[0].view).css(\"top\"),\n width: baseWidth + 34,\n height: height,\n \"z-index\": 1,\n \"text-indent\": -30,\n \"padding-top\": (height - 70) / 2,\n overflow: \"visible\",\n \"font-size\": 43,\n \"vertical-align\": \"middle\",\n color: \"#7e7ee7\"\n });\n $(div).html(\"or{\");\n }\n if (matching.length == 1) {\n $(div).html(\"\");\n }\n }\n\n // Update the Grouped Blocks Indicators\n if (this.groupedDivs) {\n for (i = 0; i < this.groupedDivs.length; i++) {\n var groupInfo = this.groupedDivs[i];\n var groupDiv = groupInfo.element;\n var groupBlocks = groupInfo.blocks;\n\n // Find which blocks from this group are in the source area\n var matchingBlocks = [];\n for (j = 0; j < blocks.length; j++) {\n block = blocks[j];\n if (block.isGroupedBlock && block.groupTag === groupInfo.groupTag) {\n matchingBlocks.push(block);\n }\n }\n if (matchingBlocks.length < 2) {\n // Hide if less than 2 blocks from group are in source\n $(groupDiv).hide();\n } else {\n $(groupDiv).show();\n\n // Sort by their current position (top value)\n matchingBlocks.sort(function (a, b) {\n return parseInt($(a.view).css(\"top\")) - parseInt($(b.view).css(\"top\"));\n });\n var firstBlock = matchingBlocks[0];\n var lastBlock = matchingBlocks[matchingBlocks.length - 1];\n\n // Get the actual block heights (without margin)\n var firstBlockTop = parseInt($(firstBlock.view).css(\"top\"));\n var lastBlockTop = parseInt($(lastBlock.view).css(\"top\"));\n var lastBlockHeight = $(lastBlock.view).outerHeight(false); // height without margin\n\n var topPos = firstBlockTop;\n var bottomPos = lastBlockTop + lastBlockHeight;\n var height = bottomPos - topPos;\n\n // Update the curly brace height\n var curlyBrace = groupDiv.querySelector('.curly-brace');\n if (curlyBrace) {\n curlyBrace.style.height = height + 'px';\n }\n $(groupDiv).css({\n position: 'absolute',\n left: -35,\n top: topPos,\n height: height,\n zIndex: 1\n });\n }\n }\n }\n }\n // Update the Answer Area\n if (updateAnswer) {\n var block, indent;\n positionTop = 0;\n width = this.areaWidth + this.indent * this.options.pixelsPerIndent - 22;\n var blocks = this.answerBlocks();\n if (newState == \"answer\") {\n var hasInserted = false;\n var x = this.movingX - this.answerArea.getBoundingClientRect().left - window.pageXOffset - baseWidth / 2 - 11;\n var movingIndent = Math.round(x / this.options.pixelsPerIndent);\n if (movingIndent < 0) {\n movingIndent = 0;\n } else if (movingIndent > this.indent) {\n movingIndent = this.indent;\n } else {\n x = movingIndent * this.options.pixelsPerIndent;\n }\n var y = this.movingY - this.answerArea.getBoundingClientRect().top - window.pageYOffset;\n this.moving.indent = movingIndent;\n var inDropZone = false;\n var currentDropZoneIndex = -1;\n for (i = 0; i < blocks.length; i++) {\n block = blocks[i];\n\n // Check if we're at a fixed block that ends a drop zone\n if (this.dropZones) {\n for (var dz = 0; dz < this.dropZones.length; dz++) {\n if (this.dropZones[dz].beforeBlock === block) {\n if (inDropZone) {\n positionTop += 8; // Bottom padding before next fixed block\n }\n inDropZone = false;\n currentDropZoneIndex = -1;\n break;\n }\n }\n }\n if (!hasInserted) {\n if (y - positionTop < (movingHeight + $(block.view).outerHeight(true)) / 2) {\n // Don't insert before the first fixed block\n if (block.isFirstFixed) {\n // Skip - can't place before first fixed block\n } else {\n hasInserted = true;\n this.answerArea.insertBefore(this.moving.view, block.view);\n $(this.moving.view).css({\n left: x,\n top: y - movingHeight / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n positionTop = positionTop + movingHeight;\n }\n }\n }\n\n // Add top padding for first non-fixed block in drop zone\n if (!block.fixed && inDropZone && currentDropZoneIndex >= 0) {\n var prevBlock = blocks[i - 1];\n if (prevBlock && prevBlock.fixed) {\n positionTop += 5;\n }\n }\n indent = block.indent * this.options.pixelsPerIndent;\n $(block.view).css({\n left: indent,\n top: positionTop,\n width: width - indent,\n \"z-index\": 2\n });\n positionTop = positionTop + $(block.view).outerHeight(true);\n\n // Check if this fixed block starts a drop zone\n if (block.fixed && this.dropZones) {\n for (var _dz = 0; _dz < this.dropZones.length; _dz++) {\n if (this.dropZones[_dz].afterBlock === block) {\n inDropZone = true;\n currentDropZoneIndex = _dz;\n // Check if next block is the beforeBlock (meaning no blocks in between)\n var nextBlock = blocks[i + 1];\n if (nextBlock && nextBlock === this.dropZones[_dz].beforeBlock) {\n // Add minimum space for empty drop zone\n positionTop += 48;\n }\n break;\n }\n }\n }\n }\n if (!hasInserted) {\n // Check if last block is the last fixed block - insert before it if so\n if (this.lastFixedBlock && blocks.length > 0 && blocks[blocks.length - 1].isLastFixed) {\n this.answerArea.insertBefore(this.moving.view, this.lastFixedBlock.view);\n } else {\n $(this.moving.view).appendTo(\"#\" + this.counterId + \"-answer\");\n }\n $(this.moving.view).css({\n left: x,\n top: y - $(this.moving.view).outerHeight(true) / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n }\n } else {\n var inDropZone = false;\n var currentDropZoneIndex = -1;\n for (var _i15 = 0; _i15 < blocks.length; _i15++) {\n block = blocks[_i15];\n indent = block.indent * this.options.pixelsPerIndent;\n\n // Check if we're entering a drop zone area (after a fixed block that starts a drop zone)\n if (this.dropZones) {\n for (var _dz2 = 0; _dz2 < this.dropZones.length; _dz2++) {\n if (this.dropZones[_dz2].beforeBlock === block) {\n // We're at the fixed block that ends this drop zone\n if (inDropZone) {\n // Add bottom padding before the next fixed block\n positionTop += 8;\n }\n inDropZone = false;\n currentDropZoneIndex = -1;\n break;\n }\n }\n }\n\n // If this is a non-fixed block and we just entered a drop zone, add top padding\n if (!block.fixed && inDropZone && currentDropZoneIndex >= 0) {\n // Check if this is the first non-fixed block in the drop zone\n var prevBlock = blocks[_i15 - 1];\n if (prevBlock && prevBlock.fixed) {\n positionTop += 5; // Top padding inside drop zone\n }\n }\n $(block.view).css({\n left: indent,\n top: positionTop,\n width: width - indent,\n \"z-index\": 2\n });\n positionTop = positionTop + $(block.view).outerHeight(true);\n\n // Check if this fixed block starts a drop zone\n if (block.fixed && this.dropZones) {\n for (var _dz3 = 0; _dz3 < this.dropZones.length; _dz3++) {\n if (this.dropZones[_dz3].afterBlock === block) {\n inDropZone = true;\n currentDropZoneIndex = _dz3;\n // Check if next block is the beforeBlock (meaning no blocks in between)\n var nextBlock = blocks[_i15 + 1];\n if (nextBlock && nextBlock === this.dropZones[_dz3].beforeBlock) {\n // Add minimum space for empty drop zone\n positionTop += 48;\n }\n break;\n }\n }\n }\n }\n }\n // Position drop zone backgrounds after all blocks are positioned\n this.updateDropZoneBackgrounds(blocks, width);\n }\n // Update the Moving Area\n if (updateMoving) {\n // Add it to the lowest place in the source area\n movingBin = this.moving.pairedBin();\n if (movingBin == -1) {\n $(this.moving.view).appendTo(\"#\" + this.counterId + \"-source\");\n } else {\n var before;\n blocks = this.sourceBlocks;\n for (i = 0; i < blocks.length; i++) {\n block = blocks[i];\n if (block.pairedBin() == movingBin) {\n before = i + 1;\n }\n }\n if (before == undefined || before == blocks.length) {\n $(this.moving.view).appendTo(\"#\" + this.counterId + \"-source\");\n } else {\n this.sourceArea.insertBefore(this.moving.view, blocks[before].view);\n }\n }\n // Place in the middle of the mouse cursor\n $(this.moving.view).css({\n left: this.movingX - this.sourceArea.getBoundingClientRect().left - window.pageXOffset - $(this.moving.view).outerWidth(true) / 2,\n top: this.movingY - this.sourceArea.getBoundingClientRect().top - window.pageYOffset - movingHeight / 2,\n width: baseWidth,\n \"z-index\": 3\n });\n }\n state = newState;\n this.state = state;\n }\n }, {\n key: \"addBlockLabels\",\n value: function addBlockLabels(blocks) {\n var bin = -1;\n var binCount = 0;\n var binChildren = 0;\n var blocksNotInBins = 0;\n for (var i = 0; i < blocks.length; i++) {\n if (blocks[i].pairedBin() == -1) {\n blocksNotInBins++;\n }\n }\n for (var _i16 = 0; _i16 < blocks.length; _i16++) {\n var currentBin = blocks[_i16].pairedBin();\n if (currentBin == -1 || currentBin != bin) {\n bin = currentBin;\n binChildren = 0;\n binCount++;\n }\n var label = \"\" + binCount + (currentBin != -1 ? String.fromCharCode(97 + binChildren) : \" \");\n if (binCount < 10 && blocksNotInBins + this.pairedBins.length >= 10) {\n label += \" \";\n }\n blocks[_i16].addLabel(label, 0);\n binChildren++;\n }\n if (blocksNotInBins + this.pairedBins.length >= 10) {\n this.areaWidth += 5;\n $(this.sourceArea).css({\n width: $(this.sourceArea).width() + 5\n });\n $(this.answerArea).css({\n width: $(this.answerArea).width() + 5\n });\n }\n }\n\n // Put all the blocks back into the source area, reshuffling as necessary\n // Position drop zone backgrounds based on fixed block positions\n }, {\n key: \"updateDropZoneBackgrounds\",\n value: function updateDropZoneBackgrounds(blocks, width) {\n if (!this.dropZones || this.dropZones.length === 0) {\n return;\n }\n for (var dz = 0; dz < this.dropZones.length; dz++) {\n var dzInfo = this.dropZones[dz];\n var afterBlockTop = parseInt($(dzInfo.afterBlock.view).css(\"top\")) || 0;\n var afterBlockHeight = $(dzInfo.afterBlock.view).outerHeight(true);\n var beforeBlockTop = parseInt($(dzInfo.beforeBlock.view).css(\"top\")) || 0;\n\n // Drop zone starts right after the fixed block above\n var dzTop = afterBlockTop + afterBlockHeight;\n // Drop zone ends right before the fixed block below\n var dzHeight = beforeBlockTop - dzTop;\n\n // Only show drop zone if there's space between fixed blocks\n if (dzHeight > 5) {\n $(dzInfo.element).css({\n left: 0,\n top: dzTop,\n width: \"100%\",\n height: dzHeight,\n display: \"block\"\n });\n } else {\n $(dzInfo.element).css({\n display: \"none\"\n });\n }\n }\n }\n }, {\n key: \"resetView\",\n value: function resetView() {\n // Clear everything\n this.clearFeedback();\n // remove cloned blocks\n for (var i = 0; i < this.blocks.length; i++) {\n var clonedBlock = this.blocks[i];\n if (clonedBlock.isClone()) {\n $(clonedBlock.view).remove();\n }\n }\n // Remove all cloned lines\n if (this.lines && this.lines.length > 0) {\n var newLines = [];\n for (var _i17 = 0; _i17 < this.lines.length; _i17++) {\n var line = this.lines[_i17];\n if (!line.isCloneLine) {\n newLines.push(line);\n }\n }\n this.lines = newLines;\n }\n var scrollTop = document.body.scrollTop;\n var block;\n for (var _i18 = 0; _i18 < this.blocks.length; _i18++) {\n block = this.blocks[_i18];\n for (var j = 0; j < block.lines.length; j++) {\n var children = $(block.lines[j].view).find(\".block-label\");\n for (var c = 0; c < children.length; c++) {\n children[c].remove();\n }\n }\n block.destroy();\n $(this.blocks[_i18].view).detach();\n }\n delete this.blocks;\n this.blockIndex = 0;\n if (this.pairedDivs) {\n for (var _i19 = 0; _i19 < this.pairedDivs.length; _i19++) {\n $(this.pairedDivs[_i19]).detach();\n }\n }\n // Clean up grouped block overlays\n if (this.groupedDivs) {\n for (var _i20 = 0; _i20 < this.groupedDivs.length; _i20++) {\n $(this.groupedDivs[_i20].element).detach();\n }\n this.groupedDivs = [];\n }\n this.groupedBlocksMap = {};\n $(this.sourceArea).attr(\"style\", \"\");\n $(this.answerArea).removeClass();\n $(this.answerArea).attr(\"style\", \"\");\n // Remove existing drop zones\n if (this.dropZones) {\n for (var _i21 = 0; _i21 < this.dropZones.length; _i21++) {\n $(this.dropZones[_i21].element).detach();\n }\n this.dropZones = [];\n }\n this.noindent = this.options.noindent;\n // Reinitialize\n if (this.hasSolved) {\n this.checkCount = 0;\n this.numDistinct = 0;\n this.hasSolved = false;\n if (this.options.adaptive) {\n this.canHelp = true;\n }\n //this.helpCount = -3; // enable after 3 failed attempts\n //this.helpButton.disabled = true;\n localStorage.setItem(this.adaptiveId + \"Problem\", this.divid);\n localStorage.setItem(this.adaptiveId + this.divid + \"Count\", this.checkCount);\n localStorage.setItem(this.adaptiveId + \"Solved\", false);\n }\n this.initializeAreas(this.blocksFromSource(), this.fixedBlocks(), {});\n this.initializeInteractivity();\n document.body.scrollTop = scrollTop;\n if (this.runnableDiv) {\n // hide the rendered runnable - will get reused as is if rerevealed\n this.runnableDiv.style.display = 'none';\n }\n }\n }]);\n}(_runestonebase_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n\nParsons.counter = 0;\n\n//todo NB: The following code is drastically different from the Runestone version\n//todo Just letting you know ;)\n\n$(document).ready(function () {\n $(\"[data-component=parsons]\").each(function (index) {\n try {\n new Parsons({\n orig: $(this),\n useRunestoneServices: false\n });\n } catch (e) {}\n });\n});\n\n// Expose Parsons globally for browser access\nif (typeof window !== 'undefined') {\n window.Parsons = Parsons;\n}\n\n//# sourceURL=webpack://Parsons/./src/parsons.js?"); + +/***/ }), + +/***/ "./src/parsonsBlock.js": +/*!*****************************!*\ + !*** ./src/parsonsBlock.js ***! + \*****************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ParsonsBlock)\n/* harmony export */ });\n/* harmony import */ var _hammer_min_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./hammer.min.js */ \"./src/hammer.min.js\");\n/* harmony import */ var _hammer_min_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_hammer_min_js__WEBPACK_IMPORTED_MODULE_0__);\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n/* =====================================================================\n==== ParsonsBlock Object ===============================================\n======== The model and view of a code block.\n==== PROPERTIES ========================================================\n======== problem: the Parsons problem\n======== lines: an array of ParsonsLine in this block\n======== indent: indent based on movement\n======== view: an element for viewing this object\n======== labels: [label, line] the labels numbering the block and the lines they go on\n======== hammer: the controller based on hammer.js\n===================================================================== */\n\n\n\n// Initialize based on the problem and the lines\nvar ParsonsBlock = /*#__PURE__*/function () {\n function ParsonsBlock(problem, lines) {\n _classCallCheck(this, ParsonsBlock);\n this.problem = problem;\n this.lines = lines;\n this.indent = 0;\n this.labels = [];\n this.fixed = lines && lines.length > 0 ? Boolean(lines[0].fixed) : false;\n // Create view, adding view of lines and updating indent\n var view = document.createElement(\"div\");\n view.id = problem.counterId + \"-block-\" + problem.blockIndex;\n problem.blockIndex += 1;\n $(view).addClass(\"block\");\n var sharedIndent = lines[0].indent;\n for (var i = 1; i < lines.length; i++) {\n sharedIndent = Math.min(sharedIndent, lines[i].indent);\n }\n var lineDiv = document.createElement(\"div\");\n $(lineDiv).addClass(\"lines\");\n $(view).append(lineDiv);\n for (var _i = 0; _i < lines.length; _i++) {\n var line = lines[_i];\n var lineIndent;\n if (problem.noindent) {\n lineIndent = line.indent;\n } else {\n lineIndent = line.indent - sharedIndent;\n }\n $(line.view).removeClass(\"indent1 indent2 indent3 indent4\");\n if (this.problem.options.language != \"natural\" && this.problem.options.language != \"math\") {\n if (lineIndent > 0) {\n $(line.view).addClass(\"indent\" + lineIndent);\n }\n }\n lineDiv.appendChild(line.view);\n }\n var labelDiv = document.createElement(\"div\");\n $(labelDiv).addClass(\"labels\");\n if (this.problem.options.numbered == \"left\") {\n $(lineDiv).addClass(\"border_left\");\n $(view).prepend(labelDiv);\n $(view).css({\n \"justify-content\": \"flex-start\"\n });\n } else if (this.problem.options.numbered == \"right\") {\n $(labelDiv).addClass(\"border_left\");\n $(labelDiv).css({\n \"float\": \"right\"\n });\n $(view).css({\n \"justify-content\": \"space-between\"\n });\n $(view).append(labelDiv);\n }\n this.view = view;\n }\n // Add a line (from another block) to this block\n return _createClass(ParsonsBlock, [{\n key: \"addLine\",\n value: function addLine(line) {\n $(line.view).removeClass(\"indent1 indent2 indent3 indent4\");\n if (this.problem.noindent) {\n if (line.indent > 0) {\n $(line.view).addClass(\"indent\" + line.indent);\n }\n } else {\n var lines = this.lines;\n var sharedIndent = lines[0].indent;\n for (var i = 1; i < lines.length; i++) {\n sharedIndent = Math.min(sharedIndent, lines[i].indent);\n }\n if (sharedIndent < line.indent) {\n $(line.view).addClass(\"indent\" + (line.indent - sharedIndent));\n } else if (sharedIndent > line.indent) {\n for (var _i2 = 0; _i2 < lines.length; _i2++) {\n $(lines[_i2].view).removeClass(\"indent1 indent2 indent3 indent4\");\n // todo: if language is natural or math then don't do this\n if (this.options.language !== \"natural\" && this.options.language !== \"math\") {\n $(lines[_i2].view).addClass(\"indent\" + (lines[_i2].indent - line.indent));\n }\n }\n }\n }\n this.lines.push(line);\n $(this.view).children(\".lines\")[0].appendChild(line.view);\n }\n // Add the contents of that block to myself and then delete that block\n }, {\n key: \"consumeBlock\",\n value: function consumeBlock(block) {\n for (var i = 0; i < block.lines.length; i++) {\n this.addLine(block.lines[i]);\n }\n if ($(block.view).attr(\"tabindex\") == \"0\") {\n this.makeTabIndex();\n }\n $(block.view).detach();\n var newBlocks = [];\n for (var _i3 = 0; _i3 < this.problem.blocks.length; _i3++) {\n if (this.problem.blocks[_i3] !== block) {\n newBlocks.push(this.problem.blocks[_i3]);\n }\n }\n for (var _i4 = 0; _i4 < block.labels.length; _i4++) {\n this.addLabel(block.labels[_i4][0], this.lines.length - block.lines.length + block.labels[_i4][1]);\n }\n this.problem.blocks = newBlocks;\n this.problem.state = undefined;\n this.problem.updateView();\n }\n // Update the model and view when block is converted to contain indent\n }, {\n key: \"addIndent\",\n value: function addIndent() {\n // Update the lines model / view\n for (var i = 0; i < this.lines.length; i++) {\n var line = this.lines[i];\n if (line.indent > 0) {\n $(line.view).removeClass(\"indent1 indent2 indent3 indent4\");\n $(line.view).addClass(\"indent\" + line.indent);\n }\n }\n // Update the block model / view\n this.indent = 0;\n $(this.view).css({\n \"padding-left\": \"\",\n width: this.problem.areaWidth - 22\n });\n }\n // Add a label to block and update its view\n }, {\n key: \"addLabel\",\n value: function addLabel(label, line) {\n var div = document.createElement(\"div\");\n $(div).addClass(\"block-label\");\n if (this.problem.options.numbered == \"right\") {\n $(div).addClass(\"right-label\");\n }\n if (this.problem.options.numbered == \"left\") {\n $(div).addClass(\"left-label\");\n }\n $(div).append(document.createTextNode(label));\n var labelsContainer = $(this.view).children(\".labels\")[0];\n if (labelsContainer) {\n labelsContainer.appendChild(div);\n }\n if (this.labels.length != 0) {\n $(div).css(\"margin-top\", (line - this.labels[this.labels.length - 1][1] - 1) * this.lines[line].view.offsetHeight);\n }\n this.labels.push([label, line]);\n }\n // Initialize Interactivity\n }, {\n key: \"initializeInteractivity\",\n value: function initializeInteractivity() {\n if ($(this.view).hasClass(\"disabled\")) {\n return this;\n }\n // Skip interactivity for fixed blocks - they should not be movable\n if (this.fixed) {\n return this;\n }\n $(this.view).attr(\"tabindex\", \"-1\");\n this.hammer = new (_hammer_min_js__WEBPACK_IMPORTED_MODULE_0___default().Manager)(this.view, {\n recognizers: [[(_hammer_min_js__WEBPACK_IMPORTED_MODULE_0___default().Pan), {\n direction: (_hammer_min_js__WEBPACK_IMPORTED_MODULE_0___default().DIRECTION_ALL),\n threshold: 0,\n pointers: 1\n }]]\n });\n var that = this;\n this.hammer.on(\"panstart\", function (event) {\n that.panStart(event);\n });\n this.hammer.on(\"panend\", function (event) {\n that.panEnd(event);\n });\n this.hammer.on(\"panmove\", function (event) {\n that.panMove(event);\n });\n }\n // Return a boolean as to whether this block is able to be selected\n }, {\n key: \"enabled\",\n value: function enabled() {\n return $(this.view).attr(\"tabindex\") !== undefined;\n }\n // Return a boolean as to whether this block is a distractor\n }, {\n key: \"isDistractor\",\n value: function isDistractor() {\n return this.lines[0].distractor;\n }\n // Returns a boolean as to whether this block is a reusable block\n }, {\n key: \"isReusable\",\n value: function isReusable() {\n for (var i = 0; i < this.lines.length; i++) {\n var line = this.lines[i];\n if (line.reusable === true) {\n return true;\n }\n }\n return false;\n }\n // Returns a boolean as to whether this block is a clone\n }, {\n key: \"isClone\",\n value: function isClone() {\n for (var i = 0; i < this.lines.length; i++) {\n var line = this.lines[i];\n if (line.isCloneLine === true) {\n return true;\n }\n }\n return false;\n }\n // Return a boolean as to whether this block is in the source area\n }, {\n key: \"inSourceArea\",\n value: function inSourceArea() {\n var children = this.problem.sourceArea.childNodes;\n for (var i = 0; i < children.length; i++) {\n var item = children[i];\n if (item.id == this.view.id) {\n return true;\n }\n }\n return false;\n }\n // Return the page X position of the center of the view\n }, {\n key: \"pageXCenter\",\n value: function pageXCenter() {\n var boundingRect = this.view.getBoundingClientRect();\n var pageXCenter = window.pageXOffset + boundingRect.left + boundingRect.width / 2;\n return pageXCenter;\n }\n // Return the page Y position of the center of the view\n }, {\n key: \"pageYCenter\",\n value: function pageYCenter() {\n var boundingRect = this.view.getBoundingClientRect();\n var pageYCenter = window.pageYOffset + boundingRect.top + boundingRect.height / 2;\n return pageYCenter;\n }\n // Of all the line indents, return the minimum value\n }, {\n key: \"minimumLineIndent\",\n value: function minimumLineIndent() {\n var minimumLineIndent = this.lines[0].indent;\n for (var i = 1; i < this.lines.length; i++) {\n minimumLineIndent = Math.min(this.lines[i].indent, minimumLineIndent);\n }\n return minimumLineIndent;\n }\n // Return the last block in the source area that matches the paired bin it is in\n }, {\n key: \"slideUnderBlock\",\n value: function slideUnderBlock() {\n var sourceBlocks = this.problem.sourceBlocks();\n if (sourceBlocks.length == 0) {\n return undefined;\n }\n var pairedBin = this.pairedBin();\n if (pairedBin == -1) {\n return sourceBlocks[sourceBlocks.length - 1];\n }\n for (var i = sourceBlocks.length - 1; i >= 0; i--) {\n var block = sourceBlocks[i];\n if (block.pairedBin() == pairedBin) {\n return block;\n }\n }\n return sourceBlocks[sourceBlocks.length - 1];\n }\n // Return which paired bin it is in (-1) if not\n }, {\n key: \"pairedBin\",\n value: function pairedBin() {\n var pairedBins = this.problem.pairedBins;\n for (var i = 0; i < pairedBins.length; i++) {\n if (this.matchesBin(pairedBins[i])) {\n return i;\n }\n }\n return -1;\n }\n // Return true if all lines are in that bin\n }, {\n key: \"matchesBin\",\n value: function matchesBin(bin) {\n for (var i = 0; i < this.lines.length; i++) {\n var test = this.lines[i].index;\n if (bin.indexOf(test) == -1) {\n return false;\n }\n }\n return true;\n }\n // Return a list of indexes where this block could be inserted before\n }, {\n key: \"validSourceIndexes\",\n value: function validSourceIndexes() {\n var blocks = this.problem.sourceBlocks();\n var indexes = [];\n var pairedBin = this.pairedBin();\n var i, lastBin;\n if (pairedBin >= 0) {\n for (i = 0; i < blocks.length; i++) {\n var block = blocks[i];\n if (block.view.id !== this.view.id) {\n var blockBin = block.pairedBin();\n if (blockBin == pairedBin) {\n indexes.push(i);\n } else if (lastBin == pairedBin) {\n indexes.push(i);\n }\n lastBin = blockBin;\n }\n }\n if (lastBin == pairedBin) {\n indexes.push(blocks.length);\n }\n if (indexes.length > 0) {\n return indexes;\n }\n }\n for (i = 0; i < blocks.length; i++) {\n var _block = blocks[i];\n if (_block.view.id !== this.view.id) {\n var _blockBin = _block.pairedBin();\n if (_blockBin !== lastBin) {\n indexes.push(i);\n } else if (_blockBin == -1) {\n indexes.push(i);\n }\n lastBin = _blockBin;\n }\n }\n indexes.push(blocks.length);\n return indexes;\n }\n // A measure of how far the middle of this block is vertically positioned\n }, {\n key: \"verticalOffset\",\n value: function verticalOffset() {\n var verticalOffset;\n if (this.inSourceArea()) {\n verticalOffset = this.problem.sourceArea.getBoundingClientRect().top;\n } else {\n verticalOffset = this.problem.answerArea.getBoundingClientRect().top;\n }\n verticalOffset = this.view.getBoundingClientRect().top + this.view.getBoundingClientRect().bottom - verticalOffset * 2;\n return verticalOffset;\n }\n // This block just gained textual focus\n }, {\n key: \"newFocus\",\n value: function newFocus() {\n if (this.problem.textFocus == undefined) {\n this.problem.enterKeyboardMode();\n this.problem.textFocus = this;\n this.problem.textMove = false;\n $(this.view).addClass(\"down\");\n } else if (this.problem.textFocus == this) {\n if (this.problem.textMove) {\n $(this.view).addClass(\"up\");\n } else {\n $(this.view).addClass(\"down\");\n }\n } else {\n // already in keyboard mode\n this.problem.textFocus = this;\n this.problem.textMove = false;\n $(this.view).addClass(\"down\");\n }\n this.problem.textMoving = false;\n }\n // This block just lost textual focus\n }, {\n key: \"releaseFocus\",\n value: function releaseFocus() {\n $(this.view).removeClass(\"down up\");\n if (this.problem.textFocus == this) {\n if (!this.problem.textMoving) {\n // exit out of problem but stay way into problem\n this.problem.textFocus = undefined;\n if (this.problem.textMove) {\n this.problem.logMove(\"kmove\");\n this.problem.textMove = false;\n }\n this.problem.exitKeyboardMode();\n }\n } else {\n // become selectable, but not active\n $(this.view).attr(\"tabindex\", \"-1\");\n $(this.view).unbind(\"focus\");\n $(this.view).unbind(\"blur\");\n $(this.view).unbind(\"keydown\");\n }\n }\n // Make this block into the keyboard entry point\n }, {\n key: \"makeTabIndex\",\n value: function makeTabIndex() {\n $(this.view).attr(\"tabindex\", \"0\");\n var that = this;\n $(this.view).focus(function () {\n that.newFocus();\n });\n $(this.view).blur(function () {\n that.releaseFocus();\n });\n $(this.view).keydown(function (event) {\n that.keyDown(event);\n });\n }\n // Called to disable interaction for the future\n }, {\n key: \"disable\",\n value: function disable() {\n if (this.hammer !== undefined) {\n this.hammer.set({\n enable: false\n });\n }\n if ($(this.view).attr(\"tabindex\") == \"0\") {\n this.releaseFocus();\n $(this.view).removeAttr(\"tabindex\");\n this.problem.initializeTabIndex();\n } else {\n $(this.view).removeAttr(\"tabindex\");\n }\n }\n // Enable functionality after reset button has been pressed\n }, {\n key: \"resetView\",\n value: function resetView() {\n if (this.hammer !== undefined) {\n this.hammer.set({\n enable: true\n });\n }\n if (!$(this.view)[0].hasAttribute(\"tabindex\")) {\n $(this.view).attr(\"tabindex\", \"-1\");\n }\n $(this.view).css({\n opacity: \"\"\n });\n }\n // Called to destroy interaction for the future\n }, {\n key: \"destroy\",\n value: function destroy() {\n if (this.hammer !== undefined) {\n this.hammer.destroy();\n delete this.hammer;\n }\n if ($(this.view).attr(\"tabindex\") == \"0\") {\n this.releaseFocus();\n }\n $(this.view).removeAttr(\"tabindex\");\n }\n // Called when a block is picked up\n }, {\n key: \"panStart\",\n value: function panStart(event) {\n this.problem.clearFeedback();\n if (this.problem.started == undefined) {\n // log the first time that something gets moved\n this.problem.started = true;\n this.problem.logMove(\"start\");\n }\n if (this.problem.textFocus !== undefined) {\n // stop text focus when dragging\n this.problem.textFocus.releaseFocus();\n }\n this.problem.moving = this;\n // Update the view\n this.problem.movingX = event.srcEvent.pageX;\n this.problem.movingY = event.srcEvent.pageY;\n this.problem.updateView();\n }\n // Called when a block is dropped\n }, {\n key: \"panEnd\",\n value: function panEnd(event) {\n this.problem.isAnswered = true;\n // Keeps cloned blocks in the answer area\n if (this.isClone()) {\n if (this.inSourceArea()) {\n $(this.view).remove();\n var newBlocks = [];\n for (var i = 0; i < this.problem.blocks.length; i++) {\n var block = this.problem.blocks[i];\n if (block !== this) newBlocks.push(block);\n }\n this.problem.blocks = newBlocks;\n }\n }\n // Creates a cloned block in the answer area and returns the original back to the source\n else if (this.isReusable() && !this.inSourceArea()) {\n var offset = this.verticalOffset();\n var answerBlocks = this.problem.answerBlocks();\n var beforeBlock;\n for (var _i5 = 0; _i5 < answerBlocks.length; _i5++) {\n var item = answerBlocks[_i5];\n if (item.verticalOffset() >= offset) {\n beforeBlock = item.view;\n break;\n }\n }\n var cloned = this.cloneBlockForReusable();\n if (beforeBlock) {\n this.problem.answerArea.insertBefore(cloned.view, beforeBlock);\n } else {\n this.problem.answerArea.appendChild(cloned.view);\n }\n this.problem.blocks.push(cloned);\n cloned.initializeInteractivity();\n this.problem.sourceArea.appendChild(this.view);\n }\n delete this.problem.moving;\n delete this.problem.movingX;\n delete this.problem.movingY;\n this.problem.updateView();\n this.problem.logMove(\"move\");\n }\n // Called when a block is moved\n }, {\n key: \"panMove\",\n value: function panMove(event) {\n // Update the view\n this.problem.movingX = event.srcEvent.pageX;\n this.problem.movingY = event.srcEvent.pageY;\n this.problem.updateView();\n }\n // Handle a keypress event when in focus\n }, {\n key: \"keyDown\",\n value: function keyDown(event) {\n if (this.problem.started == undefined) {\n // log the first time that something gets moved\n this.problem.started = true;\n this.problem.logMove(\"kstart\");\n }\n switch (event.keyCode) {\n case 37:\n // left\n if (this.problem.textMove) {\n this.moveLeft();\n } else {\n this.selectLeft();\n }\n break;\n case 38:\n // up\n if (this.problem.textMove) {\n this.moveUp();\n } else {\n this.selectUp();\n }\n event.preventDefault();\n break;\n case 39:\n // right\n if (this.problem.textMove) {\n this.moveRight();\n } else {\n this.selectRight();\n }\n event.preventDefault();\n break;\n case 40:\n // down\n if (this.problem.textMove) {\n this.moveDown();\n } else {\n this.selectDown();\n }\n event.preventDefault();\n break;\n case 32:\n // space\n this.toggleMove();\n event.preventDefault();\n break;\n case 13:\n // enter\n this.toggleMove();\n event.preventDefault();\n break;\n }\n }\n // Move block left\n }, {\n key: \"moveLeft\",\n value: function moveLeft() {\n var index, block;\n if (!this.inSourceArea()) {\n if (this.indent == 0) {\n // move to source area\n var blocks = this.problem.sourceBlocks();\n var offset = this.verticalOffset();\n var validSourceIndexes = this.validSourceIndexes();\n for (var i = 0; i < validSourceIndexes.length; i++) {\n index = validSourceIndexes[i];\n if (index == blocks.length) {\n this.problem.textMoving = true;\n this.problem.sourceArea.appendChild(this.view);\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n return this;\n } else {\n block = blocks[index];\n if (block.verticalOffset() >= offset) {\n break;\n }\n }\n }\n this.problem.textMoving = true;\n this.problem.sourceArea.insertBefore(this.view, block.view);\n $(this.view).focus();\n } else {\n // reduce indent\n this.indent = this.indent - 1;\n }\n this.problem.state = undefined;\n this.problem.updateView();\n }\n }\n // Move block up\n }, {\n key: \"moveUp\",\n value: function moveUp() {\n if (this.inSourceArea()) {\n var blocks = this.problem.sourceBlocks();\n var offset = this.verticalOffset();\n var validSourceIndexes = this.validSourceIndexes();\n for (var i = 0; i < validSourceIndexes.length; i++) {\n var index = validSourceIndexes[validSourceIndexes.length - 1 - i];\n if (index < blocks.length) {\n var block = blocks[index];\n if (block.verticalOffset() < offset) {\n this.problem.textMoving = true;\n this.problem.sourceArea.insertBefore(this.view, block.view);\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n return this;\n }\n }\n }\n } else {\n var _blocks = this.problem.answerBlocks();\n for (var _i6 = 0; _i6 < _blocks.length; _i6++) {\n if (_blocks[_i6].view.id == this.view.id) {\n if (_i6 == 0) {\n return this;\n }\n // Don't move up past first fixed block\n if (_blocks[_i6 - 1].isFirstFixed) {\n return this;\n }\n this.problem.textMoving = true;\n this.problem.answerArea.insertBefore(this.view, _blocks[_i6 - 1].view);\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n }\n }\n }\n }\n // Move block right\n }, {\n key: \"moveRight\",\n value: function moveRight() {\n if (this.inSourceArea()) {\n // move to answer area\n this.indent = 0;\n var offset = this.verticalOffset();\n var answerBlocks = this.problem.answerBlocks();\n for (var i = 0; i < answerBlocks.length; i++) {\n var item = answerBlocks[i];\n var itemOffset = item.verticalOffset();\n if (itemOffset >= offset) {\n // Don't insert before the first fixed block\n if (item.isFirstFixed) {\n continue;\n }\n this.problem.textMoving = true;\n this.problem.answerArea.insertBefore(this.view, item.view);\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n return this;\n }\n }\n // Append at end, but before last fixed block if present\n this.problem.textMoving = true;\n if (this.problem.lastFixedBlock && answerBlocks.length > 0 && answerBlocks[answerBlocks.length - 1].isLastFixed) {\n this.problem.answerArea.insertBefore(this.view, this.problem.lastFixedBlock.view);\n } else {\n this.problem.answerArea.appendChild(this.view);\n }\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n } else {\n // in answer area: increase the indent\n if (this.indent !== this.problem.indent) {\n this.indent = this.indent + 1;\n this.problem.state = undefined;\n this.problem.updateView();\n }\n }\n }\n // Move block down\n }, {\n key: \"moveDown\",\n value: function moveDown() {\n if (this.inSourceArea()) {\n var blocks = this.problem.sourceBlocks();\n var validSourceIndexes = this.validSourceIndexes();\n for (var _i7 = 0; _i7 < blocks.length; _i7++) {\n if (blocks[_i7].view.id == this.view.id) {\n var myIndex = _i7;\n }\n }\n for (var _i8 = 0; _i8 < validSourceIndexes.length; _i8++) {\n var index = validSourceIndexes[_i8];\n if (index == blocks.length) {\n this.problem.textMoving = true;\n this.problem.sourceArea.appendChild(this.view);\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n return this;\n } else if (index - myIndex > 1) {\n this.problem.textMoving = true;\n this.problem.sourceArea.insertBefore(this.view, blocks[index].view);\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n return this;\n }\n }\n } else {\n var _blocks2 = this.problem.answerBlocks();\n for (var i = 0; i < _blocks2.length; i++) {\n if (_blocks2[i].view.id == this.view.id) {\n if (i == _blocks2.length - 1) {\n return this;\n }\n // Don't move down past last fixed block\n if (_blocks2[i + 1].isLastFixed) {\n return this;\n }\n if (i == _blocks2.length - 2) {\n // Check if last block is last fixed block\n if (_blocks2[_blocks2.length - 1].isLastFixed) {\n return this; // Can't move past last fixed block\n }\n this.problem.textMoving = true;\n this.problem.answerArea.appendChild(this.view);\n } else {\n this.problem.textMoving = true;\n this.problem.answerArea.insertBefore(this.view, _blocks2[i + 2].view);\n }\n $(this.view).focus();\n this.problem.state = undefined;\n this.problem.updateView();\n }\n }\n }\n }\n // Move selection left\n }, {\n key: \"selectLeft\",\n value: function selectLeft() {\n if (!this.inSourceArea()) {\n var offset = this.verticalOffset();\n var sourceBlocks = this.problem.enabledSourceBlocks();\n if (sourceBlocks.length == 0) {\n return this;\n }\n var chooseNext = sourceBlocks[0];\n var chooseOffset = chooseNext.verticalOffset() - offset;\n for (var i = 1; i < sourceBlocks.length; i++) {\n var item = sourceBlocks[i];\n var itemOffset = item.verticalOffset() - offset;\n if (Math.abs(itemOffset) < Math.abs(chooseOffset)) {\n chooseNext = item;\n chooseOffset = itemOffset;\n }\n }\n this.problem.textFocus = chooseNext;\n chooseNext.makeTabIndex();\n $(chooseNext.view).focus();\n }\n }\n // Move selection up\n }, {\n key: \"selectUp\",\n value: function selectUp() {\n var chooseNext = false;\n var blocks;\n if (this.inSourceArea()) {\n blocks = this.problem.enabledSourceBlocks();\n } else {\n blocks = this.problem.enabledAnswerBlocks();\n }\n for (var i = blocks.length - 1; i >= 0; i--) {\n var item = blocks[i];\n if (chooseNext) {\n this.problem.textFocus = item;\n item.makeTabIndex();\n $(item.view).focus();\n return this;\n } else {\n if (item.view.id == this.view.id) {\n chooseNext = true;\n }\n }\n }\n }\n // Move selection right\n }, {\n key: \"selectRight\",\n value: function selectRight() {\n if (this.inSourceArea()) {\n var offset = this.verticalOffset();\n var blocks = this.problem.enabledAnswerBlocks();\n if (blocks.length == 0) {\n return this;\n }\n var chooseNext = blocks[0];\n var chooseOffset = chooseNext.verticalOffset() - offset;\n for (var i = 1; i < blocks.length; i++) {\n var item = blocks[i];\n var itemOffset = item.verticalOffset() - offset;\n if (Math.abs(itemOffset) < Math.abs(chooseOffset)) {\n chooseNext = item;\n chooseOffset = itemOffset;\n }\n }\n this.problem.textFocus = chooseNext;\n chooseNext.makeTabIndex();\n $(chooseNext.view).focus();\n }\n }\n // Move selection down\n }, {\n key: \"selectDown\",\n value: function selectDown() {\n var chooseNext = false;\n var blocks;\n if (this.inSourceArea()) {\n blocks = this.problem.enabledSourceBlocks();\n } else {\n blocks = this.problem.enabledAnswerBlocks();\n }\n for (var i = 0; i < blocks.length; i++) {\n var item = blocks[i];\n if (chooseNext) {\n this.problem.textFocus = item;\n item.makeTabIndex();\n $(item.view).focus();\n return this;\n } else {\n if (item.view.id == this.view.id) {\n chooseNext = true;\n }\n }\n }\n }\n // Toggle whether to move this block\n }, {\n key: \"toggleMove\",\n value: function toggleMove() {\n if (this.problem.textMove) {\n $(this.view).removeClass(\"up\");\n $(this.view).addClass(\"down\");\n this.problem.textMove = false;\n this.problem.logMove(\"kmove\");\n } else {\n $(this.view).removeClass(\"down\");\n $(this.view).addClass(\"up\");\n this.problem.textMove = true;\n }\n }\n // Answer a string that represents this codeblock for saving\n }, {\n key: \"hash\",\n value: function hash() {\n var hash = \"\";\n for (var i = 0; i < this.lines.length; i++) {\n hash += this.lines[i].index + \"_\";\n }\n hash += this.indent;\n return hash;\n }\n // Answer what the indent should be for the solution\n }, {\n key: \"solutionIndent\",\n value: function solutionIndent() {\n var sharedIndent = this.lines[0].indent;\n for (var i = 1; i < this.lines.length; i++) {\n sharedIndent = Math.min(sharedIndent, this.lines[i].indent);\n }\n return sharedIndent;\n }\n // Clones a block\n }, {\n key: \"cloneBlockForReusable\",\n value: function cloneBlockForReusable() {\n var clonedLines = [];\n for (var i = 0; i < this.lines.length; i++) {\n var originalLine = this.lines[i];\n var clonedLine = originalLine.cloneLineForReusable();\n clonedLines.push(clonedLine);\n }\n var clonedBlock = new ParsonsBlock(this.problem, clonedLines);\n $(clonedBlock.view).removeClass(\"disabled\");\n return clonedBlock;\n }\n }]);\n}();\n\n\n//# sourceURL=webpack://Parsons/./src/parsonsBlock.js?"); + +/***/ }), + +/***/ "./src/parsonsLine.js": +/*!****************************!*\ + !*** ./src/parsonsLine.js ***! + \****************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ParsonsLine)\n/* harmony export */ });\n/* harmony import */ var _parsonsToggle_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./parsonsToggle.js */ \"./src/parsonsToggle.js\");\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\n/* =====================================================================\n==== ParsonsLine Object ================================================\n======== The model and view of a line of code.\n======== Based on what is specified in the problem.\n======== ParsonBlock objects have one or more of these.\n==== PROPERTIES ========================================================\n======== problem: the Parsons problem\n======== index: the index of the line in the problem\n======== text: the text of the code line\n======== indent: the indent level\n======== view: an element for viewing this object\n======== distractor: whether it is a distractor\n======== paired: whether it is a paired distractor\n======== groupWithNext: whether it is grouped with the following line\n======== width: the pixel width when rendered\n============ in the initial grouping\n===================================================================== */\n// Initialize from codestring\n\n\nvar ParsonsLine = /*#__PURE__*/function () {\n function ParsonsLine(problem, codestring, displaymath, togglesArray) {\n _classCallCheck(this, ParsonsLine);\n this.problem = problem;\n this.index = problem.lines.length;\n var trimmed = codestring.replace(/\\s*$/, \"\");\n this.text = trimmed.replace(/^\\s*/, \"\");\n this.toggles = [];\n\n //28-31: Not from Runestone\n // this.text = this.text.replace(/\\*\\*(.*?)\\*\\*/g, '\\(\\textbf{$1}\\)');\n // this.text = this.text.replace(/\\*(.*?)\\*/g, '\\(\\textif{$1}\\)');\n // this.text = this.text.replace(/(.*?)<\\/b>/g, '\\(\\textbf{$1}\\)');\n // this.text = this.text.replace(/(.*?)<\\/i>/g, '\\(\\textif{$1}\\)');\n\n this.indent = trimmed.length - this.text.length;\n // Create the View\n var view;\n // TODO: this does not work with display math... Perhaps with pretext we should have html as a language and do nothing?\n\n if (problem.options.language == \"natural\" || problem.options.language == \"math\") {\n if (!displaymath) {\n view = document.createElement(\"p\");\n } else {\n view = document.createElement(\"div\");\n }\n } else {\n view = document.createElement(\"code\");\n $(view).addClass(problem.options.prettifyLanguage);\n }\n view.id = problem.counterId + \"-line-\" + this.index;\n\n //creating toggles within text\n if (togglesArray.length > 0) {\n for (var i = 0; i < togglesArray.length; i++) {\n //creates a new toggleobject\n var toggle = new _parsonsToggle_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"](togglesArray[i].values);\n this.toggles.push(toggle);\n\n //inserts toggle into the inner html\n var index = togglesArray[i].pos;\n var endString = this.text.slice(index);\n var startString = this.text.slice(0, index);\n this.text = startString + \" \" + toggle.htmlContent + \" \" + endString;\n }\n }\n view.innerHTML += this.text;\n this.view = view;\n problem.lines.push(this);\n }\n // Initialize what width the line would naturally have (without indent)\n return _createClass(ParsonsLine, [{\n key: \"initializeWidth\",\n value: function initializeWidth() {\n // this.width does not appear to be used anywhere later\n // since changing the value of this.width appears to have no effect. - Vincent Qiu (September 2020)\n this.width = $(this.view).outerWidth(true) - this.problem.options.pixelsPerIndent * this.indent;\n\n // Pass this information on to be used in class Parsons function initializeAreas\n // to manually determine appropriate widths - Vincent Qiu (September 2020)\n this.view.fontSize = window.getComputedStyle(this.view, null).getPropertyValue(\"font-size\");\n this.view.pixelsPerIndent = this.problem.options.pixelsPerIndent;\n this.view.indent = this.indent;\n\n // Figure out which typeface will be rendered by comparing text widths to browser default - Vincent Qiu (September 2020)\n var tempCanvas = document.createElement(\"canvas\");\n var tempCanvasCtx = tempCanvas.getContext(\"2d\");\n var possibleFonts = window.getComputedStyle(this.view, null).getPropertyValue(\"font-family\").split(\", \");\n var fillerText = \"abcdefghijklmnopqrstuvwxyz0123456789,./!@#$%^&*-+\";\n tempCanvasCtx.font = this.view.fontSize + \" serif\";\n var serifWidth = tempCanvasCtx.measureText(fillerText).width;\n for (var i = 0; i < possibleFonts.length; i++) {\n if (possibleFonts[i].includes('\"')) {\n possibleFonts[i] = possibleFonts[i].replaceAll('\"', \"\");\n }\n if (possibleFonts[i].includes(\"'\")) {\n possibleFonts[i] = possibleFonts[i].replaceAll(\"'\", \"\");\n }\n tempCanvasCtx.font = this.view.fontSize + \" \" + possibleFonts[i];\n if (tempCanvasCtx.measureText(fillerText).width !== serifWidth) {\n this.view.fontFamily = possibleFonts[i];\n break;\n }\n }\n }\n // Answer the block that this line is currently in\n }, {\n key: \"block\",\n value: function block() {\n for (var i = 0; i < this.problem.blocks.length; i++) {\n var block = this.problem.blocks[i];\n for (var j = 0; j < block.lines.length; j++) {\n if (block.lines[j] === this) {\n return block;\n }\n }\n }\n return undefined;\n }\n // Answer the indent based on the view\n }, {\n key: \"viewIndent\",\n value: function viewIndent() {\n if (this.problem.noindent) {\n return this.indent;\n } else {\n var block = this.block();\n return this.indent - block.solutionIndent() + block.indent;\n }\n }\n // Clones a line\n }, {\n key: \"cloneLineForReusable\",\n value: function cloneLineForReusable() {\n var cloned = new ParsonsLine(this.problem, this.text, false, []);\n cloned.indent = this.indent;\n cloned.reusable = this.reusable;\n cloned.distractor = this.distractor;\n cloned.paired = this.paired;\n cloned.fixed = this.fixed;\n cloned.groupWithNext = this.groupWithNext;\n cloned.isCloneLine = true;\n return cloned;\n }\n }]);\n}();\n\n\n//# sourceURL=webpack://Parsons/./src/parsonsLine.js?"); + +/***/ }), + +/***/ "./src/parsonsToggle.js": +/*!******************************!*\ + !*** ./src/parsonsToggle.js ***! + \******************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ParsonsToggle)\n/* harmony export */ });\nfunction _typeof(o) { \"@babel/helpers - typeof\"; return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o; }, _typeof(o); }\nfunction _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError(\"Cannot call a class as a function\"); }\nfunction _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, \"value\" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }\nfunction _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, \"prototype\", { writable: !1 }), e; }\nfunction _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }\nfunction _toPropertyKey(t) { var i = _toPrimitive(t, \"string\"); return \"symbol\" == _typeof(i) ? i : i + \"\"; }\nfunction _toPrimitive(t, r) { if (\"object\" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || \"default\"); if (\"object\" != _typeof(i)) return i; throw new TypeError(\"@@toPrimitive must return a primitive value.\"); } return (\"string\" === r ? String : Number)(t); }\nvar ParsonsToggle = /*#__PURE__*/function () {\n function ParsonsToggle(values) {\n _classCallCheck(this, ParsonsToggle);\n ParsonsToggle.toggleCount++;\n this.values = values;\n this.id = \"toggle\" + ParsonsToggle.toggleCount;\n this.currentIndex = 0;\n this.htmlContent = \"\");\n }\n return _createClass(ParsonsToggle, [{\n key: \"nextValue\",\n value: function nextValue() {\n this.currentIndex++;\n return this.values[this.currentIndex % this.values.length];\n }\n }, {\n key: \"attachListeners\",\n value: function attachListeners() {\n var _this = this;\n var toggleButton = document.getElementById(this.id);\n toggleButton.addEventListener('click', function () {\n toggleButton.textContent = _this.nextValue();\n });\n }\n }]);\n}();\n_defineProperty(ParsonsToggle, \"toggleCount\", 0);\n\n\n//# sourceURL=webpack://Parsons/./src/parsonsToggle.js?"); + +/***/ }), + +/***/ "./src/prettify.js": +/*!*************************!*\ + !*** ./src/prettify.js ***! + \*************************/ +/***/ (() => { + +eval("// TODO - How much of this is needed? Lots of overlap with what syntax highlighting\n// via pretext theme does...\n\nfunction H() {\n var x = navigator && navigator.userAgent && /\\bMSIE 6\\./.test(navigator.userAgent);\n H = function H() {\n return x;\n };\n return x;\n}\n(function () {\n function x(b) {\n b = b.split(/ /g);\n var a = {};\n for (var c = b.length; --c >= 0;) {\n var d = b[c];\n if (d) a[d] = null;\n }\n return a;\n }\n var y = \"break continue do else for if return while \",\n U = y + \"auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile \",\n D = U + \"catch class delete false import new operator private protected public this throw true try \",\n I = D + \"alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename typeof using virtual wchar_t where \",\n J = D + \"boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient \",\n V = J + \"as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var \",\n K = D + \"debugger eval export function get null set undefined var with Infinity NaN \",\n L = \"caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END \",\n M = y + \"and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None \",\n N = y + \"alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END \",\n O = y + \"case done elif esac eval fi function in local set then until \",\n W = I + V + K + L + M + N + O;\n function X(b) {\n return b >= \"a\" && b <= \"z\" || b >= \"A\" && b <= \"Z\";\n }\n function u(b, a, c, d) {\n b.unshift(c, d || 0);\n try {\n a.splice.apply(a, b);\n } finally {\n b.splice(0, 2);\n }\n }\n var Y = function () {\n var b = [\"!\", \"!=\", \"!==\", \"#\", \"%\", \"%=\", \"&\", \"&&\", \"&&=\", \"&=\", \"(\", \"*\", \"*=\", \"+=\", \",\", \"-=\", \"->\", \"/\", \"/=\", \":\", \"::\", \";\", \"<\", \"<<\", \"<<=\", \"<=\", \"=\", \"==\", \"===\", \">\", \">=\", \">>\", \">>=\", \">>>\", \">>>=\", \"?\", \"@\", \"[\", \"^\", \"^=\", \"^^\", \"^^=\", \"{\", \"|\", \"|=\", \"||\", \"||=\", \"~\", \"break\", \"case\", \"continue\", \"delete\", \"do\", \"else\", \"finally\", \"instanceof\", \"return\", \"throw\", \"try\", \"typeof\"],\n a = \"(?:(?:(?:^|[^0-9.])\\\\.{1,3})|(?:(?:^|[^\\\\+])\\\\+)|(?:(?:^|[^\\\\-])-)\";\n for (var c = 0; c < b.length; ++c) {\n var d = b[c];\n a += X(d.charAt(0)) ? \"|\\\\b\" + d : \"|\" + d.replace(/([^=<>:&])/g, \"\\\\$1\");\n }\n a += \"|^)\\\\s*$\";\n return new RegExp(a);\n }(),\n P = /&/g,\n Q = //g,\n Z = /\\\"/g;\n function $(b) {\n return b.replace(P, \"&\").replace(Q, \"<\").replace(R, \">\").replace(Z, \""\");\n }\n function E(b) {\n return b.replace(P, \"&\").replace(Q, \"<\").replace(R, \">\");\n }\n var aa = /</g,\n ba = />/g,\n ca = /'/g,\n da = /"/g,\n ea = /&/g,\n fa = / /g;\n function ga(b) {\n var a = b.indexOf(\"&\");\n if (a < 0) return b;\n for (--a; (a = b.indexOf(\"&#\", a + 1)) >= 0;) {\n var c = b.indexOf(\";\", a);\n if (c >= 0) {\n var d = b.substring(a + 3, c),\n g = 10;\n if (d && d.charAt(0) === \"x\") {\n d = d.substring(1);\n g = 16;\n }\n var e = parseInt(d, g);\n if (!isNaN(e)) b = b.substring(0, a) + String.fromCharCode(e) + b.substring(c + 1);\n }\n }\n return b.replace(aa, \"<\").replace(ba, \">\").replace(ca, \"'\").replace(da, '\"').replace(ea, \"&\").replace(fa, \" \");\n }\n function S(b) {\n return \"XMP\" === b.tagName;\n }\n function z(b, a) {\n switch (b.nodeType) {\n case 1:\n var c = b.tagName.toLowerCase();\n a.push(\"<\", c);\n for (var d = 0; d < b.attributes.length; ++d) {\n var g = b.attributes[d];\n if (!g.specified) continue;\n a.push(\" \");\n z(g, a);\n }\n a.push(\">\");\n for (var e = b.firstChild; e; e = e.nextSibling) z(e, a);\n if (b.firstChild || !/^(?:br|link|img)$/.test(c)) a.push(\"\");\n break;\n case 2:\n a.push(b.name.toLowerCase(), '=\"', $(b.value), '\"');\n break;\n case 3:\n case 4:\n a.push(E(b.nodeValue));\n break;\n }\n }\n var F = null;\n function ha(b) {\n if (null === F) {\n var a = document.createElement(\"PRE\");\n a.appendChild(document.createTextNode('\\n'));\n F = !/= 0; i -= \" \".length) d.push(\" \".substring(0, i));\n g = e + 1;\n break;\n case \"\\n\":\n a = 0;\n break;\n default:\n ++a;\n }\n }\n if (!d) return c;\n d.push(c.substring(g));\n return d.join(\"\");\n };\n }\n var ja = /(?:[^<]+| + +## Core Principles + +### [PRINCIPLE_1_NAME] + +[PRINCIPLE_1_DESCRIPTION] + + +### [PRINCIPLE_2_NAME] + +[PRINCIPLE_2_DESCRIPTION] + + +### [PRINCIPLE_3_NAME] + +[PRINCIPLE_3_DESCRIPTION] + + +### [PRINCIPLE_4_NAME] + +[PRINCIPLE_4_DESCRIPTION] + + +### [PRINCIPLE_5_NAME] + +[PRINCIPLE_5_DESCRIPTION] + + +## [SECTION_2_NAME] + + +[SECTION_2_CONTENT] + + +## [SECTION_3_NAME] + + +[SECTION_3_CONTENT] + + +## Governance + + +[GOVERNANCE_RULES] + + +**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] + diff --git a/.specify/scripts/bash/check-prerequisites.sh b/.specify/scripts/bash/check-prerequisites.sh new file mode 100755 index 00000000..98e387c2 --- /dev/null +++ b/.specify/scripts/bash/check-prerequisites.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# Consolidated prerequisite checking script +# +# This script provides unified prerequisite checking for Spec-Driven Development workflow. +# It replaces the functionality previously spread across multiple scripts. +# +# Usage: ./check-prerequisites.sh [OPTIONS] +# +# OPTIONS: +# --json Output in JSON format +# --require-tasks Require tasks.md to exist (for implementation phase) +# --include-tasks Include tasks.md in AVAILABLE_DOCS list +# --paths-only Only output path variables (no validation) +# --help, -h Show help message +# +# OUTPUTS: +# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]} +# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md +# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc. + +set -e + +# Parse command line arguments +JSON_MODE=false +REQUIRE_TASKS=false +INCLUDE_TASKS=false +PATHS_ONLY=false + +for arg in "$@"; do + case "$arg" in + --json) + JSON_MODE=true + ;; + --require-tasks) + REQUIRE_TASKS=true + ;; + --include-tasks) + INCLUDE_TASKS=true + ;; + --paths-only) + PATHS_ONLY=true + ;; + --help|-h) + cat << 'EOF' +Usage: check-prerequisites.sh [OPTIONS] + +Consolidated prerequisite checking for Spec-Driven Development workflow. + +OPTIONS: + --json Output in JSON format + --require-tasks Require tasks.md to exist (for implementation phase) + --include-tasks Include tasks.md in AVAILABLE_DOCS list + --paths-only Only output path variables (no prerequisite validation) + --help, -h Show this help message + +EXAMPLES: + # Check task prerequisites (plan.md required) + ./check-prerequisites.sh --json + + # Check implementation prerequisites (plan.md + tasks.md required) + ./check-prerequisites.sh --json --require-tasks --include-tasks + + # Get feature paths only (no validation) + ./check-prerequisites.sh --paths-only + +EOF + exit 0 + ;; + *) + echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2 + exit 1 + ;; + esac +done + +# Source common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get feature paths and validate branch +eval $(get_feature_paths) +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 + +# If paths-only mode, output paths and exit (support JSON + paths-only combined) +if $PATHS_ONLY; then + if $JSON_MODE; then + # Minimal JSON paths payload (no validation performed) + printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \ + "$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS" + else + echo "REPO_ROOT: $REPO_ROOT" + echo "BRANCH: $CURRENT_BRANCH" + echo "FEATURE_DIR: $FEATURE_DIR" + echo "FEATURE_SPEC: $FEATURE_SPEC" + echo "IMPL_PLAN: $IMPL_PLAN" + echo "TASKS: $TASKS" + fi + exit 0 +fi + +# Validate required directories and files +if [[ ! -d "$FEATURE_DIR" ]]; then + echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2 + echo "Run /speckit.specify first to create the feature structure." >&2 + exit 1 +fi + +if [[ ! -f "$IMPL_PLAN" ]]; then + echo "ERROR: plan.md not found in $FEATURE_DIR" >&2 + echo "Run /speckit.plan first to create the implementation plan." >&2 + exit 1 +fi + +# Check for tasks.md if required +if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then + echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2 + echo "Run /speckit.tasks first to create the task list." >&2 + exit 1 +fi + +# Build list of available documents +docs=() + +# Always check these optional docs +[[ -f "$RESEARCH" ]] && docs+=("research.md") +[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md") + +# Check contracts directory (only if it exists and has files) +if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then + docs+=("contracts/") +fi + +[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md") + +# Include tasks.md if requested and it exists +if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then + docs+=("tasks.md") +fi + +# Output results +if $JSON_MODE; then + # Build JSON array of documents + if [[ ${#docs[@]} -eq 0 ]]; then + json_docs="[]" + else + json_docs=$(printf '"%s",' "${docs[@]}") + json_docs="[${json_docs%,}]" + fi + + printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs" +else + # Text output + echo "FEATURE_DIR:$FEATURE_DIR" + echo "AVAILABLE_DOCS:" + + # Show status of each potential document + check_file "$RESEARCH" "research.md" + check_file "$DATA_MODEL" "data-model.md" + check_dir "$CONTRACTS_DIR" "contracts/" + check_file "$QUICKSTART" "quickstart.md" + + if $INCLUDE_TASKS; then + check_file "$TASKS" "tasks.md" + fi +fi diff --git a/.specify/scripts/bash/common.sh b/.specify/scripts/bash/common.sh new file mode 100755 index 00000000..2c3165e4 --- /dev/null +++ b/.specify/scripts/bash/common.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# Common functions and variables for all scripts + +# Get repository root, with fallback for non-git repositories +get_repo_root() { + if git rev-parse --show-toplevel >/dev/null 2>&1; then + git rev-parse --show-toplevel + else + # Fall back to script location for non-git repos + local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + (cd "$script_dir/../../.." && pwd) + fi +} + +# Get current branch, with fallback for non-git repositories +get_current_branch() { + # First check if SPECIFY_FEATURE environment variable is set + if [[ -n "${SPECIFY_FEATURE:-}" ]]; then + echo "$SPECIFY_FEATURE" + return + fi + + # Then check git if available + if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then + git rev-parse --abbrev-ref HEAD + return + fi + + # For non-git repos, try to find the latest feature directory + local repo_root=$(get_repo_root) + local specs_dir="$repo_root/specs" + + if [[ -d "$specs_dir" ]]; then + local latest_feature="" + local highest=0 + + for dir in "$specs_dir"/*; do + if [[ -d "$dir" ]]; then + local dirname=$(basename "$dir") + if [[ "$dirname" =~ ^([0-9]{3})- ]]; then + local number=${BASH_REMATCH[1]} + number=$((10#$number)) + if [[ "$number" -gt "$highest" ]]; then + highest=$number + latest_feature=$dirname + fi + fi + fi + done + + if [[ -n "$latest_feature" ]]; then + echo "$latest_feature" + return + fi + fi + + echo "main" # Final fallback +} + +# Check if we have git available +has_git() { + git rev-parse --show-toplevel >/dev/null 2>&1 +} + +check_feature_branch() { + local branch="$1" + local has_git_repo="$2" + + # For non-git repos, we can't enforce branch naming but still provide output + if [[ "$has_git_repo" != "true" ]]; then + echo "[specify] Warning: Git repository not detected; skipped branch validation" >&2 + return 0 + fi + + if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then + echo "ERROR: Not on a feature branch. Current branch: $branch" >&2 + echo "Feature branches should be named like: 001-feature-name" >&2 + return 1 + fi + + return 0 +} + +get_feature_dir() { echo "$1/specs/$2"; } + +# Find feature directory by numeric prefix instead of exact branch match +# This allows multiple branches to work on the same spec (e.g., 004-fix-bug, 004-add-feature) +find_feature_dir_by_prefix() { + local repo_root="$1" + local branch_name="$2" + local specs_dir="$repo_root/specs" + + # Extract numeric prefix from branch (e.g., "004" from "004-whatever") + if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then + # If branch doesn't have numeric prefix, fall back to exact match + echo "$specs_dir/$branch_name" + return + fi + + local prefix="${BASH_REMATCH[1]}" + + # Search for directories in specs/ that start with this prefix + local matches=() + if [[ -d "$specs_dir" ]]; then + for dir in "$specs_dir"/"$prefix"-*; do + if [[ -d "$dir" ]]; then + matches+=("$(basename "$dir")") + fi + done + fi + + # Handle results + if [[ ${#matches[@]} -eq 0 ]]; then + # No match found - return the branch name path (will fail later with clear error) + echo "$specs_dir/$branch_name" + elif [[ ${#matches[@]} -eq 1 ]]; then + # Exactly one match - perfect! + echo "$specs_dir/${matches[0]}" + else + # Multiple matches - this shouldn't happen with proper naming convention + echo "ERROR: Multiple spec directories found with prefix '$prefix': ${matches[*]}" >&2 + echo "Please ensure only one spec directory exists per numeric prefix." >&2 + echo "$specs_dir/$branch_name" # Return something to avoid breaking the script + fi +} + +get_feature_paths() { + local repo_root=$(get_repo_root) + local current_branch=$(get_current_branch) + local has_git_repo="false" + + if has_git; then + has_git_repo="true" + fi + + # Use prefix-based lookup to support multiple branches per spec + local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch") + + cat </dev/null) ]] && echo " ✓ $2" || echo " ✗ $2"; } + diff --git a/.specify/scripts/bash/create-new-feature.sh b/.specify/scripts/bash/create-new-feature.sh new file mode 100755 index 00000000..c40cfd77 --- /dev/null +++ b/.specify/scripts/bash/create-new-feature.sh @@ -0,0 +1,297 @@ +#!/usr/bin/env bash + +set -e + +JSON_MODE=false +SHORT_NAME="" +BRANCH_NUMBER="" +ARGS=() +i=1 +while [ $i -le $# ]; do + arg="${!i}" + case "$arg" in + --json) + JSON_MODE=true + ;; + --short-name) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + # Check if the next argument is another option (starts with --) + if [[ "$next_arg" == --* ]]; then + echo 'Error: --short-name requires a value' >&2 + exit 1 + fi + SHORT_NAME="$next_arg" + ;; + --number) + if [ $((i + 1)) -gt $# ]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + i=$((i + 1)) + next_arg="${!i}" + if [[ "$next_arg" == --* ]]; then + echo 'Error: --number requires a value' >&2 + exit 1 + fi + BRANCH_NUMBER="$next_arg" + ;; + --help|-h) + echo "Usage: $0 [--json] [--short-name ] [--number N] " + echo "" + echo "Options:" + echo " --json Output in JSON format" + echo " --short-name Provide a custom short name (2-4 words) for the branch" + echo " --number N Specify branch number manually (overrides auto-detection)" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " $0 'Add user authentication system' --short-name 'user-auth'" + echo " $0 'Implement OAuth2 integration for API' --number 5" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac + i=$((i + 1)) +done + +FEATURE_DESCRIPTION="${ARGS[*]}" +if [ -z "$FEATURE_DESCRIPTION" ]; then + echo "Usage: $0 [--json] [--short-name ] [--number N] " >&2 + exit 1 +fi + +# Function to find the repository root by searching for existing project markers +find_repo_root() { + local dir="$1" + while [ "$dir" != "/" ]; do + if [ -d "$dir/.git" ] || [ -d "$dir/.specify" ]; then + echo "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 +} + +# Function to get highest number from specs directory +get_highest_from_specs() { + local specs_dir="$1" + local highest=0 + + if [ -d "$specs_dir" ]; then + for dir in "$specs_dir"/*; do + [ -d "$dir" ] || continue + dirname=$(basename "$dir") + number=$(echo "$dirname" | grep -o '^[0-9]\+' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + done + fi + + echo "$highest" +} + +# Function to get highest number from git branches +get_highest_from_branches() { + local highest=0 + + # Get all branches (local and remote) + branches=$(git branch -a 2>/dev/null || echo "") + + if [ -n "$branches" ]; then + while IFS= read -r branch; do + # Clean branch name: remove leading markers and remote prefixes + clean_branch=$(echo "$branch" | sed 's/^[* ]*//; s|^remotes/[^/]*/||') + + # Extract feature number if branch matches pattern ###-* + if echo "$clean_branch" | grep -q '^[0-9]\{3\}-'; then + number=$(echo "$clean_branch" | grep -o '^[0-9]\{3\}' || echo "0") + number=$((10#$number)) + if [ "$number" -gt "$highest" ]; then + highest=$number + fi + fi + done <<< "$branches" + fi + + echo "$highest" +} + +# Function to check existing branches (local and remote) and return next available number +check_existing_branches() { + local specs_dir="$1" + + # Fetch all remotes to get latest branch info (suppress errors if no remotes) + git fetch --all --prune 2>/dev/null || true + + # Get highest number from ALL branches (not just matching short name) + local highest_branch=$(get_highest_from_branches) + + # Get highest number from ALL specs (not just matching short name) + local highest_spec=$(get_highest_from_specs "$specs_dir") + + # Take the maximum of both + local max_num=$highest_branch + if [ "$highest_spec" -gt "$max_num" ]; then + max_num=$highest_spec + fi + + # Return next number + echo $((max_num + 1)) +} + +# Function to clean and format a branch name +clean_branch_name() { + local name="$1" + echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//' +} + +# Resolve repository root. Prefer git information when available, but fall back +# to searching for repository markers so the workflow still functions in repositories that +# were initialised with --no-git. +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if git rev-parse --show-toplevel >/dev/null 2>&1; then + REPO_ROOT=$(git rev-parse --show-toplevel) + HAS_GIT=true +else + REPO_ROOT="$(find_repo_root "$SCRIPT_DIR")" + if [ -z "$REPO_ROOT" ]; then + echo "Error: Could not determine repository root. Please run this script from within the repository." >&2 + exit 1 + fi + HAS_GIT=false +fi + +cd "$REPO_ROOT" + +SPECS_DIR="$REPO_ROOT/specs" +mkdir -p "$SPECS_DIR" + +# Function to generate branch name with stop word filtering and length filtering +generate_branch_name() { + local description="$1" + + # Common stop words to filter out + local stop_words="^(i|a|an|the|to|for|of|in|on|at|by|with|from|is|are|was|were|be|been|being|have|has|had|do|does|did|will|would|should|could|can|may|might|must|shall|this|that|these|those|my|your|our|their|want|need|add|get|set)$" + + # Convert to lowercase and split into words + local clean_name=$(echo "$description" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/ /g') + + # Filter words: remove stop words and words shorter than 3 chars (unless they're uppercase acronyms in original) + local meaningful_words=() + for word in $clean_name; do + # Skip empty words + [ -z "$word" ] && continue + + # Keep words that are NOT stop words AND (length >= 3 OR are potential acronyms) + if ! echo "$word" | grep -qiE "$stop_words"; then + if [ ${#word} -ge 3 ]; then + meaningful_words+=("$word") + elif echo "$description" | grep -q "\b${word^^}\b"; then + # Keep short words if they appear as uppercase in original (likely acronyms) + meaningful_words+=("$word") + fi + fi + done + + # If we have meaningful words, use first 3-4 of them + if [ ${#meaningful_words[@]} -gt 0 ]; then + local max_words=3 + if [ ${#meaningful_words[@]} -eq 4 ]; then max_words=4; fi + + local result="" + local count=0 + for word in "${meaningful_words[@]}"; do + if [ $count -ge $max_words ]; then break; fi + if [ -n "$result" ]; then result="$result-"; fi + result="$result$word" + count=$((count + 1)) + done + echo "$result" + else + # Fallback to original logic if no meaningful words found + local cleaned=$(clean_branch_name "$description") + echo "$cleaned" | tr '-' '\n' | grep -v '^$' | head -3 | tr '\n' '-' | sed 's/-$//' + fi +} + +# Generate branch name +if [ -n "$SHORT_NAME" ]; then + # Use provided short name, just clean it up + BRANCH_SUFFIX=$(clean_branch_name "$SHORT_NAME") +else + # Generate from description with smart filtering + BRANCH_SUFFIX=$(generate_branch_name "$FEATURE_DESCRIPTION") +fi + +# Determine branch number +if [ -z "$BRANCH_NUMBER" ]; then + if [ "$HAS_GIT" = true ]; then + # Check existing branches on remotes + BRANCH_NUMBER=$(check_existing_branches "$SPECS_DIR") + else + # Fall back to local directory check + HIGHEST=$(get_highest_from_specs "$SPECS_DIR") + BRANCH_NUMBER=$((HIGHEST + 1)) + fi +fi + +# Force base-10 interpretation to prevent octal conversion (e.g., 010 → 8 in octal, but should be 10 in decimal) +FEATURE_NUM=$(printf "%03d" "$((10#$BRANCH_NUMBER))") +BRANCH_NAME="${FEATURE_NUM}-${BRANCH_SUFFIX}" + +# GitHub enforces a 244-byte limit on branch names +# Validate and truncate if necessary +MAX_BRANCH_LENGTH=244 +if [ ${#BRANCH_NAME} -gt $MAX_BRANCH_LENGTH ]; then + # Calculate how much we need to trim from suffix + # Account for: feature number (3) + hyphen (1) = 4 chars + MAX_SUFFIX_LENGTH=$((MAX_BRANCH_LENGTH - 4)) + + # Truncate suffix at word boundary if possible + TRUNCATED_SUFFIX=$(echo "$BRANCH_SUFFIX" | cut -c1-$MAX_SUFFIX_LENGTH) + # Remove trailing hyphen if truncation created one + TRUNCATED_SUFFIX=$(echo "$TRUNCATED_SUFFIX" | sed 's/-$//') + + ORIGINAL_BRANCH_NAME="$BRANCH_NAME" + BRANCH_NAME="${FEATURE_NUM}-${TRUNCATED_SUFFIX}" + + >&2 echo "[specify] Warning: Branch name exceeded GitHub's 244-byte limit" + >&2 echo "[specify] Original: $ORIGINAL_BRANCH_NAME (${#ORIGINAL_BRANCH_NAME} bytes)" + >&2 echo "[specify] Truncated to: $BRANCH_NAME (${#BRANCH_NAME} bytes)" +fi + +if [ "$HAS_GIT" = true ]; then + git checkout -b "$BRANCH_NAME" +else + >&2 echo "[specify] Warning: Git repository not detected; skipped branch creation for $BRANCH_NAME" +fi + +FEATURE_DIR="$SPECS_DIR/$BRANCH_NAME" +mkdir -p "$FEATURE_DIR" + +TEMPLATE="$REPO_ROOT/.specify/templates/spec-template.md" +SPEC_FILE="$FEATURE_DIR/spec.md" +if [ -f "$TEMPLATE" ]; then cp "$TEMPLATE" "$SPEC_FILE"; else touch "$SPEC_FILE"; fi + +# Set the SPECIFY_FEATURE environment variable for the current session +export SPECIFY_FEATURE="$BRANCH_NAME" + +if $JSON_MODE; then + printf '{"BRANCH_NAME":"%s","SPEC_FILE":"%s","FEATURE_NUM":"%s"}\n' "$BRANCH_NAME" "$SPEC_FILE" "$FEATURE_NUM" +else + echo "BRANCH_NAME: $BRANCH_NAME" + echo "SPEC_FILE: $SPEC_FILE" + echo "FEATURE_NUM: $FEATURE_NUM" + echo "SPECIFY_FEATURE environment variable set to: $BRANCH_NAME" +fi diff --git a/.specify/scripts/bash/setup-plan.sh b/.specify/scripts/bash/setup-plan.sh new file mode 100755 index 00000000..d01c6d6c --- /dev/null +++ b/.specify/scripts/bash/setup-plan.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash + +set -e + +# Parse command line arguments +JSON_MODE=false +ARGS=() + +for arg in "$@"; do + case "$arg" in + --json) + JSON_MODE=true + ;; + --help|-h) + echo "Usage: $0 [--json]" + echo " --json Output results in JSON format" + echo " --help Show this help message" + exit 0 + ;; + *) + ARGS+=("$arg") + ;; + esac +done + +# Get script directory and load common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get all paths and variables from common functions +eval $(get_feature_paths) + +# Check if we're on a proper feature branch (only for git repos) +check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1 + +# Ensure the feature directory exists +mkdir -p "$FEATURE_DIR" + +# Copy plan template if it exists +TEMPLATE="$REPO_ROOT/.specify/templates/plan-template.md" +if [[ -f "$TEMPLATE" ]]; then + cp "$TEMPLATE" "$IMPL_PLAN" + echo "Copied plan template to $IMPL_PLAN" +else + echo "Warning: Plan template not found at $TEMPLATE" + # Create a basic plan file if template doesn't exist + touch "$IMPL_PLAN" +fi + +# Output results +if $JSON_MODE; then + printf '{"FEATURE_SPEC":"%s","IMPL_PLAN":"%s","SPECS_DIR":"%s","BRANCH":"%s","HAS_GIT":"%s"}\n' \ + "$FEATURE_SPEC" "$IMPL_PLAN" "$FEATURE_DIR" "$CURRENT_BRANCH" "$HAS_GIT" +else + echo "FEATURE_SPEC: $FEATURE_SPEC" + echo "IMPL_PLAN: $IMPL_PLAN" + echo "SPECS_DIR: $FEATURE_DIR" + echo "BRANCH: $CURRENT_BRANCH" + echo "HAS_GIT: $HAS_GIT" +fi + diff --git a/.specify/scripts/bash/update-agent-context.sh b/.specify/scripts/bash/update-agent-context.sh new file mode 100755 index 00000000..6d3e0b37 --- /dev/null +++ b/.specify/scripts/bash/update-agent-context.sh @@ -0,0 +1,799 @@ +#!/usr/bin/env bash + +# Update agent context files with information from plan.md +# +# This script maintains AI agent context files by parsing feature specifications +# and updating agent-specific configuration files with project information. +# +# MAIN FUNCTIONS: +# 1. Environment Validation +# - Verifies git repository structure and branch information +# - Checks for required plan.md files and templates +# - Validates file permissions and accessibility +# +# 2. Plan Data Extraction +# - Parses plan.md files to extract project metadata +# - Identifies language/version, frameworks, databases, and project types +# - Handles missing or incomplete specification data gracefully +# +# 3. Agent File Management +# - Creates new agent context files from templates when needed +# - Updates existing agent files with new project information +# - Preserves manual additions and custom configurations +# - Supports multiple AI agent formats and directory structures +# +# 4. Content Generation +# - Generates language-specific build/test commands +# - Creates appropriate project directory structures +# - Updates technology stacks and recent changes sections +# - Maintains consistent formatting and timestamps +# +# 5. Multi-Agent Support +# - Handles agent-specific file paths and naming conventions +# - Supports: Claude, Gemini, Copilot, Cursor, Qwen, opencode, Codex, Windsurf, Kilo Code, Auggie CLI, Roo Code, CodeBuddy CLI, Qoder CLI, Amp, SHAI, or Amazon Q Developer CLI +# - Can update single agents or all existing agent files +# - Creates default Claude file if no agent files exist +# +# Usage: ./update-agent-context.sh [agent_type] +# Agent types: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|shai|q|bob|qoder +# Leave empty to update all existing agent files + +set -e + +# Enable strict error handling +set -u +set -o pipefail + +#============================================================================== +# Configuration and Global Variables +#============================================================================== + +# Get script directory and load common functions +SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/common.sh" + +# Get all paths and variables from common functions +eval $(get_feature_paths) + +NEW_PLAN="$IMPL_PLAN" # Alias for compatibility with existing code +AGENT_TYPE="${1:-}" + +# Agent-specific file paths +CLAUDE_FILE="$REPO_ROOT/CLAUDE.md" +GEMINI_FILE="$REPO_ROOT/GEMINI.md" +COPILOT_FILE="$REPO_ROOT/.github/agents/copilot-instructions.md" +CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc" +QWEN_FILE="$REPO_ROOT/QWEN.md" +AGENTS_FILE="$REPO_ROOT/AGENTS.md" +WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md" +KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md" +AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" +ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" +CODEBUDDY_FILE="$REPO_ROOT/CODEBUDDY.md" +QODER_FILE="$REPO_ROOT/QODER.md" +AMP_FILE="$REPO_ROOT/AGENTS.md" +SHAI_FILE="$REPO_ROOT/SHAI.md" +Q_FILE="$REPO_ROOT/AGENTS.md" +BOB_FILE="$REPO_ROOT/AGENTS.md" + +# Template file +TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md" + +# Global variables for parsed plan data +NEW_LANG="" +NEW_FRAMEWORK="" +NEW_DB="" +NEW_PROJECT_TYPE="" + +#============================================================================== +# Utility Functions +#============================================================================== + +log_info() { + echo "INFO: $1" +} + +log_success() { + echo "✓ $1" +} + +log_error() { + echo "ERROR: $1" >&2 +} + +log_warning() { + echo "WARNING: $1" >&2 +} + +# Cleanup function for temporary files +cleanup() { + local exit_code=$? + rm -f /tmp/agent_update_*_$$ + rm -f /tmp/manual_additions_$$ + exit $exit_code +} + +# Set up cleanup trap +trap cleanup EXIT INT TERM + +#============================================================================== +# Validation Functions +#============================================================================== + +validate_environment() { + # Check if we have a current branch/feature (git or non-git) + if [[ -z "$CURRENT_BRANCH" ]]; then + log_error "Unable to determine current feature" + if [[ "$HAS_GIT" == "true" ]]; then + log_info "Make sure you're on a feature branch" + else + log_info "Set SPECIFY_FEATURE environment variable or create a feature first" + fi + exit 1 + fi + + # Check if plan.md exists + if [[ ! -f "$NEW_PLAN" ]]; then + log_error "No plan.md found at $NEW_PLAN" + log_info "Make sure you're working on a feature with a corresponding spec directory" + if [[ "$HAS_GIT" != "true" ]]; then + log_info "Use: export SPECIFY_FEATURE=your-feature-name or create a new feature first" + fi + exit 1 + fi + + # Check if template exists (needed for new files) + if [[ ! -f "$TEMPLATE_FILE" ]]; then + log_warning "Template file not found at $TEMPLATE_FILE" + log_warning "Creating new agent files will fail" + fi +} + +#============================================================================== +# Plan Parsing Functions +#============================================================================== + +extract_plan_field() { + local field_pattern="$1" + local plan_file="$2" + + grep "^\*\*${field_pattern}\*\*: " "$plan_file" 2>/dev/null | \ + head -1 | \ + sed "s|^\*\*${field_pattern}\*\*: ||" | \ + sed 's/^[ \t]*//;s/[ \t]*$//' | \ + grep -v "NEEDS CLARIFICATION" | \ + grep -v "^N/A$" || echo "" +} + +parse_plan_data() { + local plan_file="$1" + + if [[ ! -f "$plan_file" ]]; then + log_error "Plan file not found: $plan_file" + return 1 + fi + + if [[ ! -r "$plan_file" ]]; then + log_error "Plan file is not readable: $plan_file" + return 1 + fi + + log_info "Parsing plan data from $plan_file" + + NEW_LANG=$(extract_plan_field "Language/Version" "$plan_file") + NEW_FRAMEWORK=$(extract_plan_field "Primary Dependencies" "$plan_file") + NEW_DB=$(extract_plan_field "Storage" "$plan_file") + NEW_PROJECT_TYPE=$(extract_plan_field "Project Type" "$plan_file") + + # Log what we found + if [[ -n "$NEW_LANG" ]]; then + log_info "Found language: $NEW_LANG" + else + log_warning "No language information found in plan" + fi + + if [[ -n "$NEW_FRAMEWORK" ]]; then + log_info "Found framework: $NEW_FRAMEWORK" + fi + + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then + log_info "Found database: $NEW_DB" + fi + + if [[ -n "$NEW_PROJECT_TYPE" ]]; then + log_info "Found project type: $NEW_PROJECT_TYPE" + fi +} + +format_technology_stack() { + local lang="$1" + local framework="$2" + local parts=() + + # Add non-empty parts + [[ -n "$lang" && "$lang" != "NEEDS CLARIFICATION" ]] && parts+=("$lang") + [[ -n "$framework" && "$framework" != "NEEDS CLARIFICATION" && "$framework" != "N/A" ]] && parts+=("$framework") + + # Join with proper formatting + if [[ ${#parts[@]} -eq 0 ]]; then + echo "" + elif [[ ${#parts[@]} -eq 1 ]]; then + echo "${parts[0]}" + else + # Join multiple parts with " + " + local result="${parts[0]}" + for ((i=1; i<${#parts[@]}; i++)); do + result="$result + ${parts[i]}" + done + echo "$result" + fi +} + +#============================================================================== +# Template and Content Generation Functions +#============================================================================== + +get_project_structure() { + local project_type="$1" + + if [[ "$project_type" == *"web"* ]]; then + echo "backend/\\nfrontend/\\ntests/" + else + echo "src/\\ntests/" + fi +} + +get_commands_for_language() { + local lang="$1" + + case "$lang" in + *"Python"*) + echo "cd src && pytest && ruff check ." + ;; + *"Rust"*) + echo "cargo test && cargo clippy" + ;; + *"JavaScript"*|*"TypeScript"*) + echo "npm test \\&\\& npm run lint" + ;; + *) + echo "# Add commands for $lang" + ;; + esac +} + +get_language_conventions() { + local lang="$1" + echo "$lang: Follow standard conventions" +} + +create_new_agent_file() { + local target_file="$1" + local temp_file="$2" + local project_name="$3" + local current_date="$4" + + if [[ ! -f "$TEMPLATE_FILE" ]]; then + log_error "Template not found at $TEMPLATE_FILE" + return 1 + fi + + if [[ ! -r "$TEMPLATE_FILE" ]]; then + log_error "Template file is not readable: $TEMPLATE_FILE" + return 1 + fi + + log_info "Creating new agent context file from template..." + + if ! cp "$TEMPLATE_FILE" "$temp_file"; then + log_error "Failed to copy template file" + return 1 + fi + + # Replace template placeholders + local project_structure + project_structure=$(get_project_structure "$NEW_PROJECT_TYPE") + + local commands + commands=$(get_commands_for_language "$NEW_LANG") + + local language_conventions + language_conventions=$(get_language_conventions "$NEW_LANG") + + # Perform substitutions with error checking using safer approach + # Escape special characters for sed by using a different delimiter or escaping + local escaped_lang=$(printf '%s\n' "$NEW_LANG" | sed 's/[\[\.*^$()+{}|]/\\&/g') + local escaped_framework=$(printf '%s\n' "$NEW_FRAMEWORK" | sed 's/[\[\.*^$()+{}|]/\\&/g') + local escaped_branch=$(printf '%s\n' "$CURRENT_BRANCH" | sed 's/[\[\.*^$()+{}|]/\\&/g') + + # Build technology stack and recent change strings conditionally + local tech_stack + if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then + tech_stack="- $escaped_lang + $escaped_framework ($escaped_branch)" + elif [[ -n "$escaped_lang" ]]; then + tech_stack="- $escaped_lang ($escaped_branch)" + elif [[ -n "$escaped_framework" ]]; then + tech_stack="- $escaped_framework ($escaped_branch)" + else + tech_stack="- ($escaped_branch)" + fi + + local recent_change + if [[ -n "$escaped_lang" && -n "$escaped_framework" ]]; then + recent_change="- $escaped_branch: Added $escaped_lang + $escaped_framework" + elif [[ -n "$escaped_lang" ]]; then + recent_change="- $escaped_branch: Added $escaped_lang" + elif [[ -n "$escaped_framework" ]]; then + recent_change="- $escaped_branch: Added $escaped_framework" + else + recent_change="- $escaped_branch: Added" + fi + + local substitutions=( + "s|\[PROJECT NAME\]|$project_name|" + "s|\[DATE\]|$current_date|" + "s|\[EXTRACTED FROM ALL PLAN.MD FILES\]|$tech_stack|" + "s|\[ACTUAL STRUCTURE FROM PLANS\]|$project_structure|g" + "s|\[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES\]|$commands|" + "s|\[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE\]|$language_conventions|" + "s|\[LAST 3 FEATURES AND WHAT THEY ADDED\]|$recent_change|" + ) + + for substitution in "${substitutions[@]}"; do + if ! sed -i.bak -e "$substitution" "$temp_file"; then + log_error "Failed to perform substitution: $substitution" + rm -f "$temp_file" "$temp_file.bak" + return 1 + fi + done + + # Convert \n sequences to actual newlines + newline=$(printf '\n') + sed -i.bak2 "s/\\\\n/${newline}/g" "$temp_file" + + # Clean up backup files + rm -f "$temp_file.bak" "$temp_file.bak2" + + return 0 +} + + + + +update_existing_agent_file() { + local target_file="$1" + local current_date="$2" + + log_info "Updating existing agent context file..." + + # Use a single temporary file for atomic update + local temp_file + temp_file=$(mktemp) || { + log_error "Failed to create temporary file" + return 1 + } + + # Process the file in one pass + local tech_stack=$(format_technology_stack "$NEW_LANG" "$NEW_FRAMEWORK") + local new_tech_entries=() + local new_change_entry="" + + # Prepare new technology entries + if [[ -n "$tech_stack" ]] && ! grep -q "$tech_stack" "$target_file"; then + new_tech_entries+=("- $tech_stack ($CURRENT_BRANCH)") + fi + + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]] && ! grep -q "$NEW_DB" "$target_file"; then + new_tech_entries+=("- $NEW_DB ($CURRENT_BRANCH)") + fi + + # Prepare new change entry + if [[ -n "$tech_stack" ]]; then + new_change_entry="- $CURRENT_BRANCH: Added $tech_stack" + elif [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]] && [[ "$NEW_DB" != "NEEDS CLARIFICATION" ]]; then + new_change_entry="- $CURRENT_BRANCH: Added $NEW_DB" + fi + + # Check if sections exist in the file + local has_active_technologies=0 + local has_recent_changes=0 + + if grep -q "^## Active Technologies" "$target_file" 2>/dev/null; then + has_active_technologies=1 + fi + + if grep -q "^## Recent Changes" "$target_file" 2>/dev/null; then + has_recent_changes=1 + fi + + # Process file line by line + local in_tech_section=false + local in_changes_section=false + local tech_entries_added=false + local changes_entries_added=false + local existing_changes_count=0 + local file_ended=false + + while IFS= read -r line || [[ -n "$line" ]]; do + # Handle Active Technologies section + if [[ "$line" == "## Active Technologies" ]]; then + echo "$line" >> "$temp_file" + in_tech_section=true + continue + elif [[ $in_tech_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then + # Add new tech entries before closing the section + if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + echo "$line" >> "$temp_file" + in_tech_section=false + continue + elif [[ $in_tech_section == true ]] && [[ -z "$line" ]]; then + # Add new tech entries before empty line in tech section + if [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + echo "$line" >> "$temp_file" + continue + fi + + # Handle Recent Changes section + if [[ "$line" == "## Recent Changes" ]]; then + echo "$line" >> "$temp_file" + # Add new change entry right after the heading + if [[ -n "$new_change_entry" ]]; then + echo "$new_change_entry" >> "$temp_file" + fi + in_changes_section=true + changes_entries_added=true + continue + elif [[ $in_changes_section == true ]] && [[ "$line" =~ ^##[[:space:]] ]]; then + echo "$line" >> "$temp_file" + in_changes_section=false + continue + elif [[ $in_changes_section == true ]] && [[ "$line" == "- "* ]]; then + # Keep only first 2 existing changes + if [[ $existing_changes_count -lt 2 ]]; then + echo "$line" >> "$temp_file" + ((existing_changes_count++)) + fi + continue + fi + + # Update timestamp + if [[ "$line" =~ \*\*Last\ updated\*\*:.*[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] ]]; then + echo "$line" | sed "s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/$current_date/" >> "$temp_file" + else + echo "$line" >> "$temp_file" + fi + done < "$target_file" + + # Post-loop check: if we're still in the Active Technologies section and haven't added new entries + if [[ $in_tech_section == true ]] && [[ $tech_entries_added == false ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + + # If sections don't exist, add them at the end of the file + if [[ $has_active_technologies -eq 0 ]] && [[ ${#new_tech_entries[@]} -gt 0 ]]; then + echo "" >> "$temp_file" + echo "## Active Technologies" >> "$temp_file" + printf '%s\n' "${new_tech_entries[@]}" >> "$temp_file" + tech_entries_added=true + fi + + if [[ $has_recent_changes -eq 0 ]] && [[ -n "$new_change_entry" ]]; then + echo "" >> "$temp_file" + echo "## Recent Changes" >> "$temp_file" + echo "$new_change_entry" >> "$temp_file" + changes_entries_added=true + fi + + # Move temp file to target atomically + if ! mv "$temp_file" "$target_file"; then + log_error "Failed to update target file" + rm -f "$temp_file" + return 1 + fi + + return 0 +} +#============================================================================== +# Main Agent File Update Function +#============================================================================== + +update_agent_file() { + local target_file="$1" + local agent_name="$2" + + if [[ -z "$target_file" ]] || [[ -z "$agent_name" ]]; then + log_error "update_agent_file requires target_file and agent_name parameters" + return 1 + fi + + log_info "Updating $agent_name context file: $target_file" + + local project_name + project_name=$(basename "$REPO_ROOT") + local current_date + current_date=$(date +%Y-%m-%d) + + # Create directory if it doesn't exist + local target_dir + target_dir=$(dirname "$target_file") + if [[ ! -d "$target_dir" ]]; then + if ! mkdir -p "$target_dir"; then + log_error "Failed to create directory: $target_dir" + return 1 + fi + fi + + if [[ ! -f "$target_file" ]]; then + # Create new file from template + local temp_file + temp_file=$(mktemp) || { + log_error "Failed to create temporary file" + return 1 + } + + if create_new_agent_file "$target_file" "$temp_file" "$project_name" "$current_date"; then + if mv "$temp_file" "$target_file"; then + log_success "Created new $agent_name context file" + else + log_error "Failed to move temporary file to $target_file" + rm -f "$temp_file" + return 1 + fi + else + log_error "Failed to create new agent file" + rm -f "$temp_file" + return 1 + fi + else + # Update existing file + if [[ ! -r "$target_file" ]]; then + log_error "Cannot read existing file: $target_file" + return 1 + fi + + if [[ ! -w "$target_file" ]]; then + log_error "Cannot write to existing file: $target_file" + return 1 + fi + + if update_existing_agent_file "$target_file" "$current_date"; then + log_success "Updated existing $agent_name context file" + else + log_error "Failed to update existing agent file" + return 1 + fi + fi + + return 0 +} + +#============================================================================== +# Agent Selection and Processing +#============================================================================== + +update_specific_agent() { + local agent_type="$1" + + case "$agent_type" in + claude) + update_agent_file "$CLAUDE_FILE" "Claude Code" + ;; + gemini) + update_agent_file "$GEMINI_FILE" "Gemini CLI" + ;; + copilot) + update_agent_file "$COPILOT_FILE" "GitHub Copilot" + ;; + cursor-agent) + update_agent_file "$CURSOR_FILE" "Cursor IDE" + ;; + qwen) + update_agent_file "$QWEN_FILE" "Qwen Code" + ;; + opencode) + update_agent_file "$AGENTS_FILE" "opencode" + ;; + codex) + update_agent_file "$AGENTS_FILE" "Codex CLI" + ;; + windsurf) + update_agent_file "$WINDSURF_FILE" "Windsurf" + ;; + kilocode) + update_agent_file "$KILOCODE_FILE" "Kilo Code" + ;; + auggie) + update_agent_file "$AUGGIE_FILE" "Auggie CLI" + ;; + roo) + update_agent_file "$ROO_FILE" "Roo Code" + ;; + codebuddy) + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + ;; + qoder) + update_agent_file "$QODER_FILE" "Qoder CLI" + ;; + amp) + update_agent_file "$AMP_FILE" "Amp" + ;; + shai) + update_agent_file "$SHAI_FILE" "SHAI" + ;; + q) + update_agent_file "$Q_FILE" "Amazon Q Developer CLI" + ;; + bob) + update_agent_file "$BOB_FILE" "IBM Bob" + ;; + *) + log_error "Unknown agent type '$agent_type'" + log_error "Expected: claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|roo|amp|shai|q|bob|qoder" + exit 1 + ;; + esac +} + +update_all_existing_agents() { + local found_agent=false + + # Check each possible agent file and update if it exists + if [[ -f "$CLAUDE_FILE" ]]; then + update_agent_file "$CLAUDE_FILE" "Claude Code" + found_agent=true + fi + + if [[ -f "$GEMINI_FILE" ]]; then + update_agent_file "$GEMINI_FILE" "Gemini CLI" + found_agent=true + fi + + if [[ -f "$COPILOT_FILE" ]]; then + update_agent_file "$COPILOT_FILE" "GitHub Copilot" + found_agent=true + fi + + if [[ -f "$CURSOR_FILE" ]]; then + update_agent_file "$CURSOR_FILE" "Cursor IDE" + found_agent=true + fi + + if [[ -f "$QWEN_FILE" ]]; then + update_agent_file "$QWEN_FILE" "Qwen Code" + found_agent=true + fi + + if [[ -f "$AGENTS_FILE" ]]; then + update_agent_file "$AGENTS_FILE" "Codex/opencode" + found_agent=true + fi + + if [[ -f "$WINDSURF_FILE" ]]; then + update_agent_file "$WINDSURF_FILE" "Windsurf" + found_agent=true + fi + + if [[ -f "$KILOCODE_FILE" ]]; then + update_agent_file "$KILOCODE_FILE" "Kilo Code" + found_agent=true + fi + + if [[ -f "$AUGGIE_FILE" ]]; then + update_agent_file "$AUGGIE_FILE" "Auggie CLI" + found_agent=true + fi + + if [[ -f "$ROO_FILE" ]]; then + update_agent_file "$ROO_FILE" "Roo Code" + found_agent=true + fi + + if [[ -f "$CODEBUDDY_FILE" ]]; then + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + found_agent=true + fi + + if [[ -f "$SHAI_FILE" ]]; then + update_agent_file "$SHAI_FILE" "SHAI" + found_agent=true + fi + + if [[ -f "$QODER_FILE" ]]; then + update_agent_file "$QODER_FILE" "Qoder CLI" + found_agent=true + fi + + if [[ -f "$Q_FILE" ]]; then + update_agent_file "$Q_FILE" "Amazon Q Developer CLI" + found_agent=true + fi + + if [[ -f "$BOB_FILE" ]]; then + update_agent_file "$BOB_FILE" "IBM Bob" + found_agent=true + fi + + # If no agent files exist, create a default Claude file + if [[ "$found_agent" == false ]]; then + log_info "No existing agent files found, creating default Claude file..." + update_agent_file "$CLAUDE_FILE" "Claude Code" + fi +} +print_summary() { + echo + log_info "Summary of changes:" + + if [[ -n "$NEW_LANG" ]]; then + echo " - Added language: $NEW_LANG" + fi + + if [[ -n "$NEW_FRAMEWORK" ]]; then + echo " - Added framework: $NEW_FRAMEWORK" + fi + + if [[ -n "$NEW_DB" ]] && [[ "$NEW_DB" != "N/A" ]]; then + echo " - Added database: $NEW_DB" + fi + + echo + + log_info "Usage: $0 [claude|gemini|copilot|cursor-agent|qwen|opencode|codex|windsurf|kilocode|auggie|codebuddy|shai|q|bob|qoder]" +} + +#============================================================================== +# Main Execution +#============================================================================== + +main() { + # Validate environment before proceeding + validate_environment + + log_info "=== Updating agent context files for feature $CURRENT_BRANCH ===" + + # Parse the plan file to extract project information + if ! parse_plan_data "$NEW_PLAN"; then + log_error "Failed to parse plan data" + exit 1 + fi + + # Process based on agent type argument + local success=true + + if [[ -z "$AGENT_TYPE" ]]; then + # No specific agent provided - update all existing agent files + log_info "No agent specified, updating all existing agent files..." + if ! update_all_existing_agents; then + success=false + fi + else + # Specific agent provided - update only that agent + log_info "Updating specific agent: $AGENT_TYPE" + if ! update_specific_agent "$AGENT_TYPE"; then + success=false + fi + fi + + # Print summary + print_summary + + if [[ "$success" == true ]]; then + log_success "Agent context update completed successfully" + exit 0 + else + log_error "Agent context update completed with errors" + exit 1 + fi +} + +# Execute main function if script is run directly +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi + diff --git a/.specify/templates/agent-file-template.md b/.specify/templates/agent-file-template.md new file mode 100644 index 00000000..4cc7fd66 --- /dev/null +++ b/.specify/templates/agent-file-template.md @@ -0,0 +1,28 @@ +# [PROJECT NAME] Development Guidelines + +Auto-generated from all feature plans. Last updated: [DATE] + +## Active Technologies + +[EXTRACTED FROM ALL PLAN.MD FILES] + +## Project Structure + +```text +[ACTUAL STRUCTURE FROM PLANS] +``` + +## Commands + +[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES] + +## Code Style + +[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE] + +## Recent Changes + +[LAST 3 FEATURES AND WHAT THEY ADDED] + + + diff --git a/.specify/templates/checklist-template.md b/.specify/templates/checklist-template.md new file mode 100644 index 00000000..806657da --- /dev/null +++ b/.specify/templates/checklist-template.md @@ -0,0 +1,40 @@ +# [CHECKLIST TYPE] Checklist: [FEATURE NAME] + +**Purpose**: [Brief description of what this checklist covers] +**Created**: [DATE] +**Feature**: [Link to spec.md or relevant documentation] + +**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements. + + + +## [Category 1] + +- [ ] CHK001 First checklist item with clear action +- [ ] CHK002 Second checklist item +- [ ] CHK003 Third checklist item + +## [Category 2] + +- [ ] CHK004 Another category item +- [ ] CHK005 Item with specific criteria +- [ ] CHK006 Final item in this category + +## Notes + +- Check items off as completed: `[x]` +- Add comments or findings inline +- Link to relevant resources or documentation +- Items are numbered sequentially for easy reference diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md new file mode 100644 index 00000000..6a8bfc6c --- /dev/null +++ b/.specify/templates/plan-template.md @@ -0,0 +1,104 @@ +# Implementation Plan: [FEATURE] + +**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link] +**Input**: Feature specification from `/specs/[###-feature-name]/spec.md` + +**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow. + +## Summary + +[Extract from feature spec: primary requirement + technical approach from research] + +## Technical Context + + + +**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION] +**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION] +**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A] +**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION] +**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION] +**Project Type**: [single/web/mobile - determines source structure] +**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION] +**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION] +**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION] + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +[Gates determined based on constitution file] + +## Project Structure + +### Documentation (this feature) + +```text +specs/[###-feature]/ +├── plan.md # This file (/speckit.plan command output) +├── research.md # Phase 0 output (/speckit.plan command) +├── data-model.md # Phase 1 output (/speckit.plan command) +├── quickstart.md # Phase 1 output (/speckit.plan command) +├── contracts/ # Phase 1 output (/speckit.plan command) +└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan) +``` + +### Source Code (repository root) + + +```text +# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT) +src/ +├── models/ +├── services/ +├── cli/ +└── lib/ + +tests/ +├── contract/ +├── integration/ +└── unit/ + +# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected) +backend/ +├── src/ +│ ├── models/ +│ ├── services/ +│ └── api/ +└── tests/ + +frontend/ +├── src/ +│ ├── components/ +│ ├── pages/ +│ └── services/ +└── tests/ + +# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected) +api/ +└── [same as backend above] + +ios/ or android/ +└── [platform-specific structure: feature modules, UI flows, platform tests] +``` + +**Structure Decision**: [Document the selected structure and reference the real +directories captured above] + +## Complexity Tracking + +> **Fill ONLY if Constitution Check has violations that must be justified** + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| [e.g., 4th project] | [current need] | [why 3 projects insufficient] | +| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] | diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md new file mode 100644 index 00000000..c67d9149 --- /dev/null +++ b/.specify/templates/spec-template.md @@ -0,0 +1,115 @@ +# Feature Specification: [FEATURE NAME] + +**Feature Branch**: `[###-feature-name]` +**Created**: [DATE] +**Status**: Draft +**Input**: User description: "$ARGUMENTS" + +## User Scenarios & Testing *(mandatory)* + + + +### User Story 1 - [Brief Title] (Priority: P1) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] +2. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +### User Story 2 - [Brief Title] (Priority: P2) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +### User Story 3 - [Brief Title] (Priority: P3) + +[Describe this user journey in plain language] + +**Why this priority**: [Explain the value and why it has this priority level] + +**Independent Test**: [Describe how this can be tested independently] + +**Acceptance Scenarios**: + +1. **Given** [initial state], **When** [action], **Then** [expected outcome] + +--- + +[Add more user stories as needed, each with an assigned priority] + +### Edge Cases + + + +- What happens when [boundary condition]? +- How does system handle [error scenario]? + +## Requirements *(mandatory)* + + + +### Functional Requirements + +- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"] +- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"] +- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"] +- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"] +- **FR-005**: System MUST [behavior, e.g., "log all security events"] + +*Example of marking unclear requirements:* + +- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] +- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] + +### Key Entities *(include if feature involves data)* + +- **[Entity 1]**: [What it represents, key attributes without implementation] +- **[Entity 2]**: [What it represents, relationships to other entities] + +## Success Criteria *(mandatory)* + + + +### Measurable Outcomes + +- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"] +- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"] +- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"] +- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"] diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md new file mode 100644 index 00000000..60f9be45 --- /dev/null +++ b/.specify/templates/tasks-template.md @@ -0,0 +1,251 @@ +--- + +description: "Task list template for feature implementation" +--- + +# Tasks: [FEATURE NAME] + +**Input**: Design documents from `/specs/[###-feature-name]/` +**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/ + +**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification. + +**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story. + +## Format: `[ID] [P?] [Story] Description` + +- **[P]**: Can run in parallel (different files, no dependencies) +- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) +- Include exact file paths in descriptions + +## Path Conventions + +- **Single project**: `src/`, `tests/` at repository root +- **Web app**: `backend/src/`, `frontend/src/` +- **Mobile**: `api/src/`, `ios/src/` or `android/src/` +- Paths shown below assume single project - adjust based on plan.md structure + + + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Project initialization and basic structure + +- [ ] T001 Create project structure per implementation plan +- [ ] T002 Initialize [language] project with [framework] dependencies +- [ ] T003 [P] Configure linting and formatting tools + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented + +**⚠️ CRITICAL**: No user story work can begin until this phase is complete + +Examples of foundational tasks (adjust based on your project): + +- [ ] T004 Setup database schema and migrations framework +- [ ] T005 [P] Implement authentication/authorization framework +- [ ] T006 [P] Setup API routing and middleware structure +- [ ] T007 Create base models/entities that all stories depend on +- [ ] T008 Configure error handling and logging infrastructure +- [ ] T009 Setup environment configuration management + +**Checkpoint**: Foundation ready - user story implementation can now begin in parallel + +--- + +## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️ + +> **NOTE: Write these tests FIRST, ensure they FAIL before implementation** + +- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py + +### Implementation for User Story 1 + +- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py +- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py +- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013) +- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T016 [US1] Add validation and error handling +- [ ] T017 [US1] Add logging for user story 1 operations + +**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently + +--- + +## Phase 4: User Story 2 - [Title] (Priority: P2) + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️ + +- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py + +### Implementation for User Story 2 + +- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py +- [ ] T021 [US2] Implement [Service] in src/services/[service].py +- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T023 [US2] Integrate with User Story 1 components (if needed) + +**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently + +--- + +## Phase 5: User Story 3 - [Title] (Priority: P3) + +**Goal**: [Brief description of what this story delivers] + +**Independent Test**: [How to verify this story works on its own] + +### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️ + +- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py + +### Implementation for User Story 3 + +- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py +- [ ] T027 [US3] Implement [Service] in src/services/[service].py +- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py + +**Checkpoint**: All user stories should now be independently functional + +--- + +[Add more user story phases as needed, following the same pattern] + +--- + +## Phase N: Polish & Cross-Cutting Concerns + +**Purpose**: Improvements that affect multiple user stories + +- [ ] TXXX [P] Documentation updates in docs/ +- [ ] TXXX Code cleanup and refactoring +- [ ] TXXX Performance optimization across all stories +- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/ +- [ ] TXXX Security hardening +- [ ] TXXX Run quickstart.md validation + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies - can start immediately +- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories +- **User Stories (Phase 3+)**: All depend on Foundational phase completion + - User stories can then proceed in parallel (if staffed) + - Or sequentially in priority order (P1 → P2 → P3) +- **Polish (Final Phase)**: Depends on all desired user stories being complete + +### User Story Dependencies + +- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories +- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable +- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable + +### Within Each User Story + +- Tests (if included) MUST be written and FAIL before implementation +- Models before services +- Services before endpoints +- Core implementation before integration +- Story complete before moving to next priority + +### Parallel Opportunities + +- All Setup tasks marked [P] can run in parallel +- All Foundational tasks marked [P] can run in parallel (within Phase 2) +- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows) +- All tests for a user story marked [P] can run in parallel +- Models within a story marked [P] can run in parallel +- Different user stories can be worked on in parallel by different team members + +--- + +## Parallel Example: User Story 1 + +```bash +# Launch all tests for User Story 1 together (if tests requested): +Task: "Contract test for [endpoint] in tests/contract/test_[name].py" +Task: "Integration test for [user journey] in tests/integration/test_[name].py" + +# Launch all models for User Story 1 together: +Task: "Create [Entity1] model in src/models/[entity1].py" +Task: "Create [Entity2] model in src/models/[entity2].py" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup +2. Complete Phase 2: Foundational (CRITICAL - blocks all stories) +3. Complete Phase 3: User Story 1 +4. **STOP and VALIDATE**: Test User Story 1 independently +5. Deploy/demo if ready + +### Incremental Delivery + +1. Complete Setup + Foundational → Foundation ready +2. Add User Story 1 → Test independently → Deploy/Demo (MVP!) +3. Add User Story 2 → Test independently → Deploy/Demo +4. Add User Story 3 → Test independently → Deploy/Demo +5. Each story adds value without breaking previous stories + +### Parallel Team Strategy + +With multiple developers: + +1. Team completes Setup + Foundational together +2. Once Foundational is done: + - Developer A: User Story 1 + - Developer B: User Story 2 + - Developer C: User Story 3 +3. Stories complete and integrate independently + +--- + +## Notes + +- [P] tasks = different files, no dependencies +- [Story] label maps task to specific user story for traceability +- Each user story should be independently completable and testable +- Verify tests fail before implementing +- Commit after each task or logical group +- Stop at any checkpoint to validate story independently +- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000..85acdc8c --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,35 @@ + +# CodeWorkout Project + +## Project Overview + +CodeWorkout is a web-based platform built with Ruby on Rails, designed to help users learn programming. It provides a space for users to practice coding exercises and multiple-choice questions, receiving immediate feedback. The platform supports both self-paced learning and structured courses for instructors. + +The application uses a MariaDB database and is set up to run in a Dockerized environment. Key features include user authentication, course management, exercise creation, and a "gym" for practicing workouts. + +## Building and Running + +The project uses Docker to manage its development environment. The following commands are essential for getting the application running: + +* **Build and start the application:** + ```bash + docker-compose up -d + ``` +* **Stop the application:** + ```bash + docker-compose down + ``` +* **Run database migrations and seed data:** + ```bash + docker-compose run web rake db:populate + ``` +* **Access the running application:** + [https://localhost:9292](https://localhost:9292) + +## Development Conventions + +* **Dependencies:** Ruby gems are managed by `Bundler` and are listed in the `Gemfile`. +* **Database:** The database schema is defined in `db/schema.rb`. Migrations are used for schema changes. +* **Testing:** The project uses RSpec for testing. Test files are located in the `spec` directory. +* **Routing:** Application routes are defined in `config/routes.rb`. +* **Docker:** The Docker environment is configured in `docker-compose.yml` and the `Dockerfile`. diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 7a7f6543..865f08cf 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -1,8 +1,10 @@ +require 'ims/lti' +require 'oauth/request_proxy/rack_request' +require 'zip' +require 'tempfile' + class ExercisesController < ApplicationController - require 'ims/lti' - require 'oauth/request_proxy/rack_request' - require 'zip' - require 'tempfile' + load_and_authorize_resource skip_authorize_resource only: [:practice, :call_open_pop, :export] diff --git a/app/utils/peml_parsing_util.rb b/app/utils/peml_parsing_util.rb index 9a58bd08..2cb59d27 100644 --- a/app/utils/peml_parsing_util.rb +++ b/app/utils/peml_parsing_util.rb @@ -114,14 +114,14 @@ def get_test_file_content(prompt, files) Rails.logger.debug 'get_test_file_content(prompt, files), files:' Rails.logger.debug '==========' Rails.logger.debug files.to_yaml - Rails.logger.debug files.inspect + Rails.logger.debug files.pretty_inspect if files and !prompt['tests'] file = files['files'][0] Rails.logger.debug '==========' Rails.logger.debug 'get_test_file_content(prompt, files), file:' Rails.logger.debug '==========' Rails.logger.debug file.to_yaml - Rails.logger.debug file.inspect + Rails.logger.debug file.pretty_inspect file.dottie! # copy class and method names from file pattern properties, if present @@ -183,7 +183,7 @@ def get_file_content(files) Rails.logger.debug 'get_file_content(files), files:' Rails.logger.debug '==========' Rails.logger.debug files.to_yaml - Rails.logger.debug files.inspect + Rails.logger.debug files.pretty_inspect file = files if file and files['files'] file = files['files'][0] @@ -192,7 +192,7 @@ def get_file_content(files) Rails.logger.debug 'get_file_content(files), file:' Rails.logger.debug '==========' Rails.logger.debug file.to_yaml - Rails.logger.debug file.inspect + Rails.logger.debug file.pretty_inspect if file.is_a? String file.strip! if file.sub!(/^url\((.*)\)$/, '\1') diff --git a/config/deploy/staging.rb b/config/deploy/staging.rb index 815c836e..4a7ee9e5 100644 --- a/config/deploy/staging.rb +++ b/config/deploy/staging.rb @@ -16,9 +16,11 @@ # ====================== # This can be used to drop a more detailed server # definition into the server list. The second argument -# something that quacks like a hash can be used to set # extended properties on the server. -server 'codeworkoutdev.cs.vt.edu', user: 'codeworkout', roles: %w{web app db}, branch: 'rails-5-upgrade', my_property: :my_value +server 'codeworkoutdev.cs.vt.edu', + user: 'codeworkout', + roles: %w{web app db} + # , branch: 'rails-5-upgrade', my_property: :my_value # you can set custom ssh options # it's possible to pass any option but you need to keep in mind that net/ssh understand limited list of options diff --git a/db/schema.rb b/db/schema.rb index a73a45ac..ff77cbb8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,7 +12,7 @@ ActiveRecord::Schema.define(version: 2025_02_18_154318) do - create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "namespace" t.text "body" t.string "resource_id", default: "", null: false @@ -26,7 +26,7 @@ t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id" end - create_table "attempts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "attempts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.integer "exercise_version_id", null: false t.datetime "submit_time", null: false @@ -49,14 +49,14 @@ t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id" end - create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "tag_user_score_id" t.index ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk" end - create_table "choices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "choices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "multiple_choice_prompt_id", null: false t.integer "position", null: false t.text "feedback" @@ -67,20 +67,20 @@ t.index ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id" end - create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "choice_id" t.integer "multiple_choice_prompt_answer_id" t.index ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk" end - create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.text "answer" t.text "error" t.integer "error_line_no" end - create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.datetime "created_at" t.datetime "updated_at" t.string "class_name" @@ -91,7 +91,7 @@ t.boolean "hide_examples", default: false, null: false end - create_table "course_enrollments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "course_enrollments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.integer "course_offering_id", null: false t.integer "course_role_id", null: false @@ -101,7 +101,7 @@ t.index ["user_id"], name: "index_course_enrollments_on_user_id" end - create_table "course_exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "course_exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "course_id", null: false t.integer "exercise_id", null: false t.datetime "created_at" @@ -110,7 +110,7 @@ t.index ["exercise_id"], name: "course_exercises_exercise_id_fk" end - create_table "course_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "course_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "course_id", null: false t.integer "term_id", null: false t.string "label", default: "", null: false @@ -125,7 +125,7 @@ t.index ["term_id"], name: "index_course_offerings_on_term_id" end - create_table "course_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "course_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "can_manage_course", default: false, null: false t.boolean "can_manage_assignments", default: false, null: false @@ -134,7 +134,7 @@ t.boolean "builtin", default: false, null: false end - create_table "courses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "courses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.string "number", default: "", null: false t.integer "organization_id", null: false @@ -149,7 +149,7 @@ t.index ["user_group_id"], name: "index_courses_on_user_group_id" end - create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "usable_type" t.integer "usable_id" t.text "class_name" @@ -166,7 +166,7 @@ t.index ["created_at"], name: "index_errors_on_created_at" end - create_table "exercise_collections", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercise_collections", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" t.integer "user_group_id" @@ -181,20 +181,20 @@ t.index ["user_id"], name: "index_exercise_collections_on_user_id" end - create_table "exercise_families", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercise_families", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" end - create_table "exercise_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercise_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "exercise_id", null: false t.integer "owner_id", null: false t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true t.index ["owner_id"], name: "exercise_owners_owner_id_fk" end - create_table "exercise_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercise_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "stem_id" t.datetime "created_at" t.datetime "updated_at" @@ -204,19 +204,18 @@ t.integer "irt_data_id" t.text "text_representation", limit: 16777215 t.index ["creator_id"], name: "exercise_versions_creator_id_fk" - t.index ["exercise_id"], name: "index_exercise_versions_on_exercise_id" t.index ["irt_data_id"], name: "exercise_versions_irt_data_id_fk" t.index ["stem_id"], name: "index_exercise_versions_on_stem_id" end - create_table "exercise_versions_resource_files", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercise_versions_resource_files", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "exercise_version_id", null: false t.integer "resource_file_id", null: false t.index ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id" t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id" end - create_table "exercise_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercise_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "exercise_id", null: false t.integer "workout_id", null: false t.integer "position", null: false @@ -227,7 +226,7 @@ t.index ["workout_id"], name: "exercise_workouts_workout_id_fk" end - create_table "exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "question_type", null: false t.integer "current_version_id" t.datetime "created_at" @@ -240,7 +239,6 @@ t.integer "irt_data_id" t.string "external_id" t.integer "exercise_collection_id" - t.index ["current_version_id"], name: "index_exercises_on_current_version_id" t.index ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id" t.index ["exercise_family_id"], name: "index_exercises_on_exercise_family_id" t.index ["external_id"], name: "index_exercises_on_external_id", unique: true @@ -248,7 +246,7 @@ t.index ["is_public"], name: "index_exercises_on_is_public" end - create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "slug", default: "", null: false t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 @@ -260,14 +258,14 @@ t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" end - create_table "global_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "global_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "can_manage_all_courses", default: false, null: false t.boolean "can_edit_system_configuration", default: false, null: false t.boolean "builtin", default: false, null: false end - create_table "group_access_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "group_access_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "user_group_id" t.boolean "pending", default: true @@ -278,7 +276,7 @@ t.index ["user_id"], name: "index_group_access_requests_on_user_id" end - create_table "identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.string "provider", default: "", null: false t.string "uid", default: "", null: false @@ -288,14 +286,14 @@ t.index ["user_id"], name: "index_identities_on_user_id" end - create_table "irt_data", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "irt_data", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "attempt_count", null: false t.float "sum_of_scores", null: false t.float "difficulty", null: false t.float "discrimination", null: false end - create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" t.boolean "can_fork" @@ -304,7 +302,7 @@ t.datetime "updated_at" end - create_table "licenses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "licenses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.text "description" t.string "url" @@ -314,7 +312,7 @@ t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id" end - create_table "lms_instances", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "lms_instances", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "consumer_key" t.string "consumer_secret" t.datetime "created_at" @@ -327,14 +325,14 @@ t.index ["url"], name: "index_lms_instances_on_url", unique: true end - create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.index ["name"], name: "index_lms_types_on_name", unique: true end - create_table "lti_identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "lti_identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "lti_user_id" t.integer "user_id" t.integer "lms_instance_id" @@ -345,7 +343,7 @@ t.index ["user_id"], name: "index_lti_identities_on_user_id" end - create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "workout_id" t.string "lms_assignment_id", null: false t.datetime "created_at" @@ -355,22 +353,22 @@ t.index ["workout_id"], name: "index_lti_workouts_on_workout_id" end - create_table "memberships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "memberships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "user_group_id" t.datetime "created_at" t.datetime "updated_at" end - create_table "multiple_choice_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "multiple_choice_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| end - create_table "multiple_choice_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "multiple_choice_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.boolean "allow_multiple", default: false, null: false t.boolean "is_scrambled", default: true, null: false end - create_table "organizations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "organizations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.datetime "created_at" t.datetime "updated_at" @@ -380,7 +378,7 @@ t.index ["slug"], name: "index_organizations_on_slug", unique: true end - create_table "ownerships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "ownerships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "filename" t.integer "resource_file_id" t.integer "exercise_version_id" @@ -388,10 +386,9 @@ t.datetime "updated_at", null: false t.index ["exercise_version_id"], name: "index_ownerships_on_exercise_version_id" t.index ["filename"], name: "index_ownerships_on_filename" - t.index ["resource_file_id"], name: "index_ownerships_on_resource_file_id" end - create_table "prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "attempt_id" t.integer "prompt_id" t.integer "actable_id" @@ -402,7 +399,7 @@ t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id" end - create_table "prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "exercise_version_id", null: false t.text "question", null: false t.integer "position", null: false @@ -413,11 +410,10 @@ t.string "actable_type" t.integer "irt_data_id" t.index ["actable_id"], name: "index_prompts_on_actable_id" - t.index ["exercise_version_id"], name: "index_prompts_on_exercise_version_id" t.index ["irt_data_id"], name: "prompts_irt_data_id_fk" end - create_table "resource_files", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "resource_files", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "filename" t.string "token", default: "", null: false t.integer "user_id", null: false @@ -430,7 +426,7 @@ t.index ["user_id"], name: "index_resource_files_on_user_id" end - create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "first_name" t.string "last_name_name" t.string "email" @@ -440,13 +436,13 @@ t.datetime "updated_at" end - create_table "stems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "stems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.text "preamble" t.datetime "created_at" t.datetime "updated_at" end - create_table "student_extensions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "student_extensions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "workout_offering_id" t.datetime "soft_deadline" @@ -459,7 +455,7 @@ t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id" end - create_table "tag_user_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "tag_user_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id", null: false t.integer "experience", default: 0 t.datetime "created_at" @@ -468,7 +464,7 @@ t.index ["user_id"], name: "index_tag_user_scores_on_user_id" end - create_table "taggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "taggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -487,13 +483,13 @@ t.index ["tagger_id"], name: "index_taggings_on_tagger_id" end - create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.integer "taggings_count", default: 0 t.index ["name"], name: "index_tags_on_name", unique: true end - create_table "terms", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "terms", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "season", null: false t.date "starts_on", null: false t.date "ends_on", null: false @@ -505,7 +501,7 @@ t.index ["year", "season"], name: "index_terms_on_year_and_season" end - create_table "test_case_results", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "test_case_results", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "test_case_id", null: false t.integer "user_id", null: false t.text "execution_feedback" @@ -519,7 +515,7 @@ t.index ["user_id"], name: "index_test_case_results_on_user_id" end - create_table "test_cases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "test_cases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.text "negative_feedback" t.float "weight", null: false t.text "description" @@ -532,10 +528,9 @@ t.boolean "screening", default: false, null: false t.boolean "example", default: false, null: false t.boolean "hidden", default: false, null: false - t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id" end - create_table "time_zones", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "time_zones", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.string "zone" t.string "display_as" @@ -543,14 +538,14 @@ t.datetime "updated_at" end - create_table "user_groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "user_groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" t.text "description" end - create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -582,7 +577,7 @@ t.index ["time_zone_id"], name: "index_users_on_time_zone_id" end - create_table "visualization_loggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "visualization_loggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "user_id" t.integer "exercise_id" t.integer "workout_id" @@ -595,7 +590,7 @@ t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id" end - create_table "workout_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "workout_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "course_offering_id", null: false t.integer "workout_id", null: false t.datetime "created_at" @@ -618,14 +613,14 @@ t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id" end - create_table "workout_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "workout_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "workout_id", null: false t.integer "owner_id", null: false t.index ["owner_id"], name: "workout_owners_owner_id_fk" t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true end - create_table "workout_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "workout_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.boolean "hide_thumbnails_before_start" t.boolean "hide_feedback_before_finish" t.boolean "hide_compilation_feedback_before_finish" @@ -643,7 +638,7 @@ t.boolean "hide_score_in_review_before_close" end - create_table "workout_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "workout_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.integer "workout_id", null: false t.integer "user_id", null: false t.float "score" @@ -666,7 +661,7 @@ t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk" end - create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci", force: :cascade do |t| + create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| t.string "name", default: "", null: false t.boolean "scrambled", default: false t.datetime "created_at" @@ -698,9 +693,7 @@ add_foreign_key "course_offerings", "courses", name: "course_offerings_course_id_fk" add_foreign_key "course_offerings", "terms", name: "course_offerings_term_id_fk" add_foreign_key "courses", "organizations", name: "courses_organization_id_fk" - add_foreign_key "exercise_owners", "exercises", name: "exercise_owners_exercise_id_fk" add_foreign_key "exercise_owners", "users", column: "owner_id", name: "exercise_owners_owner_id_fk" - add_foreign_key "exercise_versions", "exercises", name: "exercise_versions_exercise_id_fk" add_foreign_key "exercise_versions", "irt_data", column: "irt_data_id", name: "exercise_versions_irt_data_id_fk" add_foreign_key "exercise_versions", "stems", name: "exercise_versions_stem_id_fk" add_foreign_key "exercise_versions", "users", column: "creator_id", name: "exercise_versions_creator_id_fk" @@ -709,16 +702,13 @@ add_foreign_key "exercise_workouts", "exercises", name: "exercise_workouts_exercise_id_fk" add_foreign_key "exercise_workouts", "workouts", name: "exercise_workouts_workout_id_fk" add_foreign_key "exercises", "exercise_families", name: "exercises_exercise_family_id_fk" - add_foreign_key "exercises", "exercise_versions", column: "current_version_id", name: "exercises_current_version_id_fk" add_foreign_key "exercises", "irt_data", column: "irt_data_id", name: "exercises_irt_data_id_fk" add_foreign_key "identities", "users", name: "identities_user_id_fk" add_foreign_key "lms_instances", "lms_types", name: "lms_instances_lms_type_id_fk" add_foreign_key "lti_workouts", "lms_instances" add_foreign_key "ownerships", "exercise_versions" - add_foreign_key "ownerships", "resource_files" add_foreign_key "prompt_answers", "attempts", name: "prompt_answers_attempt_id_fk" add_foreign_key "prompt_answers", "prompts", name: "prompt_answers_prompt_id_fk" - add_foreign_key "prompts", "exercise_versions", name: "prompts_exercise_version_id_fk" add_foreign_key "prompts", "irt_data", column: "irt_data_id", name: "prompts_irt_data_id_fk" add_foreign_key "resource_files", "users", name: "resource_files_user_id_fk" add_foreign_key "student_extensions", "users", name: "student_extensions_user_id_fk" @@ -727,7 +717,6 @@ add_foreign_key "test_case_results", "coding_prompt_answers", name: "test_case_results_coding_prompt_answer_id_fk" add_foreign_key "test_case_results", "test_cases", name: "test_case_results_test_case_id_fk" add_foreign_key "test_case_results", "users", name: "test_case_results_user_id_fk" - add_foreign_key "test_cases", "coding_prompts", name: "test_cases_coding_prompt_id_fk" add_foreign_key "users", "global_roles", name: "users_global_role_id_fk" add_foreign_key "users", "time_zones", name: "users_time_zone_id_fk" add_foreign_key "users", "workout_scores", column: "current_workout_score_id", name: "users_current_workout_score_id_fk" From e46963b14dcc02b5b9cfd3d80aa0ce82fac28056 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 24 Feb 2026 11:23:10 -0500 Subject: [PATCH 056/117] fix: migrations updated to 5.2, modern maria, SPLICE export --- app/controllers/sse_controller.rb | 6 +- app/models/exercise.rb | 38 ++ .../20131010184442_change_tags_in_exercise.rb | 3 +- .../20131016023405_change_language_to_tags.rb | 3 +- ...20150205221653_change_score_in_attempts.rb | 2 +- ...123644_add_null_constraints_and_indices.rb | 10 +- ...50326163802_add_foreign_key_constraints.rb | 13 +- ...0327213048_create_active_admin_comments.rb | 2 +- db/migrate/20150331153411_create_errors.rb | 18 + .../20150501192055_rename_prompt_features.rb | 18 +- db/migrate/20150503004546_add_to_attempt.rb | 4 +- .../20150503153952_create_workout_owners.rb | 4 +- .../20150503232731_create_prompt_answer.rb | 2 - ...50504000235_create_coding_prompt_answer.rb | 4 +- ...11_create_multiple_choice_prompt_answer.rb | 4 +- db/migrate/20150504004247_create_irt_data.rb | 30 +- ...on_migration.acts_as_taggable_on_engine.rb | 2 +- ...150515205710_revise_test_case_structure.rb | 10 +- ...1620_add_current_workout_score_to_users.rb | 2 +- ...ue_from_workout_id_to_workout_offerings.rb | 2 +- db/migrate/20160512005525_add_keys.rb | 2 + ...2182220_add_lms_type_id_to_lms_instance.rb | 3 +- ..._on_taggings.acts_as_taggable_on_engine.rb | 12 +- ...191207_add_started_at_to_workout_scores.rb | 2 +- ...0201051224_add_hashval_to_resource_file.rb | 2 +- .../20210201051643_create_ownerships.rb | 2 +- ...ndices_to_resource_files_and_ownerships.rb | 2 +- ...101_add_score_options_to_workout_policy.rb | 2 +- db/schema.rb | 598 ++++++++++-------- spec/factories/exercises.rb | 8 +- spec/models/exercise_spec.rb | 26 + 31 files changed, 507 insertions(+), 329 deletions(-) create mode 100644 db/migrate/20150331153411_create_errors.rb diff --git a/app/controllers/sse_controller.rb b/app/controllers/sse_controller.rb index 023f8da1..049c5419 100644 --- a/app/controllers/sse_controller.rb +++ b/app/controllers/sse_controller.rb @@ -58,7 +58,11 @@ def feedback_update def feedback_poll @attempt = Attempt.find_by(id: params[:att_id]) @exercise_version = @attempt.exercise_version - @student_drift_user = current_user ? current_user : session[:student_drift_user_id]? User.find_by(session[:student_drift_user_id]) : User.find_by(params[:drift_user_id]) + @student_drift_user = current_user ? + current_user : + (session[:student_drift_user_id] ? + User.find_by(id: session[:student_drift_user_id]) : + User.find_by(id: params[:drift_user_id])) @exercise = @exercise_version.exercise # authorize! :read, @attempt if !@attempt.feedback_ready diff --git a/app/models/exercise.rb b/app/models/exercise.rb index b6dff0a9..13cead5d 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -343,6 +343,44 @@ def is_publicly_available? end end + def self.generate_slc_catalog(filename) + exercises = Exercise.publicly_visible + + catalog = exercises.map do |exercise| + description = case exercise.question_type + when Q_MC + "Multiple-choice question" + when Q_CODING + "Coding question" + when Q_BLANKS + "Fill in the blanks question" + else + exercise.type_name + end + + item = { + catalog_type: "SLCItem", + persistentID: exercise.external_id.to_s, + platform_name: "CodeWorkout", + iframe_url: "https://codeworkout.cs.vt.edu/gym/exercises/#{exercise.id}/practice", + title: exercise.name.to_s, + description: description, + author: exercise.owners.map(&:display_name), + institution: ["Virginia Tech"], + keywords: exercise.tags.map(&:name), + programming_language: [exercise.language].compact, + natural_language: ["English"] + } + + license = exercise.exercise_collection.andand.license.andand.name + item[:license] = license if license + + item + end + + File.write(filename, JSON.pretty_generate(catalog)) + end + def self.progsnap2_attempt_csv(exercise_id, course_id=nil, term_id=nil) denormalized = Exercise.denormalized_attempt_data(exercise_id, course_id, term_id) main_events = Exercise.progsnap2_main_events_csv(denormalized) diff --git a/db/migrate/20131010184442_change_tags_in_exercise.rb b/db/migrate/20131010184442_change_tags_in_exercise.rb index 72d95623..9c63c9e8 100644 --- a/db/migrate/20131010184442_change_tags_in_exercise.rb +++ b/db/migrate/20131010184442_change_tags_in_exercise.rb @@ -1,5 +1,6 @@ class ChangeTagsInExercise < ActiveRecord::Migration[5.1] def change - remove_column :exercises, :language, :belongs_to #language now represented as a tag with tagtype=2 + # remove_foreign_key :exercises, :languages + remove_column :exercises, :language_id, :integer #language now represented as a tag with tagtype=2 end end diff --git a/db/migrate/20131016023405_change_language_to_tags.rb b/db/migrate/20131016023405_change_language_to_tags.rb index 407fbeb8..b00942ea 100644 --- a/db/migrate/20131016023405_change_language_to_tags.rb +++ b/db/migrate/20131016023405_change_language_to_tags.rb @@ -1,6 +1,7 @@ class ChangeLanguageToTags < ActiveRecord::Migration[5.1] def change drop_table :languages - remove_column :exercises, :language_id + # remove_column :exercises, :language_id + # already removed in db/migrate/20131010184442_change_tags_in_exercise.rb end end diff --git a/db/migrate/20150205221653_change_score_in_attempts.rb b/db/migrate/20150205221653_change_score_in_attempts.rb index f3281a22..52e09959 100644 --- a/db/migrate/20150205221653_change_score_in_attempts.rb +++ b/db/migrate/20150205221653_change_score_in_attempts.rb @@ -1,5 +1,5 @@ class ChangeScoreInAttempts < ActiveRecord::Migration[5.1] def change - change_column :attempts, :score, :number, default: 0.0 + change_column :attempts, :score, :float, default: 0.0 end end diff --git a/db/migrate/20150326123644_add_null_constraints_and_indices.rb b/db/migrate/20150326123644_add_null_constraints_and_indices.rb index e5ab06cf..0ef3cad9 100644 --- a/db/migrate/20150326123644_add_null_constraints_and_indices.rb +++ b/db/migrate/20150326123644_add_null_constraints_and_indices.rb @@ -78,8 +78,8 @@ def change # exercises_tags change_column_null :exercises_tags, :exercise_id, false change_column_null :exercises_tags, :tag_id, false - add_index :exercises_tags, :exercise_id - add_index :exercises_tags, :tag_id + # add_index :exercises_tags, :exercise_id # overlaps existing index, not needed + # add_index :exercises_tags, :tag_id # overlaps existing index, not needed # prompts change_column_null :prompts, :max_user_attempts, false @@ -89,7 +89,7 @@ def change # resource_files change_column_null :resource_files, :user_id, false change_column_null :resource_files, :token, false - add_index :resource_files, :user_id + # add_index :resource_files, :user_id add_index :resource_files, :token # tags_workouts @@ -100,8 +100,8 @@ def change change_column_null :test_case_results, :user_id, false change_column_null :test_case_results, :test_case_id, false change_column_null :test_case_results, :pass, false - add_index :test_case_results, :user_id - add_index :test_case_results, :test_case_id + # add_index :test_case_results, :user_id + # add_index :test_case_results, :test_case_id # test_cases change_column_null :test_cases, :input, false diff --git a/db/migrate/20150326163802_add_foreign_key_constraints.rb b/db/migrate/20150326163802_add_foreign_key_constraints.rb index 0e57ca08..6e92cdea 100644 --- a/db/migrate/20150326163802_add_foreign_key_constraints.rb +++ b/db/migrate/20150326163802_add_foreign_key_constraints.rb @@ -2,22 +2,29 @@ class AddForeignKeyConstraints < ActiveRecord::Migration[5.1] def change add_foreign_key :attempts, :exercises, dependent: :delete add_foreign_key :attempts, :users, dependent: :delete - add_foreign_key :attempts, :workout_offerings, dependent: :delete + change_column :base_exercises, :current_version_id, :bigint add_foreign_key :base_exercises, :exercises, column: :current_version_id + change_column :base_exercises, :user_id, :bigint add_foreign_key :base_exercises, :users + change_column :base_exercises, :variation_group_id, :bigint add_foreign_key :base_exercises, :variation_groups add_foreign_key :choices, :exercises, dependent: :delete add_foreign_key :coding_questions, :exercises, dependent: :delete add_foreign_key :course_enrollments, :course_offerings, dependent: :delete add_foreign_key :course_enrollments, :course_roles add_foreign_key :course_enrollments, :users, dependent: :delete + change_column :course_exercises, :course_id, :bigint add_foreign_key :course_exercises, :courses, dependent: :delete + change_column :course_exercises, :exercise_id, :bigint add_foreign_key :course_exercises, :exercises, dependent: :delete add_foreign_key :course_offerings, :courses, dependent: :delete add_foreign_key :course_offerings, :terms, dependent: :delete add_foreign_key :courses, :organizations, dependent: :delete + change_column :exercise_workouts, :exercise_id, :bigint add_foreign_key :exercise_workouts, :exercises, dependent: :delete + change_column :exercise_workouts, :workout_id, :bigint add_foreign_key :exercise_workouts, :workouts, dependent: :delete + change_column :exercises, :base_exercise_id, :bigint add_foreign_key :exercises, :base_exercises, dependent: :delete add_foreign_key :exercises_resource_files, :exercises, dependent: :delete add_foreign_key :exercises_resource_files, :resource_files, @@ -32,11 +39,15 @@ def change add_foreign_key :tag_user_scores, :users, dependent: :delete add_foreign_key :tags_workouts, :tags, dependent: :delete add_foreign_key :tags_workouts, :workouts, dependent: :delete + change_column :test_case_results, :test_case_id, :bigint add_foreign_key :test_case_results, :test_cases, dependent: :delete + change_column :test_case_results, :user_id, :bigint add_foreign_key :test_case_results, :users, dependent: :delete add_foreign_key :test_cases, :coding_questions, dependent: :delete add_foreign_key :users, :global_roles + change_column :workout_offerings, :course_offering_id, :bigint add_foreign_key :workout_offerings, :course_offerings, dependent: :delete + change_column :workout_offerings, :workout_id, :bigint add_foreign_key :workout_offerings, :workouts, dependent: :delete add_foreign_key :workout_scores, :users, dependent: :delete add_foreign_key :workout_scores, :workouts, dependent: :delete diff --git a/db/migrate/20150327213048_create_active_admin_comments.rb b/db/migrate/20150327213048_create_active_admin_comments.rb index fd0c5aee..24dc70d5 100644 --- a/db/migrate/20150327213048_create_active_admin_comments.rb +++ b/db/migrate/20150327213048_create_active_admin_comments.rb @@ -9,7 +9,7 @@ def self.up t.timestamps end add_index :active_admin_comments, [:namespace] - add_index :active_admin_comments, [:author_type, :author_id] + # add_index :active_admin_comments, [:author_type, :author_id] add_index :active_admin_comments, [:resource_type, :resource_id] end diff --git a/db/migrate/20150331153411_create_errors.rb b/db/migrate/20150331153411_create_errors.rb new file mode 100644 index 00000000..474be886 --- /dev/null +++ b/db/migrate/20150331153411_create_errors.rb @@ -0,0 +1,18 @@ +class CreateErrors < ActiveRecord::Migration[5.1] + def change + create_table :errors do |t| + t.string :usable_type + t.integer :usable_id + t.text :class_name + t.text :message + t.text :trace + t.text :target_url + t.text :referer_url + t.text :params + t.text :user_agent + t.timestamps + end + add_index :errors, :class_name + add_index :errors, :created_at + end +end diff --git a/db/migrate/20150501192055_rename_prompt_features.rb b/db/migrate/20150501192055_rename_prompt_features.rb index 3030de55..95c7c5cd 100644 --- a/db/migrate/20150501192055_rename_prompt_features.rb +++ b/db/migrate/20150501192055_rename_prompt_features.rb @@ -1,20 +1,20 @@ class RenamePromptFeatures < ActiveRecord::Migration[5.1] def change change_table :prompts do |t| - t.remove :language_id, :integer + t.remove :language_id t.rename :instruction, :prompt - t.remove :max_user_attempts, :integer + t.remove :max_user_attempts t.rename :attempts, :attempt_count t.rename :correct, :correct_count - t.remove :type, :integer - t.remove :allow_multiple, :boolean + t.remove :type + t.remove :allow_multiple t.actable t.index :actable_id, unique: true end rename_table :coding_questions, :coding_prompts change_table :coding_prompts do |t| - t.remove :exercise_version_id, :integer + t.remove :exercise_version_id end create_table :multiple_choice_prompts do |t| @@ -23,12 +23,12 @@ def change end change_table :exercise_versions do |t| - t.remove :feedback, :text - t.remove :priority, :integer + t.remove :feedback + t.remove :priority t.rename :count_attempts, :attempt_count t.rename :count_correct, :correct_count - t.remove :mcq_allow_multiple, :boolean - t.remove :mcq_is_scrambled, :boolean + t.remove :mcq_allow_multiple + t.remove :mcq_is_scrambled end end end diff --git a/db/migrate/20150503004546_add_to_attempt.rb b/db/migrate/20150503004546_add_to_attempt.rb index 7a03e20c..1f5d8bd2 100644 --- a/db/migrate/20150503004546_add_to_attempt.rb +++ b/db/migrate/20150503004546_add_to_attempt.rb @@ -2,9 +2,9 @@ class AddToAttempt < ActiveRecord::Migration[5.1] def change change_table :attempts do |t| t.belongs_to :workout_score - t.integer :active_score_id + t.bigint :active_score_id - t.index :workout_score_id + #t.index :workout_score_id t.index :active_score_id end change_column :attempts, :score, :float diff --git a/db/migrate/20150503153952_create_workout_owners.rb b/db/migrate/20150503153952_create_workout_owners.rb index dccd31c9..623819d8 100644 --- a/db/migrate/20150503153952_create_workout_owners.rb +++ b/db/migrate/20150503153952_create_workout_owners.rb @@ -2,12 +2,12 @@ class CreateWorkoutOwners < ActiveRecord::Migration[5.1] def change create_table :workout_owners do |t| t.belongs_to :workout, null: false - t.integer :owner_id, null: false + t.bigint :owner_id, null: false end add_index :workout_owners, [:workout_id, :owner_id], unique: true add_foreign_key :workout_owners, :workouts - add_foreign_key :workout_owners, :users, foreign_key: :owner_id + add_foreign_key :workout_owners, :users, column: :owner_id rename_column :exercise_owners, :user_id, :owner_id end diff --git a/db/migrate/20150503232731_create_prompt_answer.rb b/db/migrate/20150503232731_create_prompt_answer.rb index df3c70f1..cb50a635 100644 --- a/db/migrate/20150503232731_create_prompt_answer.rb +++ b/db/migrate/20150503232731_create_prompt_answer.rb @@ -5,8 +5,6 @@ def change t.belongs_to :prompt, required: true t.actable - t.index :attempt_id - t.index :prompt_id t.index [:attempt_id, :prompt_id], unique: true end end diff --git a/db/migrate/20150504000235_create_coding_prompt_answer.rb b/db/migrate/20150504000235_create_coding_prompt_answer.rb index 1b9668ed..47e2e557 100644 --- a/db/migrate/20150504000235_create_coding_prompt_answer.rb +++ b/db/migrate/20150504000235_create_coding_prompt_answer.rb @@ -1,7 +1,7 @@ class CreateCodingPromptAnswer < ActiveRecord::Migration[5.1] def change remove_column :attempts, :answer, :text - remove_column :attempts, :workout_offering_id, :integer + # remove_column :attempts, :workout_offering_id, :integer create_table :coding_prompt_answers do |t| t.text :answer, null: false @@ -9,7 +9,7 @@ def change change_table :test_case_results do |t| t.belongs_to :coding_prompt_answer, required: true - t.index :coding_prompt_answer_id + # t.index :coding_prompt_answer_id end add_index :prompt_answers, :actable_id, unique: true diff --git a/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb b/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb index 5b39cfa8..dd7e775f 100644 --- a/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb +++ b/db/migrate/20150504002111_create_multiple_choice_prompt_answer.rb @@ -5,7 +5,9 @@ def change create_table :choices_multiple_choice_prompt_answers, id: false do |t| t.belongs_to :choice, required: true - t.belongs_to :multiple_choice_prompt_answer, required: true + t.belongs_to :multiple_choice_prompt_answer, required: true, index: { + name: 'prompt_answer_idx' + } t.index [:choice_id, :multiple_choice_prompt_answer_id], unique: true, name: 'choices_multiple_choice_prompt_answers_idx' diff --git a/db/migrate/20150504004247_create_irt_data.rb b/db/migrate/20150504004247_create_irt_data.rb index 42d5c4eb..03cc01ed 100644 --- a/db/migrate/20150504004247_create_irt_data.rb +++ b/db/migrate/20150504004247_create_irt_data.rb @@ -8,33 +8,33 @@ def change end change_table :exercises do |t| - t.remove :attempt_count, :integer - t.remove :correct_count, :float - t.remove :difficulty, :float - t.remove :discrimination, :float + t.remove :attempt_count + t.remove :correct_count + t.remove :difficulty + t.remove :discrimination t.belongs_to :irt_data - t.foreign_key :irt_datas + t.foreign_key :irt_data, column: :irt_data_id end change_table :exercise_versions do |t| - t.remove :attempt_count, :integer - t.remove :correct_count, :float - t.remove :difficulty, :float - t.remove :discrimination, :float + t.remove :attempt_count + t.remove :correct_count + t.remove :difficulty + t.remove :discrimination t.belongs_to :irt_data - t.foreign_key :irt_datas + t.foreign_key :irt_data, column: :irt_data_id end change_table :prompts do |t| - t.remove :attempt_count, :integer - t.remove :correct_count, :float - t.remove :difficulty, :float - t.remove :discrimination, :float + t.remove :attempt_count + t.remove :correct_count + t.remove :difficulty + t.remove :discrimination t.belongs_to :irt_data - t.foreign_key :irt_datas + t.foreign_key :irt_data, column: :irt_data_id end end end diff --git a/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb b/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb index 27b770f7..53ad813f 100644 --- a/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb +++ b/db/migrate/20150507164239_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb @@ -20,7 +20,7 @@ def self.up t.datetime :created_at end - add_index :taggings, :tag_id + # add_index :taggings, :tag_id add_index :taggings, [:taggable_id, :taggable_type, :context] end diff --git a/db/migrate/20150515205710_revise_test_case_structure.rb b/db/migrate/20150515205710_revise_test_case_structure.rb index b1ff1a11..8e6e5d70 100644 --- a/db/migrate/20150515205710_revise_test_case_structure.rb +++ b/db/migrate/20150515205710_revise_test_case_structure.rb @@ -1,9 +1,9 @@ class ReviseTestCaseStructure < ActiveRecord::Migration[5.1] def up change_table :test_cases do |t| - t.remove :test_script, :string - t.remove :input, :string - t.remove :expected_output, :string + t.remove :test_script + t.remove :input + t.remove :expected_output t.text :input t.text :expected_output @@ -16,8 +16,8 @@ def up def down change_table :test_cases do |t| - t.remove :input, :text - t.remove :expected_output, :text + t.remove :input + t.remove :expected_output t.string :test_script t.string :input diff --git a/db/migrate/20150916031620_add_current_workout_score_to_users.rb b/db/migrate/20150916031620_add_current_workout_score_to_users.rb index 4271cf23..3d737561 100644 --- a/db/migrate/20150916031620_add_current_workout_score_to_users.rb +++ b/db/migrate/20150916031620_add_current_workout_score_to_users.rb @@ -1,7 +1,7 @@ class AddCurrentWorkoutScoreToUsers < ActiveRecord::Migration[5.1] def change change_table :users do |t| - t.integer :current_workout_score_id + t.bigint :current_workout_score_id t.index :current_workout_score_id, unique: true t.foreign_key :workout_scores, column: :current_workout_score_id end diff --git a/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb b/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb index 08e26ea7..a893d2ac 100644 --- a/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb +++ b/db/migrate/20160225005739_add_continue_from_workout_id_to_workout_offerings.rb @@ -1,7 +1,7 @@ class AddContinueFromWorkoutIdToWorkoutOfferings < ActiveRecord::Migration[5.1] def change change_table :workout_offerings do |t| - t.integer :continue_from_workout_id + t.bigint :continue_from_workout_id t.foreign_key :workout_offerings, column: :continue_from_workout_id, index: true end diff --git a/db/migrate/20160512005525_add_keys.rb b/db/migrate/20160512005525_add_keys.rb index 2cb120ee..0d882f2e 100644 --- a/db/migrate/20160512005525_add_keys.rb +++ b/db/migrate/20160512005525_add_keys.rb @@ -19,6 +19,7 @@ def change add_foreign_key "courses", "organizations", name: "courses_organization_id_fk" add_foreign_key "exercise_owners", "exercises", name: "exercise_owners_exercise_id_fk" add_foreign_key "exercise_owners", "users", name: "exercise_owners_owner_id_fk", column: "owner_id" + change_column :exercise_versions, :creator_id, :bigint add_foreign_key "exercise_versions", "users", name: "exercise_versions_creator_id_fk", column: "creator_id" add_foreign_key "exercise_versions", "exercises", name: "exercise_versions_exercise_id_fk" add_foreign_key "exercise_versions", "irt_data", name: "exercise_versions_irt_data_id_fk", column: "irt_data_id" @@ -55,6 +56,7 @@ def change add_foreign_key "workout_scores", "users", name: "workout_scores_user_id_fk" add_foreign_key "workout_scores", "workouts", name: "workout_scores_workout_id_fk" add_foreign_key "workout_scores", "workout_offerings", name: "workout_scores_workout_offering_id_fk" + change_column :workouts, :creator_id, :bigint add_foreign_key "workouts", "users", name: "workouts_creator_id_fk", column: "creator_id" end end \ No newline at end of file diff --git a/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb b/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb index 33cecc60..0ce3bd4f 100644 --- a/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb +++ b/db/migrate/20160602182220_add_lms_type_id_to_lms_instance.rb @@ -1,6 +1,5 @@ class AddLmsTypeIdToLmsInstance < ActiveRecord::Migration[5.1] def change - add_column :lms_instances, :lms_type_id, :integer - add_foreign_key :lms_instances, :lms_types + add_reference :lms_instances, :lms_type, foreign_key: true end end diff --git a/db/migrate/20200929144816_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb b/db/migrate/20200929144816_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb index 94e1f2e4..667fe49f 100644 --- a/db/migrate/20200929144816_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb +++ b/db/migrate/20200929144816_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb @@ -1,10 +1,10 @@ # This migration comes from acts_as_taggable_on_engine (originally 6) -if ActiveRecord.gem_version >= Gem::Version.new('5.0') - class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end -else - class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end -end -AddMissingIndexesOnTaggings.class_eval do +# if ActiveRecord.gem_version >= Gem::Version.new('5.0') +# class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end +# else +# class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end +# end +class AddMissingIndexesOnTaggings < ActiveRecord::Migration[5.1] def change add_index :taggings, :tag_id unless index_exists? :taggings, :tag_id add_index :taggings, :taggable_id unless index_exists? :taggings, :taggable_id diff --git a/db/migrate/20201216191207_add_started_at_to_workout_scores.rb b/db/migrate/20201216191207_add_started_at_to_workout_scores.rb index edf3a598..94be72d6 100644 --- a/db/migrate/20201216191207_add_started_at_to_workout_scores.rb +++ b/db/migrate/20201216191207_add_started_at_to_workout_scores.rb @@ -1,4 +1,4 @@ -class AddStartedAtToWorkoutScores < ActiveRecord::Migration +class AddStartedAtToWorkoutScores < ActiveRecord::Migration[5.1] def change add_column :workout_scores, :started_at, :datetime diff --git a/db/migrate/20210201051224_add_hashval_to_resource_file.rb b/db/migrate/20210201051224_add_hashval_to_resource_file.rb index 79b40341..6e581af6 100644 --- a/db/migrate/20210201051224_add_hashval_to_resource_file.rb +++ b/db/migrate/20210201051224_add_hashval_to_resource_file.rb @@ -1,4 +1,4 @@ -class AddHashvalToResourceFile < ActiveRecord::Migration +class AddHashvalToResourceFile < ActiveRecord::Migration[5.1] def change add_column :resource_files, :hashval, :string end diff --git a/db/migrate/20210201051643_create_ownerships.rb b/db/migrate/20210201051643_create_ownerships.rb index c6b9e1b2..1fc76ac6 100644 --- a/db/migrate/20210201051643_create_ownerships.rb +++ b/db/migrate/20210201051643_create_ownerships.rb @@ -1,4 +1,4 @@ -class CreateOwnerships < ActiveRecord::Migration +class CreateOwnerships < ActiveRecord::Migration[5.1] def change create_table :ownerships do |t| t.string :filename diff --git a/db/migrate/20210410232421_add_indices_to_resource_files_and_ownerships.rb b/db/migrate/20210410232421_add_indices_to_resource_files_and_ownerships.rb index e1c7685d..6b20ee45 100644 --- a/db/migrate/20210410232421_add_indices_to_resource_files_and_ownerships.rb +++ b/db/migrate/20210410232421_add_indices_to_resource_files_and_ownerships.rb @@ -1,4 +1,4 @@ -class AddIndicesToResourceFilesAndOwnerships < ActiveRecord::Migration +class AddIndicesToResourceFilesAndOwnerships < ActiveRecord::Migration[5.1] def change add_index :resource_files, :hashval add_index :ownerships, :filename diff --git a/db/migrate/20211101005101_add_score_options_to_workout_policy.rb b/db/migrate/20211101005101_add_score_options_to_workout_policy.rb index 126c6dc0..34cf737c 100644 --- a/db/migrate/20211101005101_add_score_options_to_workout_policy.rb +++ b/db/migrate/20211101005101_add_score_options_to_workout_policy.rb @@ -1,4 +1,4 @@ -class AddScoreOptionsToWorkoutPolicy < ActiveRecord::Migration +class AddScoreOptionsToWorkoutPolicy < ActiveRecord::Migration[5.1] def change add_column :workout_policies, :hide_score_before_finish, :boolean add_column :workout_policies, :hide_score_in_review_before_close, :boolean diff --git a/db/schema.rb b/db/schema.rb index ff77cbb8..509ccb56 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -12,31 +12,31 @@ ActiveRecord::Schema.define(version: 2025_02_18_154318) do - create_table "active_admin_comments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "active_admin_comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "namespace" t.text "body" - t.string "resource_id", default: "", null: false - t.string "resource_type", default: "", null: false - t.integer "author_id" + t.string "resource_id", null: false + t.string "resource_type", null: false t.string "author_type" - t.datetime "created_at" - t.datetime "updated_at" + t.bigint "author_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id" t.index ["namespace"], name: "index_active_admin_comments_on_namespace" t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id" end - create_table "attempts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "exercise_version_id", null: false + create_table "attempts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "exercise_version_id", null: false t.datetime "submit_time", null: false t.integer "submit_num", null: false t.float "score", default: 0.0 t.integer "experience_earned" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "workout_score_id" - t.integer "active_score_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "workout_score_id" + t.bigint "active_score_id" t.boolean "feedback_ready" t.decimal "time_taken", precision: 10 t.decimal "feedback_timeout", precision: 10 @@ -49,40 +49,42 @@ t.index ["workout_score_id"], name: "index_attempts_on_workout_score_id" end - create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "attempt_id" - t.integer "tag_user_score_id" + create_table "attempts_tag_user_scores", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "attempt_id" + t.bigint "tag_user_score_id" t.index ["attempt_id", "tag_user_score_id"], name: "attempts_tag_user_scores_idx", unique: true - t.index ["tag_user_score_id"], name: "attempts_tag_user_scores_tag_user_score_id_fk" + t.index ["attempt_id"], name: "index_attempts_tag_user_scores_on_attempt_id" + t.index ["tag_user_score_id"], name: "index_attempts_tag_user_scores_on_tag_user_score_id" end - create_table "choices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "multiple_choice_prompt_id", null: false + create_table "choices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "multiple_choice_prompt_id", null: false t.integer "position", null: false t.text "feedback" t.float "value", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "answer", null: false t.index ["multiple_choice_prompt_id"], name: "index_choices_on_multiple_choice_prompt_id" end - create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "choice_id" - t.integer "multiple_choice_prompt_answer_id" + create_table "choices_multiple_choice_prompt_answers", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "choice_id" + t.bigint "multiple_choice_prompt_answer_id" t.index ["choice_id", "multiple_choice_prompt_answer_id"], name: "choices_multiple_choice_prompt_answers_idx", unique: true - t.index ["multiple_choice_prompt_answer_id"], name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk" + t.index ["choice_id"], name: "index_choices_multiple_choice_prompt_answers_on_choice_id" + t.index ["multiple_choice_prompt_answer_id"], name: "prompt_answer_idx" end - create_table "coding_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "coding_prompt_answers", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.text "answer" t.text "error" t.integer "error_line_no" end - create_table "coding_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.datetime "created_at" - t.datetime "updated_at" + create_table "coding_prompts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "class_name" t.text "wrapper_code", null: false t.text "test_script", null: false @@ -91,42 +93,47 @@ t.boolean "hide_examples", default: false, null: false end - create_table "course_enrollments", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "course_offering_id", null: false - t.integer "course_role_id", null: false + create_table "course_enrollments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id", null: false + t.bigint "course_offering_id", null: false + t.bigint "course_role_id", null: false t.index ["course_offering_id"], name: "index_course_enrollments_on_course_offering_id" t.index ["course_role_id"], name: "index_course_enrollments_on_course_role_id" t.index ["user_id", "course_offering_id"], name: "index_course_enrollments_on_user_id_and_course_offering_id", unique: true t.index ["user_id"], name: "index_course_enrollments_on_user_id" end - create_table "course_exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "course_id", null: false - t.integer "exercise_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + create_table "course_exercises", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "course_id", null: false + t.bigint "exercise_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["course_id"], name: "course_exercises_course_id_fk" t.index ["exercise_id"], name: "course_exercises_exercise_id_fk" end - create_table "course_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "course_id", null: false - t.integer "term_id", null: false - t.string "label", default: "", null: false + create_table "course_offerings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "course_id", null: false + t.bigint "term_id", null: false + t.string "label", null: false t.string "url" t.boolean "self_enrollment_allowed" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.date "cutoff_date" - t.integer "lms_instance_id" + t.bigint "lms_instance_id" t.index ["course_id"], name: "index_course_offerings_on_course_id" t.index ["lms_instance_id"], name: "index_course_offerings_on_lms_instance_id" t.index ["term_id"], name: "index_course_offerings_on_term_id" end - create_table "course_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false + create_table "course_offerings_workouts", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "course_offering_id", null: false + t.bigint "workout_id", null: false + end + + create_table "course_roles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false t.boolean "can_manage_course", default: false, null: false t.boolean "can_manage_assignments", default: false, null: false t.boolean "can_grade_submissions", default: false, null: false @@ -134,22 +141,22 @@ t.boolean "builtin", default: false, null: false end - create_table "courses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false - t.string "number", default: "", null: false - t.integer "organization_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + create_table "courses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false + t.string "number", null: false + t.bigint "organization_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "creator_id" - t.string "slug", default: "", null: false - t.integer "user_group_id" + t.string "slug", null: false + t.bigint "user_group_id" t.boolean "is_hidden", default: false t.index ["organization_id"], name: "index_courses_on_organization_id" t.index ["slug"], name: "index_courses_on_slug" t.index ["user_group_id"], name: "index_courses_on_user_group_id" end - create_table "errors", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "errors", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "usable_type" t.integer "usable_id" t.text "class_name" @@ -159,95 +166,109 @@ t.text "referrer" t.text "params" t.text "user_agent" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "status" - t.index ["class_name"], name: "index_errors_on_class_name", length: 1024 + t.index ["class_name"], name: "index_errors_on_class_name", length: 768 t.index ["created_at"], name: "index_errors_on_created_at" end - create_table "exercise_collections", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercise_collections", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "name" t.text "description" - t.integer "user_group_id" - t.integer "license_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "user_id" - t.integer "course_offering_id" + t.bigint "user_group_id" + t.bigint "license_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "user_id" + t.bigint "course_offering_id" t.index ["course_offering_id"], name: "index_exercise_collections_on_course_offering_id" t.index ["license_id"], name: "index_exercise_collections_on_license_id" t.index ["user_group_id"], name: "index_exercise_collections_on_user_group_id" t.index ["user_id"], name: "index_exercise_collections_on_user_id" end - create_table "exercise_families", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false - t.datetime "created_at" - t.datetime "updated_at" + create_table "exercise_families", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "exercise_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "exercise_id", null: false - t.integer "owner_id", null: false + create_table "exercise_owners", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "exercise_id", null: false + t.bigint "owner_id", null: false t.index ["exercise_id", "owner_id"], name: "index_exercise_owners_on_exercise_id_and_owner_id", unique: true - t.index ["owner_id"], name: "exercise_owners_owner_id_fk" + t.index ["exercise_id"], name: "index_exercise_owners_on_exercise_id" + t.index ["owner_id"], name: "index_exercise_owners_on_owner_id" end - create_table "exercise_versions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "stem_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "exercise_id", null: false + create_table "exercise_versions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "stem_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "exercise_id", null: false t.integer "version", null: false - t.integer "creator_id" - t.integer "irt_data_id" + t.bigint "creator_id" + t.bigint "irt_data_id" t.text "text_representation", limit: 16777215 t.index ["creator_id"], name: "exercise_versions_creator_id_fk" - t.index ["irt_data_id"], name: "exercise_versions_irt_data_id_fk" + t.index ["exercise_id"], name: "index_exercise_versions_on_exercise_id" + t.index ["irt_data_id"], name: "index_exercise_versions_on_irt_data_id" t.index ["stem_id"], name: "index_exercise_versions_on_stem_id" end - create_table "exercise_versions_resource_files", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "exercise_version_id", null: false - t.integer "resource_file_id", null: false + create_table "exercise_versions_resource_files", id: false, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "exercise_version_id", null: false + t.bigint "resource_file_id", null: false t.index ["exercise_version_id"], name: "index_exercise_versions_resource_files_on_exercise_version_id" t.index ["resource_file_id"], name: "index_exercise_versions_resource_files_on_resource_file_id" end - create_table "exercise_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "exercise_id", null: false - t.integer "workout_id", null: false + create_table "exercise_workouts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "exercise_id", null: false + t.bigint "workout_id", null: false t.integer "position", null: false t.float "points", default: 1.0 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["exercise_id"], name: "exercise_workouts_exercise_id_fk" t.index ["workout_id"], name: "exercise_workouts_workout_id_fk" end - create_table "exercises", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "exercises", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.integer "question_type", null: false - t.integer "current_version_id" - t.datetime "created_at" - t.datetime "updated_at" + t.bigint "current_version_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "versions" - t.integer "exercise_family_id" + t.bigint "exercise_family_id" t.string "name" t.boolean "is_public", default: false, null: false t.integer "experience", null: false - t.integer "irt_data_id" + t.bigint "irt_data_id" t.string "external_id" - t.integer "exercise_collection_id" + t.bigint "exercise_collection_id" + t.index ["current_version_id"], name: "index_exercises_on_current_version_id" t.index ["exercise_collection_id"], name: "index_exercises_on_exercise_collection_id" t.index ["exercise_family_id"], name: "index_exercises_on_exercise_family_id" t.index ["external_id"], name: "index_exercises_on_external_id", unique: true - t.index ["irt_data_id"], name: "exercises_irt_data_id_fk" + t.index ["irt_data_id"], name: "index_exercises_on_irt_data_id" t.index ["is_public"], name: "index_exercises_on_is_public" end - create_table "friendly_id_slugs", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "slug", default: "", null: false + create_table "exercises_workouts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "workout_id", null: false + t.bigint "exercise_id", null: false + t.integer "points" + t.integer "order" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["exercise_id"], name: "index_exercises_workouts_on_exercise_id" + t.index ["workout_id"], name: "index_exercises_workouts_on_workout_id" + end + + create_table "friendly_id_slugs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "slug", null: false t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 t.string "scope" @@ -258,218 +279,214 @@ t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" end - create_table "global_roles", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false + create_table "global_roles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false t.boolean "can_manage_all_courses", default: false, null: false t.boolean "can_edit_system_configuration", default: false, null: false t.boolean "builtin", default: false, null: false end - create_table "group_access_requests", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id" - t.integer "user_group_id" + create_table "group_access_requests", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id" + t.bigint "user_group_id" t.boolean "pending", default: true t.boolean "decision" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["user_group_id"], name: "index_group_access_requests_on_user_group_id" t.index ["user_id"], name: "index_group_access_requests_on_user_id" end - create_table "identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id", null: false - t.string "provider", default: "", null: false - t.string "uid", default: "", null: false - t.datetime "created_at" - t.datetime "updated_at" + create_table "identities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id", null: false + t.string "provider", null: false + t.string "uid", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["uid", "provider"], name: "index_identities_on_uid_and_provider" t.index ["user_id"], name: "index_identities_on_user_id" end - create_table "irt_data", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "irt_data", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.integer "attempt_count", null: false t.float "sum_of_scores", null: false t.float "difficulty", null: false t.float "discrimination", null: false end - create_table "license_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "license_policies", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "name" t.text "description" t.boolean "can_fork" t.boolean "is_public" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "licenses", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "licenses", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "name" t.text "description" t.string "url" - t.integer "license_policy_id" - t.datetime "created_at" - t.datetime "updated_at" + t.bigint "license_policy_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["license_policy_id"], name: "index_licenses_on_license_policy_id" end - create_table "lms_instances", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "lms_instances", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "consumer_key" t.string "consumer_secret" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "url" - t.integer "lms_type_id" - t.integer "organization_id" - t.index ["lms_type_id"], name: "lms_instances_lms_type_id_fk" + t.bigint "lms_type_id" + t.bigint "organization_id" + t.index ["lms_type_id"], name: "index_lms_instances_on_lms_type_id" t.index ["organization_id"], name: "index_lms_instances_on_organization_id" t.index ["url"], name: "index_lms_instances_on_url", unique: true end - create_table "lms_types", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "lms_types", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["name"], name: "index_lms_types_on_name", unique: true end - create_table "lti_identities", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "lti_identities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "lti_user_id" - t.integer "user_id" - t.integer "lms_instance_id" - t.datetime "created_at" - t.datetime "updated_at" + t.bigint "user_id" + t.bigint "lms_instance_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["lms_instance_id"], name: "index_lti_identities_on_lms_instance_id" t.index ["lti_user_id"], name: "index_lti_identities_on_lti_user_id" t.index ["user_id"], name: "index_lti_identities_on_user_id" end - create_table "lti_workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "workout_id" + create_table "lti_workouts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "workout_id" t.string "lms_assignment_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "lms_instance_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "lms_instance_id" t.index ["lms_instance_id"], name: "index_lti_workouts_on_lms_instance_id" t.index ["workout_id"], name: "index_lti_workouts_on_workout_id" end - create_table "memberships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "memberships", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.integer "user_id" t.integer "user_group_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "multiple_choice_prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "multiple_choice_prompt_answers", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| end - create_table "multiple_choice_prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "multiple_choice_prompts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.boolean "allow_multiple", default: false, null: false t.boolean "is_scrambled", default: true, null: false end - create_table "organizations", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false - t.datetime "created_at" - t.datetime "updated_at" + create_table "organizations", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "abbreviation" - t.string "slug", default: "", null: false + t.string "slug", null: false t.boolean "is_hidden", default: false + t.index ["name"], name: "index_organizations_on_name", unique: true t.index ["slug"], name: "index_organizations_on_slug", unique: true end - create_table "ownerships", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "ownerships", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "filename" - t.integer "resource_file_id" - t.integer "exercise_version_id" + t.bigint "resource_file_id" + t.bigint "exercise_version_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["exercise_version_id"], name: "index_ownerships_on_exercise_version_id" t.index ["filename"], name: "index_ownerships_on_filename" + t.index ["resource_file_id"], name: "index_ownerships_on_resource_file_id" end - create_table "prompt_answers", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "attempt_id" - t.integer "prompt_id" - t.integer "actable_id" + create_table "prompt_answers", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "attempt_id" + t.bigint "prompt_id" t.string "actable_type" + t.bigint "actable_id" t.index ["actable_id"], name: "index_prompt_answers_on_actable_id" + t.index ["actable_type", "actable_id"], name: "index_prompt_answers_on_actable_type_and_actable_id" t.index ["attempt_id", "prompt_id"], name: "index_prompt_answers_on_attempt_id_and_prompt_id", unique: true t.index ["attempt_id"], name: "index_prompt_answers_on_attempt_id" t.index ["prompt_id"], name: "index_prompt_answers_on_prompt_id" end - create_table "prompts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "exercise_version_id", null: false + create_table "prompts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "exercise_version_id", null: false t.text "question", null: false t.integer "position", null: false t.text "feedback" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "actable_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "actable_type" - t.integer "irt_data_id" + t.bigint "actable_id" + t.bigint "irt_data_id" t.index ["actable_id"], name: "index_prompts_on_actable_id" - t.index ["irt_data_id"], name: "prompts_irt_data_id_fk" + t.index ["actable_type", "actable_id"], name: "index_prompts_on_actable_type_and_actable_id" + t.index ["exercise_version_id"], name: "index_prompts_on_exercise_version_id" + t.index ["irt_data_id"], name: "index_prompts_on_irt_data_id" end - create_table "resource_files", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "resource_files", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "filename" - t.string "token", default: "", null: false - t.integer "user_id", null: false + t.string "token", null: false + t.bigint "user_id", null: false t.boolean "public", default: true - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "hashval" t.index ["hashval"], name: "index_resource_files_on_hashval" t.index ["token"], name: "index_resource_files_on_token" t.index ["user_id"], name: "index_resource_files_on_user_id" end - create_table "signups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "first_name" - t.string "last_name_name" - t.string "email" - t.string "institution" - t.text "comments" - t.datetime "created_at" - t.datetime "updated_at" - end - - create_table "stems", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "stems", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.text "preamble" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "student_extensions", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id" - t.integer "workout_offering_id" + create_table "student_extensions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id" + t.bigint "workout_offering_id" t.datetime "soft_deadline" t.datetime "hard_deadline" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "time_limit" t.datetime "opening_date" t.index ["user_id"], name: "index_student_extensions_on_user_id" t.index ["workout_offering_id"], name: "index_student_extensions_on_workout_offering_id" end - create_table "tag_user_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id", null: false + create_table "tag_user_scores", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id", null: false t.integer "experience", default: 0 - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "completed_exercises", default: 0 + t.index ["experience"], name: "index_tag_user_scores_on_experience" t.index ["user_id"], name: "index_tag_user_scores_on_user_id" end - create_table "taggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" + create_table "taggings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "tag_id" t.string "taggable_type" - t.integer "tagger_id" + t.bigint "taggable_id" t.string "tagger_type" + t.bigint "tagger_id" t.string "context", limit: 128 t.datetime "created_at" t.index ["context"], name: "index_taggings_on_context" @@ -478,74 +495,78 @@ t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy" t.index ["taggable_id"], name: "index_taggings_on_taggable_id" + t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable_type_and_taggable_id" t.index ["taggable_type"], name: "index_taggings_on_taggable_type" t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type" t.index ["tagger_id"], name: "index_taggings_on_tagger_id" + t.index ["tagger_type", "tagger_id"], name: "index_taggings_on_tagger_type_and_tagger_id" end - create_table "tags", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name" + create_table "tags", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", collation: "utf8mb3_bin" t.integer "taggings_count", default: 0 t.index ["name"], name: "index_tags_on_name", unique: true end - create_table "terms", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "terms", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.integer "season", null: false t.date "starts_on", null: false t.date "ends_on", null: false t.integer "year", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.string "slug", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "slug", null: false t.index ["slug"], name: "index_terms_on_slug", unique: true + t.index ["starts_on"], name: "index_terms_on_starts_on" t.index ["year", "season"], name: "index_terms_on_year_and_season" end - create_table "test_case_results", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "test_case_id", null: false - t.integer "user_id", null: false + create_table "test_case_results", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "test_case_id", null: false + t.bigint "user_id", null: false t.text "execution_feedback" t.integer "feedback_line_no" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "pass", null: false - t.integer "coding_prompt_answer_id" + t.bigint "coding_prompt_answer_id" t.index ["coding_prompt_answer_id"], name: "index_test_case_results_on_coding_prompt_answer_id" t.index ["test_case_id"], name: "index_test_case_results_on_test_case_id" t.index ["user_id"], name: "index_test_case_results_on_user_id" end - create_table "test_cases", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "test_cases", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.text "negative_feedback" t.float "weight", null: false t.text "description" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "coding_prompt_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "coding_prompt_id", null: false t.text "input", null: false t.text "expected_output", null: false t.boolean "static", default: false, null: false t.boolean "screening", default: false, null: false t.boolean "example", default: false, null: false t.boolean "hidden", default: false, null: false + t.index ["coding_prompt_id"], name: "index_test_cases_on_coding_prompt_id" end - create_table "time_zones", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "time_zones", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "name" t.string "zone" t.string "display_as" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - create_table "user_groups", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "user_groups", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "description" end - create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -559,15 +580,15 @@ t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "first_name" t.string "last_name" - t.integer "global_role_id", null: false + t.bigint "global_role_id", null: false t.string "avatar" - t.string "slug", default: "", null: false - t.integer "current_workout_score_id" - t.integer "time_zone_id" + t.string "slug", null: false + t.bigint "time_zone_id" + t.bigint "current_workout_score_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["current_workout_score_id"], name: "index_users_on_current_workout_score_id", unique: true t.index ["email"], name: "index_users_on_email", unique: true @@ -577,31 +598,31 @@ t.index ["time_zone_id"], name: "index_users_on_time_zone_id" end - create_table "visualization_loggings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "user_id" - t.integer "exercise_id" - t.integer "workout_id" - t.integer "workout_offering_id" - t.datetime "created_at" - t.datetime "updated_at" + create_table "visualization_loggings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "user_id" + t.bigint "exercise_id" + t.bigint "workout_id" + t.bigint "workout_offering_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.index ["exercise_id"], name: "index_visualization_loggings_on_exercise_id" t.index ["user_id"], name: "index_visualization_loggings_on_user_id" t.index ["workout_id"], name: "index_visualization_loggings_on_workout_id" t.index ["workout_offering_id"], name: "index_visualization_loggings_on_workout_offering_id" end - create_table "workout_offerings", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "course_offering_id", null: false - t.integer "workout_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + create_table "workout_offerings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "course_offering_id", null: false + t.bigint "workout_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.datetime "opening_date" t.datetime "soft_deadline" t.datetime "hard_deadline" t.boolean "published", default: true, null: false t.integer "time_limit" - t.integer "workout_policy_id" - t.integer "continue_from_workout_id" + t.bigint "workout_policy_id" + t.bigint "continue_from_workout_id" t.string "lms_assignment_id" t.boolean "most_recent", default: true t.string "lms_assignment_url" @@ -613,14 +634,15 @@ t.index ["workout_policy_id"], name: "index_workout_offerings_on_workout_policy_id" end - create_table "workout_owners", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "workout_id", null: false - t.integer "owner_id", null: false + create_table "workout_owners", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "workout_id", null: false + t.bigint "owner_id", null: false t.index ["owner_id"], name: "workout_owners_owner_id_fk" t.index ["workout_id", "owner_id"], name: "index_workout_owners_on_workout_id_and_owner_id", unique: true + t.index ["workout_id"], name: "index_workout_owners_on_workout_id" end - create_table "workout_policies", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| + create_table "workout_policies", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| t.boolean "hide_thumbnails_before_start" t.boolean "hide_feedback_before_finish" t.boolean "hide_compilation_feedback_before_finish" @@ -630,45 +652,45 @@ t.boolean "no_hints" t.boolean "no_faq" t.string "name" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.boolean "invisible_before_review" t.string "description" t.boolean "hide_score_before_finish" t.boolean "hide_score_in_review_before_close" end - create_table "workout_scores", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.integer "workout_id", null: false - t.integer "user_id", null: false + create_table "workout_scores", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.bigint "workout_id", null: false + t.bigint "user_id", null: false t.float "score" t.boolean "completed" - t.datetime "completed_at" - t.datetime "last_attempted_at" + t.timestamp "completed_at" + t.timestamp "last_attempted_at" t.integer "exercises_completed" t.integer "exercises_remaining" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "workout_offering_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "workout_offering_id" t.string "lis_outcome_service_url" t.string "lis_result_sourcedid" - t.integer "lti_workout_id" + t.bigint "lti_workout_id" t.datetime "started_at" t.index ["lti_workout_id"], name: "index_workout_scores_on_lti_workout_id" t.index ["user_id", "workout_id", "workout_offering_id"], name: "idx_ws_on_user_workout_workout_offering" t.index ["user_id"], name: "index_workout_scores_on_user_id" t.index ["workout_id"], name: "index_workout_scores_on_workout_id" - t.index ["workout_offering_id"], name: "workout_scores_workout_offering_id_fk" + t.index ["workout_offering_id"], name: "index_workout_scores_on_workout_offering_id" end - create_table "workouts", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci", force: :cascade do |t| - t.string "name", default: "", null: false + create_table "workouts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci", force: :cascade do |t| + t.string "name", null: false t.boolean "scrambled", default: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.text "description" t.integer "points_multiplier" - t.integer "creator_id" + t.bigint "creator_id" t.string "external_id" t.boolean "is_public" t.index ["creator_id"], name: "workouts_creator_id_fk" @@ -676,59 +698,115 @@ t.index ["is_public"], name: "index_workouts_on_is_public" end + add_foreign_key "attempts", "exercise_versions" add_foreign_key "attempts", "exercise_versions", name: "attempts_exercise_version_id_fk" + add_foreign_key "attempts", "users" add_foreign_key "attempts", "users", name: "attempts_user_id_fk" add_foreign_key "attempts", "workout_scores", column: "active_score_id", name: "attempts_active_score_id_fk" add_foreign_key "attempts", "workout_scores", name: "attempts_workout_score_id_fk" add_foreign_key "attempts_tag_user_scores", "attempts", name: "attempts_tag_user_scores_attempt_id_fk" add_foreign_key "attempts_tag_user_scores", "tag_user_scores", name: "attempts_tag_user_scores_tag_user_score_id_fk" + add_foreign_key "choices", "multiple_choice_prompts" add_foreign_key "choices", "multiple_choice_prompts", name: "choices_multiple_choice_prompt_id_fk" add_foreign_key "choices_multiple_choice_prompt_answers", "choices", name: "choices_multiple_choice_prompt_answers_choice_id_fk" add_foreign_key "choices_multiple_choice_prompt_answers", "multiple_choice_prompt_answers", name: "choices_MC_prompt_answers_MC_prompt_answer_id_fk" + add_foreign_key "course_enrollments", "course_offerings" add_foreign_key "course_enrollments", "course_offerings", name: "course_enrollments_course_offering_id_fk" + add_foreign_key "course_enrollments", "course_roles" add_foreign_key "course_enrollments", "course_roles", name: "course_enrollments_course_role_id_fk" + add_foreign_key "course_enrollments", "users" add_foreign_key "course_enrollments", "users", name: "course_enrollments_user_id_fk" + add_foreign_key "course_exercises", "courses" add_foreign_key "course_exercises", "courses", name: "course_exercises_course_id_fk" + add_foreign_key "course_exercises", "exercise_versions", column: "exercise_id" add_foreign_key "course_exercises", "exercises", name: "course_exercises_exercise_id_fk" + add_foreign_key "course_offerings", "courses" add_foreign_key "course_offerings", "courses", name: "course_offerings_course_id_fk" + add_foreign_key "course_offerings", "terms" add_foreign_key "course_offerings", "terms", name: "course_offerings_term_id_fk" + add_foreign_key "courses", "organizations" add_foreign_key "courses", "organizations", name: "courses_organization_id_fk" + add_foreign_key "courses", "user_groups" + add_foreign_key "exercise_collections", "course_offerings" + add_foreign_key "exercise_collections", "licenses" + add_foreign_key "exercise_collections", "user_groups" + add_foreign_key "exercise_collections", "users" + add_foreign_key "exercise_owners", "exercises" + add_foreign_key "exercise_owners", "exercises", name: "exercise_owners_exercise_id_fk" + add_foreign_key "exercise_owners", "users", column: "owner_id" add_foreign_key "exercise_owners", "users", column: "owner_id", name: "exercise_owners_owner_id_fk" + add_foreign_key "exercise_versions", "exercises" + add_foreign_key "exercise_versions", "exercises", name: "exercise_versions_exercise_id_fk" + add_foreign_key "exercise_versions", "irt_data", column: "irt_data_id" add_foreign_key "exercise_versions", "irt_data", column: "irt_data_id", name: "exercise_versions_irt_data_id_fk" + add_foreign_key "exercise_versions", "stems" add_foreign_key "exercise_versions", "stems", name: "exercise_versions_stem_id_fk" add_foreign_key "exercise_versions", "users", column: "creator_id", name: "exercise_versions_creator_id_fk" + add_foreign_key "exercise_versions_resource_files", "exercise_versions" add_foreign_key "exercise_versions_resource_files", "exercise_versions", name: "exercise_versions_resource_files_exercise_version_id_fk" + add_foreign_key "exercise_versions_resource_files", "resource_files" add_foreign_key "exercise_versions_resource_files", "resource_files", name: "exercise_versions_resource_files_resource_file_id_fk" + add_foreign_key "exercise_workouts", "exercise_versions", column: "exercise_id" add_foreign_key "exercise_workouts", "exercises", name: "exercise_workouts_exercise_id_fk" + add_foreign_key "exercise_workouts", "workouts" add_foreign_key "exercise_workouts", "workouts", name: "exercise_workouts_workout_id_fk" + add_foreign_key "exercises", "exercise_collections" + add_foreign_key "exercises", "exercise_families" add_foreign_key "exercises", "exercise_families", name: "exercises_exercise_family_id_fk" + add_foreign_key "exercises", "exercise_versions", column: "current_version_id" + add_foreign_key "exercises", "exercise_versions", column: "current_version_id", name: "exercises_current_version_id_fk" + add_foreign_key "exercises", "irt_data", column: "irt_data_id" add_foreign_key "exercises", "irt_data", column: "irt_data_id", name: "exercises_irt_data_id_fk" + add_foreign_key "group_access_requests", "user_groups" + add_foreign_key "group_access_requests", "users" + add_foreign_key "identities", "users" add_foreign_key "identities", "users", name: "identities_user_id_fk" - add_foreign_key "lms_instances", "lms_types", name: "lms_instances_lms_type_id_fk" + add_foreign_key "licenses", "license_policies" + add_foreign_key "lms_instances", "lms_types" add_foreign_key "lti_workouts", "lms_instances" add_foreign_key "ownerships", "exercise_versions" + add_foreign_key "ownerships", "resource_files" add_foreign_key "prompt_answers", "attempts", name: "prompt_answers_attempt_id_fk" add_foreign_key "prompt_answers", "prompts", name: "prompt_answers_prompt_id_fk" + add_foreign_key "prompts", "exercise_versions" + add_foreign_key "prompts", "exercise_versions", name: "prompts_exercise_version_id_fk" + add_foreign_key "prompts", "irt_data", column: "irt_data_id" add_foreign_key "prompts", "irt_data", column: "irt_data_id", name: "prompts_irt_data_id_fk" + add_foreign_key "resource_files", "users" add_foreign_key "resource_files", "users", name: "resource_files_user_id_fk" add_foreign_key "student_extensions", "users", name: "student_extensions_user_id_fk" add_foreign_key "student_extensions", "workout_offerings", name: "student_extensions_workout_offering_id_fk" + add_foreign_key "tag_user_scores", "users" add_foreign_key "tag_user_scores", "users", name: "tag_user_scores_user_id_fk" add_foreign_key "test_case_results", "coding_prompt_answers", name: "test_case_results_coding_prompt_answer_id_fk" + add_foreign_key "test_case_results", "test_cases" add_foreign_key "test_case_results", "test_cases", name: "test_case_results_test_case_id_fk" + add_foreign_key "test_case_results", "users" add_foreign_key "test_case_results", "users", name: "test_case_results_user_id_fk" + add_foreign_key "test_cases", "coding_prompts" + add_foreign_key "test_cases", "coding_prompts", name: "test_cases_coding_prompt_id_fk" + add_foreign_key "users", "global_roles" add_foreign_key "users", "global_roles", name: "users_global_role_id_fk" add_foreign_key "users", "time_zones", name: "users_time_zone_id_fk" + add_foreign_key "users", "workout_scores", column: "current_workout_score_id" add_foreign_key "users", "workout_scores", column: "current_workout_score_id", name: "users_current_workout_score_id_fk" + add_foreign_key "workout_offerings", "course_offerings" add_foreign_key "workout_offerings", "course_offerings", name: "workout_offerings_course_offering_id_fk" + add_foreign_key "workout_offerings", "workout_offerings", column: "continue_from_workout_id" add_foreign_key "workout_offerings", "workout_offerings", column: "continue_from_workout_id", name: "workout_offerings_continue_from_workout_id_fk" add_foreign_key "workout_offerings", "workout_policies", name: "workout_offerings_workout_policy_id_fk" + add_foreign_key "workout_offerings", "workouts" add_foreign_key "workout_offerings", "workouts", name: "workout_offerings_workout_id_fk" + add_foreign_key "workout_owners", "users", column: "owner_id" add_foreign_key "workout_owners", "users", column: "owner_id", name: "workout_owners_owner_id_fk" + add_foreign_key "workout_owners", "workouts" add_foreign_key "workout_owners", "workouts", name: "workout_owners_workout_id_fk" add_foreign_key "workout_scores", "lti_workouts" + add_foreign_key "workout_scores", "users" add_foreign_key "workout_scores", "users", name: "workout_scores_user_id_fk" + add_foreign_key "workout_scores", "workout_offerings" add_foreign_key "workout_scores", "workout_offerings", name: "workout_scores_workout_offering_id_fk" + add_foreign_key "workout_scores", "workouts" add_foreign_key "workout_scores", "workouts", name: "workout_scores_workout_id_fk" add_foreign_key "workouts", "users", column: "creator_id", name: "workouts_creator_id_fk" end diff --git a/spec/factories/exercises.rb b/spec/factories/exercises.rb index e7d40a05..c8bf47e3 100644 --- a/spec/factories/exercises.rb +++ b/spec/factories/exercises.rb @@ -46,7 +46,7 @@ factory :coding_exercise do transient do - creator_id { 1 } + creator { FactoryBot.create :user } question { "Write a function in Java called `factorial()` that will "\ "take a\npositive integer as input and returns its factorial as "\ "output.\n" } @@ -79,7 +79,7 @@ after :create do |e, v| e.current_version = FactoryBot.create :exercise_version, exercise: e, - creator_id: v.creator_id + creator: v.creator e.exercise_versions << e.current_version FactoryBot.create :coding_prompt, exercise_version: e.current_version, @@ -96,7 +96,7 @@ factory :mc_exercise do transient do - creator_id { 2 } + creator { FactoryBot.create :user } question { "This is a sample multiple choice question. It has only "\ "one correct answer.\n" } feedback { "Explanation for the correct answer goes here. This is "\ @@ -111,7 +111,7 @@ after :create do |e, v| e.current_version = FactoryBot.create :exercise_version, exercise: e, - creator_id: v.creator_id + creator: v.creator e.exercise_versions << e.current_version FactoryBot.create :mc_with_choices, exercise_version: e.current_version, diff --git a/spec/models/exercise_spec.rb b/spec/models/exercise_spec.rb index 2fab7487..380da4b5 100644 --- a/spec/models/exercise_spec.rb +++ b/spec/models/exercise_spec.rb @@ -38,6 +38,32 @@ @user = FactoryBot.build :confirmed_user end + context 'SLC items catalog generation' do + it 'generates a valid JSON catalog for public exercises' do + # Create a public exercise using the factory which sets tags automatically + ex = FactoryBot.create :coding_exercise, is_public: true, name: 'Test Exercise', external_id: 'test-123' + + filename = Rails.root.join('tmp', 'slc_catalog.json') + Exercise.generate_slc_catalog(filename) + + expect(File.exist?(filename)).to be_truthy + catalog = JSON.parse(File.read(filename)) + expect(catalog).to be_an(Array) + + item = catalog.find { |i| i['persistentID'] == 'test-123' } + expect(item).not_to be_nil + expect(item['catalog_type']).to eq('SLCItem') + expect(item['title']).to eq('Test Exercise') + expect(item['description']).to eq('Coding question') + # The coding_exercise factory provides these tags: factorial, function, multiplication + expect(item['keywords']).to include('factorial', 'function', 'multiplication') + expect(item['iframe_url']).to eq("https://codeworkout.cs.vt.edu/gym/exercises/#{ex.id}/practice") + expect(item['institution']).to eq(["Virginia Tech"]) + + File.delete(filename) if File.exist?(filename) + end + end + context 'creator edit permissions' do it 'should not be editable by the exercise creator' do ex = FactoryBot.build :mc_exercise From 11223c3b9da547337421bc49fe58e3390f7905ed Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 24 Feb 2026 11:40:04 -0500 Subject: [PATCH 057/117] fix: Remove duplicate CreateErrors migration (already in gem) --- db/migrate/20150331153411_create_errors.rb | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 db/migrate/20150331153411_create_errors.rb diff --git a/db/migrate/20150331153411_create_errors.rb b/db/migrate/20150331153411_create_errors.rb deleted file mode 100644 index 474be886..00000000 --- a/db/migrate/20150331153411_create_errors.rb +++ /dev/null @@ -1,18 +0,0 @@ -class CreateErrors < ActiveRecord::Migration[5.1] - def change - create_table :errors do |t| - t.string :usable_type - t.integer :usable_id - t.text :class_name - t.text :message - t.text :trace - t.text :target_url - t.text :referer_url - t.text :params - t.text :user_agent - t.timestamps - end - add_index :errors, :class_name - add_index :errors, :created_at - end -end From bd2fc9a5196b24849a110d49156996bde5a031a0 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 24 Feb 2026 11:44:44 -0500 Subject: [PATCH 058/117] fix: Add blank yarn.lock for cap deploy --- yarn.lock | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 yarn.lock diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..c37fc613 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + From 4e35ee7a9bfb6d77c9aa0bdf46d2cec75fcc308f Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 24 Feb 2026 12:43:38 -0500 Subject: [PATCH 059/117] fix: Add lti_launch to SLC catalog iframe URLs --- app/models/exercise.rb | 6 ++++-- spec/models/exercise_spec.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 13cead5d..298db36c 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -343,7 +343,7 @@ def is_publicly_available? end end - def self.generate_slc_catalog(filename) + def self.generate_slc_catalog(filename, base_url="https://codeworkout.cs.vt.edu") exercises = Exercise.publicly_visible catalog = exercises.map do |exercise| @@ -362,7 +362,9 @@ def self.generate_slc_catalog(filename) catalog_type: "SLCItem", persistentID: exercise.external_id.to_s, platform_name: "CodeWorkout", - iframe_url: "https://codeworkout.cs.vt.edu/gym/exercises/#{exercise.id}/practice", + iframe_url: base_url + + Rails.application.routes.url_helpers.exercise_practice_path(exercise) + + "?lti_launch=true", title: exercise.name.to_s, description: description, author: exercise.owners.map(&:display_name), diff --git a/spec/models/exercise_spec.rb b/spec/models/exercise_spec.rb index 380da4b5..b51312bb 100644 --- a/spec/models/exercise_spec.rb +++ b/spec/models/exercise_spec.rb @@ -57,7 +57,7 @@ expect(item['description']).to eq('Coding question') # The coding_exercise factory provides these tags: factorial, function, multiplication expect(item['keywords']).to include('factorial', 'function', 'multiplication') - expect(item['iframe_url']).to eq("https://codeworkout.cs.vt.edu/gym/exercises/#{ex.id}/practice") + expect(item['iframe_url']).to eq("https://codeworkout.cs.vt.edu/gym/exercises/#{ex.id}/practice?lti_launch=true") expect(item['institution']).to eq(["Virginia Tech"]) File.delete(filename) if File.exist?(filename) From ce37f2470d687e67c775f249817c51d93cc23984 Mon Sep 17 00:00:00 2001 From: s-edwards Date: Tue, 24 Feb 2026 16:21:45 -0500 Subject: [PATCH 060/117] fix: SPLICE SLC Item catalog generation formatting --- app/models/exercise.rb | 3 ++- app/models/user.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 298db36c..f7339d1e 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -367,7 +367,8 @@ def self.generate_slc_catalog(filename, base_url="https://codeworkout.cs.vt.edu" "?lti_launch=true", title: exercise.name.to_s, description: description, - author: exercise.owners.map(&:display_name), + author: exercise.owners.empty? ? [exercise.current_version&.creator&.display_name_with_email].compact : exercise.owners.map(&:display_name_with_email), + features: exercise.question_type == Q_CODING ? ["Free Coding Problem"] : ["Question"], institution: ["Virginia Tech"], keywords: exercise.tags.map(&:name), programming_language: [exercise.language].compact, diff --git a/app/models/user.rb b/app/models/user.rb index cd04faf6..d582c6c9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -353,6 +353,16 @@ def display_name end + # ------------------------------------------------------------- + # Gets the user's "display name", which is their full name if it is in the + # database, otherwise it is their e-mail address. + def display_name_with_email + last_name.blank? ? + (first_name.blank? ? email : first_name + ' <' + email + '>') : + (first_name.blank? ? last_name + ' <' + email + '>' : (first_name + ' ' + last_name + ' <' + email + '>')) + end + + # ------------------------------------------------------------- # Gets the user's "label name", which is their last name, first name, or # email_without_domain, in decreasing order of preference. For use in From d75e2d99bd17e72f0f595135a65135818a47dd62 Mon Sep 17 00:00:00 2001 From: Bob Edmison Date: Wed, 25 Feb 2026 14:10:27 +0100 Subject: [PATCH 061/117] Added lang param to HTML tags --- app/assets/javascripts/exercises.js | 2 +- app/views/layouts/header.html.haml | 4 ++-- public/404.html | 2 +- public/422.html | 2 +- public/500.html | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/exercises.js b/app/assets/javascripts/exercises.js index 4536b8d8..2c195fe5 100644 --- a/app/assets/javascripts/exercises.js +++ b/app/assets/javascripts/exercises.js @@ -44,7 +44,7 @@ }; function doModal2(heading, studentCode, exercise_trace) { - var html = "\n" + "\