diff --git a/book/code_smells/duplicated_code.md b/book/code_smells/duplicated_code.md index d135ece..43e78f8 100644 --- a/book/code_smells/duplicated_code.md +++ b/book/code_smells/duplicated_code.md @@ -37,7 +37,7 @@ def update question_params = params. require(:question). permit(:title, :options_attributes, :minimum, :maximum) - @question.update_attributes(question_params) + @question.update(question_params) if @question.save redirect_to @question.survey diff --git a/book/solutions/extract_validator.md b/book/solutions/extract_validator.md index 419af27..949c104 100644 --- a/book/solutions/extract_validator.md +++ b/book/solutions/extract_validator.md @@ -58,6 +58,10 @@ is validated against it. ` app/validators/enumerable_validator.rb@21f7a57 +Please note that in the latest version of the example application the +`EmailValidator` class was renamed to `EmailAddressValidator` to avoid a naming +conflict with an external gem. + ### Next Steps * Verify the extracted validator does not have any [long methods](#long-methods). diff --git a/book/solutions/replace_conditional_with_polymorphism.md b/book/solutions/replace_conditional_with_polymorphism.md index 36c2f4e..91c8872 100644 --- a/book/solutions/replace_conditional_with_polymorphism.md +++ b/book/solutions/replace_conditional_with_polymorphism.md @@ -153,7 +153,7 @@ to the appropriate class. First, let's move the method class MultipleChoiceQuestion < Question def summary total = answers.count - counts = answers.group(:text).order('COUNT(*) DESC').count + counts = answers.group(:text).order(Arel.sql('COUNT(*) DESC')).count percents = counts.map do |text, count| percent = (100.0 * count / total).round "#{percent}% #{text}" diff --git a/example_app/.gitignore b/example_app/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/example_app/.ruby-version b/example_app/.ruby-version deleted file mode 100644 index c031dda..0000000 --- a/example_app/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.0.0-p0 diff --git a/example_app/.tool-versions b/example_app/.tool-versions new file mode 100644 index 0000000..f2a971a --- /dev/null +++ b/example_app/.tool-versions @@ -0,0 +1 @@ +ruby 3.2.2 diff --git a/example_app/Gemfile b/example_app/Gemfile index 41c57f7..de5ca3d 100644 --- a/example_app/Gemfile +++ b/example_app/Gemfile @@ -1,29 +1,29 @@ source 'https://rubygems.org' -gem 'rails', '3.2.8' +ruby '3.2.2' + +gem 'rails', '7.0.8' gem 'clearance' -gem 'jquery-rails' gem 'simple_form' -gem 'strong_parameters' gem 'sqlite3' -gem 'thin' - -group :assets do - gem 'coffee-rails', '~> 3.2' - gem 'sass-rails', '~> 3.2' - gem 'uglifier', '>= 1.0' -end +gem 'sprockets-rails' +gem 'puma' +gem 'importmap-rails' +gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] +gem 'bootsnap', require: false group :development, :test do - gem 'bourne' + gem 'bourne', '1.3.0' gem 'foreman' gem 'rspec-rails' + gem 'rails-dom-testing' + gem 'listen' end group :test do gem 'capybara' gem 'email_spec' - gem 'factory_girl_rails' + gem 'factory_bot_rails' gem 'shoulda-matchers' end diff --git a/example_app/Gemfile.lock b/example_app/Gemfile.lock index 3cd0f80..bca0f45 100644 --- a/example_app/Gemfile.lock +++ b/example_app/Gemfile.lock @@ -1,194 +1,271 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.8) - actionpack (= 3.2.8) - mail (~> 2.4.4) - actionpack (3.2.8) - activemodel (= 3.2.8) - activesupport (= 3.2.8) - builder (~> 3.0.0) - erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.0) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.1.3) - activemodel (3.2.8) - activesupport (= 3.2.8) - builder (~> 3.0.0) - activerecord (3.2.8) - activemodel (= 3.2.8) - activesupport (= 3.2.8) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.8) - activemodel (= 3.2.8) - activesupport (= 3.2.8) - activesupport (3.2.8) - i18n (~> 0.6) - multi_json (~> 1.0) - addressable (2.3.2) - arel (3.0.2) - bourne (1.2.1) - mocha (= 0.12.7) - builder (3.0.4) - capybara (2.0.1) - mime-types (>= 1.16) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - selenium-webdriver (~> 2.0) - xpath (~> 1.0.0) - childprocess (0.3.6) - ffi (~> 1.0, >= 1.0.6) - clearance (0.16.3) - diesel (~> 0.1.5) - rails (>= 3.0) - coffee-rails (3.2.2) - coffee-script (>= 2.2.0) - railties (~> 3.2.0) - coffee-script (2.2.0) - coffee-script-source - execjs - coffee-script-source (1.4.0) - daemons (1.1.9) - diesel (0.1.5) - railties - diff-lcs (1.1.3) - email_spec (1.4.0) + actioncable (7.0.8) + actionpack (= 7.0.8) + activesupport (= 7.0.8) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (7.0.8) + actionpack (= 7.0.8) + activejob (= 7.0.8) + activerecord (= 7.0.8) + activestorage (= 7.0.8) + activesupport (= 7.0.8) + mail (>= 2.7.1) + net-imap + net-pop + net-smtp + actionmailer (7.0.8) + actionpack (= 7.0.8) + actionview (= 7.0.8) + activejob (= 7.0.8) + activesupport (= 7.0.8) + mail (~> 2.5, >= 2.5.4) + net-imap + net-pop + net-smtp + rails-dom-testing (~> 2.0) + actionpack (7.0.8) + actionview (= 7.0.8) + activesupport (= 7.0.8) + rack (~> 2.0, >= 2.2.4) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (7.0.8) + actionpack (= 7.0.8) + activerecord (= 7.0.8) + activestorage (= 7.0.8) + activesupport (= 7.0.8) + globalid (>= 0.6.0) + nokogiri (>= 1.8.5) + actionview (7.0.8) + activesupport (= 7.0.8) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (7.0.8) + activesupport (= 7.0.8) + globalid (>= 0.3.6) + activemodel (7.0.8) + activesupport (= 7.0.8) + activerecord (7.0.8) + activemodel (= 7.0.8) + activesupport (= 7.0.8) + activestorage (7.0.8) + actionpack (= 7.0.8) + activejob (= 7.0.8) + activerecord (= 7.0.8) + activesupport (= 7.0.8) + marcel (~> 1.0) + mini_mime (>= 1.1.0) + activesupport (7.0.8) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) + argon2 (2.3.0) + ffi (~> 1.15) + ffi-compiler (~> 1.0) + bcrypt (3.1.19) + bootsnap (1.13.0) + msgpack (~> 1.2) + bourne (1.3.0) + mocha (= 0.13.0) + builder (3.2.4) + capybara (3.39.2) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + clearance (2.6.1) + actionmailer (>= 5.0) + activemodel (>= 5.0) + activerecord (>= 5.0) + argon2 (~> 2.0, >= 2.0.2) + bcrypt (>= 3.1.1) + email_validator (~> 2.0) + railties (>= 5.0) + concurrent-ruby (1.2.2) + crass (1.0.6) + date (3.3.3) + diff-lcs (1.5.0) + email_spec (2.2.2) + htmlentities (~> 4.3.3) launchy (~> 2.1) - mail (~> 2.2) - erubis (2.7.0) - eventmachine (1.0.3) - execjs (1.4.0) - multi_json (~> 1.0) - factory_girl (4.1.0) - activesupport (>= 3.0.0) - factory_girl_rails (4.1.0) - factory_girl (~> 4.1.0) - railties (>= 3.0.0) - ffi (1.2.0) + mail (~> 2.7) + email_validator (2.2.4) + activemodel + erubi (1.12.0) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + ffi (1.16.3) + ffi-compiler (1.0.1) + ffi (>= 1.0.0) + rake foreman (0.62.0) thor (>= 0.13.6) - hike (1.2.1) - i18n (0.6.1) - journey (1.0.4) - jquery-rails (2.1.3) - railties (>= 3.1.0, < 5.0) - thor (~> 0.14) - json (1.7.5) - launchy (2.1.2) - addressable (~> 2.3) - libwebsocket (0.1.6.1) - websocket - mail (2.4.4) - i18n (>= 0.4.0) - mime-types (~> 1.16) - treetop (~> 1.4.8) - metaclass (0.0.1) - mime-types (1.19) - mocha (0.12.7) + globalid (1.2.1) + activesupport (>= 6.1) + htmlentities (4.3.4) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + importmap-rails (1.2.1) + actionpack (>= 6.0.0) + railties (>= 6.0.0) + launchy (2.5.2) + addressable (~> 2.8) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.19.1) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.8.1) + mini_mime (>= 0.1.1) + net-imap + net-pop + net-smtp + marcel (1.0.2) + matrix (0.4.2) + metaclass (0.0.4) + method_source (1.0.0) + mini_mime (1.1.5) + mini_portile2 (2.8.4) + minitest (5.20.0) + mocha (0.13.0) metaclass (~> 0.0.1) - multi_json (1.3.7) - nokogiri (1.5.5) - polyglot (0.3.3) - rack (1.4.1) - rack-cache (1.2) - rack (>= 0.4) - rack-ssl (1.3.2) - rack - rack-test (0.6.2) - rack (>= 1.0) - rails (3.2.8) - actionmailer (= 3.2.8) - actionpack (= 3.2.8) - activerecord (= 3.2.8) - activeresource (= 3.2.8) - activesupport (= 3.2.8) - bundler (~> 1.0) - railties (= 3.2.8) - railties (3.2.8) - actionpack (= 3.2.8) - activesupport (= 3.2.8) - rack-ssl (~> 1.3.2) - rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.0.2) - rdoc (3.12) - json (~> 1.4) - rspec-core (2.12.0) - rspec-expectations (2.12.0) - diff-lcs (~> 1.1.3) - rspec-mocks (2.12.0) - rspec-rails (2.12.0) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 2.12.0) - rspec-expectations (~> 2.12.0) - rspec-mocks (~> 2.12.0) - rubyzip (0.9.9) - sass (3.2.3) - sass-rails (3.2.5) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) - selenium-webdriver (2.26.0) - childprocess (>= 0.2.5) - libwebsocket (~> 0.1.3) - multi_json (~> 1.0) - rubyzip - shoulda-matchers (1.4.1) - activesupport (>= 3.0.0) - simple_form (2.0.4) - actionpack (~> 3.0) - activemodel (~> 3.0) - sprockets (2.1.3) - hike (~> 1.2) - rack (~> 1.0) - tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.6) - strong_parameters (0.1.5) - actionpack (~> 3.1) - activemodel (~> 3.1) - railties (~> 3.1) - thin (1.5.0) - daemons (>= 1.0.9) - eventmachine (>= 0.12.6) - rack (>= 1.0.0) - thor (0.16.0) - tilt (1.3.3) - treetop (1.4.12) - polyglot - polyglot (>= 0.3.1) - tzinfo (0.3.35) - uglifier (1.3.0) - execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - websocket (1.0.4) - xpath (1.0.0) - nokogiri (~> 1.3) + msgpack (1.7.2) + net-imap (0.4.0) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.1) + timeout + net-smtp (0.4.0) + net-protocol + nio4r (2.5.9) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + public_suffix (5.0.3) + puma (6.4.0) + nio4r (~> 2.0) + racc (1.7.1) + rack (2.2.8) + rack-test (2.1.0) + rack (>= 1.3) + rails (7.0.8) + actioncable (= 7.0.8) + actionmailbox (= 7.0.8) + actionmailer (= 7.0.8) + actionpack (= 7.0.8) + actiontext (= 7.0.8) + actionview (= 7.0.8) + activejob (= 7.0.8) + activemodel (= 7.0.8) + activerecord (= 7.0.8) + activestorage (= 7.0.8) + activesupport (= 7.0.8) + bundler (>= 1.15.0) + railties (= 7.0.8) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.5.0) + loofah (~> 2.19, >= 2.19.1) + railties (7.0.8) + actionpack (= 7.0.8) + activesupport (= 7.0.8) + method_source + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) + rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + regexp_parser (2.8.1) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-rails (6.0.3) + actionpack (>= 6.1) + activesupport (>= 6.1) + railties (>= 6.1) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) + rspec-support (3.12.1) + shoulda-matchers (4.5.1) + activesupport (>= 4.2.0) + simple_form (5.2.0) + actionpack (>= 5.2) + activemodel (>= 5.2) + sprockets (4.2.1) + concurrent-ruby (~> 1.0) + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) + sprockets (>= 3.0.0) + sqlite3 (1.6.6) + mini_portile2 (~> 2.8.0) + thor (1.2.2) + timeout (0.4.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + websocket-driver (0.7.6) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.6.12) PLATFORMS ruby DEPENDENCIES - bourne + bootsnap + bourne (= 1.3.0) capybara clearance - coffee-rails (~> 3.2) email_spec - factory_girl_rails + factory_bot_rails foreman - jquery-rails - rails (= 3.2.8) + importmap-rails + listen + puma + rails (= 7.0.8) + rails-dom-testing rspec-rails - sass-rails (~> 3.2) shoulda-matchers simple_form + sprockets-rails sqlite3 - strong_parameters - thin - uglifier (>= 1.0) + tzinfo-data + +RUBY VERSION + ruby 3.2.2p53 + +BUNDLED WITH + 2.3.26 diff --git a/example_app/app/assets/config/manifest.js b/example_app/app/assets/config/manifest.js new file mode 100644 index 0000000..ddd546a --- /dev/null +++ b/example_app/app/assets/config/manifest.js @@ -0,0 +1,4 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css +//= link_tree ../../javascript .js +//= link_tree ../../../vendor/javascript .js diff --git a/example_app/app/assets/images/.keep b/example_app/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/example_app/app/assets/javascripts/application.js b/example_app/app/assets/javascripts/application.js index 9097d83..15ebed9 100644 --- a/example_app/app/assets/javascripts/application.js +++ b/example_app/app/assets/javascripts/application.js @@ -10,6 +10,4 @@ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD // GO AFTER THE REQUIRES BELOW. // -//= require jquery -//= require jquery_ujs //= require_tree . diff --git a/example_app/app/controllers/application_controller.rb b/example_app/app/controllers/application_controller.rb index c953024..a6b4593 100644 --- a/example_app/app/controllers/application_controller.rb +++ b/example_app/app/controllers/application_controller.rb @@ -1,5 +1,5 @@ class ApplicationController < ActionController::Base - include Clearance::Authentication + include Clearance::Controller protect_from_forgery - before_filter :authorize + before_action :require_login end diff --git a/example_app/app/controllers/completions_controller.rb b/example_app/app/controllers/completions_controller.rb index 9349e50..df48e65 100644 --- a/example_app/app/controllers/completions_controller.rb +++ b/example_app/app/controllers/completions_controller.rb @@ -1,7 +1,7 @@ class CompletionsController < ApplicationController def create @survey = Survey.find(params[:survey_id]) - completion_params = params.require(:completion).permit(:answers_attributes) + completion_params = params.require(:completion).permit(answers_attributes: [:text]) completion = @survey.completions.new(completion_params) completion.user = current_user completion.save! diff --git a/example_app/app/controllers/questions_controller.rb b/example_app/app/controllers/questions_controller.rb index 70fb08a..88a81df 100644 --- a/example_app/app/controllers/questions_controller.rb +++ b/example_app/app/controllers/questions_controller.rb @@ -20,7 +20,7 @@ def edit def update @question = Question.find(params[:id]) - @question.update_attributes(question_params) + @question.update(question_params) if @question.save redirect_to @question.survey else @@ -46,7 +46,7 @@ def question_params def submittable_params if submittable_attributes = params[:question][:submittable_attributes] - submittable_attributes.permit(:minimum, :maximum, :options_attributes) + submittable_attributes.permit(:minimum, :maximum, options_attributes: [:text]) else {} end diff --git a/example_app/app/controllers/types_controller.rb b/example_app/app/controllers/types_controller.rb index 206c7d4..f09bcc2 100644 --- a/example_app/app/controllers/types_controller.rb +++ b/example_app/app/controllers/types_controller.rb @@ -21,7 +21,11 @@ def create private def submittable_attributes - params[:question][:submittable_attributes] || {} + params.require(:question).permit( + submittable_attributes: [ + :minimum, :maximum, :options_attributes + ] + ).fetch(:submittable_attributes, {}) end def type diff --git a/example_app/app/controllers/unsubscribes_controller.rb b/example_app/app/controllers/unsubscribes_controller.rb index 0843d4f..16e6498 100644 --- a/example_app/app/controllers/unsubscribes_controller.rb +++ b/example_app/app/controllers/unsubscribes_controller.rb @@ -1,13 +1,19 @@ class UnsubscribesController < ApplicationController - skip_before_filter :authorize + skip_before_action :require_login def new @unsubscribe = Unsubscribe.new(email: params[:email]) end def create - @unsubscribe = Unsubscribe.new(params[:unsubscribe]) + @unsubscribe = Unsubscribe.new(unsubscribe_params) @unsubscribe.save! redirect_to root_url end + + protected + + def unsubscribe_params + params.require(:unsubscribe).permit(:email) + end end diff --git a/example_app/app/javascript/application.js b/example_app/app/javascript/application.js new file mode 100644 index 0000000..beff742 --- /dev/null +++ b/example_app/app/javascript/application.js @@ -0,0 +1 @@ +// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails diff --git a/example_app/app/mailers/mailer.rb b/example_app/app/mailers/mailer.rb index 3fb23e4..2a01e24 100644 --- a/example_app/app/mailers/mailer.rb +++ b/example_app/app/mailers/mailer.rb @@ -13,8 +13,9 @@ def completion_notification(recipient) def invitation_notification(invitation, body) mail( to: invitation.recipient_email, - subject: 'You have been invited to take an online survey', - body: body - ) + subject: 'You have been invited to take an online survey' + ) do |format| + format.html { render html: body } + end end end diff --git a/example_app/app/mailers/unsubscribeable_mailer.rb b/example_app/app/mailers/unsubscribeable_mailer.rb index ecf4081..675cca6 100644 --- a/example_app/app/mailers/unsubscribeable_mailer.rb +++ b/example_app/app/mailers/unsubscribeable_mailer.rb @@ -14,7 +14,7 @@ def self.unsubscribed?(invitation) end class NullMessage - def deliver + def deliver_now end end end diff --git a/example_app/app/models/completion.rb b/example_app/app/models/completion.rb index 33f7e04..556ac37 100644 --- a/example_app/app/models/completion.rb +++ b/example_app/app/models/completion.rb @@ -27,6 +27,6 @@ def score private def completion_notification - Mailer.completion_notification(user).deliver + Mailer.completion_notification(user).deliver_now end end diff --git a/example_app/app/models/invitation.rb b/example_app/app/models/invitation.rb index 222658b..2b0a471 100644 --- a/example_app/app/models/invitation.rb +++ b/example_app/app/models/invitation.rb @@ -1,5 +1,4 @@ class Invitation < ActiveRecord::Base - EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i STATUSES = %w(pending accepted) belongs_to :sender, class_name: 'User' @@ -7,7 +6,7 @@ class Invitation < ActiveRecord::Base before_create :set_token - validates :recipient_email, presence: true, format: EMAIL_REGEX + validates :recipient_email, presence: true, email_address: true validates :status, inclusion: { in: STATUSES } def to_param @@ -16,7 +15,7 @@ def to_param def deliver(mailer) body = InvitationMessage.new(self).body - mailer.invitation_notification(self, body).deliver + mailer.invitation_notification(self, body).deliver_now end private diff --git a/example_app/app/models/invitation_message.rb b/example_app/app/models/invitation_message.rb index 00e80ff..7a680ec 100644 --- a/example_app/app/models/invitation_message.rb +++ b/example_app/app/models/invitation_message.rb @@ -1,5 +1,6 @@ class InvitationMessage < AbstractController::Base include AbstractController::Rendering + include ActionView::Rendering include Rails.application.routes.url_helpers self.view_paths = 'app/views' diff --git a/example_app/app/models/multiple_choice_submittable.rb b/example_app/app/models/multiple_choice_submittable.rb index 4f56914..e25b7a9 100644 --- a/example_app/app/models/multiple_choice_submittable.rb +++ b/example_app/app/models/multiple_choice_submittable.rb @@ -6,7 +6,7 @@ class MultipleChoiceSubmittable < ActiveRecord::Base def breakdown total = answers.count - counts = answers.group(:text).order('COUNT(*) DESC').count + counts = answers.group(:text).order(Arel.sql('COUNT(*) DESC')).count percents = counts.map do |text, count| percent = (100.0 * count / total).round "#{percent}% #{text}" diff --git a/example_app/app/models/question.rb b/example_app/app/models/question.rb index fcaf765..9988fbf 100644 --- a/example_app/app/models/question.rb +++ b/example_app/app/models/question.rb @@ -34,7 +34,9 @@ def most_recent_answer_text def build_submittable(type, attributes) submittable_class = type.sub('Question', 'Submittable').constantize - self.submittable = submittable_class.new(attributes.merge(question: self)) + self.submittable = submittable_class.new(attributes).tap do |submittable| + submittable.question = self + end end def summary_using(summarizer) diff --git a/example_app/app/models/survey_inviter.rb b/example_app/app/models/survey_inviter.rb index b0c0ca6..777f659 100644 --- a/example_app/app/models/survey_inviter.rb +++ b/example_app/app/models/survey_inviter.rb @@ -9,8 +9,8 @@ class SurveyInviter validates_with EnumerableValidator, attributes: [:recipients], - unless: 'recipients.nil?', - validator: EmailValidator + if: :has_recipients, + validator: EmailAddressValidator def recipients=(recipients) @recipients = RecipientList.new(recipients) @@ -24,6 +24,10 @@ def invite private + def has_recipients + recipients.present? + end + def deliver_invitations create_invitations.each do |invitation| invitation.deliver(UnsubscribeableMailer) diff --git a/example_app/app/validators/email_validator.rb b/example_app/app/validators/email_address_validator.rb similarity index 64% rename from example_app/app/validators/email_validator.rb rename to example_app/app/validators/email_address_validator.rb index 1d0161a..cc88631 100644 --- a/example_app/app/validators/email_validator.rb +++ b/example_app/app/validators/email_address_validator.rb @@ -1,8 +1,8 @@ -class EmailValidator < ActiveModel::EachValidator +class EmailAddressValidator < ActiveModel::EachValidator EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i def validate_each(record, attribute, value) - unless value.match EMAIL_REGEX + if value.present? && !value.match(EMAIL_REGEX) record.errors.add(attribute, "#{value} is not a valid email") end end diff --git a/example_app/app/views/layouts/application.html.erb b/example_app/app/views/layouts/application.html.erb index 9335474..009b4da 100644 --- a/example_app/app/views/layouts/application.html.erb +++ b/example_app/app/views/layouts/application.html.erb @@ -3,7 +3,7 @@ ExampleApp <%= stylesheet_link_tag "application", :media => "all" %> - <%= javascript_include_tag "application" %> + <%= javascript_importmap_tags %> <%= csrf_meta_tags %> diff --git a/example_app/bin/bundle b/example_app/bin/bundle new file mode 100755 index 0000000..f19acf5 --- /dev/null +++ b/example_app/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +load Gem.bin_path('bundler', 'bundle') diff --git a/example_app/bin/importmap b/example_app/bin/importmap new file mode 100755 index 0000000..36502ab --- /dev/null +++ b/example_app/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../config/application" +require "importmap/commands" diff --git a/example_app/bin/rails b/example_app/bin/rails new file mode 100755 index 0000000..efc0377 --- /dev/null +++ b/example_app/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path("../config/application", __dir__) +require_relative "../config/boot" +require "rails/commands" diff --git a/example_app/bin/rake b/example_app/bin/rake new file mode 100755 index 0000000..4fbf10b --- /dev/null +++ b/example_app/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative "../config/boot" +require "rake" +Rake.application.run diff --git a/example_app/bin/setup b/example_app/bin/setup new file mode 100755 index 0000000..ec47b79 --- /dev/null +++ b/example_app/bin/setup @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +require "fileutils" + +# path to your application root. +APP_ROOT = File.expand_path("..", __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +FileUtils.chdir APP_ROOT do + # This script is a way to set up or update your development environment automatically. + # This script is idempotent, so that you can run it at any time and get an expectable outcome. + # 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") + # FileUtils.cp "config/database.yml.sample", "config/database.yml" + # end + + puts "\n== Preparing database ==" + system! "bin/rails db:prepare" + + 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/example_app/bin/update b/example_app/bin/update new file mode 100755 index 0000000..58bfaed --- /dev/null +++ b/example_app/bin/update @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +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') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + 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/example_app/bin/yarn b/example_app/bin/yarn new file mode 100755 index 0000000..9fab2c3 --- /dev/null +++ b/example_app/bin/yarn @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + yarn = ENV["PATH"].split(File::PATH_SEPARATOR). + select { |dir| File.expand_path(dir) != __dir__ }. + product(["yarn", "yarn.cmd", "yarn.ps1"]). + map { |dir, file| File.expand_path(file, dir) }. + find { |file| File.executable?(file) } + + if yarn + exec yarn, *ARGV + else + $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/example_app/config.ru b/example_app/config.ru index 8a63f7a..4a3c09a 100644 --- a/example_app/config.ru +++ b/example_app/config.ru @@ -1,4 +1,6 @@ # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) -run ExampleApp::Application +require_relative "config/environment" + +run Rails.application +Rails.application.load_server diff --git a/example_app/config/application.rb b/example_app/config/application.rb index fc14ed9..4eda091 100644 --- a/example_app/config/application.rb +++ b/example_app/config/application.rb @@ -1,65 +1,22 @@ -require File.expand_path('../boot', __FILE__) +require_relative "boot" -# Pick the frameworks you want: -require "active_record/railtie" -require "action_controller/railtie" -require "action_mailer/railtie" -require "active_resource/railtie" -require "sprockets/railtie" -# require "rails/test_unit/railtie" +require "rails/all" -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - Bundler.require(*Rails.groups(:assets => %w(development test))) - # If you want your assets lazily compiled in production, use this line - # Bundler.require(:default, :assets, Rails.env) -end +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) module ExampleApp 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. - - # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib) - - # Only load the plugins named here, in the order given (default is alphabetical). - # :all can be used as a placeholder for all plugins not explicitly named. - # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - - # Activate observers that should always be running. - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - - # 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 - - # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" - - # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password] - - # Enable escaping HTML in JSON. - config.active_support.escape_html_entities_in_json = true - - # Use SQL instead of Active Record's schema dumper when creating the database. - # This is necessary if your schema can't be completely dumped by the schema dumper, - # like if you have constraints or database-specific column types - # config.active_record.schema_format = :sql - - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - config.active_record.whitelist_attributes = false - - # Enable the asset pipeline - config.assets.enabled = true + # Initialize configuration defaults for originally generated Rails version. + config.load_defaults 7.0 + + # Configuration for the application, engines, and railties goes here. + # + # These settings can be overridden in specific environments using the files + # in config/environments, which are processed later. + # + # config.time_zone = "Central Time (US & Canada)" + # config.eager_load_paths << Rails.root.join("extras") end end diff --git a/example_app/config/boot.rb b/example_app/config/boot.rb index 4489e58..988a5dd 100644 --- a/example_app/config/boot.rb +++ b/example_app/config/boot.rb @@ -1,6 +1,4 @@ -require 'rubygems' +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) -# Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) - -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/example_app/config/cable.yml b/example_app/config/cable.yml new file mode 100644 index 0000000..d77ba74 --- /dev/null +++ b/example_app/config/cable.yml @@ -0,0 +1,10 @@ +development: + adapter: async + +test: + adapter: test + +production: + adapter: redis + url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + channel_prefix: example_app_production diff --git a/example_app/config/environment.rb b/example_app/config/environment.rb index b5a72d5..cac5315 100644 --- a/example_app/config/environment.rb +++ b/example_app/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application -require File.expand_path('../application', __FILE__) +# Load the Rails application. +require_relative "application" -# Initialize the rails application -ExampleApp::Application.initialize! +# Initialize the Rails application. +Rails.application.initialize! diff --git a/example_app/config/environments/development.rb b/example_app/config/environments/development.rb index 91caff2..a263b83 100644 --- a/example_app/config/environments/development.rb +++ b/example_app/config/environments/development.rb @@ -1,33 +1,70 @@ -ExampleApp::Application.configure do - # Settings specified here will take precedence over those in config/application.rb +require "active_support/core_ext/integer/time" - # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development +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 any time + # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable server timing + config.server_timing = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + config.action_controller.enable_fragment_cache_logging = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end - # Show full error reports and disable caching - config.consider_all_requests_local = true - config.action_controller.perform_caching = false + # 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 + # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false - # Print deprecation notices to the Rails logger + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log - # Only use best-standards-support built into browsers - config.action_dispatch.best_standards_support = :builtin + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # 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 + + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) - config.active_record.auto_explain_threshold_in_seconds = 0.5 + # Uncomment if you wish to allow Action Cable access from any origin. + # config.action_cable.disable_request_forgery_protection = true config.action_mailer.default_url_options = { :host => 'localhost:3000' } end diff --git a/example_app/config/environments/production.rb b/example_app/config/environments/production.rb index 61c0266..92a0eab 100644 --- a/example_app/config/environments/production.rb +++ b/example_app/config/environments/production.rb @@ -1,54 +1,87 @@ -ExampleApp::Application.configure do - # Settings specified here will take precedence over those in config/application.rb +require "active_support/core_ext/integer/time" - # Code is not reloaded between requests +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 - # Full error reports are disabled and caching is turned on + # Eager load code on boot. This eager loads most of Rails and + # 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 + + # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Disable Rails's static asset server (Apache or nginx will already do this) - config.serve_static_assets = false + # 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. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.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 + # Store uploaded files on the local file system (see config/storage.yml for options). + config.active_storage.service = :local - # 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 + # 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 - # See everything in the log (default is :info) - # config.log_level = :debug + # Include generic and useful information about system operation, but avoid logging too much + # information to avoid inadvertent exposure of personally identifiable information (PII). + config.log_level = :info - # Prepend all log lines with the following tags - # config.log_tags = [ :subdomain, :uuid ] + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] - # Use a different logger for distributed setups - # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - - # Use a different cache store in production + # 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" + # 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 = "example_app_production" + config.action_mailer.perform_caching = false - # Disable delivery errors, bad email addresses will be ignored + # 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 threaded mode - # config.threadsafe! - # 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 + # Don't log any deprecations. + config.active_support.report_deprecations = 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 - # Log the query plan for queries taking more than this (works - # with SQLite, MySQL, and PostgreSQL) - # config.active_record.auto_explain_threshold_in_seconds = 0.5 + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false end diff --git a/example_app/config/environments/test.rb b/example_app/config/environments/test.rb index 6a493c2..ec4a481 100644 --- a/example_app/config/environments/test.rb +++ b/example_app/config/environments/test.rb @@ -1,39 +1,62 @@ -ExampleApp::Application.configure do - # Settings specified here will take precedence over those in config/application.rb +require "active_support/core_ext/integer/time" - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! + +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Turn false under Spring and add config.action_view.cache_template_loading = true. config.cache_classes = true - # Configure static asset server for tests with Cache-Control for performance - config.serve_static_assets = true - config.static_cache_control = "public, max-age=3600" + # Eager loading loads your whole application. When running a single test locally, + # this probably isn't necessary. It's a good idea to do in a continuous integration + # system, or in some way before deploying your code. + config.eager_load = ENV["CI"].present? - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true + # 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.to_i}" + } - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false + config.cache_store = :null_store - # Raise exceptions instead of rendering exception templates + # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false - # Disable request forgery protection in test environment - config.action_controller.allow_forgery_protection = false + # 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. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test - # Raise exception on mass assignment protection for Active Record models - config.active_record.mass_assignment_sanitizer = :strict - - # Print deprecation notices to the stderr + # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr + # Raise exceptions for disallowed deprecations. + config.active_support.disallowed_deprecation = :raise + + # Tell Active Support which deprecation messages to disallow. + config.active_support.disallowed_deprecation_warnings = [] + + # Raises error for missing translations. + # config.i18n.raise_on_missing_translations = true + + # Annotate rendered view with file names. + # config.action_view.annotate_rendered_view_with_filenames = true + config.action_mailer.default_url_options = { :host => 'example.com' } end diff --git a/example_app/config/importmap.rb b/example_app/config/importmap.rb new file mode 100644 index 0000000..9d84985 --- /dev/null +++ b/example_app/config/importmap.rb @@ -0,0 +1,3 @@ +# Pin npm packages by running ./bin/importmap + +pin "application", preload: true diff --git a/example_app/config/initializers/application_controller_renderer.rb b/example_app/config/initializers/application_controller_renderer.rb new file mode 100644 index 0000000..89d2efa --- /dev/null +++ b/example_app/config/initializers/application_controller_renderer.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# ActiveSupport::Reloader.to_prepare do +# ApplicationController.renderer.defaults.merge!( +# http_host: 'example.org', +# https: false +# ) +# end diff --git a/example_app/config/initializers/assets.rb b/example_app/config/initializers/assets.rb new file mode 100644 index 0000000..d6156df --- /dev/null +++ b/example_app/config/initializers/assets.rb @@ -0,0 +1,12 @@ +# 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 the app/assets +# folder are already added. +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/example_app/config/initializers/backtrace_silencers.rb b/example_app/config/initializers/backtrace_silencers.rb index 59385cd..33699c3 100644 --- a/example_app/config/initializers/backtrace_silencers.rb +++ b/example_app/config/initializers/backtrace_silencers.rb @@ -1,7 +1,8 @@ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. -# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } +# Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code +# by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". +Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] diff --git a/example_app/config/initializers/content_security_policy.rb b/example_app/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..54f47cf --- /dev/null +++ b/example_app/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. +# See the Securing Rails Applications Guide for more information: +# https://guides.rubyonrails.org/security.html#content-security-policy-header + +# Rails.application.configure do +# 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 +# +# # Generate session nonces for permitted importmap and inline scripts +# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } +# config.content_security_policy_nonce_directives = %w(script-src) +# +# # Report violations without enforcing the policy. +# # config.content_security_policy_report_only = true +# end diff --git a/example_app/config/initializers/cookies_serializer.rb b/example_app/config/initializers/cookies_serializer.rb new file mode 100644 index 0000000..5a6a32d --- /dev/null +++ b/example_app/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 = :json diff --git a/example_app/config/initializers/filter_parameter_logging.rb b/example_app/config/initializers/filter_parameter_logging.rb new file mode 100644 index 0000000..adc6568 --- /dev/null +++ b/example_app/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +# Configure parameters to be filtered from the log file. Use this to limit dissemination of +# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported +# notations and behaviors. +Rails.application.config.filter_parameters += [ + :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn +] diff --git a/example_app/config/initializers/inflections.rb b/example_app/config/initializers/inflections.rb index 5d8d9be..3860f65 100644 --- a/example_app/config/initializers/inflections.rb +++ b/example_app/config/initializers/inflections.rb @@ -1,15 +1,16 @@ # Be sure to restart your server when you modify this file. -# Add new inflection rules using the following format -# (all these examples are active by default): -# ActiveSupport::Inflector.inflections do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' +# 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| +# inflect.plural /^(ox)$/i, "\\1en" +# inflect.singular /^(ox)en/i, "\\1" +# inflect.irregular "person", "people" # inflect.uncountable %w( fish sheep ) # end -# + # These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections do |inflect| -# inflect.acronym 'RESTful' +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym "RESTful" # end diff --git a/example_app/config/initializers/mime_types.rb b/example_app/config/initializers/mime_types.rb index 72aca7e..dc18996 100644 --- a/example_app/config/initializers/mime_types.rb +++ b/example_app/config/initializers/mime_types.rb @@ -2,4 +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 diff --git a/example_app/config/initializers/new_framework_defaults.rb b/example_app/config/initializers/new_framework_defaults.rb new file mode 100644 index 0000000..d74e1d8 --- /dev/null +++ b/example_app/config/initializers/new_framework_defaults.rb @@ -0,0 +1,10 @@ +# 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 Guide for Upgrading Ruby on Rails for more info on each option. + +# Require `belongs_to` associations by default. Previous versions had false. +Rails.application.config.active_record.belongs_to_required_by_default = false diff --git a/example_app/config/initializers/permissions_policy.rb b/example_app/config/initializers/permissions_policy.rb new file mode 100644 index 0000000..00f64d7 --- /dev/null +++ b/example_app/config/initializers/permissions_policy.rb @@ -0,0 +1,11 @@ +# Define an application-wide HTTP permissions policy. For further +# information see https://developers.google.com/web/updates/2018/06/feature-policy +# +# Rails.application.config.permissions_policy do |f| +# f.camera :none +# f.gyroscope :none +# f.microphone :none +# f.usb :none +# f.fullscreen :self +# f.payment :self, "https://secure.example.com" +# end diff --git a/example_app/config/initializers/secret_token.rb b/example_app/config/initializers/secret_token.rb index dfbe32c..10b8a41 100644 --- a/example_app/config/initializers/secret_token.rb +++ b/example_app/config/initializers/secret_token.rb @@ -1,7 +1,12 @@ # Be sure to restart your server when you modify this file. -# Your secret key for verifying the integrity of signed cookies. +# 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. -ExampleApp::Application.config.secret_token = 'b2fad122a6269eba3bdd2f723ac26a900e599c1a24ec70d900a3546db7b507ff9b9bba5157df699188e342d26b180b114cc4a0e53f7aea4f294af3e9d5353401' +# You can use `rake secret` to generate a secure secret key. + +# Make sure your secret_key_base is kept private +# if you're sharing your code publicly. +ExampleApp::Application.config.secret_key_base = 'b2fad122a6269eba3bdd2f723ac26a900e599c1a24ec70d900a3546db7b507ff9b9bba5157df699188e342d26b180b114cc4a0e53f7aea4f294af3e9d5353401' diff --git a/example_app/config/initializers/session_store.rb b/example_app/config/initializers/session_store.rb index 99f2e7e..77b846e 100644 --- a/example_app/config/initializers/session_store.rb +++ b/example_app/config/initializers/session_store.rb @@ -1,8 +1,3 @@ # Be sure to restart your server when you modify this file. -ExampleApp::Application.config.session_store :cookie_store, key: '_example_app_session' - -# Use the database for sessions instead of the cookie-based default, -# which shouldn't be used to store highly confidential information -# (create the session table with "rails generate session_migration") -# ExampleApp::Application.config.session_store :active_record_store +Rails.application.config.session_store :cookie_store, key: '_example_app_session' diff --git a/example_app/config/initializers/wrap_parameters.rb b/example_app/config/initializers/wrap_parameters.rb index 999df20..bbfc396 100644 --- a/example_app/config/initializers/wrap_parameters.rb +++ b/example_app/config/initializers/wrap_parameters.rb @@ -1,5 +1,5 @@ # Be sure to restart your server when you modify this file. -# + # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. @@ -8,7 +8,7 @@ wrap_parameters format: [:json] end -# Disable root element in JSON by default. -ActiveSupport.on_load(:active_record) do - self.include_root_in_json = false -end +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/example_app/config/puma.rb b/example_app/config/puma.rb new file mode 100644 index 0000000..1e19380 --- /dev/null +++ b/example_app/config/puma.rb @@ -0,0 +1,56 @@ +# 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 } +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! + +# 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 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/example_app/config/routes.rb b/example_app/config/routes.rb index c0c692b..87c01a4 100644 --- a/example_app/config/routes.rb +++ b/example_app/config/routes.rb @@ -1,4 +1,5 @@ -ExampleApp::Application.routes.draw do +Rails.application.routes.draw do + # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html resources :completions, only: [:show] resources :questions, only: [:edit, :update] do diff --git a/example_app/config/secrets.yml b/example_app/config/secrets.yml new file mode 100644 index 0000000..4505dd0 --- /dev/null +++ b/example_app/config/secrets.yml @@ -0,0 +1,32 @@ +# 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. + +# Shared secrets are available across all environments. + +# shared: +# api_key: a1B2c3D4e5F6 + +# Environmental secrets are only available for that specific environment. + +development: + secret_key_base: a584a03bedb5de6bc6af039858c16d5cb939bd567c77043ae941df4284cbc5c70fe1fe2dfc42030bac7cefd833cc390ca0626f773b7bf0bd783529ab13fd13ba + +test: + secret_key_base: f25cdb15163a6486f1492780d83c381ac7220de2f561f459eed9aed36f6ee07cb30947cd65fa4a687211d0665c9cce0b27ce34e781179aeb908407f2b76a59fa + +# 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. + +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/example_app/config/storage.yml b/example_app/config/storage.yml new file mode 100644 index 0000000..d32f76e --- /dev/null +++ b/example_app/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 ] diff --git a/example_app/db/migrate/20231006184256_add_service_name_to_active_storage_blobs.active_storage.rb b/example_app/db/migrate/20231006184256_add_service_name_to_active_storage_blobs.active_storage.rb new file mode 100644 index 0000000..a15c6ce --- /dev/null +++ b/example_app/db/migrate/20231006184256_add_service_name_to_active_storage_blobs.active_storage.rb @@ -0,0 +1,22 @@ +# This migration comes from active_storage (originally 20190112182829) +class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] + def up + return unless table_exists?(:active_storage_blobs) + + unless column_exists?(:active_storage_blobs, :service_name) + add_column :active_storage_blobs, :service_name, :string + + if configured_service = ActiveStorage::Blob.service.name + ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) + end + + change_column :active_storage_blobs, :service_name, :string, null: false + end + end + + def down + return unless table_exists?(:active_storage_blobs) + + remove_column :active_storage_blobs, :service_name + end +end diff --git a/example_app/db/migrate/20231006184257_create_active_storage_variant_records.active_storage.rb b/example_app/db/migrate/20231006184257_create_active_storage_variant_records.active_storage.rb new file mode 100644 index 0000000..94ac83a --- /dev/null +++ b/example_app/db/migrate/20231006184257_create_active_storage_variant_records.active_storage.rb @@ -0,0 +1,27 @@ +# This migration comes from active_storage (originally 20191206030411) +class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] + def change + return unless table_exists?(:active_storage_blobs) + + # Use Active Record's configured type for primary key + create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t| + t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type + t.string :variation_digest, null: false + + t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end + + private + def primary_key_type + config = Rails.configuration.generators + config.options[config.orm][:primary_key_type] || :primary_key + end + + def blobs_primary_key_type + pkey_name = connection.primary_key(:active_storage_blobs) + pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name } + pkey_column.bigint? ? :bigint : pkey_column.type + end +end diff --git a/example_app/db/schema.rb b/example_app/db/schema.rb index bb5c464..8ba2083 100644 --- a/example_app/db/schema.rb +++ b/example_app/db/schema.rb @@ -1,123 +1,116 @@ -# 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. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # -# It's strongly recommended to check this file into your version control system. - -ActiveRecord::Schema.define(:version => 20130425182110) do - - create_table "answers", :force => true do |t| - t.integer "completion_id", :null => false - t.integer "question_id", :null => false - t.string "text", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.0].define(version: 2023_10_06_184257) do + create_table "answers", force: :cascade do |t| + t.integer "completion_id", null: false + t.integer "question_id", null: false + t.string "text", limit: 255, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["completion_id"], name: "index_answers_on_completion_id" + t.index ["question_id"], name: "index_answers_on_question_id" end - add_index "answers", ["completion_id"], :name => "index_answers_on_completion_id" - add_index "answers", ["question_id"], :name => "index_answers_on_question_id" - - create_table "completions", :force => true do |t| - t.integer "survey_id", :null => false - t.integer "user_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "completions", force: :cascade do |t| + t.integer "survey_id", null: false + t.integer "user_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["survey_id"], name: "index_completions_on_survey_id" + t.index ["user_id"], name: "index_completions_on_user_id" end - add_index "completions", ["survey_id"], :name => "index_completions_on_survey_id" - add_index "completions", ["user_id"], :name => "index_completions_on_user_id" - - create_table "invitations", :force => true do |t| - t.integer "sender_id" - t.integer "survey_id" - t.string "recipient_email" - t.string "status", :default => "pending" - t.string "token" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.text "message", :default => "", :null => false + create_table "invitations", force: :cascade do |t| + t.integer "sender_id" + t.integer "survey_id" + t.string "recipient_email", limit: 255 + t.string "status", limit: 255, default: "pending" + t.string "token", limit: 255 + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.text "message", default: "", null: false + t.index ["survey_id"], name: "index_invitations_on_survey_id" + t.index ["token"], name: "index_invitations_on_token", unique: true end - add_index "invitations", ["survey_id"], :name => "index_invitations_on_survey_id" - add_index "invitations", ["token"], :name => "index_invitations_on_token", :unique => true - - create_table "messages", :force => true do |t| - t.integer "sender_id", :null => false - t.integer "recipient_id", :null => false - t.text "body", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "messages", force: :cascade do |t| + t.integer "sender_id", null: false + t.integer "recipient_id", null: false + t.text "body", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end - create_table "multiple_choice_submittables", :force => true do |t| - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "multiple_choice_submittables", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end - create_table "open_submittables", :force => true do |t| - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "open_submittables", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false end - create_table "options", :force => true do |t| - t.integer "question_id", :null => false - t.string "text", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "score", :default => 0, :null => false + create_table "options", force: :cascade do |t| + t.integer "question_id", null: false + t.string "text", limit: 255, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "score", default: 0, null: false end - create_table "questions", :force => true do |t| - t.string "title", :null => false - t.integer "survey_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "submittable_id" - t.string "submittable_type" + create_table "questions", force: :cascade do |t| + t.string "title", limit: 255, null: false + t.integer "survey_id", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "submittable_id" + t.string "submittable_type", limit: 255 end - create_table "scale_submittables", :force => true do |t| - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "minimum" - t.integer "maximum" + create_table "scale_submittables", force: :cascade do |t| + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "minimum" + t.integer "maximum" end - create_table "surveys", :force => true do |t| - t.string "title", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.integer "author_id", :null => false + create_table "surveys", force: :cascade do |t| + t.string "title", limit: 255, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.integer "author_id", null: false end - create_table "unsubscribes", :force => true do |t| - t.string "email", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + create_table "unsubscribes", force: :cascade do |t| + t.string "email", limit: 255, null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["email"], name: "index_unsubscribes_on_email" end - add_index "unsubscribes", ["email"], :name => "index_unsubscribes_on_email" - - create_table "users", :force => true do |t| - t.string "email" - t.string "encrypted_password", :limit => 128 - t.string "salt", :limit => 128 - t.string "confirmation_token", :limit => 128 - t.string "remember_token", :limit => 128 - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - t.string "first_name" - t.string "last_name" + create_table "users", force: :cascade do |t| + t.string "email", limit: 255 + t.string "encrypted_password", limit: 128 + t.string "salt", limit: 128 + t.string "confirmation_token", limit: 128 + t.string "remember_token", limit: 128 + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.string "first_name", limit: 255 + t.string "last_name", limit: 255 + t.index ["email"], name: "index_users_on_email" + t.index ["remember_token"], name: "index_users_on_remember_token" end - add_index "users", ["email"], :name => "index_users_on_email" - add_index "users", ["remember_token"], :name => "index_users_on_remember_token" - end diff --git a/example_app/lib/active_model/model.rb b/example_app/lib/active_model/model.rb deleted file mode 100644 index 62383a0..0000000 --- a/example_app/lib/active_model/model.rb +++ /dev/null @@ -1,97 +0,0 @@ -module ActiveModel - - # == Active \Model Basic \Model - # - # Includes the required interface for an object to interact with - # ActionPack, using different ActiveModel modules. - # It includes model name introspections, conversions, translations and - # validations. Besides that, it allows you to initialize the object with a - # hash of attributes, pretty much like ActiveRecord does. - # - # A minimal implementation could be: - # - # class Person - # include ActiveModel::Model - # attr_accessor :name, :age - # end - # - # person = Person.new(name: 'bob', age: '18') - # person.name # => 'bob' - # person.age # => 18 - # - # Note that, by default, ActiveModel::Model implements persisted? - # to return +false+, which is the most common case. You may want to override - # it in your class to simulate a different scenario: - # - # class Person - # include ActiveModel::Model - # attr_accessor :id, :name - # - # def persisted? - # self.id == 1 - # end - # end - # - # person = Person.new(id: 1, name: 'bob') - # person.persisted? # => true - # - # Also, if for some reason you need to run code on initialize, make - # sure you call +super+ if you want the attributes hash initialization to - # happen. - # - # class Person - # include ActiveModel::Model - # attr_accessor :id, :name, :omg - # - # def initialize(attributes={}) - # super - # @omg ||= true - # end - # end - # - # person = Person.new(id: 1, name: 'bob') - # person.omg # => true - # - # For more detailed information on other functionalities available, please - # refer to the specific modules included in ActiveModel::Model - # (see below). - module Model - def self.included(base) #:nodoc: - base.class_eval do - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Conversion - end - end - - # Initializes a new model with the given +params+. - # - # class Person - # include ActiveModel::Model - # attr_accessor :name, :age - # end - # - # person = Person.new(name: 'bob', age: '18') - # person.name # => "bob" - # person.age # => 18 - def initialize(params={}) - params.each do |attr, value| - self.public_send("#{attr}=", value) - end if params - end - - # Indicates if the model is persisted. Default is +false+. - # - # class Person - # include ActiveModel::Model - # attr_accessor :id, :name - # end - # - # person = Person.new(id: 1, name: 'bob') - # person.persisted? # => false - def persisted? - false - end - end -end diff --git a/example_app/spec/factories/application.rb b/example_app/spec/factories/application.rb index 78e5a61..aca387a 100644 --- a/example_app/spec/factories/application.rb +++ b/example_app/spec/factories/application.rb @@ -1,8 +1,8 @@ -FactoryGirl.define do +FactoryBot.define do factory :answer do completion question - text 'Hello' + text { 'Hello' } end factory :completion do @@ -11,13 +11,13 @@ end factory :multiple_choice_submittable do - ignore do + transient do options_texts { [] } end options do |attributes| attributes.options_texts.map do |text| - FactoryGirl.build(:option, text: text, question_id: attributes.id) + FactoryBot.build(:option, text: text, question_id: attributes.id) end end end @@ -26,7 +26,7 @@ end factory :option do - text 'Hello' + text { 'Hello' } end factory :question do @@ -48,8 +48,8 @@ end factory :scale_submittable do - minimum 1 - maximum 2 + minimum { 1 } + maximum { 2 } end factory :survey do diff --git a/example_app/spec/factories/invitation.rb b/example_app/spec/factories/invitation.rb index 8857719..d2ec312 100644 --- a/example_app/spec/factories/invitation.rb +++ b/example_app/spec/factories/invitation.rb @@ -1,4 +1,4 @@ -FactoryGirl.define do +FactoryBot.define do factory :invitation do sender recipient_email { generate(:email) } diff --git a/example_app/spec/factories/user.rb b/example_app/spec/factories/user.rb index 65a896b..5209574 100644 --- a/example_app/spec/factories/user.rb +++ b/example_app/spec/factories/user.rb @@ -1,12 +1,12 @@ -FactoryGirl.define do +FactoryBot.define do sequence :email do |n| "user#{n}@example.com" end factory :user, aliases: [:sender] do - first_name 'Ron' - last_name 'Burgundy' + first_name { 'Ron' } + last_name { 'Burgundy' } email - password 'password' + password { 'password' } end end diff --git a/example_app/spec/features/user_edits_open_question_spec.rb b/example_app/spec/features/user_edits_open_question_spec.rb index 6df5cf6..e3f7246 100644 --- a/example_app/spec/features/user_edits_open_question_spec.rb +++ b/example_app/spec/features/user_edits_open_question_spec.rb @@ -6,7 +6,7 @@ sign_in update_question(question, 'Updated title') - expect(page).to have_content('Updated title') + page.should have_content('Updated title') end def update_question(question, title) diff --git a/example_app/spec/features/user_views_survey_results_spec.rb b/example_app/spec/features/user_views_survey_results_spec.rb index 8dea8a0..6157a6f 100644 --- a/example_app/spec/features/user_views_survey_results_spec.rb +++ b/example_app/spec/features/user_views_survey_results_spec.rb @@ -14,7 +14,10 @@ taker.complete 'Billy', 'Blue', '5' summary_for_question('Name?').should eq('Brian, Billy') - summary_for_question('Favorite color?').should eq('50% Blue, 50% Red') + summary_for_question('Favorite color?').split(', ').should contain_exactly( + '50% Blue', + '50% Red' + ) summary_for_question('Airspeed velocity?').should eq('Average: 7.50') view_completions survey diff --git a/example_app/spec/features/user_views_survey_score_spec.rb b/example_app/spec/features/user_views_survey_score_spec.rb index 77e712e..0a4bb99 100644 --- a/example_app/spec/features/user_views_survey_score_spec.rb +++ b/example_app/spec/features/user_views_survey_score_spec.rb @@ -22,7 +22,7 @@ def make_scored_survey def answer_survey(survey) sign_in taker = SurveyTaker.new(survey) - taker.complete 'Y', 6 + taker.complete 'Y', '6' end def have_survey_score diff --git a/example_app/spec/mailers/unsubscribeable_mailer_spec.rb b/example_app/spec/mailers/unsubscribeable_mailer_spec.rb index ca11c13..371b10b 100644 --- a/example_app/spec/mailers/unsubscribeable_mailer_spec.rb +++ b/example_app/spec/mailers/unsubscribeable_mailer_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe UnsubscribeableMailer, '#deliver' do +describe UnsubscribeableMailer, '#deliver_now' do it 'sends email notifications to new users' do invitation = create(:invitation) @@ -19,6 +19,6 @@ end def deliver_invitation(invitation, body) - UnsubscribeableMailer.invitation_notification(invitation, body).deliver + UnsubscribeableMailer.invitation_notification(invitation, body).deliver_now end end diff --git a/example_app/spec/models/completion_spec.rb b/example_app/spec/models/completion_spec.rb index 9f86648..1f92ada 100644 --- a/example_app/spec/models/completion_spec.rb +++ b/example_app/spec/models/completion_spec.rb @@ -46,7 +46,7 @@ describe Completion, '#save' do it 'delivers a completion notification' do - Mailer.stubs(completion_notification: stub(deliver: true)) + Mailer.stubs(completion_notification: stub(deliver_now: true)) user = create(:user) completion = create(:completion, user: user) diff --git a/example_app/spec/models/invitation_spec.rb b/example_app/spec/models/invitation_spec.rb index 5239fe3..32be588 100644 --- a/example_app/spec/models/invitation_spec.rb +++ b/example_app/spec/models/invitation_spec.rb @@ -9,7 +9,7 @@ it { should validate_presence_of(:recipient_email) } it { should allow_value('user@example.com').for(:recipient_email) } it { should_not allow_value('invalid_email').for(:recipient_email) } - it { should ensure_inclusion_of(:status).in_array(Invitation::STATUSES) } + it { should validate_inclusion_of(:status).in_array(Invitation::STATUSES) } end describe Invitation, '#deliver' do diff --git a/example_app/spec/models/multiple_choice_submittable_spec.rb b/example_app/spec/models/multiple_choice_submittable_spec.rb index 12ebb90..e27c9a5 100644 --- a/example_app/spec/models/multiple_choice_submittable_spec.rb +++ b/example_app/spec/models/multiple_choice_submittable_spec.rb @@ -40,13 +40,15 @@ describe MultipleChoiceSubmittable, '#score' do it 'returns the score for the option with the given text' do - question = build_stubbed(:multiple_choice_question) - submittable = MultipleChoiceSubmittable.new(question: question) - submittable.options.target.stubs(score: 2) + question = create(:multiple_choice_question) + submittable = create( + :multiple_choice_submittable, + question: question + ) + create(:option, text: 'two', score: 2, question_id: submittable.id) - result = submittable.score('two') + result = question.reload.submittable.score('two') - submittable.options.target.should have_received(:score).with('two') result.should eq 2 end end diff --git a/example_app/spec/models/question_spec.rb b/example_app/spec/models/question_spec.rb index d6d3775..445b34b 100644 --- a/example_app/spec/models/question_spec.rb +++ b/example_app/spec/models/question_spec.rb @@ -7,7 +7,7 @@ it { should allow_value(type).for(:submittable_type) } end - it { should_not allow_value('Other').for(:submittable_type) } + it { should_not allow_value('Question').for(:submittable_type) } it { should validate_presence_of :title } diff --git a/example_app/spec/models/survey_inviter_spec.rb b/example_app/spec/models/survey_inviter_spec.rb index 866d001..27ef3c6 100644 --- a/example_app/spec/models/survey_inviter_spec.rb +++ b/example_app/spec/models/survey_inviter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe SurveyInviter, 'Validations' do - it { should ensure_length_of(:recipients).is_at_least(1) } + it { should validate_length_of(:recipients).is_at_least(1) } it { should validate_presence_of(:message) } it { should validate_presence_of(:sender) } it { should validate_presence_of(:survey) } @@ -17,7 +17,7 @@ end it 'returns false for an invalid recipient' do - SurveyInviter.new(invalid_params).invite.should be_false + SurveyInviter.new(invalid_params).invite.should be_falsey end it "doesn't send emails if any invitations fail to save" do @@ -27,7 +27,7 @@ params = valid_params(recipients: 'one@example.com,two@example.com') inviter = SurveyInviter.new(params) - expect { inviter.invite }.to raise_error(StandardError, 'failure') + lambda { inviter.invite }.should raise_error(StandardError, 'failure') invitation.should have_received(:deliver).never end diff --git a/example_app/spec/models/user_spec.rb b/example_app/spec/models/user_spec.rb index ba5e3d3..ac6afa8 100644 --- a/example_app/spec/models/user_spec.rb +++ b/example_app/spec/models/user_spec.rb @@ -30,6 +30,6 @@ it 'returns the users full name' do user = User.new(first_name: 'First', last_name: 'Last') - expect(user.full_name).to eq 'First Last' + user.full_name.should eq 'First Last' end end diff --git a/example_app/spec/spec_helper.rb b/example_app/spec/spec_helper.rb index 7b28caf..7be0cf9 100644 --- a/example_app/spec/spec_helper.rb +++ b/example_app/spec/spec_helper.rb @@ -2,7 +2,6 @@ ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' -require 'rspec/autorun' require 'capybara/rspec' # Requires supporting ruby files with custom matchers and macros, etc, @@ -12,9 +11,19 @@ RSpec.configure do |config| config.use_transactional_fixtures = true config.infer_base_class_for_anonymous_controllers = false + config.infer_spec_type_from_file_location! config.order = 'random' config.mock_framework = :mocha - config.include FactoryGirl::Syntax::Methods + config.raise_errors_for_deprecations! + config.include FactoryBot::Syntax::Methods config.include Features, type: :feature + config.expect_with(:rspec) { |c| c.syntax = :should } config.before(:each) { ActionMailer::Base.deliveries.clear } end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/example_app/spec/support/answer_creator.rb b/example_app/spec/support/answer_creator.rb index 016541b..dd69b99 100644 --- a/example_app/spec/support/answer_creator.rb +++ b/example_app/spec/support/answer_creator.rb @@ -1,5 +1,5 @@ class AnswerCreator - include FactoryGirl::Syntax::Methods + include FactoryBot::Syntax::Methods def initialize(survey, options = {}) @survey = survey diff --git a/example_app/spec/support/survey_maker.rb b/example_app/spec/support/survey_maker.rb index 9f2093b..a37d5e1 100644 --- a/example_app/spec/support/survey_maker.rb +++ b/example_app/spec/support/survey_maker.rb @@ -1,5 +1,5 @@ class SurveyMaker - include FactoryGirl::Syntax::Methods + include FactoryBot::Syntax::Methods def open_question(title) create :open_question, title: title, survey: survey diff --git a/example_app/spec/validators/email_validator_spec.rb b/example_app/spec/validators/email_address_validator_spec.rb similarity index 77% rename from example_app/spec/validators/email_validator_spec.rb rename to example_app/spec/validators/email_address_validator_spec.rb index 7c9a3df..2bf69ac 100644 --- a/example_app/spec/validators/email_validator_spec.rb +++ b/example_app/spec/validators/email_address_validator_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe EmailValidator, '#validate_each' do +describe EmailAddressValidator, '#validate_each' do it 'adds errors when given invalid email' do record = build_record('invalid_email') - EmailValidator.new(attributes: :email).validate(record) + EmailAddressValidator.new(attributes: :email).validate(record) record.errors.full_messages.should eq [ 'Email invalid_email is not a valid email' @@ -12,7 +12,7 @@ it 'does not add errors when given valid email' do record = build_record('valid@example.com') - EmailValidator.new(attributes: :email).validate(record) + EmailAddressValidator.new(attributes: :email).validate(record) record.errors.should be_empty end diff --git a/example_app/spec/validators/enumerable_validator_spec.rb b/example_app/spec/validators/enumerable_validator_spec.rb index 9800225..0287d90 100644 --- a/example_app/spec/validators/enumerable_validator_spec.rb +++ b/example_app/spec/validators/enumerable_validator_spec.rb @@ -6,7 +6,7 @@ EnumerableValidator.new( attributes: :emails, - validator: EmailValidator + validator: EmailAddressValidator ).validate(record) record.errors.full_messages.should eq [ diff --git a/example_app/vendor/javascript/.keep b/example_app/vendor/javascript/.keep new file mode 100644 index 0000000..e69de29