From f882e31df49ef9fcb8fd5eba60683940eb1d3b42 Mon Sep 17 00:00:00 2001 From: David Bird Date: Sat, 15 Sep 2018 20:41:45 +1000 Subject: [PATCH 01/41] DSC-9: WIP --- lib/generators/disco_app/USAGE | 5 - .../disco_app/disco_app_generator.rb | 236 ----------------- lib/generators/disco_app/install/USAGE | 5 + .../disco_app/install/install_generator.rb | 240 ++++++++++++++++++ .../assets/javascripts/application.js | 0 .../assets/javascripts/components.js | 0 .../assets/stylesheets/application.scss | 0 .../templates/config/database.yml.tt | 0 .../templates/config/newrelic.yml | 0 .../{ => install}/templates/config/puma.rb | 0 .../templates/controllers/home_controller.rb | 0 .../templates/initializers/disco_app.rb | 0 .../templates/initializers/rollbar.rb | 0 .../templates/initializers/session_store.rb | 0 .../templates/initializers/shopify_app.rb | 0 .../shopify_session_repository.rb | 0 .../templates/root/.codeclimate.yml | 0 .../templates/root/.editorconfig | 0 .../{ => install}/templates/root/.env | 0 .../root/.github/PULL_REQUEST_TEMPLATE.md | 0 .../{ => install}/templates/root/.gitignore | 0 .../{ => install}/templates/root/.rubocop.yml | 0 .../templates/root/.ruby-version | 0 .../{ => install}/templates/root/CHECKS | 0 .../{ => install}/templates/root/Procfile | 0 .../{ => install}/templates/root/README.md | 0 .../templates/views/home/index.html.erb | 0 lib/generators/disco_app/react/USAGE | 5 + .../disco_app/react/react_generator.rb | 99 ++++++++ .../embedded/api/base_controller.rb | 18 ++ .../embedded/api/home_controller.rb | 10 + .../embedded/api/shops_controller.rb | 11 + .../embedded/api/users_controller.rb | 11 + .../controllers/embedded/home_controller.rb | 13 + .../app/views/embedded/home/index.html.erb | 12 + .../app/views/layouts/embedded.html.erb | 10 + .../javascripts/embedded/components/App.jsx | 75 ++++++ .../embedded/components/HomePage.jsx | 34 +++ .../components/Shared/EmbeddedPage.jsx | 50 ++++ .../components/Shared/ErrorBanner.jsx | 57 +++++ .../components/Shared/PaginationWrapper.jsx | 10 + .../components/Shared/ScrollToTop.jsx | 23 ++ .../embedded/components/withApi.jsx | 125 +++++++++ .../webpack/javascripts/embedded/index.jsx | 38 +++ .../app/webpack/javascripts/embedded/utils.js | 15 ++ .../templates/app/webpack/packs/embedded.js | 2 + .../app/webpack/stylesheets/embedded.scss | 1 + .../config/initializers/mime_types.rb | 13 + .../templates/config/initializers/omniauth.rb | 27 ++ .../config/initializers/version.rb.tt | 7 + .../react/templates/config/webpack/staging.js | 5 + .../react/templates/config/webpacker.yml | 68 +++++ .../disco_app/react/templates/root/.babelrc | 33 +++ .../disco_app/react/templates/root/.eslintrc | 48 ++++ .../react/templates/root/.postcssrc.yml | 6 + .../react/templates/root/.prettierrc | 3 + .../disco_app/react/templates/root/VERSION | 1 + .../react/templates/root/package.json.tt | 40 +++ .../disco_app/templates/root/.env.local | 25 -- 59 files changed, 1115 insertions(+), 266 deletions(-) delete mode 100644 lib/generators/disco_app/USAGE delete mode 100644 lib/generators/disco_app/disco_app_generator.rb create mode 100644 lib/generators/disco_app/install/USAGE create mode 100644 lib/generators/disco_app/install/install_generator.rb rename lib/generators/disco_app/{ => install}/templates/assets/javascripts/application.js (100%) rename lib/generators/disco_app/{ => install}/templates/assets/javascripts/components.js (100%) rename lib/generators/disco_app/{ => install}/templates/assets/stylesheets/application.scss (100%) rename lib/generators/disco_app/{ => install}/templates/config/database.yml.tt (100%) rename lib/generators/disco_app/{ => install}/templates/config/newrelic.yml (100%) rename lib/generators/disco_app/{ => install}/templates/config/puma.rb (100%) rename lib/generators/disco_app/{ => install}/templates/controllers/home_controller.rb (100%) rename lib/generators/disco_app/{ => install}/templates/initializers/disco_app.rb (100%) rename lib/generators/disco_app/{ => install}/templates/initializers/rollbar.rb (100%) rename lib/generators/disco_app/{ => install}/templates/initializers/session_store.rb (100%) rename lib/generators/disco_app/{ => install}/templates/initializers/shopify_app.rb (100%) rename lib/generators/disco_app/{ => install}/templates/initializers/shopify_session_repository.rb (100%) rename lib/generators/disco_app/{ => install}/templates/root/.codeclimate.yml (100%) rename lib/generators/disco_app/{ => install}/templates/root/.editorconfig (100%) rename lib/generators/disco_app/{ => install}/templates/root/.env (100%) rename lib/generators/disco_app/{ => install}/templates/root/.github/PULL_REQUEST_TEMPLATE.md (100%) rename lib/generators/disco_app/{ => install}/templates/root/.gitignore (100%) rename lib/generators/disco_app/{ => install}/templates/root/.rubocop.yml (100%) rename lib/generators/disco_app/{ => install}/templates/root/.ruby-version (100%) rename lib/generators/disco_app/{ => install}/templates/root/CHECKS (100%) rename lib/generators/disco_app/{ => install}/templates/root/Procfile (100%) rename lib/generators/disco_app/{ => install}/templates/root/README.md (100%) rename lib/generators/disco_app/{ => install}/templates/views/home/index.html.erb (100%) create mode 100644 lib/generators/disco_app/react/USAGE create mode 100644 lib/generators/disco_app/react/react_generator.rb create mode 100644 lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb create mode 100644 lib/generators/disco_app/react/templates/app/controllers/embedded/api/home_controller.rb create mode 100644 lib/generators/disco_app/react/templates/app/controllers/embedded/api/shops_controller.rb create mode 100644 lib/generators/disco_app/react/templates/app/controllers/embedded/api/users_controller.rb create mode 100644 lib/generators/disco_app/react/templates/app/controllers/embedded/home_controller.rb create mode 100644 lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb create mode 100644 lib/generators/disco_app/react/templates/app/views/layouts/embedded.html.erb create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/PaginationWrapper.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ScrollToTop.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx create mode 100644 lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js create mode 100644 lib/generators/disco_app/react/templates/app/webpack/packs/embedded.js create mode 100644 lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss create mode 100644 lib/generators/disco_app/react/templates/config/initializers/mime_types.rb create mode 100644 lib/generators/disco_app/react/templates/config/initializers/omniauth.rb create mode 100644 lib/generators/disco_app/react/templates/config/initializers/version.rb.tt create mode 100644 lib/generators/disco_app/react/templates/config/webpack/staging.js create mode 100644 lib/generators/disco_app/react/templates/config/webpacker.yml create mode 100644 lib/generators/disco_app/react/templates/root/.babelrc create mode 100644 lib/generators/disco_app/react/templates/root/.eslintrc create mode 100644 lib/generators/disco_app/react/templates/root/.postcssrc.yml create mode 100644 lib/generators/disco_app/react/templates/root/.prettierrc create mode 100644 lib/generators/disco_app/react/templates/root/VERSION create mode 100644 lib/generators/disco_app/react/templates/root/package.json.tt delete mode 100644 lib/generators/disco_app/templates/root/.env.local diff --git a/lib/generators/disco_app/USAGE b/lib/generators/disco_app/USAGE deleted file mode 100644 index 75001248..00000000 --- a/lib/generators/disco_app/USAGE +++ /dev/null @@ -1,5 +0,0 @@ -Description: - Generate a new Shopify application. - -Example: - rails generate disco_app \ No newline at end of file diff --git a/lib/generators/disco_app/disco_app_generator.rb b/lib/generators/disco_app/disco_app_generator.rb deleted file mode 100644 index c155db51..00000000 --- a/lib/generators/disco_app/disco_app_generator.rb +++ /dev/null @@ -1,236 +0,0 @@ -class DiscoAppGenerator < Rails::Generators::Base - - source_root File.expand_path('../templates', __FILE__) - - # Copy a number of template files to the top-level directory of our application: - # - # - .env and .env.local for settings environment variables in development with dotenv-rails; - # - Slightly customised version of the default Rails .gitignore; - # - Default simple Procfile for Heroku; - # - .editorconfig to help enforce 2-space tabs, newlines and truncated whitespace for editors that support it. - # - README/PULL REQUEST template - # - def copy_root_files - %w(.editorconfig .env .env.local .gitignore .rubocop.yml .codeclimate.yml Procfile CHECKS README.md).each do |file| - copy_file "root/#{file}", file - end - directory 'root/.github' - end - - # Remove a number of root files. - def remove_root_files - %w(README.rdoc).each do |file| - remove_file file - end - end - - # Configure the application's Gemfile. - def configure_gemfile - # Remove sqlite. - gsub_file 'Gemfile', /^# Use sqlite3 as the database for Active Record\ngem 'sqlite3'/m, '' - - # Add gem requirements. - gem 'active_link_to' - gem 'acts_as_singleton' - gem 'classnames-rails' - gem 'newrelic_rpm' - gem 'nokogiri' - gem 'oj' - gem 'pg' - gem 'premailer-rails' - gem 'react-rails' - gem 'render_anywhere' - gem 'rollbar' - gem 'shopify_app' - gem 'sidekiq' - gem 'activeresource' - - # Indicate which gems should only be used in production. - gem_group :production do - gem 'mailgun_rails' - gem 'rails_12factor' - end - - # Indicate which gems should only be used in development and test. - gem_group :development, :test do - gem 'dotenv-rails' - gem 'minitest-reporters' - gem 'webmock' - gem 'mechanize' - end - end - - # copy template for pg configuration - def update_database_config - template 'config/database.yml.tt' - end - - # Run bundle install to add our new gems before running tasks. - def bundle_install - Bundler.with_clean_env do - run 'bundle install' - end - end - - # Make any required adjustments to the application configuration. - def configure_application - # The force_ssl flag is commented by default for production. - # Uncomment to ensure config.force_ssl = true in production. - uncomment_lines 'config/environments/production.rb', /force_ssl/ - - # Set time zone to UTC - application "config.time_zone = 'UTC'" - application "# Ensure UTC is the default timezone" - - # Set server side rendereing for components.js - application "config.react.server_renderer_options = {\nfiles: ['components.js'], # files to load for prerendering\n}" - application "# Enable server side react rendering" - - # Set defaults for various charge attributes. - application "config.x.shopify_charges_default_trial_days = 14\n" - application "config.x.shopify_charges_default_price = 10.00" - application "config.x.shopify_charges_default_type = :recurring" - application "# Set defaults for charges created by the application" - - # Set the "real charges" config variable to false explicitly by default. - # Only in production do we read from the environment variable and - # potentially have it become true. - application "config.x.shopify_charges_real = false\n" - application "# Explicitly prevent real charges being created by default" - application "config.x.shopify_charges_real = ENV['SHOPIFY_CHARGES_REAL'] == 'true'\n", env: :production - application "# Allow real charges in production with an ENV variable", env: :production - - # Configure session storage. - application "ActiveRecord::SessionStore::Session.table_name = 'disco_app_sessions'" - application "ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session" - application "# Configure custom session storage" - - # Set Sidekiq as the queue adapter in production. - application "config.active_job.queue_adapter = :sidekiq\n", env: :production - application "# Use Sidekiq as the active job backend", env: :production - - # Ensure the application configuration uses the DEFAULT_HOST environment - # variable to set up support for reverse routing absolute URLS (needed when - # generating Webhook URLs for example). - application "routes.default_url_options[:host] = ENV['DEFAULT_HOST']\n" - application "# Set the default host for absolute URL routing purposes" - - # Configure React in development and production. - application "config.react.variant = :development", env: :development - application "# Use development variant of React in development.", env: :development - application "config.react.variant = :production", env: :production - application "# Use production variant of React in production.", env: :production - - # Copy over the default puma configuration. - copy_file 'config/puma.rb', 'config/puma.rb' - - # Mail configuration - configuration = <<-CONFIG.strip_heredoc - - # Configure ActionMailer to use MailGun - if ENV['MAILGUN_API_KEY'] - config.action_mailer.delivery_method = :mailgun - config.action_mailer.mailgun_settings = { - api_key: ENV['MAILGUN_API_KEY'], - domain: ENV['MAILGUN_API_DOMAIN'] - } - end - CONFIG - application configuration, env: :production - - # Monitoring configuration - copy_file 'initializers/rollbar.rb', 'config/initializers/rollbar.rb' - copy_file 'config/newrelic.yml', 'config/newrelic.yml' - end - - - # Add entries to .env and .env.local - def add_env_variables - configuration = <<-CONFIG.strip_heredoc - - MAILGUN_API_KEY= - MAILGUN_API_DOMAIN= - CONFIG - append_to_file '.env', configuration - append_to_file '.env.local', configuration - end - - # Set up routes. - def setup_routes - route "mount DiscoApp::Engine, at: '/'" - end - - # Run generators. - def run_generators - generate 'shopify_app:install' - generate 'shopify_app:home_controller' - generate 'react:install' - end - - # Copy template files to the appropriate location. In some cases, we'll be - # overwriting or removing existing files or those created by ShopifyApp. - def copy_and_remove_files - # Copy initializers - copy_file 'initializers/shopify_app.rb', 'config/initializers/shopify_app.rb' - copy_file 'initializers/disco_app.rb', 'config/initializers/disco_app.rb' - copy_file 'initializers/shopify_session_repository.rb', 'config/initializers/shopify_session_repository.rb' - copy_file 'initializers/session_store.rb', 'config/initializers/session_store.rb' - - # Copy default home controller and view - copy_file 'controllers/home_controller.rb', 'app/controllers/home_controller.rb' - copy_file 'views/home/index.html.erb', 'app/views/home/index.html.erb' - - # Copy assets - copy_file 'assets/javascripts/application.js', 'app/assets/javascripts/application.js' - copy_file 'assets/javascripts/components.js', 'app/assets/javascripts/components.js' - copy_file 'assets/stylesheets/application.scss', 'app/assets/stylesheets/application.scss' - - # Remove application.css - remove_file 'app/assets/stylesheets/application.css' - - # Remove the layout files created by ShopifyApp - remove_file 'app/views/layouts/application.html.erb' - remove_file 'app/views/layouts/embedded_app.html.erb' - end - - # Add the Disco App test helper to test/test_helper.rb - def add_test_helper - inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", { after: "require 'rails/test_help'\n" } - end - - # Copy engine migrations over. - def install_migrations - rake 'disco_app:install:migrations' - end - - # Create PG database - def create_database - rake 'db:create' - end - - # Run migrations. - def migrate - rake 'db:migrate' - end - - # Lock down the application to a specific Ruby version: - # - # - Via .ruby-version file for rbenv in development; - # - Via a Gemfile line in production. - # - # This should be the last operation, to allow all other operations to run in the initial Ruby version. - def set_ruby_version - copy_file 'root/.ruby-version', '.ruby-version' - prepend_to_file 'Gemfile', "ruby '2.5.0'\n" - end - - private - - # This method of finding the component.js manifest taken from the - # install generator in react-rails. - # See https://github.com/reactjs/react-rails/blob/3f0af13fa755d6e95969c17728d0354c234f3a37/lib/generators/react/install_generator.rb#L53-L55 - def components - Pathname.new(destination_root).join('app/assets/javascripts', 'components.js') - end - -end diff --git a/lib/generators/disco_app/install/USAGE b/lib/generators/disco_app/install/USAGE new file mode 100644 index 00000000..82910c42 --- /dev/null +++ b/lib/generators/disco_app/install/USAGE @@ -0,0 +1,5 @@ +Description: + Generate a new Shopify application. + +Example: + rails generate disco_app:install diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb new file mode 100644 index 00000000..fd232a9d --- /dev/null +++ b/lib/generators/disco_app/install/install_generator.rb @@ -0,0 +1,240 @@ +module DiscoApp + module Generators + class InstallGenerator < Rails::Generators::Base + + source_root File.expand_path('templates', __dir__) + + # Copy a number of template files to the top-level directory of our application: + # + # - .env and .env.local for settings environment variables in development with dotenv-rails; + # - Slightly customised version of the default Rails .gitignore; + # - Default simple Procfile for Heroku; + # - .editorconfig to help enforce 2-space tabs, newlines and truncated whitespace for editors that support it. + # - README/PULL REQUEST template + # + def copy_root_files + %w(.editorconfig .env .env.local .gitignore .rubocop.yml .codeclimate.yml Procfile CHECKS README.md).each do |file| + copy_file "root/#{file}", file + end + directory 'root/.github' + end + + # Remove a number of root files. + def remove_root_files + %w(README.rdoc).each do |file| + remove_file file + end + end + + # Configure the application's Gemfile. + def configure_gemfile + # Remove sqlite. + gsub_file 'Gemfile', /^# Use sqlite3 as the database for Active Record\ngem 'sqlite3'/m, '' + + # Add gem requirements. + gem 'active_link_to' + gem 'acts_as_singleton' + gem 'classnames-rails' + gem 'newrelic_rpm' + gem 'nokogiri' + gem 'oj' + gem 'pg' + gem 'premailer-rails' + gem 'react-rails' + gem 'render_anywhere' + gem 'rollbar' + gem 'shopify_app' + gem 'sidekiq' + gem 'activeresource' + + # Indicate which gems should only be used in production. + gem_group :production do + gem 'mailgun_rails' + gem 'rails_12factor' + end + + # Indicate which gems should only be used in development and test. + gem_group :development, :test do + gem 'dotenv-rails' + gem 'minitest-reporters' + gem 'webmock' + gem 'mechanize' + end + end + + # copy template for pg configuration + def update_database_config + template 'config/database.yml.tt' + end + + # Run bundle install to add our new gems before running tasks. + def bundle_install + Bundler.with_clean_env do + run 'bundle install' + end + end + + # Make any required adjustments to the application configuration. + def configure_application + # The force_ssl flag is commented by default for production. + # Uncomment to ensure config.force_ssl = true in production. + uncomment_lines 'config/environments/production.rb', /force_ssl/ + + # Set time zone to UTC + application "config.time_zone = 'UTC'" + application "# Ensure UTC is the default timezone" + + # Set server side rendereing for components.js + application "config.react.server_renderer_options = {\nfiles: ['components.js'], # files to load for prerendering\n}" + application "# Enable server side react rendering" + + # Set defaults for various charge attributes. + application "config.x.shopify_charges_default_trial_days = 14\n" + application "config.x.shopify_charges_default_price = 10.00" + application "config.x.shopify_charges_default_type = :recurring" + application "# Set defaults for charges created by the application" + + # Set the "real charges" config variable to false explicitly by default. + # Only in production do we read from the environment variable and + # potentially have it become true. + application "config.x.shopify_charges_real = false\n" + application "# Explicitly prevent real charges being created by default" + application "config.x.shopify_charges_real = ENV['SHOPIFY_CHARGES_REAL'] == 'true'\n", env: :production + application "# Allow real charges in production with an ENV variable", env: :production + + # Configure session storage. + application "ActiveRecord::SessionStore::Session.table_name = 'disco_app_sessions'" + application "ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session" + application "# Configure custom session storage" + + # Set Sidekiq as the queue adapter in production. + application "config.active_job.queue_adapter = :sidekiq\n", env: :production + application "# Use Sidekiq as the active job backend", env: :production + + # Ensure the application configuration uses the DEFAULT_HOST environment + # variable to set up support for reverse routing absolute URLS (needed when + # generating Webhook URLs for example). + application "routes.default_url_options[:host] = ENV['DEFAULT_HOST']\n" + application "# Set the default host for absolute URL routing purposes" + + # Configure React in development and production. + application "config.react.variant = :development", env: :development + application "# Use development variant of React in development.", env: :development + application "config.react.variant = :production", env: :production + application "# Use production variant of React in production.", env: :production + + # Copy over the default puma configuration. + copy_file 'config/puma.rb', 'config/puma.rb' + + # Mail configuration + configuration = <<-CONFIG.strip_heredoc + + # Configure ActionMailer to use MailGun + if ENV['MAILGUN_API_KEY'] + config.action_mailer.delivery_method = :mailgun + config.action_mailer.mailgun_settings = { + api_key: ENV['MAILGUN_API_KEY'], + domain: ENV['MAILGUN_API_DOMAIN'] + } + end + CONFIG + application configuration, env: :production + + # Monitoring configuration + copy_file 'initializers/rollbar.rb', 'config/initializers/rollbar.rb' + copy_file 'config/newrelic.yml', 'config/newrelic.yml' + end + + + # Add entries to .env and .env.local + def add_env_variables + configuration = <<-CONFIG.strip_heredoc + + MAILGUN_API_KEY= + MAILGUN_API_DOMAIN= + CONFIG + append_to_file '.env', configuration + append_to_file '.env.local', configuration + end + + # Set up routes. + def setup_routes + route "mount DiscoApp::Engine, at: '/'" + end + + # Run generators. + def run_generators + generate 'shopify_app:install' + generate 'shopify_app:home_controller' + generate 'react:install' + end + + # Copy template files to the appropriate location. In some cases, we'll be + # overwriting or removing existing files or those created by ShopifyApp. + def copy_and_remove_files + # Copy initializers + copy_file 'initializers/shopify_app.rb', 'config/initializers/shopify_app.rb' + copy_file 'initializers/disco_app.rb', 'config/initializers/disco_app.rb' + copy_file 'initializers/shopify_session_repository.rb', 'config/initializers/shopify_session_repository.rb' + copy_file 'initializers/session_store.rb', 'config/initializers/session_store.rb' + + # Copy default home controller and view + copy_file 'controllers/home_controller.rb', 'app/controllers/home_controller.rb' + copy_file 'views/home/index.html.erb', 'app/views/home/index.html.erb' + + # Copy assets + copy_file 'assets/javascripts/application.js', 'app/assets/javascripts/application.js' + copy_file 'assets/javascripts/components.js', 'app/assets/javascripts/components.js' + copy_file 'assets/stylesheets/application.scss', 'app/assets/stylesheets/application.scss' + + # Remove application.css + remove_file 'app/assets/stylesheets/application.css' + + # Remove the layout files created by ShopifyApp + remove_file 'app/views/layouts/application.html.erb' + remove_file 'app/views/layouts/embedded_app.html.erb' + end + + # Add the Disco App test helper to test/test_helper.rb + def add_test_helper + inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", { after: "require 'rails/test_help'\n" } + end + + # Copy engine migrations over. + def install_migrations + rake 'disco_app:install:migrations' + end + + # Create PG database + def create_database + rake 'db:create' + end + + # Run migrations. + def migrate + rake 'db:migrate' + end + + # Lock down the application to a specific Ruby version: + # + # - Via .ruby-version file for rbenv in development; + # - Via a Gemfile line in production. + # + # This should be the last operation, to allow all other operations to run in the initial Ruby version. + def set_ruby_version + copy_file 'root/.ruby-version', '.ruby-version' + prepend_to_file 'Gemfile', "ruby '2.5.0'\n" + end + + private + + # This method of finding the component.js manifest taken from the + # install generator in react-rails. + # See https://github.com/reactjs/react-rails/blob/3f0af13fa755d6e95969c17728d0354c234f3a37/lib/generators/react/install_generator.rb#L53-L55 + def components + Pathname.new(destination_root).join('app/assets/javascripts', 'components.js') + end + + end + end +end \ No newline at end of file diff --git a/lib/generators/disco_app/templates/assets/javascripts/application.js b/lib/generators/disco_app/install/templates/assets/javascripts/application.js similarity index 100% rename from lib/generators/disco_app/templates/assets/javascripts/application.js rename to lib/generators/disco_app/install/templates/assets/javascripts/application.js diff --git a/lib/generators/disco_app/templates/assets/javascripts/components.js b/lib/generators/disco_app/install/templates/assets/javascripts/components.js similarity index 100% rename from lib/generators/disco_app/templates/assets/javascripts/components.js rename to lib/generators/disco_app/install/templates/assets/javascripts/components.js diff --git a/lib/generators/disco_app/templates/assets/stylesheets/application.scss b/lib/generators/disco_app/install/templates/assets/stylesheets/application.scss similarity index 100% rename from lib/generators/disco_app/templates/assets/stylesheets/application.scss rename to lib/generators/disco_app/install/templates/assets/stylesheets/application.scss diff --git a/lib/generators/disco_app/templates/config/database.yml.tt b/lib/generators/disco_app/install/templates/config/database.yml.tt similarity index 100% rename from lib/generators/disco_app/templates/config/database.yml.tt rename to lib/generators/disco_app/install/templates/config/database.yml.tt diff --git a/lib/generators/disco_app/templates/config/newrelic.yml b/lib/generators/disco_app/install/templates/config/newrelic.yml similarity index 100% rename from lib/generators/disco_app/templates/config/newrelic.yml rename to lib/generators/disco_app/install/templates/config/newrelic.yml diff --git a/lib/generators/disco_app/templates/config/puma.rb b/lib/generators/disco_app/install/templates/config/puma.rb similarity index 100% rename from lib/generators/disco_app/templates/config/puma.rb rename to lib/generators/disco_app/install/templates/config/puma.rb diff --git a/lib/generators/disco_app/templates/controllers/home_controller.rb b/lib/generators/disco_app/install/templates/controllers/home_controller.rb similarity index 100% rename from lib/generators/disco_app/templates/controllers/home_controller.rb rename to lib/generators/disco_app/install/templates/controllers/home_controller.rb diff --git a/lib/generators/disco_app/templates/initializers/disco_app.rb b/lib/generators/disco_app/install/templates/initializers/disco_app.rb similarity index 100% rename from lib/generators/disco_app/templates/initializers/disco_app.rb rename to lib/generators/disco_app/install/templates/initializers/disco_app.rb diff --git a/lib/generators/disco_app/templates/initializers/rollbar.rb b/lib/generators/disco_app/install/templates/initializers/rollbar.rb similarity index 100% rename from lib/generators/disco_app/templates/initializers/rollbar.rb rename to lib/generators/disco_app/install/templates/initializers/rollbar.rb diff --git a/lib/generators/disco_app/templates/initializers/session_store.rb b/lib/generators/disco_app/install/templates/initializers/session_store.rb similarity index 100% rename from lib/generators/disco_app/templates/initializers/session_store.rb rename to lib/generators/disco_app/install/templates/initializers/session_store.rb diff --git a/lib/generators/disco_app/templates/initializers/shopify_app.rb b/lib/generators/disco_app/install/templates/initializers/shopify_app.rb similarity index 100% rename from lib/generators/disco_app/templates/initializers/shopify_app.rb rename to lib/generators/disco_app/install/templates/initializers/shopify_app.rb diff --git a/lib/generators/disco_app/templates/initializers/shopify_session_repository.rb b/lib/generators/disco_app/install/templates/initializers/shopify_session_repository.rb similarity index 100% rename from lib/generators/disco_app/templates/initializers/shopify_session_repository.rb rename to lib/generators/disco_app/install/templates/initializers/shopify_session_repository.rb diff --git a/lib/generators/disco_app/templates/root/.codeclimate.yml b/lib/generators/disco_app/install/templates/root/.codeclimate.yml similarity index 100% rename from lib/generators/disco_app/templates/root/.codeclimate.yml rename to lib/generators/disco_app/install/templates/root/.codeclimate.yml diff --git a/lib/generators/disco_app/templates/root/.editorconfig b/lib/generators/disco_app/install/templates/root/.editorconfig similarity index 100% rename from lib/generators/disco_app/templates/root/.editorconfig rename to lib/generators/disco_app/install/templates/root/.editorconfig diff --git a/lib/generators/disco_app/templates/root/.env b/lib/generators/disco_app/install/templates/root/.env similarity index 100% rename from lib/generators/disco_app/templates/root/.env rename to lib/generators/disco_app/install/templates/root/.env diff --git a/lib/generators/disco_app/templates/root/.github/PULL_REQUEST_TEMPLATE.md b/lib/generators/disco_app/install/templates/root/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from lib/generators/disco_app/templates/root/.github/PULL_REQUEST_TEMPLATE.md rename to lib/generators/disco_app/install/templates/root/.github/PULL_REQUEST_TEMPLATE.md diff --git a/lib/generators/disco_app/templates/root/.gitignore b/lib/generators/disco_app/install/templates/root/.gitignore similarity index 100% rename from lib/generators/disco_app/templates/root/.gitignore rename to lib/generators/disco_app/install/templates/root/.gitignore diff --git a/lib/generators/disco_app/templates/root/.rubocop.yml b/lib/generators/disco_app/install/templates/root/.rubocop.yml similarity index 100% rename from lib/generators/disco_app/templates/root/.rubocop.yml rename to lib/generators/disco_app/install/templates/root/.rubocop.yml diff --git a/lib/generators/disco_app/templates/root/.ruby-version b/lib/generators/disco_app/install/templates/root/.ruby-version similarity index 100% rename from lib/generators/disco_app/templates/root/.ruby-version rename to lib/generators/disco_app/install/templates/root/.ruby-version diff --git a/lib/generators/disco_app/templates/root/CHECKS b/lib/generators/disco_app/install/templates/root/CHECKS similarity index 100% rename from lib/generators/disco_app/templates/root/CHECKS rename to lib/generators/disco_app/install/templates/root/CHECKS diff --git a/lib/generators/disco_app/templates/root/Procfile b/lib/generators/disco_app/install/templates/root/Procfile similarity index 100% rename from lib/generators/disco_app/templates/root/Procfile rename to lib/generators/disco_app/install/templates/root/Procfile diff --git a/lib/generators/disco_app/templates/root/README.md b/lib/generators/disco_app/install/templates/root/README.md similarity index 100% rename from lib/generators/disco_app/templates/root/README.md rename to lib/generators/disco_app/install/templates/root/README.md diff --git a/lib/generators/disco_app/templates/views/home/index.html.erb b/lib/generators/disco_app/install/templates/views/home/index.html.erb similarity index 100% rename from lib/generators/disco_app/templates/views/home/index.html.erb rename to lib/generators/disco_app/install/templates/views/home/index.html.erb diff --git a/lib/generators/disco_app/react/USAGE b/lib/generators/disco_app/react/USAGE new file mode 100644 index 00000000..4d474854 --- /dev/null +++ b/lib/generators/disco_app/react/USAGE @@ -0,0 +1,5 @@ +Description: + Generate boilerplate for a React-flavoured embedded app. + +Example: + rails generate disco_app:react diff --git a/lib/generators/disco_app/react/react_generator.rb b/lib/generators/disco_app/react/react_generator.rb new file mode 100644 index 00000000..27a60eef --- /dev/null +++ b/lib/generators/disco_app/react/react_generator.rb @@ -0,0 +1,99 @@ +module DiscoApp + module Generators + class ReactGenerator < Rails::Generators::Base + + source_root File.expand_path('templates', __dir__) + + # def prepare_application + # copy_file 'root/VERSION', 'VERSION' + # end + + # def configure_gemfile + # gem 'fast_jsonapi' + # gem 'multi_json' + # gem 'oj' + # gem 'olive_branch' + # gem 'webpacker' + # end + + # def bundle_install + # Bundler.with_clean_env do + # run 'bundle install' + # end + # end + + # def configure_application + # application 'config.middleware.use OliveBranch::Middleware' + # application '# Camel-case to underscore transformation for JSON requests.' + + # copy_file 'config/initializers/mime_types.rb' + # copy_file 'config/initializers/omniauth.rb' + # template 'config/initializers/version.rb.tt', 'config/initializers/version.rb' + + # %w[.env .env.local].each do |file| + # append_to_file file, 'BUGSNAG_API_KEY=00000000' + # end + # end + + # def update_routes + # routes = <<-ROUTES.gsub(/^ {8}/, '') + # # Embedded React routes. + # root to: 'embedded/home#index' + + # # Embedded API. + # namespace :embedded do + # namespace :api, constraints: { format: :json }, defaults: { format: :json } do + # resource :shop, only: [:show] + + # resources :users, only: [] do + # get :current, on: :collection + # end + # end + # end + # ROUTES + + # route routes + + # comment_lines 'config/routes.rb', "root to: 'home#index'" + # end + + # def install_webpacker + # rake 'webpacker:install' + # end + + # def configure_webpack + # %w[.babelrc .eslintrc .postcssrc.yml .prettierrc].each do |file| + # copy_file "root/#{file}", file + # end + + # template 'root/package.json.tt', 'package.json' + + # copy_file 'config/webpacker.yml' + # copy_file 'config/webpack/staging.js' + + # run "if [ -d 'app/javascript' ]; then mv -f app/javascript app/webpack; fi" + # end + + # def yarn_install + # run 'yarn install' + # end + + def configure_api + directory 'app/controllers/embedded' + end + + def configure_views + directory 'app/views/embedded' + copy_file 'app/views/layouts/embedded.html.erb' + end + + def configure_react + directory 'app/webpack/javascripts' + directory 'app/webpack/stylesheets' + copy_file 'app/webpack/packs/embedded.js' + remove_file 'app/webpack/packs/application.js' + end + + end + end +end \ No newline at end of file diff --git a/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb new file mode 100644 index 00000000..4d3842af --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb @@ -0,0 +1,18 @@ +module Embedded + module Api + class BaseController < ApplicationController + + include DiscoApp::Concerns::AuthenticatedController + include DiscoApp::Concerns::UserAuthenticatedController + + rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity + + private + + def unprocessable_entity(exception) + render json: ApiResponse.serialize(exception.record.errors), status: 422 + end + + end + end +end diff --git a/lib/generators/disco_app/react/templates/app/controllers/embedded/api/home_controller.rb b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/home_controller.rb new file mode 100644 index 00000000..f6f28930 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/home_controller.rb @@ -0,0 +1,10 @@ +module Embedded + module Api + class HomeController < BaseController + + def show + end + + end + end +end diff --git a/lib/generators/disco_app/react/templates/app/controllers/embedded/api/shops_controller.rb b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/shops_controller.rb new file mode 100644 index 00000000..c61aca44 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/shops_controller.rb @@ -0,0 +1,11 @@ +module Embedded + module Api + class ShopsController < BaseController + + def show + render json: ApiResponse.serialize(@shop) + end + + end + end +end diff --git a/lib/generators/disco_app/react/templates/app/controllers/embedded/api/users_controller.rb b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/users_controller.rb new file mode 100644 index 00000000..3ebfdca6 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/users_controller.rb @@ -0,0 +1,11 @@ +module Embedded + module Api + class UsersController < BaseController + + def current + render json: ApiResponse.serialize(@user) + end + + end + end +end diff --git a/lib/generators/disco_app/react/templates/app/controllers/embedded/home_controller.rb b/lib/generators/disco_app/react/templates/app/controllers/embedded/home_controller.rb new file mode 100644 index 00000000..b0660bf6 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/controllers/embedded/home_controller.rb @@ -0,0 +1,13 @@ +module Embedded + class HomeController < ApplicationController + + include DiscoApp::Concerns::AuthenticatedController + include DiscoApp::Concerns::UserAuthenticatedController + + layout 'embedded' + + def index + end + + end +end diff --git a/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb b/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb new file mode 100644 index 00000000..b2e3ea20 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb @@ -0,0 +1,12 @@ +
+<%= javascript_pack_tag 'embedded' %> +<%= stylesheet_pack_tag 'embedded' %> diff --git a/lib/generators/disco_app/react/templates/app/views/layouts/embedded.html.erb b/lib/generators/disco_app/react/templates/app/views/layouts/embedded.html.erb new file mode 100644 index 00000000..a9ec5a85 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/views/layouts/embedded.html.erb @@ -0,0 +1,10 @@ + + + + <%= ENV['SHOPIFY_APP_NAME'] %> + <%= csrf_meta_tags %> + + + <%= yield %> + + diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx new file mode 100644 index 00000000..ccf348da --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx @@ -0,0 +1,75 @@ +import axios from 'axios'; +import * as PropTypes from 'prop-types'; +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; +import HomePage from './HomePage'; +import withApi from './withApi'; + +class App extends React.Component { + static propTypes = { + api: PropTypes.func.isRequired, + parseApiResponse: PropTypes.func.isRequired + }; + + static childContextTypes = { + shop: PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + plan: PropTypes.string, + shopifyDomain: PropTypes.string + }), + user: PropTypes.shape({ + id: PropTypes.string, + email: PropTypes.string, + firstName: PropTypes.string, + lastName: PropTypes.string, + initials: PropTypes.string + }) + }; + + state = { + shop: null, + user: null + }; + + getChildContext() { + return { + shop: this.state.shop, + user: this.state.user + }; + } + + componentWillMount() { + const { api } = this.props; + + axios + .all([ + api.get('/embedded/api/shop'), + api.get('/embedded/api/users/current') + ]) + .then( + axios.spread( + async (shopResponse, usersResponse) => { + this.setState({ + shop: await this.props.parseApiResponse(shopResponse), + user: await this.props.parseApiResponse(usersResponse) + }); + } + ) + ); + } + + render() { + if (!this.state.user) return
; + + const HomePageWithApi = withApi(HomePage); + + return ( + + + + ); + } +} + +export default App; diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx new file mode 100644 index 00000000..1e7c5d32 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { EmptyState, FooterHelp, Layout, Link } from '@shopify/polaris'; +import EmbeddedPage from './Shared/EmbeddedPage'; + +const HomePage = props => ( + + +

Time to build a killer UI

+
+ + + Learn more about{' '} + + Disco + + 's{' '} + + Printed Mint app + + . + + +
+); + +export default HomePage; diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx new file mode 100644 index 00000000..7f5ef4c0 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx @@ -0,0 +1,50 @@ +import _ from 'lodash'; +import * as PropTypes from 'prop-types'; +import React from 'react'; +import ReactRouterPropTypes from 'react-router-prop-types'; +import { Page } from '@shopify/polaris'; + +class EmbeddedPage extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + history: ReactRouterPropTypes.history.isRequired, + title: PropTypes.string.isRequired + }; + + static contextTypes = { + easdk: PropTypes.object + }; + + componentDidMount() { + window.addEventListener('message', this.handleMessage); + + this.context.easdk.pushState(this.props.history.location.pathname); + } + + componentWillUnmount() { + window.removeEventListener('message', this.handleMessage); + } + + handleMessage = e => { + if (e.isTrusted) { + if (_.isString(e.data)) { + const json = JSON.parse(e.data); + + if (json.message === 'Shopify.API.setWindowLocation') { + const url = new URL(json.data); + this.props.history.push(url.pathname); + } + } + } + }; + + render() { + return ( + + {this.props.children} + + ); + } +} + +export default EmbeddedPage; diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx new file mode 100644 index 00000000..d198af6e --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx @@ -0,0 +1,57 @@ +import * as PropTypes from 'prop-types'; +import React from 'react'; +import { Banner, TextStyle } from '@shopify/polaris'; + +const ErrorBanner = ({ errors, prologue }) => { + const errorKeys = () => Object.keys(errors).sort(); + + const errorCount = () => errorKeys().length; + + const errorMessage = () => { + let msg = '1 field needs changes'; + + if (errorCount() > 1) { + msg = `${errorCount()} fields need changes`; + } + + return msg; + }; + + const separator = index => { + if (index === 0) return ' '; + + if (index === errorCount() - 1) return ' and '; + + return ', '; + }; + + if (errorCount() === 0) return null; + + return ( + +

+ {prologue}, {errorMessage()}: + {errorKeys().map((key, index) => ( + + {separator(index)} + + {key} + + + ))}. +

+
+ ); +}; + +ErrorBanner.defaultProps = { + errors: {}, + prologue: 'To save this form' +}; + +ErrorBanner.propTypes = { + errors: PropTypes.shape({}), + prologue: PropTypes.string +}; + +export default ErrorBanner; diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/PaginationWrapper.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/PaginationWrapper.jsx new file mode 100644 index 00000000..b2dfc968 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/PaginationWrapper.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Pagination } from '@shopify/polaris'; + +const PaginationWrapper = props => ( +
+ +
+); + +export default PaginationWrapper; diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ScrollToTop.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ScrollToTop.jsx new file mode 100644 index 00000000..840fffd8 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ScrollToTop.jsx @@ -0,0 +1,23 @@ +import * as PropTypes from 'prop-types'; +import React from 'react'; +import ReactRouterPropTypes from 'react-router-prop-types'; +import { withRouter } from 'react-router-dom'; + +class ScrollToTop extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + location: ReactRouterPropTypes.location.isRequired + }; + + componentDidUpdate(prevProps) { + if (this.props.location !== prevProps.location) { + window.scrollTo(0, 0); + } + } + + render() { + return this.props.children; + } +} + +export default withRouter(ScrollToTop); diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx new file mode 100644 index 00000000..b6d72620 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx @@ -0,0 +1,125 @@ +import axios from 'axios'; +import { deserialise } from 'kitsu-core'; +import _ from 'lodash'; +import qs from 'qs'; +import React from 'react'; + +function withApi(WrappedComponent) { + class WithApi extends React.Component { + constructor(props) { + super(props); + + this.initApi(); + } + + getErrorsFor = (field, errors) => { + const fieldErrors = errors[field]; + + if (!fieldErrors) return ''; + + return fieldErrors.join(', '); + }; + + initApi = () => { + const csrfToken = document + .getElementsByName('csrf-token')[0] + .getAttribute('content'); + + this.api = axios.create({ + headers: { + Accept: 'application/json', + 'X-CSRF-Token': csrfToken, + 'X-Key-Inflection': 'camel' + }, + paramsSerializer: params => qs.stringify(params), + timeout: 5000 + }); + }; + + parseApiResponse = async (response, includeMeta) => { + const result = await deserialise(response.data); + + return includeMeta ? result : result.data; + }; + + parseApiError = async errorResponse => { + const result = await deserialise(errorResponse.response.data); + + const parsedErrors = {}; + + result.errors.forEach(error => { + if ( + _.has(error, 'source.pointer') && + error.source.pointer.startsWith('/data/attributes/') + ) { + const attr = _.last(error.source.pointer.split('/')); + const msg = error.detail; + + if (_.has(parsedErrors, attr)) { + parsedErrors[attr].push(msg); + } else { + parsedErrors[attr] = [msg]; + } + } + }); + + return parsedErrors; + }; + + resourceListParams = state => { + const defaultPageSize = 25; + const params = {}; + + if (!state) return params; + + if (state.filters) { + params.filter = {}; + + state.filters.forEach( + filter => (params.filter[filter.key] = filter.value) + ); + } + + if (state.pageNumber) { + params.page = { + number: state.pageNumber, + size: state.pageSize || defaultPageSize + }; + } + + if (state.searchQuery) { + params.search = state.searchQuery; + } + + if (state.sortBy) { + const match = /(.*)_((?:a|de)sc)/.exec(state.sortBy); + + if (match) { + const field = match[1]; + const descSignifier = match[2] === 'desc' ? '-' : ''; + + params.sort = `${descSignifier}${field}`; + } + } + + return params; + }; + + render() { + return ( + + ); + } + } + + return WithApi; +} + +export default withApi; diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx new file mode 100644 index 00000000..ca5c5246 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx @@ -0,0 +1,38 @@ +import bugsnag from 'bugsnag-js'; +import createPlugin from 'bugsnag-react'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { AppProvider } from '@shopify/polaris'; +import App from './components/App'; +import ScrollToTop from './components/Shared/ScrollToTop'; +import withApi from './components/withApi'; + +const app = document.getElementById('app'); + +const bugsnagClient = bugsnag({ + apiKey: app.dataset.bugsnagApiKey, + appVersion: app.dataset.version, + releaseStage: app.dataset.environment +}); + +const ErrorBoundary = bugsnagClient.use(createPlugin(React)); + +const AppWithApi = withApi(App); + +ReactDOM.render( + + + + + + + + + , + app +); diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js new file mode 100644 index 00000000..1f58aea6 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js @@ -0,0 +1,15 @@ +import * as dateFormat from 'dateformat'; +import numeral from 'numeral'; + +export const numberToCurrency = amount => { + const format = '$0,0.00'; + const floatAmount = parseFloat(amount); + + if (floatAmount < 0) { + return `(${numeral(Math.abs(floatAmount)).format(format)})`; + } + + return numeral(floatAmount).format(format); +}; + +export const strftime = (date, format) => dateFormat(date, format); diff --git a/lib/generators/disco_app/react/templates/app/webpack/packs/embedded.js b/lib/generators/disco_app/react/templates/app/webpack/packs/embedded.js new file mode 100644 index 00000000..6d03264f --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/packs/embedded.js @@ -0,0 +1,2 @@ +import '../javascripts/embedded'; +import '../stylesheets/embedded.scss'; diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss new file mode 100644 index 00000000..0c02ec95 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss @@ -0,0 +1 @@ +@import '@shopify/polaris/styles.css'; diff --git a/lib/generators/disco_app/react/templates/config/initializers/mime_types.rb b/lib/generators/disco_app/react/templates/config/initializers/mime_types.rb new file mode 100644 index 00000000..0f09b36b --- /dev/null +++ b/lib/generators/disco_app/react/templates/config/initializers/mime_types.rb @@ -0,0 +1,13 @@ +API_JSON = 'application/vnd.api+json'.freeze + +Mime::Type.register(API_JSON, :jsonapi) + +parsers = ActionDispatch::Request.parameter_parsers.merge( + Mime[:jsonapi].symbol => ->(body) { JSON.parse(body) } +) +ActionDispatch::Request.parameter_parsers = parsers + +ActionController::Renderers.add :jsonapi do |obj, _options| + self.content_type ||= Mime[:jsonapi] + self.response_body = obj.to_json +end diff --git a/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb b/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb new file mode 100644 index 00000000..f08f6036 --- /dev/null +++ b/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb @@ -0,0 +1,27 @@ +module OmniAuth::Strategies + class ShopifyUser < Shopify + + def name + :shopify_user + end + + end +end + +SETUP_PROC = lambda do |env| + env['omniauth.strategy'].options[:per_user_permissions] = true + params = Rack::Utils.parse_query(env['QUERY_STRING']) + env['omniauth.strategy'].options[:client_options][:site] = "https://#{params['shop']}" +end + +Rails.application.config.middleware.use OmniAuth::Builder do + provider :shopify, + ShopifyApp.configuration.api_key, + ShopifyApp.configuration.secret, + scope: ShopifyApp.configuration.scope + provider :shopify_user, + ShopifyApp.configuration.api_key, + ShopifyApp.configuration.secret, + scope: ShopifyApp.configuration.scope, + setup: SETUP_PROC +end diff --git a/lib/generators/disco_app/react/templates/config/initializers/version.rb.tt b/lib/generators/disco_app/react/templates/config/initializers/version.rb.tt new file mode 100644 index 00000000..0d0c12a5 --- /dev/null +++ b/lib/generators/disco_app/react/templates/config/initializers/version.rb.tt @@ -0,0 +1,7 @@ +module <%= Rails.application.class.parent_name %> + class Application + + VERSION = File.read(Rails.root.join('VERSION')).chomp.freeze + + end +end diff --git a/lib/generators/disco_app/react/templates/config/webpack/staging.js b/lib/generators/disco_app/react/templates/config/webpack/staging.js new file mode 100644 index 00000000..93eb5804 --- /dev/null +++ b/lib/generators/disco_app/react/templates/config/webpack/staging.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'staging' + +const environment = require('./environment') + +module.exports = environment.toWebpackConfig() diff --git a/lib/generators/disco_app/react/templates/config/webpacker.yml b/lib/generators/disco_app/react/templates/config/webpacker.yml new file mode 100644 index 00000000..b70f13f2 --- /dev/null +++ b/lib/generators/disco_app/react/templates/config/webpacker.yml @@ -0,0 +1,68 @@ +# Note: You must restart bin/webpack-dev-server for changes to take effect + +default: &default + source_path: app/webpack + source_entry_path: packs + public_output_path: packs + cache_path: tmp/cache/webpacker + + # Additional paths webpack should lookup modules + # ['app/assets', 'engine/foo/app/assets'] + resolved_paths: [] + + # Reload manifest.json on all requests so we reload latest compiled packs + cache_manifest: false + + extensions: + - .jsx + - .js + - .sass + - .scss + - .css + - .module.sass + - .module.scss + - .module.css + - .png + - .svg + - .gif + - .jpeg + - .jpg + +development: + <<: *default + compile: true + + # Reference: https://webpack.js.org/configuration/dev-server/ + dev_server: + https: false + host: localhost + port: 3035 + public: localhost:3035 + hmr: false + # Inline should be set to true if using HMR + inline: true + overlay: true + compress: true + disable_host_check: true + use_local_ip: false + quiet: false + headers: + 'Access-Control-Allow-Origin': '*' + watch_options: + ignored: /node_modules/ + +test: + <<: *default + compile: true + + # Compile test packs to a separate directory + public_output_path: packs-test + +production: + <<: *default + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Cache manifest.json for performance + cache_manifest: true diff --git a/lib/generators/disco_app/react/templates/root/.babelrc b/lib/generators/disco_app/react/templates/root/.babelrc new file mode 100644 index 00000000..4f6a0ea4 --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/.babelrc @@ -0,0 +1,33 @@ +{ + "presets": [ + [ + "env", + { + "modules": false, + "targets": { + "browsers": "> 1%", + "uglify": true + }, + "useBuiltIns": true + } + ], + "react" + ], + "plugins": [ + "syntax-dynamic-import", + "transform-object-rest-spread", + [ + "transform-class-properties", + { + "spec": true + } + ], + [ + "transform-runtime", + { + "polyfill": false, + "regenerator": true + } + ] + ] +} diff --git a/lib/generators/disco_app/react/templates/root/.eslintrc b/lib/generators/disco_app/react/templates/root/.eslintrc new file mode 100644 index 00000000..4a6be233 --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/.eslintrc @@ -0,0 +1,48 @@ +{ + "root": true, + "extends": [ + "airbnb", + "plugin:prettier/recommended", + "plugin:import/warnings" + ], + "env": { + "browser": true, + "es6": true + }, + "parser": "babel-eslint", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 8, + "sourceType": "module" + }, + "rules": { + "import/first": "off", + "import/order": [ + "error", + { + "groups": [ + "builtin", + "external", + "parent", + "sibling", + "index" + ] + } + ], + "jsx-a11y/anchor-is-valid": "off", + "no-console": [ + "error", + { + "allow": [ + "error" + ] + } + ], + "no-return-assign": [ + "error", + "except-parens" + ] + } +} diff --git a/lib/generators/disco_app/react/templates/root/.postcssrc.yml b/lib/generators/disco_app/react/templates/root/.postcssrc.yml new file mode 100644 index 00000000..460b9da8 --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/.postcssrc.yml @@ -0,0 +1,6 @@ +plugins: + postcss-import: {} + postcss-cssnext: + features: + customProperties: + warnings: false diff --git a/lib/generators/disco_app/react/templates/root/.prettierrc b/lib/generators/disco_app/react/templates/root/.prettierrc new file mode 100644 index 00000000..544138be --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/lib/generators/disco_app/react/templates/root/VERSION b/lib/generators/disco_app/react/templates/root/VERSION new file mode 100644 index 00000000..8acdd82b --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/lib/generators/disco_app/react/templates/root/package.json.tt b/lib/generators/disco_app/react/templates/root/package.json.tt new file mode 100644 index 00000000..db13b25c --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/package.json.tt @@ -0,0 +1,40 @@ +{ + "name": "<%= Rails.application.class.parent_name.underscore.dasherize %>", + "private": true, + "dependencies": { + "@rails/webpacker": "3.5", + "@shopify/polaris": "2.1.2", + "axios": "^0.18.0", + "babel-preset-react": "^6.24.1", + "bugsnag-js": "^4.7.2", + "bugsnag-react": "^1.1.1", + "dateformat": "^3.0.3", + "kitsu-core": "^5.1.1", + "lodash": "^4.17.10", + "moment": "^2.22.2", + "numeral": "^2.0.6", + "pluralize": "^7.0.0", + "prop-types": "^15.6.1", + "qs": "^6.5.2", + "query-string": "^6.1.0", + "react": "^16.4.0", + "react-dom": "^16.4.0", + "react-router-dom": "^4.2.2", + "react-router-prop-types": "^1.0.3", + "regenerator": "^0.13.2", + "url-parse": "^1.4.1" + }, + "devDependencies": { + "babel-eslint": "8.2.3", + "babel-plugin-transform-runtime": "^6.23.0", + "eslint": "4.19.1", + "eslint-config-airbnb": "^16.1.0", + "eslint-config-prettier": "^2.9.0", + "eslint-plugin-import": "^2.12.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-prettier": "^2.6.0", + "eslint-plugin-react": "^7.9.1", + "prettier": "^1.13.4", + "webpack-dev-server": "2.11.2" + } +} diff --git a/lib/generators/disco_app/templates/root/.env.local b/lib/generators/disco_app/templates/root/.env.local deleted file mode 100644 index bfaeb228..00000000 --- a/lib/generators/disco_app/templates/root/.env.local +++ /dev/null @@ -1,25 +0,0 @@ -DEFAULT_HOST= - -SHOPIFY_APP_NAME= -SHOPIFY_APP_API_KEY= -SHOPIFY_APP_SECRET= -SHOPIFY_APP_SCOPE= -SHOPIFY_APP_PROXY_PREFIX= - -ADMIN_APP_USERNAME= -ADMIN_APP_PASSWORD= - -SHOPIFY_REAL_CHARGES= - -SKIP_PROXY_VERIFICATION= -SKIP_WEBHOOK_VERIFICATION= -SKIP_CARRIER_REQUEST_VERIFICATION= -SKIP_OAUTH= - -SECRET_KEY_BASE= - -REDIS_PROVIDER= - -DISCO_API_URL= - -WHITELISTED_DOMAINS= From 02225abd80997a2cfd44a3f4b776989ec0c5cf0c Mon Sep 17 00:00:00 2001 From: David Bird Date: Sat, 15 Sep 2018 20:52:11 +1000 Subject: [PATCH 02/41] DSC-9: WIP --- .../disco_app/react/react_generator.rb | 146 +++++++++--------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/lib/generators/disco_app/react/react_generator.rb b/lib/generators/disco_app/react/react_generator.rb index 27a60eef..564c3308 100644 --- a/lib/generators/disco_app/react/react_generator.rb +++ b/lib/generators/disco_app/react/react_generator.rb @@ -4,79 +4,79 @@ class ReactGenerator < Rails::Generators::Base source_root File.expand_path('templates', __dir__) - # def prepare_application - # copy_file 'root/VERSION', 'VERSION' - # end - - # def configure_gemfile - # gem 'fast_jsonapi' - # gem 'multi_json' - # gem 'oj' - # gem 'olive_branch' - # gem 'webpacker' - # end - - # def bundle_install - # Bundler.with_clean_env do - # run 'bundle install' - # end - # end - - # def configure_application - # application 'config.middleware.use OliveBranch::Middleware' - # application '# Camel-case to underscore transformation for JSON requests.' - - # copy_file 'config/initializers/mime_types.rb' - # copy_file 'config/initializers/omniauth.rb' - # template 'config/initializers/version.rb.tt', 'config/initializers/version.rb' - - # %w[.env .env.local].each do |file| - # append_to_file file, 'BUGSNAG_API_KEY=00000000' - # end - # end - - # def update_routes - # routes = <<-ROUTES.gsub(/^ {8}/, '') - # # Embedded React routes. - # root to: 'embedded/home#index' - - # # Embedded API. - # namespace :embedded do - # namespace :api, constraints: { format: :json }, defaults: { format: :json } do - # resource :shop, only: [:show] - - # resources :users, only: [] do - # get :current, on: :collection - # end - # end - # end - # ROUTES - - # route routes - - # comment_lines 'config/routes.rb', "root to: 'home#index'" - # end - - # def install_webpacker - # rake 'webpacker:install' - # end - - # def configure_webpack - # %w[.babelrc .eslintrc .postcssrc.yml .prettierrc].each do |file| - # copy_file "root/#{file}", file - # end - - # template 'root/package.json.tt', 'package.json' - - # copy_file 'config/webpacker.yml' - # copy_file 'config/webpack/staging.js' - - # run "if [ -d 'app/javascript' ]; then mv -f app/javascript app/webpack; fi" - # end - - # def yarn_install - # run 'yarn install' - # end + def prepare_application + copy_file 'root/VERSION', 'VERSION' + end + + def configure_gemfile + gem 'fast_jsonapi' + gem 'multi_json' + gem 'oj' + gem 'olive_branch' + gem 'webpacker' + end + + def bundle_install + Bundler.with_clean_env do + run 'bundle install' + end + end + + def configure_application + application 'config.middleware.use OliveBranch::Middleware' + application '# Camel-case to underscore transformation for JSON requests.' + + copy_file 'config/initializers/mime_types.rb' + copy_file 'config/initializers/omniauth.rb' + template 'config/initializers/version.rb.tt', 'config/initializers/version.rb' + + %w[.env .env.local].each do |file| + append_to_file file, 'BUGSNAG_API_KEY=00000000' + end + end + + def update_routes + routes = <<-ROUTES.gsub(/^ {8}/, '') + # Embedded React routes. + root to: 'embedded/home#index' + + # Embedded API. + namespace :embedded do + namespace :api, constraints: { format: :json }, defaults: { format: :json } do + resource :shop, only: [:show] + + resources :users, only: [] do + get :current, on: :collection + end + end + end + ROUTES + + route routes + + comment_lines 'config/routes.rb', "root to: 'home#index'" + end + + def install_webpacker + rake 'webpacker:install' + end + + def configure_webpack + %w[.babelrc .eslintrc .postcssrc.yml .prettierrc].each do |file| + copy_file "root/#{file}", file + end + + template 'root/package.json.tt', 'package.json' + + copy_file 'config/webpacker.yml' + copy_file 'config/webpack/staging.js' + + run "if [ -d 'app/javascript' ]; then mv -f app/javascript app/webpack; fi" + end + + def yarn_install + run 'yarn install' + end def configure_api directory 'app/controllers/embedded' From 3f324365bf5ead01a85763b216eba6997c07350d Mon Sep 17 00:00:00 2001 From: David Bird Date: Fri, 28 Sep 2018 11:19:21 +1000 Subject: [PATCH 03/41] DSC-9: typos --- .../app/webpack/javascripts/embedded/components/HomePage.jsx | 2 +- .../react/templates/app/webpack/javascripts/embedded/index.jsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx index 1e7c5d32..67aa23c0 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/HomePage.jsx @@ -23,7 +23,7 @@ const HomePage = props => ( 's{' '} - Printed Mint app + [Disco app] . diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx index ca5c5246..4b390e3a 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/index.jsx @@ -25,8 +25,9 @@ ReactDOM.render( From bb17d54ec43b731fcc1d08c628305c9d5828b6db Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Wed, 28 Nov 2018 11:20:27 +1100 Subject: [PATCH 04/41] Supress webhooks output in tests --- test/jobs/disco_app/app_installed_job_test.rb | 30 +++++++++--- .../synchronise_webhooks_job_test.rb | 46 +++++++++++++------ test/test_helper.rb | 3 +- 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/test/jobs/disco_app/app_installed_job_test.rb b/test/jobs/disco_app/app_installed_job_test.rb index 71b16018..043b017b 100644 --- a/test/jobs/disco_app/app_installed_job_test.rb +++ b/test/jobs/disco_app/app_installed_job_test.rb @@ -20,9 +20,11 @@ def teardown end test 'app installed job performs shop update job' do - # Assert the main install job can be enqueued and performed. - perform_enqueued_jobs do - DiscoApp::AppInstalledJob.perform_later(@shop) + with_suppressed_output do + # Assert the main install job can be enqueued and performed. + perform_enqueued_jobs do + DiscoApp::AppInstalledJob.perform_later(@shop) + end end # Assert the update shop job was performed. @@ -33,8 +35,10 @@ def teardown test 'app installed job automatically subscribes stores to the correct default plan' do @shop.current_subscription.destroy - perform_enqueued_jobs do - DiscoApp::AppInstalledJob.perform_later(@shop) + with_suppressed_output do + perform_enqueued_jobs do + DiscoApp::AppInstalledJob.perform_later(@shop) + end end # Assert the shop was subscribed to the development plan. @@ -44,8 +48,10 @@ def teardown test 'app installed job automatically subscribes stores to the correct default plan with a plan code and a source' do @shop.current_subscription.destroy - perform_enqueued_jobs do - DiscoApp::AppInstalledJob.perform_later(@shop, 'PODCAST', 'smp') + with_suppressed_output do + perform_enqueued_jobs do + DiscoApp::AppInstalledJob.perform_later(@shop, 'PODCAST', 'smp') + end end # Assert the shop was subscribed to the development plan. @@ -54,4 +60,14 @@ def teardown assert_equal 'smpodcast', @shop.current_subscription.source.name end + private + # Prevents the output from the webhook synchronisation from + # printing to STDOUT and messing up the test output + def with_suppressed_output + original_stdout = $stdout.clone + $stdout.reopen(File.new('/dev/null', 'w')) + yield + ensure + $stdout.reopen(original_stdout) + end end diff --git a/test/jobs/disco_app/synchronise_webhooks_job_test.rb b/test/jobs/disco_app/synchronise_webhooks_job_test.rb index d79d81e4..5305843e 100644 --- a/test/jobs/disco_app/synchronise_webhooks_job_test.rb +++ b/test/jobs/disco_app/synchronise_webhooks_job_test.rb @@ -13,30 +13,46 @@ def teardown end test 'webhook synchronisation job creates webhooks for all expected topics' do - stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/webhooks').to_json) - stub_request(:post, "#{@shop.admin_url}/webhooks.json").to_return(status: 200) + with_suppressed_output do + stub_request(:get, "#{@shop.admin_url}/webhooks.json").to_return(status: 200, body: api_fixture('widget_store/webhooks').to_json) + stub_request(:post, "#{@shop.admin_url}/webhooks.json").to_return(status: 200) - perform_enqueued_jobs do - DiscoApp::SynchroniseWebhooksJob.perform_later(@shop) - end + perform_enqueued_jobs do + DiscoApp::SynchroniseWebhooksJob.perform_later(@shop) + end - # Assert that all 4 expected webhook topics were POSTed to. - ['app/uninstalled', 'shop/update', 'orders/create', 'orders/paid'].each do |expected_webhook_topic| - assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 1) { |request| request.body.include?(expected_webhook_topic) } + # Assert that all 4 expected webhook topics were POSTed to. + ['app/uninstalled', 'shop/update', 'orders/create', 'orders/paid'].each do |expected_webhook_topic| + assert_requested(:post, "#{@shop.admin_url}/webhooks.json", times: 1) { |request| request.body.include?(expected_webhook_topic) } + end end end test 'returns error messages for webhooks that cannot be registered' do VCR.use_cassette('webhook_failure') do - output = capture_io do - perform_enqueued_jobs do - DiscoApp::SynchroniseWebhooksJob.perform_later(@shop) + with_suppressed_output do + output = capture_io do + perform_enqueued_jobs do + DiscoApp::SynchroniseWebhooksJob.perform_later(@shop) + end end - end - assert output.first.include?('Invalid topic specified.') - assert output.first.include?('orders/create - not registered') - end + assert output.first.include?('Invalid topic specified.') + assert output.first.include?('orders/create - not registered') + end + end end + + private + # Prevents the output from the webhook synchronisation from + # printing to STDOUT and messing up the test output + def with_suppressed_output + original_stdout = $stdout.clone + $stdout.reopen(File.new('/dev/null', 'w')) + yield + ensure + $stdout.reopen(original_stdout) + end end + diff --git a/test/test_helper.rb b/test/test_helper.rb index 627bef5f..b52af53e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -48,7 +48,7 @@ # Minitest helpers to give a better formatted and more helpful output in Rubymine require 'minitest/reporters' require 'minitest/autorun' -MiniTest::Reporters.use! +MiniTest::Reporters.use! Minitest::Reporters::SpecReporter.new # Set up the base test class. class ActiveSupport::TestCase @@ -67,5 +67,4 @@ def log_out session[:shopify] = nil session[:shopify_domain] = nil end - end From 8daf5c771d25b51ddc7b4b80050d8f267dc2b8b0 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 18 Dec 2018 15:39:05 +1100 Subject: [PATCH 05/41] Merge develop into DSC-9 --- CHANGELOG.md | 15 +- UPGRADING.md | 12 +- VERSION | 2 +- app/clients/disco_app/rollbar_client.rb | 53 --- app/clients/disco_app/rollbar_client_error.rb | 2 - app/jobs/disco_app/shop_job.rb | 17 +- .../disco_app/concerns/renders_assets.rb | 2 +- app/models/disco_app/concerns/shop.rb | 7 +- config/routes.rb | 2 +- ...0525000000_create_shops_if_not_existent.rb | 160 ++++---- ...20170315062548_create_disco_app_sources.rb | 2 + ...62629_add_sources_to_shop_subscriptions.rb | 2 + .../20170327214540_create_disco_app_users.rb | 3 +- ...0170606160751_fix_disco_app_users_index.rb | 2 + disco_app.gemspec | 4 +- initialise.sh | 4 +- lib/disco_app/version.rb | 2 +- .../install/templates/config/database.yml.tt | 9 +- .../install/templates/config/newrelic.yml | 3 + .../templates/controllers/home_controller.rb | 1 + .../install/templates/initializers/rollbar.rb | 23 -- .../templates/initializers/session_store.rb | 2 +- .../disco_app/install/templates/root/.env | 3 + .../install/templates/root/.rubocop.yml | 381 ++++++++++-------- .../disco_app/templates/config/appsignal.yml | 12 + .../disco_app/templates/config/cable.yml.tt | 11 + .../templates/config/environments/staging.rb | 108 +++++ lib/tasks/rollbar.rake | 24 -- test/dummy/config/database.yml | 3 + test/dummy/config/environments/staging.rb | 85 ++++ test/dummy/config/secrets.yml | 3 + test/dummy/db/schema.rb | 2 +- 32 files changed, 584 insertions(+), 377 deletions(-) delete mode 100644 app/clients/disco_app/rollbar_client.rb delete mode 100644 app/clients/disco_app/rollbar_client_error.rb delete mode 100644 lib/generators/disco_app/install/templates/initializers/rollbar.rb create mode 100644 lib/generators/disco_app/templates/config/appsignal.yml create mode 100644 lib/generators/disco_app/templates/config/cable.yml.tt create mode 100644 lib/generators/disco_app/templates/config/environments/staging.rb delete mode 100644 lib/tasks/rollbar.rake create mode 100644 test/dummy/config/environments/staging.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index adc658ea..f2762715 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Change Log All notable changes to this project will be documented in this file. -## Unreleased -No unreleased changes. +## 0.16.0 - 2018-10-01 +### Changed +- Update to Rails 5.2 +- Add support for staging environment +- Update Rubocop config and make sure generated files comply ## 0.15.2 - 2018-08-05 ### Changed @@ -27,7 +30,7 @@ No unreleased changes. ## 0.14.4 - 2018-05-18 ### Changed -- Bugfix for case-insensitive comparison in `has_tag?` taggable method +- Bugfix for case-insensitive comparison in `has_tag?` taggable method ## 0.14.3 - 2018-05-02 ### Changed @@ -49,7 +52,7 @@ No unreleased changes. ## 0.14.0 - 2018-02-28 ### Added - Add README template -- Add Rollbar Rake task +- Add Rollbar Rake task ### Changed - Update to Ruby 2.5.0 @@ -230,7 +233,7 @@ as the login redirection is handled from shopify omniauth ### Added - `Taggable` concern for models representing synchronised Shopify resources that can have tags applied. -- `synchronise_all` class method for models with the `Synchronises` concern. +- `synchronise_all` class method for models with the `Synchronises` concern. ## 0.9.6 - 2016-06-08 ### Added @@ -338,7 +341,7 @@ as the login redirection is handled from shopify omniauth - Move `Rails.configuration.x.shopify_app_name` to `DiscoApp.configuration` - Move `Rails.configuration.x.shopify_app_proxy_prefix` to `DiscoApp.configuration.app_proxy_prefix` -- Update to latest versions of monitoring gems +- Update to latest versions of monitoring gems ## 0.8.3 - 2016-02-04 ### Changed diff --git a/UPGRADING.md b/UPGRADING.md index 2df4b0e0..10716d52 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,19 +3,27 @@ This file contains more detailed instructions on what's required when updating an application between one release version of the gem to the next. It's intended as more in-depth accompaniment to the notes in `CHANGELOG.md` for each version. +## Upgrading from 0.15.2 to 0.16.0 (inclusive) +Upgrade your app to Rails version 5.2. See the [Rails upgrade docs](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-5-1-to-rails-5-2). + +One big change is the introduction of the [Credentials API](https://github.com/rails/rails/pull/30067), which is intended to replace `config/secrets.yml` and `config/secrets.yml.enc`, and works much like [Ansible Vault](https://docs.ansible.com/ansible/2.6/user_guide/vault.html). There's no need to migrate old secrets usage, since the two behaviours can sit side by side. However, if you do want to try it out, make sure to add the following to your `config/environments/*.rb` file: +``` +config.require_master_key = true +``` + ## Upgrading from 0.14.0 to 0.15.2 (inclusive) No changes required. ## Upgrading from 0.13.8 to 0.14.0 Update your app's `.ruby-version` to 2.5.0. -Upgrade your app to Rails version 5.1. See [the wiki](https://github.com/discolabs/disco_app/wiki/Upgrade-to-Rails-5.1) +Upgrade your app to Rails version 5.1. See [the wiki](https://github.com/discolabs/disco_app/wiki/Upgrade-to-Rails-5.1) for detailed instructions on this upgrade. ## Upgrading from 0.13.7 to 0.13.8 Update your app's `.ruby-version` to 2.4.1. -Upgrade your app to Rails version 4.2.8. +Upgrade your app to Rails version 4.2.8. ``` # when using homebrew and rbenv: diff --git a/VERSION b/VERSION index 4312e0d0..04a373ef 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.15.2 +0.16.0 diff --git a/app/clients/disco_app/rollbar_client.rb b/app/clients/disco_app/rollbar_client.rb deleted file mode 100644 index 77f0af1f..00000000 --- a/app/clients/disco_app/rollbar_client.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'rest-client' - -class DiscoApp::RollbarClient - - API_URL = 'https://api.rollbar.com/api/1' - CREATE_PROJECT_ENDPOINT = '/projects' - ACCESS_TOKEN_ENDPOINT = '/project' - ACCESS_TOKEN_SCOPE = 'post_server_item' - - def initialize(params) - @write_access_token = params[:write_account_access_token] - @read_access_token = params[:read_account_access_token] - end - - # Create project on Rollbar, returns it new post server side access token - def create_project(name) - begin - response = RestClient::Request.execute( - method: :post, - headers: { content_type: :json }, - url: create_api_url, - payload: { name: name.parameterize }.to_json - ) - request_access_token(ActiveSupport::JSON.decode(response).dig('result', 'id')) - rescue RestClient::BadRequest => e - raise RollbarClientError.new(e.message) - end - end - - private - - def request_access_token(project_id) - begin - response = RestClient.get(access_tokens_api_url(project_id)) - # Only return post_server_item server side access token - post_server_access_token(ActiveSupport::JSON.decode(response)['result']) - rescue RestClient::BadRequest => e - raise RollbarClientError.new(e.message) - end - end - - def create_api_url - API_URL + CREATE_PROJECT_ENDPOINT + "?access_token=#{@write_access_token}" - end - - def access_tokens_api_url(project_id) - API_URL + ACCESS_TOKEN_ENDPOINT + "/#{project_id}/access_tokens?access_token=#{@read_access_token}" - end - - def post_server_access_token(results) - results.select { |x| x['name'] == ACCESS_TOKEN_SCOPE }.first['access_token'] - end -end diff --git a/app/clients/disco_app/rollbar_client_error.rb b/app/clients/disco_app/rollbar_client_error.rb deleted file mode 100644 index ac4e28e6..00000000 --- a/app/clients/disco_app/rollbar_client_error.rb +++ /dev/null @@ -1,2 +0,0 @@ -class RollbarClientError < StandardError -end diff --git a/app/jobs/disco_app/shop_job.rb b/app/jobs/disco_app/shop_job.rb index b69638ad..fb02d51a 100644 --- a/app/jobs/disco_app/shop_job.rb +++ b/app/jobs/disco_app/shop_job.rb @@ -1,10 +1,10 @@ +require 'appsignal' + # The base class for all jobs that should be performed in the context of a # particular Shop's API session. The first argument to any job inheriting from # this class must be the domain of the relevant store, so that the appropriate # Shop model can be fetched and the temporary API session created. -require 'rollbar' - class DiscoApp::ShopJob < ApplicationJob queue_as :default @@ -22,13 +22,12 @@ def find_shop(job) end def shop_context(job, block) - Rollbar.scoped(rollbar_scope) do - @shop.with_api_context { block.call(job.arguments) } - end - end - - def rollbar_scope - { person: { id: @shop.id, username: @shop.shopify_domain } } + Appsignal.tag_request( + shop_id: @shop.id, + shopify_domain: @shop.shopify_domain + ) + + @shop.with_api_context { block.call(job.arguments) } end end diff --git a/app/models/disco_app/concerns/renders_assets.rb b/app/models/disco_app/concerns/renders_assets.rb index 0db3318b..b3333c48 100644 --- a/app/models/disco_app/concerns/renders_assets.rb +++ b/app/models/disco_app/concerns/renders_assets.rb @@ -67,7 +67,7 @@ def renders_assets_default_options assets: nil, triggered_by: nil, script_tags: nil, - minify: Rails.env.production? + minify: Rails.env.production? || Rails.env.staging? } end diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index e7e59de9..f17ceb3c 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -72,12 +72,7 @@ def protocol def admin_url "https://#{shopify_domain}/admin" end - - # Convenience method to get the email of the shop's admin, to display in Rollbar. - def email - data[:email] - end - + def installed_duration distance_of_time_in_words_to_now(created_at.time) end diff --git a/config/routes.rb b/config/routes.rb index 2accb69a..1ae6217f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,7 +46,7 @@ end # Make the Sidekiq Web UI accessible using the same credentials as the admin. - if Rails.env.production? + if Rails.env.production? || Rails.env.staging? Sidekiq::Web.use Rack::Auth::Basic do |username, password| [ ENV['ADMIN_APP_USERNAME'].present?, diff --git a/db/migrate/20150525000000_create_shops_if_not_existent.rb b/db/migrate/20150525000000_create_shops_if_not_existent.rb index 12f5fcce..397b0ae5 100644 --- a/db/migrate/20150525000000_create_shops_if_not_existent.rb +++ b/db/migrate/20150525000000_create_shops_if_not_existent.rb @@ -6,105 +6,105 @@ class CreateShopsIfNotExistent < ActiveRecord::Migration[5.1] def change return if table_exists? :disco_app_shops - create_table "disco_app_app_settings", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + create_table :disco_app_app_settings, force: :cascade do |t| + t.datetime :created_at, null: false + t.datetime :updated_at, null: false end - create_table "disco_app_application_charges", force: :cascade do |t| - t.integer "shop_id", limit: 8 - t.integer "subscription_id", limit: 8 - t.integer "status", default: 0 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "shopify_id", limit: 8 - t.string "confirmation_url" + create_table :disco_app_application_charges, force: :cascade do |t| + t.integer :shop_id, limit: 8 + t.integer :subscription_id, limit: 8 + t.integer :status, default: 0 + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.integer :shopify_id, limit: 8 + t.string :confirmation_url end - create_table "disco_app_plan_codes", force: :cascade do |t| - t.integer "plan_id", limit: 8 - t.string "code" - t.integer "trial_period_days" - t.integer "amount" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "status", default: 0 + create_table :disco_app_plan_codes, force: :cascade do |t| + t.integer :plan_id, limit: 8 + t.string :code + t.integer :trial_period_days + t.integer :amount + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.integer :status, default: 0 end - create_table "disco_app_plans", force: :cascade do |t| - t.integer "status", default: 0 - t.string "name" - t.integer "plan_type", default: 0 - t.integer "trial_period_days" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "amount", default: 0 - t.string "currency", default: "USD" - t.integer "interval", default: 0 - t.integer "interval_count", default: 1 + create_table :disco_app_plans, force: :cascade do |t| + t.integer :status, default: 0 + t.string :name + t.integer :plan_type, default: 0 + t.integer :trial_period_days + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.integer :amount, default: 0 + t.string :currency, default: 'USD' + t.integer :interval, default: 0 + t.integer :interval_count, default: 1 end - create_table "disco_app_recurring_application_charges", force: :cascade do |t| - t.integer "shop_id", limit: 8 - t.integer "subscription_id", limit: 8 - t.integer "status", default: 0 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "shopify_id", limit: 8 - t.string "confirmation_url" + create_table :disco_app_recurring_application_charges, force: :cascade do |t| + t.integer :shop_id, limit: 8 + t.integer :subscription_id, limit: 8 + t.integer :status, default: 0 + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.integer :shopify_id, limit: 8 + t.string :confirmation_url end - create_table "disco_app_sessions", force: :cascade do |t| - t.string "session_id", null: false - t.text "data" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "shop_id" + create_table :disco_app_sessions, force: :cascade do |t| + t.string :session_id, null: false + t.text :data + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.integer :shop_id end - add_index "disco_app_sessions", ["session_id"], name: "index_disco_app_sessions_on_session_id", unique: true, using: :btree - add_index "disco_app_sessions", ["updated_at"], name: "index_disco_app_sessions_on_updated_at", using: :btree + add_index :disco_app_sessions, [:session_id], name: 'index_disco_app_sessions_on_session_id', unique: true, using: :btree + add_index :disco_app_sessions, [:updated_at], name: 'index_disco_app_sessions_on_updated_at', using: :btree - create_table "disco_app_shops", force: :cascade do |t| - t.string "shopify_domain", null: false - t.string "shopify_token", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "status", default: 0 - t.string "domain" - t.string "plan_name" - t.string "name" - t.jsonb "data", default: {} + create_table :disco_app_shops, force: :cascade do |t| + t.string :shopify_domain, null: false + t.string :shopify_token, null: false + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.integer :status, default: 0 + t.string :domain + t.string :plan_name + t.string :name + t.jsonb :data, default: {} end - add_index "disco_app_shops", ["shopify_domain"], name: "index_disco_app_shops_on_shopify_domain", unique: true, using: :btree + add_index :disco_app_shops, [:shopify_domain], name: 'index_disco_app_shops_on_shopify_domain', unique: true, using: :btree - create_table "disco_app_subscriptions", force: :cascade do |t| - t.integer "shop_id" - t.integer "plan_id" - t.integer "status" - t.integer "subscription_type" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.datetime "trial_start_at" - t.datetime "trial_end_at" - t.datetime "cancelled_at" - t.integer "amount", default: 0 - t.integer "plan_code_id", limit: 8 - t.string "source" - t.integer "trial_period_days" + create_table :disco_app_subscriptions, force: :cascade do |t| + t.integer :shop_id + t.integer :plan_id + t.integer :status + t.integer :subscription_type + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.datetime :trial_start_at + t.datetime :trial_end_at + t.datetime :cancelled_at + t.integer :amount, default: 0 + t.integer :plan_code_id, limit: 8 + t.string :source + t.integer :trial_period_days end - add_index "disco_app_subscriptions", ["plan_id"], name: "index_disco_app_subscriptions_on_plan_id", using: :btree - add_index "disco_app_subscriptions", ["shop_id"], name: "index_disco_app_subscriptions_on_shop_id", using: :btree + add_index :disco_app_subscriptions, [:plan_id], name: 'index_disco_app_subscriptions_on_plan_id', using: :btree + add_index :disco_app_subscriptions, [:shop_id], name: 'index_disco_app_subscriptions_on_shop_id', using: :btree - add_foreign_key "disco_app_application_charges", "disco_app_shops", column: "shop_id" - add_foreign_key "disco_app_application_charges", "disco_app_subscriptions", column: "subscription_id" - add_foreign_key "disco_app_plan_codes", "disco_app_plans", column: "plan_id" - add_foreign_key "disco_app_recurring_application_charges", "disco_app_shops", column: "shop_id" - add_foreign_key "disco_app_recurring_application_charges", "disco_app_subscriptions", column: "subscription_id" - add_foreign_key "disco_app_sessions", "disco_app_shops", column: "shop_id", on_delete: :cascade - add_foreign_key "disco_app_subscriptions", "disco_app_plan_codes", column: "plan_code_id" + add_foreign_key :disco_app_application_charges, :disco_app_shops, column: :shop_id + add_foreign_key :disco_app_application_charges, :disco_app_subscriptions, column: :subscription_id + add_foreign_key :disco_app_plan_codes, :disco_app_plans, column: :plan_id + add_foreign_key :disco_app_recurring_application_charges, :disco_app_shops, column: :shop_id + add_foreign_key :disco_app_recurring_application_charges, :disco_app_subscriptions, column: :subscription_id + add_foreign_key :disco_app_sessions, :disco_app_shops, column: :shop_id, on_delete: :cascade + add_foreign_key :disco_app_subscriptions, :disco_app_plan_codes, column: :plan_code_id end end diff --git a/db/migrate/20170315062548_create_disco_app_sources.rb b/db/migrate/20170315062548_create_disco_app_sources.rb index e5dcdbc0..a099f758 100644 --- a/db/migrate/20170315062548_create_disco_app_sources.rb +++ b/db/migrate/20170315062548_create_disco_app_sources.rb @@ -1,4 +1,5 @@ class CreateDiscoAppSources < ActiveRecord::Migration[5.1] + def change create_table :disco_app_sources do |t| t.string :source, null: true @@ -7,4 +8,5 @@ def change end add_index :disco_app_sources, :source end + end diff --git a/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb b/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb index 0b6a123a..870c6e97 100644 --- a/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb +++ b/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb @@ -1,4 +1,5 @@ class AddSourcesToShopSubscriptions < ActiveRecord::Migration[5.1] + def change add_column :disco_app_subscriptions, :source_id, :integer, limit: 8, index: true add_foreign_key :disco_app_subscriptions, :disco_app_sources, column: :source_id @@ -11,4 +12,5 @@ def change remove_column :disco_app_subscriptions, :source end + end diff --git a/db/migrate/20170327214540_create_disco_app_users.rb b/db/migrate/20170327214540_create_disco_app_users.rb index 2dd039eb..af379ac4 100644 --- a/db/migrate/20170327214540_create_disco_app_users.rb +++ b/db/migrate/20170327214540_create_disco_app_users.rb @@ -1,13 +1,14 @@ class CreateDiscoAppUsers < ActiveRecord::Migration[5.1] + def change create_table :disco_app_users do |t| t.integer :shop_id, limit: 8 t.string :first_name t.string :last_name t.string :email - t.timestamps null: false end add_index :disco_app_users, :shop_id, unique: true end + end diff --git a/db/migrate/20170606160751_fix_disco_app_users_index.rb b/db/migrate/20170606160751_fix_disco_app_users_index.rb index 618561b2..310eb4f8 100644 --- a/db/migrate/20170606160751_fix_disco_app_users_index.rb +++ b/db/migrate/20170606160751_fix_disco_app_users_index.rb @@ -1,6 +1,8 @@ class FixDiscoAppUsersIndex < ActiveRecord::Migration[5.1] + def change remove_index :disco_app_users, :shop_id add_index :disco_app_users, [:id, :shop_id], unique: true end + end diff --git a/disco_app.gemspec b/disco_app.gemspec index 305f5416..25c8deea 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.test_files = Dir["test/**/*"] - s.add_runtime_dependency 'rails', '~> 5.1.0' + s.add_runtime_dependency 'rails', '~> 5.2.0' s.add_runtime_dependency 'sass-rails', '~> 5.0' s.add_runtime_dependency 'uglifier', '~> 3.2' s.add_runtime_dependency 'coffee-rails', '~> 4.2' @@ -43,7 +43,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'active_link_to', '~> 1.0' s.add_runtime_dependency 'premailer-rails', '~> 1.8' s.add_runtime_dependency 'nokogiri', '~> 1.7' - s.add_runtime_dependency 'rollbar', '~> 2.14' + s.add_runtime_dependency 'appsignal', '~> 2.7' s.add_runtime_dependency 'oj', '~> 2.14' s.add_runtime_dependency 'newrelic_rpm', '~> 3.15' s.add_runtime_dependency 'mailgun_rails', '~> 0.8' diff --git a/initialise.sh b/initialise.sh index 1b18614e..d33e173f 100755 --- a/initialise.sh +++ b/initialise.sh @@ -2,9 +2,9 @@ # Usage: initialise.sh example_app APP_NAME="$1" -RAILS_VERSION="${RAILS_VERSION:-5.1.0}" +RAILS_VERSION="${RAILS_VERSION:-5.2.0}" RUBY_VERSION="${RUBY_VERSION:-2.5.0}" -DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.15.2}" +DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.16.0}" if [ -z $APP_NAME ]; then echo "Usage: ./initialise.sh app_name (rails_version) (ruby_version) (disco_app_version)" diff --git a/lib/disco_app/version.rb b/lib/disco_app/version.rb index 66dbd56c..959324b1 100644 --- a/lib/disco_app/version.rb +++ b/lib/disco_app/version.rb @@ -1,3 +1,3 @@ module DiscoApp - VERSION = '0.15.2' + VERSION = '0.16.0' end diff --git a/lib/generators/disco_app/install/templates/config/database.yml.tt b/lib/generators/disco_app/install/templates/config/database.yml.tt index 0342d1b0..c6f777fa 100644 --- a/lib/generators/disco_app/install/templates/config/database.yml.tt +++ b/lib/generators/disco_app/install/templates/config/database.yml.tt @@ -4,18 +4,21 @@ default: &default pool: 5 timeout: 5000 prepared_statements: false - + development: <<: *default host: localhost port: 5432 database: <%= Rails.application.class.parent_name.downcase %> - + test: <<: *default host: localhost port: 5432 database: <%= Rails.application.class.parent_name.downcase %>_test - + +staging: + <<: *default + production: <<: *default diff --git a/lib/generators/disco_app/install/templates/config/newrelic.yml b/lib/generators/disco_app/install/templates/config/newrelic.yml index 9973018b..f5eb2596 100644 --- a/lib/generators/disco_app/install/templates/config/newrelic.yml +++ b/lib/generators/disco_app/install/templates/config/newrelic.yml @@ -22,5 +22,8 @@ test: <<: *default_settings monitor_mode: false +staging: + <<: *default_settings + production: <<: *default_settings diff --git a/lib/generators/disco_app/install/templates/controllers/home_controller.rb b/lib/generators/disco_app/install/templates/controllers/home_controller.rb index 1a20f94a..b8345c31 100644 --- a/lib/generators/disco_app/install/templates/controllers/home_controller.rb +++ b/lib/generators/disco_app/install/templates/controllers/home_controller.rb @@ -1,4 +1,5 @@ class HomeController < ApplicationController + include DiscoApp::Concerns::AuthenticatedController def index diff --git a/lib/generators/disco_app/install/templates/initializers/rollbar.rb b/lib/generators/disco_app/install/templates/initializers/rollbar.rb deleted file mode 100644 index 74774ce0..00000000 --- a/lib/generators/disco_app/install/templates/initializers/rollbar.rb +++ /dev/null @@ -1,23 +0,0 @@ -Rollbar.configure do |config| - # Fetch the access token from the environment. - config.access_token = ENV['ROLLBAR_ACCESS_TOKEN'] - - # Only use Rollbar in production when there's a token configured. - unless config.access_token and Rails.env.production? - config.enabled = false - end - - # Enable delayed reporting (using Sidekiq) - config.use_sidekiq - - # Enable "Person" feature of Rollbar in the context of a "Shop" - config.person_method = 'current_shop' - config.person_username_method = 'shopify_domain' - - # Add custom handlers. - config.before_process << proc do |options| - if options[:exception].is_a?(ActiveResource::ClientError) and options[:exception].message.include?('Too Many Requests') - raise Rollbar::Ignore - end - end -end diff --git a/lib/generators/disco_app/install/templates/initializers/session_store.rb b/lib/generators/disco_app/install/templates/initializers/session_store.rb index 647e382c..74aa173a 100644 --- a/lib/generators/disco_app/install/templates/initializers/session_store.rb +++ b/lib/generators/disco_app/install/templates/initializers/session_store.rb @@ -1,2 +1,2 @@ # Use an ActiveRecord-based session store. -Rails.application.config.session_store :active_record_store, :key => '_disco_app_session' +Rails.application.config.session_store :active_record_store, key: '_disco_app_session' diff --git a/lib/generators/disco_app/install/templates/root/.env b/lib/generators/disco_app/install/templates/root/.env index d97bf4dc..b0cc2b67 100644 --- a/lib/generators/disco_app/install/templates/root/.env +++ b/lib/generators/disco_app/install/templates/root/.env @@ -18,3 +18,6 @@ REDIS_PROVIDER= DISCO_API_URL= WHITELISTED_DOMAINS= + +# You can find this listed in 1Password +APPSIGNAL_PUSH_API_KEY= diff --git a/lib/generators/disco_app/install/templates/root/.rubocop.yml b/lib/generators/disco_app/install/templates/root/.rubocop.yml index c393bf8a..251bc7df 100644 --- a/lib/generators/disco_app/install/templates/root/.rubocop.yml +++ b/lib/generators/disco_app/install/templates/root/.rubocop.yml @@ -1,11 +1,146 @@ AllCops: Exclude: + - bin/* - db/schema.rb + - vendor/ruby/**/* + TargetRubyVersion: 2.5 -Style/AccessorMethodName: +# Layout + +Layout/AccessModifierIndentation: + Enabled: true + +Layout/AlignParameters: + Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' + Enabled: true, + EnforcedStyle: with_fixed_indentation + +Layout/ClassStructure: + Categories: + associations: + - belongs_to + - has_one + - has_many + attributes: + - attr_accessor + - attr_reader + - attr_writer + - attr_accessible + callbacks: + - before_validation + - after_validation + - before_save + - around_save + - before_create + - around_create + - after_create + - after_save + - after_commit + module_inclusion: + - include + - prepend + - extend + validations: + - validates + ExpectedOrder: + - module_inclusion + - constants + - attributes + - associations + - validations + - callbacks + - public_class_methods + - initializer + - public_methods + - protected_methods + - private_methods + Enabled: true + +Layout/DotPosition: + Description: 'Checks the position of the dot in multi-line method calls.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' + EnforcedStyle: leading + +Layout/EmptyLinesAroundClassBody: + Enabled: true + EnforcedStyle: empty_lines + +Layout/EmptyLinesAroundModuleBody: + Enabled: true + EnforcedStyle: empty_lines_except_namespace + +Layout/ExtraSpacing: + Description: 'Do not use unnecessary spacing.' + Enabled: true + +Layout/InitialIndentation: + Description: >- + Checks the indentation of the first non-blank non-comment line in a file. + Enabled: false + +Layout/IndentHash: + Enabled: true + EnforcedStyle: consistent + +Layout/IndentationConsistency: + EnforcedStyle: rails + Enabled: true + +Layout/MultilineBlockLayout: + Enabled: false + +Layout/MultilineOperationIndentation: + Description: >- + Checks indentation of binary operations that span more than + one line. + Enabled: true + EnforcedStyle: indented + +Layout/MultilineMethodCallIndentation: + Description: >- + Checks indentation of method calls with the dot operator + that span more than one line. + Enabled: true + EnforcedStyle: indented + +Layout/SpaceBeforeBlockBraces: + Description: >- + Checks that block braces have or don't have a space before + the opening brace depending on configuration. + Enabled: false + +# Naming + +Naming/AccessorMethodName: Description: Check the naming of accessor methods for get_/set_. Enabled: false +Naming/AsciiIdentifiers: + Description: 'Use only ascii symbols in identifiers.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' + Enabled: true + +Naming/BinaryOperatorParameterName: + Description: 'When defining binary operators, name the argument other.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' + Enabled: false + +Naming/FileName: + Description: 'Use snake_case for source file names.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' + Enabled: true + +Naming/PredicateName: + Description: 'Check the names of predicate methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' + NamePrefixBlacklist: + - is_ + Exclude: + - spec/**/* + +# Style + Style/Alias: Description: 'Use alias_method instead of alias.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' @@ -21,21 +156,11 @@ Style/AsciiComments: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' Enabled: true -Style/AsciiIdentifiers: - Description: 'Use only ascii symbols in identifiers.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' - Enabled: true - Style/Attr: Description: 'Checks for uses of Module#attr.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' Enabled: true -Metrics/BlockNesting: - Description: 'Avoid excessive block nesting' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' - Enabled: true - Style/CaseEquality: Description: 'Avoid explicit use of the case equality operator(===).' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' @@ -51,14 +176,6 @@ Style/ClassAndModuleChildren: Enabled: true EnforcedStyle: nested -Metrics/ClassLength: - Description: 'Avoid classes longer than 100 lines of code.' - Enabled: false - -Metrics/ModuleLength: - Description: 'Avoid modules longer than 100 lines of code.' - Enabled: false - Style/ClassVars: Description: 'Avoid the use of class variables.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' @@ -67,7 +184,7 @@ Style/ClassVars: Style/CollectionMethods: Enabled: true PreferredMethods: - find: find + detect: find inject: reduce collect: map find_all: select @@ -77,11 +194,6 @@ Style/ColonMethodCall: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' Enabled: true -Style/MutableConstant: - Description: 'Avoids assignment of mutable literals to constants..' - StyleGuide: 'http://www.rubydoc.info/github/bbatsov/RuboCop/RuboCop/Cop/Style/MutableConstant' - Enabled: false - Style/CommentAnnotation: Description: >- Checks formatting of special comments @@ -89,27 +201,6 @@ Style/CommentAnnotation: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' Enabled: false -Metrics/AbcSize: - Description: >- - A calculated magnitude based on number of assignments, - branches, and conditions. - Enabled: false - -Metrics/CyclomaticComplexity: - Description: >- - A complexity metric that is strongly correlated to the number - of test cases needed to validate a method. - Enabled: false - -Rails/Delegate: - Description: 'Prefer delegate method for delegations.' - Enabled: false - -Style/PreferredHashMethods: - Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' - StyleGuide: '#hash-key' - Enabled: true - Style/Documentation: Description: 'Document classes and non-namespace modules.' Enabled: false @@ -128,6 +219,9 @@ Style/EmptyLiteral: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' Enabled: true +Style/EmptyMethod: + EnforcedStyle: expanded + # Checks whether the source file has a utf-8 encoding comment or not # AutoCorrectEncodingComment must match the regex # /#.*coding\s?[:=]\s?(?:UTF|utf)-8/ @@ -139,11 +233,6 @@ Style/EvenOdd: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' Enabled: true -Style/FileName: - Description: 'Use snake_case for source file names.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' - Enabled: true - Style/FrozenStringLiteralComment: Description: >- Add the frozen_string_literal comment to the top of files @@ -203,16 +292,11 @@ Style/LineEndConcatenation: line end. Enabled: true -Metrics/LineLength: - Description: 'Limit lines to 80 characters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' - Max: 80 - Enabled: false - -Metrics/MethodLength: - Description: 'Avoid methods longer than 10 lines of code.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' - Enabled: false +Style/MixinUsage: + Description: 'Checks that `include` statements appaear inside classes.' + Enabled: true + Exclude: + - bin/* Style/ModuleFunction: Description: 'Checks for usage of `extend self` in modules.' @@ -224,6 +308,11 @@ Style/MultilineBlockChain: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' Enabled: false +Style/MutableConstant: + Description: 'Avoids assignment of mutable literals to constants..' + StyleGuide: 'http://www.rubydoc.info/github/bbatsov/RuboCop/RuboCop/Cop/Style/MutableConstant' + Enabled: true + Style/NegatedIf: Description: >- Favor unless over if for negative conditions @@ -256,7 +345,7 @@ Style/NumericLiterals: Add underscores to large numeric literals to improve their readability. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' - Enabled: true + Enabled: false Style/OneLineConditional: Description: >- @@ -265,16 +354,6 @@ Style/OneLineConditional: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' Enabled: true -Style/OpMethod: - Description: 'When defining binary operators, name the argument other.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' - Enabled: false - -Metrics/ParameterLists: - Description: 'Avoid parameter lists longer than three or four parameters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' - Enabled: true - Style/PercentLiteralDelimiters: Description: 'Use `%`-literal delimiters consistently' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' @@ -285,13 +364,10 @@ Style/PerlBackrefs: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' Enabled: true -Style/PredicateName: - Description: 'Check the names of predicate methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' - NamePrefixBlacklist: - - is_ - Exclude: - - spec/**/* +Style/PreferredHashMethods: + Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' + StyleGuide: '#hash-key' + Enabled: true Style/Proc: Description: 'Use proc instead of Proc.new.' @@ -344,13 +420,19 @@ Style/StringLiterals: Style/TrailingCommaInArguments: Description: 'Checks for trailing comma in argument lists.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' - EnforcedStyleForMultiline: comma + EnforcedStyleForMultiline: no_comma Enabled: true -Style/TrailingCommaInLiteral: - Description: 'Checks for trailing comma in array and hash literals.' +Style/TrailingCommaInArrayLiteral: + Description: 'Checks for trailing comma in array literals.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' - EnforcedStyleForMultiline: comma + EnforcedStyleForMultiline: no_comma + Enabled: false + +Style/TrailingCommaInHashLiteral: + Description: 'Checks for trailing comma in hash literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + EnforcedStyleForMultiline: no_comma Enabled: false Style/TrivialAccessors: @@ -377,13 +459,62 @@ Style/WhileUntilModifier: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' Enabled: true +Style/SymbolArray: + MinSize: 4 + Style/WordArray: Description: 'Use %w or %W for arrays of words.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' Enabled: true + MinSize: 4 -Style/SymbolArray: - MinSize: 3 +# Metrics + +Metrics/AbcSize: + Description: >- + A calculated magnitude based on number of assignments, + branches, and conditions. + Enabled: false + +Metrics/BlockLength: + Enabled: true + Exclude: + - config/environments/* + +Metrics/BlockNesting: + Description: 'Avoid excessive block nesting' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' + Enabled: true + +Metrics/ClassLength: + Description: 'Avoid classes longer than 100 lines of code.' + Enabled: false + +Metrics/CyclomaticComplexity: + Description: >- + A complexity metric that is strongly correlated to the number + of test cases needed to validate a method. + Enabled: false + +Metrics/LineLength: + Description: 'Limit lines to 80 characters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' + Max: 80 + Enabled: false + +Metrics/MethodLength: + Description: 'Avoid methods longer than 10 lines of code.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' + Enabled: false + +Metrics/ModuleLength: + Description: 'Avoid modules longer than 100 lines of code.' + Enabled: false + +Metrics/ParameterLists: + Description: 'Avoid parameter lists longer than three or four parameters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' + Enabled: true # Lint @@ -409,13 +540,6 @@ Lint/CircularArgumentReference: Description: "Don't refer to the keyword argument in the default value." Enabled: false -Lint/ConditionPosition: - Description: >- - Checks for condition placed in a confusing position relative to - the keyword. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' - Enabled: true - Lint/DeprecatedClassMethods: Description: 'Check for deprecated class method calls.' Enabled: true @@ -441,13 +565,7 @@ Lint/HandleExceptions: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' Enabled: true -Lint/InvalidCharacterLiteral: - Description: >- - Checks for invalid character literals with a non-escaped - whitespace character. - Enabled: false - -Lint/LiteralInCondition: +Lint/LiteralAsCondition: Description: 'Checks of literals used in conditions.' Enabled: false @@ -488,13 +606,6 @@ Lint/UnderscorePrefixedVariableName: Description: 'Do not use prefix `_` for a variable that is used.' Enabled: false -Lint/UnneededDisable: - Description: >- - Checks for rubocop:disable comments that can be removed. - Note: this cop is not disabled when disabling all cops. - It must be explicitly disabled. - Enabled: false - Lint/Void: Description: 'Possible use of operator/literal/variable in void context.' Enabled: false @@ -519,7 +630,7 @@ Performance/Detect: Use `detect` instead of `select.first`, `find_all.first`, `select.last`, and `find_all.last`. Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' - Enabled: false + Enabled: true Performance/FlatMap: Description: >- @@ -568,6 +679,10 @@ Rails/Date: such as Date.today, Date.current etc. Enabled: true +Rails/Delegate: + Description: 'Prefer delegate method for delegations.' + Enabled: false + Rails/FindBy: Description: 'Prefer find_by over where.first.' Enabled: true @@ -604,57 +719,7 @@ Rails/Validation: Description: 'Use validates :attribute, hash of validations.' Enabled: false -# Layout - -Layout/EmptyLinesAroundClassBody: - Enabled: true - EnforcedStyle: empty_lines - -Layout/AlignParameters: - Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' - Enabled: true - -Layout/DotPosition: - Description: 'Checks the position of the dot in multi-line method calls.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' - EnforcedStyle: leading - -Layout/ExtraSpacing: - Description: 'Do not use unnecessary spacing.' - Enabled: true - -Layout/MultilineOperationIndentation: - Description: >- - Checks indentation of binary operations that span more than - one line. - Enabled: true - EnforcedStyle: indented - -Layout/MultilineMethodCallIndentation: - Description: >- - Checks indentation of method calls with the dot operator - that span more than one line. - Enabled: true - EnforcedStyle: indented - -Layout/InitialIndentation: - Description: >- - Checks the indentation of the first non-blank non-comment line in a file. - Enabled: false - -Layout/IndentHash: - Enabled: true - EnforcedStyle: consistent - -Layout/AccessModifierIndentation: - Enabled: true - -Layout/IndentationConsistency: - EnforcedStyle: rails - Enabled: true - # Bundler Bundler/OrderedGems: - Enabled: false + Enabled: true diff --git a/lib/generators/disco_app/templates/config/appsignal.yml b/lib/generators/disco_app/templates/config/appsignal.yml new file mode 100644 index 00000000..f0fbd1b7 --- /dev/null +++ b/lib/generators/disco_app/templates/config/appsignal.yml @@ -0,0 +1,12 @@ +default: &defaults + name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> + push_api_key: <%= ENV['APPSIGNAL_PUSH_API_KEY'] %> + development: + <<: *defaults + active: false + staging: + <<: *defaults + active: true + production: + <<: *defaults + active: true diff --git a/lib/generators/disco_app/templates/config/cable.yml.tt b/lib/generators/disco_app/templates/config/cable.yml.tt new file mode 100644 index 00000000..13c8a9ca --- /dev/null +++ b/lib/generators/disco_app/templates/config/cable.yml.tt @@ -0,0 +1,11 @@ +development: + adapter: async + +test: + adapter: async + +staging: + adapter: async + +production: + adapter: async diff --git a/lib/generators/disco_app/templates/config/environments/staging.rb b/lib/generators/disco_app/templates/config/environments/staging.rb new file mode 100644 index 00000000..eaa56339 --- /dev/null +++ b/lib/generators/disco_app/templates/config/environments/staging.rb @@ -0,0 +1,108 @@ +Rails.application.configure do + # Configure ActionMailer to use MailGun + if ENV['MAILGUN_API_KEY'] + config.action_mailer.delivery_method = :mailgun + config.action_mailer.mailgun_settings = { + api_key: ENV['MAILGUN_API_KEY'], + domain: ENV['MAILGUN_API_DOMAIN'] + } + end + + # Use production variant of React in staging. + config.react.variant = :production + # Use Sidekiq as the active job backend + config.active_job.queue_adapter = :sidekiq + + # Allow real charges in production with an ENV variable + config.x.shopify_charges_real = false + + # 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 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 + + # 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? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # 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 + + # 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 + + # 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 = [:request_id] + + # Use a different cache store in staging. + # config.cache_store = :mem_cache_store + + # 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_#{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 cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # 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 + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/lib/tasks/rollbar.rake b/lib/tasks/rollbar.rake deleted file mode 100644 index 7915e248..00000000 --- a/lib/tasks/rollbar.rake +++ /dev/null @@ -1,24 +0,0 @@ -require 'yaml' - -namespace :generate do - desc "Create Rollbar project for current shopify app, and return the ROLLBAR API TOKEN" - task rollbar_project: :environment do - begin - config_path = File.join(ENV['HOME'], '.disco_app.yml') - config = YAML.load_file(config_path) - rescue StandardError - abort("Could not load configuration file from #{config_path}, aborting.") - end - - params = { - write_account_access_token: config['params']['ROLLBAR_ACCOUNT_ACCESS_TOKEN_WRITE'].to_s, - read_account_access_token: config['params']['ROLLBAR_ACCOUNT_ACCESS_TOKEN_READ'].to_s - } - - project_access_token = DiscoApp::RollbarClient.new(params).create_project(ENV['APP_NAME'].blank? ? ENV['SHOPIFY_APP_NAME'] : ENV['APP_NAME']) - puts '#' * 80 - puts 'New Rollbar project successfully created!' - puts "ROLLBAR_ACCESS_TOKEN = #{project_access_token}" - puts '#' * 80 - end -end diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml index 9449d50c..ff0c56fa 100644 --- a/test/dummy/config/database.yml +++ b/test/dummy/config/database.yml @@ -17,5 +17,8 @@ test: port: 5432 database: disco_app_test +staging: + <<: *default + production: <<: *default diff --git a/test/dummy/config/environments/staging.rb b/test/dummy/config/environments/staging.rb new file mode 100644 index 00000000..331709ce --- /dev/null +++ b/test/dummy/config/environments/staging.rb @@ -0,0 +1,85 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Use Sidekiq as the active job backend + config.active_job.queue_adapter = :sidekiq + + # Allow real charges in staging with an ENV variable + config.x.shopify_charges_real = ENV['SHOPIFY_CHARGES_REAL'] == 'true' + + # 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 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 + + # 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 serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # 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 + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + config.force_ssl = true + + # 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) + + # Use a different cache store in staging. + # 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' + + # 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 cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/test/dummy/config/secrets.yml b/test/dummy/config/secrets.yml index 4a825a63..30f1b287 100644 --- a/test/dummy/config/secrets.yml +++ b/test/dummy/config/secrets.yml @@ -18,5 +18,8 @@ test: # Do not keep production secrets in the repository, # instead read values from the environment. +staging: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> + production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index 72a5226e..80a24704 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/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: 20170606160751) do +ActiveRecord::Schema.define(version: 2017_06_06_160751) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" From c26b9cba5b3a7dd8941667e381267a42c1d1e8fd Mon Sep 17 00:00:00 2001 From: Pille Date: Tue, 30 Apr 2019 16:17:07 +0800 Subject: [PATCH 06/41] Lock shopify_api to avoid breaking changes --- disco_app.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/disco_app.gemspec b/disco_app.gemspec index 4ec1c585..3c84de5e 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -28,6 +28,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'jquery-rails', '~> 4.3' s.add_runtime_dependency 'turbolinks', '~> 5.0' s.add_runtime_dependency 'shopify_app', '~> 7.2', '>= 7.2.3' + s.add_runtime_dependency 'shopify_api', '~> 6.0' s.add_runtime_dependency 'puma', '~> 3.9' s.add_runtime_dependency 'sidekiq', '~> 5.0' s.add_runtime_dependency 'pg', '~> 0.21.0' From 40786e9c8cf9f88a53c014de34d5e35c05a0bee5 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 11 Jun 2019 13:33:01 +1000 Subject: [PATCH 07/41] Upgrade rails to avoid gem vulnerabilities --- disco_app.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disco_app.gemspec b/disco_app.gemspec index 3c84de5e..7c9c8eae 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.test_files = Dir["test/**/*"] - s.add_runtime_dependency 'rails', '~> 5.2.0' + s.add_runtime_dependency 'rails', '~> 5.2.2' s.add_runtime_dependency 'sass-rails', '~> 5.0' s.add_runtime_dependency 'uglifier', '~> 3.2' s.add_runtime_dependency 'coffee-rails', '~> 4.2' From 8d80464eade5dd9da51bc6a971b2521b24e81ec3 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 13:20:37 +1000 Subject: [PATCH 08/41] [rubocop] Rubocop autocorrections --- .rubocop.yml | 389 +++++++++++------- Rakefile | 6 +- app/clients/disco_app/api_client.rb | 7 +- app/clients/disco_app/graphql_client.rb | 2 +- .../admin/app_settings_controller.rb | 2 + .../disco_app/admin/application_controller.rb | 1 + .../admin/concerns/app_settings_controller.rb | 1 + .../concerns/authenticated_controller.rb | 5 +- .../admin/concerns/plans_controller.rb | 3 +- .../admin/concerns/shops_controller.rb | 1 + .../admin/concerns/sources_controller.rb | 1 + .../concerns/subscriptions_controller.rb | 1 + .../disco_app/admin/plans_controller.rb | 2 + .../admin/resources/shops_controller.rb | 2 + .../disco_app/admin/shops_controller.rb | 2 + .../disco_app/admin/sources_controller.rb | 2 + .../admin/subscriptions_controller.rb | 2 + .../disco_app/charges_controller.rb | 11 +- .../concerns/app_proxy_controller.rb | 10 +- .../concerns/authenticated_controller.rb | 29 +- .../concerns/carrier_request_controller.rb | 16 +- .../concerns/user_authenticated_controller.rb | 1 + .../disco_app/concerns/webhooks_controller.rb | 18 +- .../disco_app/flow/actions_controller.rb | 2 + .../flow/concerns/actions_controller.rb | 4 +- app/controllers/disco_app/frame_controller.rb | 1 - .../disco_app/install_controller.rb | 9 +- .../disco_app/subscriptions_controller.rb | 7 +- .../disco_app/user_sessions_controller.rb | 1 + .../disco_app/webhooks_controller.rb | 2 + app/controllers/sessions_controller.rb | 5 +- app/helpers/disco_app/application_helper.rb | 8 +- app/jobs/disco_app/app_installed_job.rb | 2 + app/jobs/disco_app/app_uninstalled_job.rb | 2 + .../disco_app/concerns/app_installed_job.rb | 5 +- .../disco_app/concerns/app_uninstalled_job.rb | 3 +- .../concerns/render_asset_group_job.rb | 1 + .../disco_app/concerns/shop_update_job.rb | 3 +- .../concerns/subscription_changed_job.rb | 3 +- .../synchronise_carrier_service_job.rb | 13 +- .../concerns/synchronise_resources_job.rb | 1 + .../concerns/synchronise_users_job.rb | 7 +- .../concerns/synchronise_webhooks_job.rb | 9 +- app/jobs/disco_app/render_asset_group_job.rb | 2 + app/jobs/disco_app/shop_job.rb | 2 +- app/jobs/disco_app/shop_update_job.rb | 2 + .../disco_app/subscription_changed_job.rb | 2 + .../synchronise_carrier_service_job.rb | 2 + .../disco_app/synchronise_resources_job.rb | 2 + app/jobs/disco_app/synchronise_users_job.rb | 2 + .../disco_app/synchronise_webhooks_job.rb | 2 + app/models/application_record.rb | 2 + app/models/disco_app/app_settings.rb | 2 + app/models/disco_app/application_charge.rb | 2 +- app/models/disco_app/concerns/app_settings.rb | 2 + .../disco_app/concerns/can_be_liquified.rb | 22 +- .../disco_app/concerns/has_metafields.rb | 3 +- app/models/disco_app/concerns/plan.rb | 5 +- app/models/disco_app/concerns/plan_code.rb | 3 +- .../disco_app/concerns/renders_assets.rb | 23 +- app/models/disco_app/concerns/shop.rb | 8 +- app/models/disco_app/concerns/source.rb | 3 +- app/models/disco_app/concerns/subscription.rb | 4 +- app/models/disco_app/concerns/synchronises.rb | 21 +- app/models/disco_app/concerns/taggable.rb | 1 + app/models/disco_app/concerns/user.rb | 5 +- app/models/disco_app/flow/action.rb | 2 + app/models/disco_app/flow/concerns/action.rb | 3 +- app/models/disco_app/flow/concerns/trigger.rb | 3 +- app/models/disco_app/flow/trigger.rb | 2 + app/models/disco_app/plan.rb | 2 + app/models/disco_app/plan_code.rb | 2 + app/models/disco_app/session_storage.rb | 3 + app/models/disco_app/shop.rb | 2 + app/models/disco_app/source.rb | 2 + app/models/disco_app/subscription.rb | 2 + app/models/disco_app/user.rb | 2 + .../admin/resources/concerns/shop_resource.rb | 27 +- .../admin/resources/shop_resource.rb | 1 + .../disco_app/carrier_request_service.rb | 4 +- app/services/disco_app/charges_service.rb | 58 ++- app/services/disco_app/flow/process_action.rb | 10 +- app/services/disco_app/proxy_service.rb | 4 +- .../disco_app/request_validation_service.rb | 4 +- .../disco_app/subscription_service.rb | 9 +- app/services/disco_app/webhook_service.rb | 18 +- config/routes.rb | 2 - disco_app.gemspec | 77 ++-- lib/disco_app/configuration.rb | 10 +- lib/disco_app/constants.rb | 6 +- lib/disco_app/engine.rb | 2 +- lib/disco_app/session.rb | 1 + lib/disco_app/support/file_fixtures.rb | 1 + lib/disco_app/version.rb | 4 +- .../disco_app/disco_app_generator.rb | 44 +- .../disco_app/templates/root/.rubocop.yml | 19 +- lib/tasks/api.rake | 2 - lib/tasks/carrier_service.rake | 2 - lib/tasks/database.rake | 2 +- lib/tasks/sessions.rake | 2 - lib/tasks/shops.rake | 2 - lib/tasks/users.rake | 2 - lib/tasks/webhooks.rake | 2 - test/clients/disco_app/api_client_test.rb | 6 +- .../disco_app/admin/shops_controller_test.rb | 1 + .../disco_app/charges_controller_test.rb | 15 +- .../disco_app/install_controller_test.rb | 3 +- .../subscriptions_controller_test.rb | 1 + .../disco_app/webhooks_controller_test.rb | 1 + test/controllers/home_controller_test.rb | 2 +- test/disco_app_test.rb | 4 +- test/dummy/Rakefile | 2 +- .../app/controllers/application_controller.rb | 2 + .../controllers/carrier_request_controller.rb | 1 + .../disco_app/admin/shops_controller.rb | 1 + test/dummy/app/controllers/home_controller.rb | 1 + .../dummy/app/controllers/proxy_controller.rb | 1 + .../app/jobs/disco_app/app_installed_job.rb | 5 +- .../app/jobs/disco_app/app_uninstalled_job.rb | 1 + test/dummy/app/models/application_record.rb | 2 + test/dummy/app/models/cart.rb | 7 +- test/dummy/app/models/disco_app/shop.rb | 9 +- test/dummy/app/models/js_configuration.rb | 1 + test/dummy/app/models/product.rb | 5 +- test/dummy/app/models/widget_configuration.rb | 1 + test/dummy/bin/bundle | 2 +- test/dummy/bin/rails | 2 +- test/dummy/bin/setup | 16 +- test/dummy/config/application.rb | 7 +- test/dummy/config/boot.rb | 4 +- test/dummy/config/environment.rb | 2 +- test/dummy/config/initializers/disco_app.rb | 2 +- test/dummy/config/initializers/omniauth.rb | 7 +- .../config/initializers/session_store.rb | 2 +- test/dummy/config/routes.rb | 2 - .../migrate/20160307182229_create_products.rb | 4 +- .../20160530160739_create_asset_models.rb | 4 +- .../db/migrate/20161105054746_create_carts.rb | 3 +- test/dummy/db/schema.rb | 357 ++++++++-------- test/integration/synchronises_test.rb | 1 + test/jobs/disco_app/app_installed_job_test.rb | 7 +- .../disco_app/app_uninstalled_job_test.rb | 7 +- .../disco_app/send_subscription_job_test.rb | 5 +- .../synchronise_carrier_service_job_test.rb | 1 + .../disco_app/synchronise_users_job_test.rb | 1 + .../synchronise_webhooks_job_test.rb | 9 +- .../models/disco_app/can_be_liquified_test.rb | 4 +- test/models/disco_app/has_metafields_test.rb | 51 ++- test/models/disco_app/renders_assets_test.rb | 3 +- test/models/disco_app/session_test.rb | 4 +- .../disco_app/charges_service_test.rb | 15 +- .../disco_app/subscription_service_test.rb | 1 + test/support/test_shopify_api.rb | 2 +- test/test_helper.rb | 13 +- 154 files changed, 905 insertions(+), 769 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c5d8a6f9..e0d1cbb8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,11 +1,145 @@ AllCops: Exclude: + - bin/* - db/schema.rb + - vendor/ruby/**/* + TargetRubyVersion: 2.5 + +# Layout + +Layout/AccessModifierIndentation: + Enabled: true + +Layout/AlignParameters: + Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' + Enabled: true, + EnforcedStyle: with_fixed_indentation + +Layout/ClassStructure: + Categories: + associations: + - belongs_to + - has_one + - has_many + attributes: + - attr_accessor + - attr_reader + - attr_writer + - attr_accessible + callbacks: + - before_validation + - after_validation + - before_save + - around_save + - before_create + - around_create + - after_create + - after_save + - after_commit + module_inclusion: + - include + - prepend + - extend + validations: + - validates + ExpectedOrder: + - module_inclusion + - constants + - associations + - validations + - callbacks + - public_class_methods + - initializer + - public_methods + - protected_methods + - private_methods + Enabled: true + +Layout/DotPosition: + Description: 'Checks the position of the dot in multi-line method calls.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' + EnforcedStyle: leading + +Layout/EmptyLinesAroundClassBody: + Enabled: true + EnforcedStyle: empty_lines + +Layout/EmptyLinesAroundModuleBody: + Enabled: true + EnforcedStyle: empty_lines_except_namespace + +Layout/ExtraSpacing: + Description: 'Do not use unnecessary spacing.' + Enabled: true + +Layout/InitialIndentation: + Description: >- + Checks the indentation of the first non-blank non-comment line in a file. + Enabled: false + +Layout/IndentFirstHashElement: + Enabled: true + EnforcedStyle: consistent + +Layout/IndentationConsistency: + EnforcedStyle: rails + Enabled: true + +Layout/MultilineBlockLayout: + Enabled: false + +Layout/MultilineOperationIndentation: + Description: >- + Checks indentation of binary operations that span more than + one line. + Enabled: true + EnforcedStyle: indented + +Layout/MultilineMethodCallIndentation: + Description: >- + Checks indentation of method calls with the dot operator + that span more than one line. + Enabled: true + EnforcedStyle: indented + +Layout/SpaceBeforeBlockBraces: + Description: >- + Checks that block braces have or don't have a space before + the opening brace depending on configuration. + Enabled: false + +# Naming Naming/AccessorMethodName: Description: Check the naming of accessor methods for get_/set_. Enabled: false +Naming/AsciiIdentifiers: + Description: 'Use only ascii symbols in identifiers.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' + Enabled: true + +Naming/BinaryOperatorParameterName: + Description: 'When defining binary operators, name the argument other.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' + Enabled: false + +Naming/FileName: + Description: 'Use snake_case for source file names.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' + Enabled: true + +Naming/PredicateName: + Description: 'Check the names of predicate methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' + NamePrefixBlacklist: + - is_ + Exclude: + - spec/**/* + +# Style + Style/Alias: Description: 'Use alias_method instead of alias.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' @@ -21,21 +155,11 @@ Style/AsciiComments: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' Enabled: true -Naming/AsciiIdentifiers: - Description: 'Use only ascii symbols in identifiers.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' - Enabled: true - Style/Attr: Description: 'Checks for uses of Module#attr.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' Enabled: true -Metrics/BlockNesting: - Description: 'Avoid excessive block nesting' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' - Enabled: true - Style/CaseEquality: Description: 'Avoid explicit use of the case equality operator(===).' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' @@ -48,16 +172,8 @@ Style/CharacterLiteral: Style/ClassAndModuleChildren: Description: 'Checks style of children classes and modules.' - Enabled: true - EnforcedStyle: nested - -Metrics/ClassLength: - Description: 'Avoid classes longer than 100 lines of code.' - Enabled: false - -Metrics/ModuleLength: - Description: 'Avoid modules longer than 100 lines of code.' Enabled: false + EnforcedStyle: nested Style/ClassVars: Description: 'Avoid the use of class variables.' @@ -77,11 +193,6 @@ Style/ColonMethodCall: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' Enabled: true -Style/MutableConstant: - Description: 'Avoids assignment of mutable literals to constants..' - StyleGuide: 'http://www.rubydoc.info/github/bbatsov/RuboCop/RuboCop/Cop/Style/MutableConstant' - Enabled: false - Style/CommentAnnotation: Description: >- Checks formatting of special comments @@ -89,27 +200,6 @@ Style/CommentAnnotation: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' Enabled: false -Metrics/AbcSize: - Description: >- - A calculated magnitude based on number of assignments, - branches, and conditions. - Enabled: false - -Metrics/CyclomaticComplexity: - Description: >- - A complexity metric that is strongly correlated to the number - of test cases needed to validate a method. - Enabled: false - -Rails/Delegate: - Description: 'Prefer delegate method for delegations.' - Enabled: false - -Style/PreferredHashMethods: - Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' - StyleGuide: '#hash-key' - Enabled: true - Style/Documentation: Description: 'Document classes and non-namespace modules.' Enabled: false @@ -128,6 +218,9 @@ Style/EmptyLiteral: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' Enabled: true +Style/EmptyMethod: + EnforcedStyle: expanded + # Checks whether the source file has a utf-8 encoding comment or not # AutoCorrectEncodingComment must match the regex # /#.*coding\s?[:=]\s?(?:UTF|utf)-8/ @@ -139,18 +232,13 @@ Style/EvenOdd: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' Enabled: true -Naming/FileName: - Description: 'Use snake_case for source file names.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' - Enabled: true - Style/FrozenStringLiteralComment: Description: >- Add the frozen_string_literal comment to the top of files to help transition from Ruby 2.3.0 to Ruby 3.0. Enabled: false -Style/FlipFlop: +Lint/FlipFlop: Description: 'Checks for flip flops' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' Enabled: true @@ -203,16 +291,11 @@ Style/LineEndConcatenation: line end. Enabled: true -Metrics/LineLength: - Description: 'Limit lines to 80 characters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' - Max: 80 - Enabled: false - -Metrics/MethodLength: - Description: 'Avoid methods longer than 10 lines of code.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' - Enabled: false +Style/MixinUsage: + Description: 'Checks that `include` statements appaear inside classes.' + Enabled: true + Exclude: + - bin/* Style/ModuleFunction: Description: 'Checks for usage of `extend self` in modules.' @@ -224,6 +307,11 @@ Style/MultilineBlockChain: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' Enabled: false +Style/MutableConstant: + Description: 'Avoids assignment of mutable literals to constants..' + StyleGuide: 'http://www.rubydoc.info/github/bbatsov/RuboCop/RuboCop/Cop/Style/MutableConstant' + Enabled: true + Style/NegatedIf: Description: >- Favor unless over if for negative conditions @@ -256,7 +344,7 @@ Style/NumericLiterals: Add underscores to large numeric literals to improve their readability. StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' - Enabled: true + Enabled: false Style/OneLineConditional: Description: >- @@ -265,11 +353,6 @@ Style/OneLineConditional: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' Enabled: true -Metrics/ParameterLists: - Description: 'Avoid parameter lists longer than three or four parameters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' - Enabled: true - Style/PercentLiteralDelimiters: Description: 'Use `%`-literal delimiters consistently' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' @@ -280,13 +363,10 @@ Style/PerlBackrefs: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' Enabled: true -Naming/PredicateName: - Description: 'Check the names of predicate methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' - NamePrefixBlacklist: - - is_ - Exclude: - - spec/**/* +Style/PreferredHashMethods: + Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' + StyleGuide: '#hash-key' + Enabled: true Style/Proc: Description: 'Use proc instead of Proc.new.' @@ -339,15 +419,21 @@ Style/StringLiterals: Style/TrailingCommaInArguments: Description: 'Checks for trailing comma in argument lists.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' - EnforcedStyleForMultiline: comma + EnforcedStyleForMultiline: no_comma Enabled: true -Style/TrailingCommaInLiteral: - Description: 'Checks for trailing comma in array and hash literals.' +Style/TrailingCommaInArrayLiteral: + Description: 'Checks for trailing comma in array literals.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' - EnforcedStyleForMultiline: comma + EnforcedStyleForMultiline: no_comma Enabled: false +Style/TrailingCommaInHashLiteral: + Description: 'Checks for trailing comma in hash literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + EnforcedStyleForMultiline: no_comma + Enabled: true + Style/TrivialAccessors: Description: 'Prefer attr_* methods to trivial readers/writers.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' @@ -372,13 +458,74 @@ Style/WhileUntilModifier: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' Enabled: true +Style/SymbolArray: + MinSize: 4 + Style/WordArray: Description: 'Use %w or %W for arrays of words.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' Enabled: true + MinSize: 4 -Style/SymbolArray: - MinSize: 3 +# Metrics + +Metrics/AbcSize: + Description: >- + A calculated magnitude based on number of assignments, + branches, and conditions. + Enabled: false + +Metrics/BlockLength: + Enabled: true + Exclude: + - config/environments/* + - ./*.gemspec + - lib/generators/disco_app/templates/config/environments/* + ExcludedMethods: + - context + - define + - describe + - draw + - factory + - guard + - included + - namespace + - trait + +Metrics/BlockNesting: + Description: 'Avoid excessive block nesting' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' + Enabled: true + +Metrics/ClassLength: + Description: 'Avoid classes longer than 100 lines of code.' + Enabled: false + +Metrics/CyclomaticComplexity: + Description: >- + A complexity metric that is strongly correlated to the number + of test cases needed to validate a method. + Enabled: false + +Metrics/LineLength: + Description: 'Limit lines to 80 characters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' + Max: 80 + Enabled: false + +Metrics/MethodLength: + Description: 'Avoid methods longer than 10 lines of code.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' + Enabled: false + +Metrics/ModuleLength: + Description: 'Avoid modules longer than 100 lines of code.' + Enabled: false + +Metrics/ParameterLists: + Description: 'Avoid parameter lists longer than three or four parameters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' + Enabled: true # Lint @@ -404,13 +551,6 @@ Lint/CircularArgumentReference: Description: "Don't refer to the keyword argument in the default value." Enabled: false -Lint/ConditionPosition: - Description: >- - Checks for condition placed in a confusing position relative to - the keyword. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' - Enabled: true - Lint/DeprecatedClassMethods: Description: 'Check for deprecated class method calls.' Enabled: true @@ -436,13 +576,7 @@ Lint/HandleExceptions: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' Enabled: true -Lint/InvalidCharacterLiteral: - Description: >- - Checks for invalid character literals with a non-escaped - whitespace character. - Enabled: false - -Lint/LiteralInCondition: +Lint/LiteralAsCondition: Description: 'Checks of literals used in conditions.' Enabled: false @@ -483,13 +617,6 @@ Lint/UnderscorePrefixedVariableName: Description: 'Do not use prefix `_` for a variable that is used.' Enabled: false -Lint/UnneededDisable: - Description: >- - Checks for rubocop:disable comments that can be removed. - Note: this cop is not disabled when disabling all cops. - It must be explicitly disabled. - Enabled: false - Lint/Void: Description: 'Possible use of operator/literal/variable in void context.' Enabled: false @@ -514,7 +641,7 @@ Performance/Detect: Use `detect` instead of `select.first`, `find_all.first`, `select.last`, and `find_all.last`. Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' - Enabled: false + Enabled: true Performance/FlatMap: Description: >- @@ -529,7 +656,7 @@ Performance/ReverseEach: Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' Enabled: true -Performance/Sample: +Style/Sample: Description: >- Use `sample` instead of `shuffle.first`, `shuffle.last`, and `shuffle[Fixnum]`. @@ -563,6 +690,10 @@ Rails/Date: such as Date.today, Date.current etc. Enabled: true +Rails/Delegate: + Description: 'Prefer delegate method for delegations.' + Enabled: false + Rails/FindBy: Description: 'Prefer find_by over where.first.' Enabled: true @@ -599,57 +730,7 @@ Rails/Validation: Description: 'Use validates :attribute, hash of validations.' Enabled: false -# Layout - -Layout/EmptyLinesAroundClassBody: - Enabled: true - EnforcedStyle: empty_lines - -Layout/AlignParameters: - Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' - Enabled: true - -Layout/DotPosition: - Description: 'Checks the position of the dot in multi-line method calls.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' - EnforcedStyle: leading - -Layout/ExtraSpacing: - Description: 'Do not use unnecessary spacing.' - Enabled: true - -Layout/MultilineOperationIndentation: - Description: >- - Checks indentation of binary operations that span more than - one line. - Enabled: true - EnforcedStyle: indented - -Layout/MultilineMethodCallIndentation: - Description: >- - Checks indentation of method calls with the dot operator - that span more than one line. - Enabled: true - EnforcedStyle: indented - -Layout/InitialIndentation: - Description: >- - Checks the indentation of the first non-blank non-comment line in a file. - Enabled: false - -Layout/IndentHash: - Enabled: true - EnforcedStyle: consistent - -Layout/AccessModifierIndentation: - Enabled: true - -Layout/IndentationConsistency: - EnforcedStyle: rails - Enabled: true - # Bundler Bundler/OrderedGems: - Enabled: false + Enabled: true diff --git a/Rakefile b/Rakefile index 3e7dfa3e..403de62a 100644 --- a/Rakefile +++ b/Rakefile @@ -14,14 +14,11 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' - load 'rails/tasks/statistics.rake' - - Bundler::GemHelper.install_tasks require 'rake/testtask' @@ -33,5 +30,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end - task default: :test diff --git a/app/clients/disco_app/api_client.rb b/app/clients/disco_app/api_client.rb index 08ac08c6..9a9a645f 100644 --- a/app/clients/disco_app/api_client.rb +++ b/app/clients/disco_app/api_client.rb @@ -2,7 +2,7 @@ class DiscoApp::ApiClient - SUBSCRIPTION_ENDPOINT = 'app_subscriptions.json' + SUBSCRIPTION_ENDPOINT = 'app_subscriptions.json'.freeze def initialize(shop, url) @shop = shop @@ -11,16 +11,17 @@ def initialize(shop, url) def create_app_subscription return unless @url.present? + url = @url + SUBSCRIPTION_ENDPOINT begin - response = RestClient::Request.execute( + RestClient::Request.execute( method: :post, headers: { content_type: :json }, url: url, payload: { shop: @shop, subscription: @shop.current_subscription }.to_json ) rescue RestClient::BadRequest, RestClient::ResourceNotFound => e - raise DiscoApiError.new(e.message) + raise DiscoApiError, e.message end end diff --git a/app/clients/disco_app/graphql_client.rb b/app/clients/disco_app/graphql_client.rb index 418d2880..d15a2776 100644 --- a/app/clients/disco_app/graphql_client.rb +++ b/app/clients/disco_app/graphql_client.rb @@ -43,7 +43,7 @@ def create_flow_trigger(title, resource_name, resource_url, properties) # The double .to_json.to_json below looks odd but is required to properly escape the JSON hash # when inserting it into the GraphQL mutation call. - response = execute(%Q( + response = execute(%( mutation { flowTriggerReceive(body: #{body.to_json.to_json}) { userErrors { diff --git a/app/controllers/disco_app/admin/app_settings_controller.rb b/app/controllers/disco_app/admin/app_settings_controller.rb index bb329931..f937dec4 100644 --- a/app/controllers/disco_app/admin/app_settings_controller.rb +++ b/app/controllers/disco_app/admin/app_settings_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::Admin::AppSettingsController < DiscoApp::Admin::ApplicationController + include DiscoApp::Admin::Concerns::AppSettingsController + end diff --git a/app/controllers/disco_app/admin/application_controller.rb b/app/controllers/disco_app/admin/application_controller.rb index ed519230..77fe00a2 100644 --- a/app/controllers/disco_app/admin/application_controller.rb +++ b/app/controllers/disco_app/admin/application_controller.rb @@ -1,4 +1,5 @@ class DiscoApp::Admin::ApplicationController < ActionController::Base + include DiscoApp::Admin::Concerns::AuthenticatedController private diff --git a/app/controllers/disco_app/admin/concerns/app_settings_controller.rb b/app/controllers/disco_app/admin/concerns/app_settings_controller.rb index f849814e..789251a3 100644 --- a/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +++ b/app/controllers/disco_app/admin/concerns/app_settings_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Admin::Concerns::AppSettingsController + extend ActiveSupport::Concern def edit diff --git a/app/controllers/disco_app/admin/concerns/authenticated_controller.rb b/app/controllers/disco_app/admin/concerns/authenticated_controller.rb index 689fb488..3528164e 100644 --- a/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/admin/concerns/authenticated_controller.rb @@ -1,19 +1,18 @@ module DiscoApp::Admin::Concerns::AuthenticatedController + extend ActiveSupport::Concern included do - protect_from_forgery with: :exception before_action :authenticate_administrator layout 'admin' - end private def authenticate_administrator authenticate_or_request_with_http_basic do |username, password| - (not username.blank?) && (not password.blank?) && username == ENV['ADMIN_APP_USERNAME'] && password == ENV['ADMIN_APP_PASSWORD'] + !username.blank? && !password.blank? && username == ENV['ADMIN_APP_USERNAME'] && password == ENV['ADMIN_APP_PASSWORD'] end end diff --git a/app/controllers/disco_app/admin/concerns/plans_controller.rb b/app/controllers/disco_app/admin/concerns/plans_controller.rb index b994396f..ec52d1e1 100644 --- a/app/controllers/disco_app/admin/concerns/plans_controller.rb +++ b/app/controllers/disco_app/admin/concerns/plans_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Admin::Concerns::PlansController + extend ActiveSupport::Concern included do @@ -47,7 +48,7 @@ def find_plan def plan_params params.require(:plan).permit( :name, :status, :plan_type, :trial_period_days, :amount, - :plan_codes_attributes => [:id, :_destroy, :code, :trial_period_days, :amount] + plan_codes_attributes: %i[id _destroy code trial_period_days amount] ) end diff --git a/app/controllers/disco_app/admin/concerns/shops_controller.rb b/app/controllers/disco_app/admin/concerns/shops_controller.rb index cbba6cb8..55e6eda8 100644 --- a/app/controllers/disco_app/admin/concerns/shops_controller.rb +++ b/app/controllers/disco_app/admin/concerns/shops_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Admin::Concerns::ShopsController + extend ActiveSupport::Concern def index diff --git a/app/controllers/disco_app/admin/concerns/sources_controller.rb b/app/controllers/disco_app/admin/concerns/sources_controller.rb index 957bcc5f..fcf218cc 100644 --- a/app/controllers/disco_app/admin/concerns/sources_controller.rb +++ b/app/controllers/disco_app/admin/concerns/sources_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Admin::Concerns::SourcesController + extend ActiveSupport::Concern included do diff --git a/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb b/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb index 794fb74c..097876bc 100644 --- a/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +++ b/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Admin::Concerns::SubscriptionsController + extend ActiveSupport::Concern included do diff --git a/app/controllers/disco_app/admin/plans_controller.rb b/app/controllers/disco_app/admin/plans_controller.rb index b0a23a8e..3da3f6e3 100644 --- a/app/controllers/disco_app/admin/plans_controller.rb +++ b/app/controllers/disco_app/admin/plans_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::Admin::PlansController < DiscoApp::Admin::ApplicationController + include DiscoApp::Admin::Concerns::PlansController + end diff --git a/app/controllers/disco_app/admin/resources/shops_controller.rb b/app/controllers/disco_app/admin/resources/shops_controller.rb index 4fc2ff27..b75c6cdb 100644 --- a/app/controllers/disco_app/admin/resources/shops_controller.rb +++ b/app/controllers/disco_app/admin/resources/shops_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::Admin::Resources::ShopsController < JSONAPI::ResourceController + include DiscoApp::Admin::Concerns::AuthenticatedController + end diff --git a/app/controllers/disco_app/admin/shops_controller.rb b/app/controllers/disco_app/admin/shops_controller.rb index 41d87967..01ad8baf 100644 --- a/app/controllers/disco_app/admin/shops_controller.rb +++ b/app/controllers/disco_app/admin/shops_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::Admin::ShopsController < DiscoApp::Admin::ApplicationController + include DiscoApp::Admin::Concerns::ShopsController + end diff --git a/app/controllers/disco_app/admin/sources_controller.rb b/app/controllers/disco_app/admin/sources_controller.rb index 32658dfa..1358f6d5 100644 --- a/app/controllers/disco_app/admin/sources_controller.rb +++ b/app/controllers/disco_app/admin/sources_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::Admin::SourcesController < DiscoApp::Admin::ApplicationController + include DiscoApp::Admin::Concerns::SourcesController + end diff --git a/app/controllers/disco_app/admin/subscriptions_controller.rb b/app/controllers/disco_app/admin/subscriptions_controller.rb index 503b14ed..a4f18d23 100644 --- a/app/controllers/disco_app/admin/subscriptions_controller.rb +++ b/app/controllers/disco_app/admin/subscriptions_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::Admin::SubscriptionsController < DiscoApp::Admin::ApplicationController + include DiscoApp::Admin::Concerns::SubscriptionsController + end diff --git a/app/controllers/disco_app/charges_controller.rb b/app/controllers/disco_app/charges_controller.rb index 1d14c9fc..565007e6 100644 --- a/app/controllers/disco_app/charges_controller.rb +++ b/app/controllers/disco_app/charges_controller.rb @@ -1,4 +1,5 @@ class DiscoApp::ChargesController < ApplicationController + include DiscoApp::Concerns::AuthenticatedController skip_before_action :check_active_charge @@ -13,7 +14,7 @@ def new # subscription. If successful, redirect to the (external) charge confirmation # URL. If it fails, redirect back to the new charge page. def create - if(charge = DiscoApp::ChargesService.create(@shop, @subscription)).nil? + if (charge = DiscoApp::ChargesService.create(@shop, @subscription)).nil? redirect_to action: :new else redirect_to charge.confirmation_url @@ -25,8 +26,8 @@ def create # charge wasn't accepted, the flow will start again. def activate # First attempt to find a matching charge. - if(charge = @subscription.charges.find_by(id: params[:id], shopify_id: params[:charge_id])).nil? - redirect_to action: :new and return + if (charge = @subscription.charges.find_by(id: params[:id], shopify_id: params[:charge_id])).nil? + redirect_to(action: :new) && return end if DiscoApp::ChargesService.activate(@shop, charge) redirect_to main_app.root_url @@ -39,9 +40,7 @@ def activate def find_subscription @subscription = @shop.subscriptions.find_by_id!(params[:subscription_id]) - unless @subscription.requires_active_charge? and not @subscription.active_charge? - redirect_to main_app.root_url - end + redirect_to main_app.root_url unless @subscription.requires_active_charge? && !@subscription.active_charge? end end diff --git a/app/controllers/disco_app/concerns/app_proxy_controller.rb b/app/controllers/disco_app/concerns/app_proxy_controller.rb index e45c0df6..051038de 100644 --- a/app/controllers/disco_app/concerns/app_proxy_controller.rb +++ b/app/controllers/disco_app/concerns/app_proxy_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::AppProxyController + extend ActiveSupport::Concern included do @@ -6,7 +7,7 @@ module DiscoApp::Concerns::AppProxyController before_action :shopify_shop after_action :add_liquid_header - rescue_from ActiveRecord::RecordNotFound do |exception| + rescue_from ActiveRecord::RecordNotFound do |_exception| render_error 404 end end @@ -14,13 +15,12 @@ module DiscoApp::Concerns::AppProxyController private def verify_proxy_signature - unless proxy_signature_is_valid? - head :unauthorized - end + head :unauthorized unless proxy_signature_is_valid? end def proxy_signature_is_valid? - return true if (Rails.env.development? || Rails.env.test?) and DiscoApp.configuration.skip_proxy_verification? + return true if (Rails.env.development? || Rails.env.test?) && DiscoApp.configuration.skip_proxy_verification? + DiscoApp::ProxyService.proxy_signature_is_valid?(request.query_string, ShopifyApp.configuration.secret) end diff --git a/app/controllers/disco_app/concerns/authenticated_controller.rb b/app/controllers/disco_app/concerns/authenticated_controller.rb index c7974172..59ae0503 100644 --- a/app/controllers/disco_app/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/concerns/authenticated_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::AuthenticatedController + extend ActiveSupport::Concern include ShopifyApp::LoginProtection @@ -17,8 +18,8 @@ module DiscoApp::Concerns::AuthenticatedController private def auto_login - if shop_session.nil? and request_hmac_valid? - if(shop = DiscoApp::Shop.find_by_shopify_domain(sanitized_shop_name)).present? + if shop_session.nil? && request_hmac_valid? + if (shop = DiscoApp::Shop.find_by_shopify_domain(sanitized_shop_name)).present? session[:shopify] = shop.id session[:shopify_domain] = sanitized_shop_name end @@ -34,35 +35,27 @@ def shopify_shop end def check_installed - if @shop.awaiting_install? or @shop.installing? + if @shop.awaiting_install? || @shop.installing? redirect_if_not_current_path disco_app.installing_path return end - if @shop.awaiting_uninstall? or @shop.uninstalling? + if @shop.awaiting_uninstall? || @shop.uninstalling? redirect_if_not_current_path disco_app.uninstalling_path return end - unless @shop.installed? - redirect_if_not_current_path disco_app.install_path - end + redirect_if_not_current_path disco_app.install_path unless @shop.installed? end def check_current_subscription - unless @shop.current_subscription? - redirect_if_not_current_path disco_app.new_subscription_path - end + redirect_if_not_current_path disco_app.new_subscription_path unless @shop.current_subscription? end def check_active_charge - if @shop.current_subscription? and @shop.current_subscription.requires_active_charge? and not @shop.development? and not @shop.current_subscription.active_charge? - redirect_if_not_current_path disco_app.new_subscription_charge_path(@shop.current_subscription) - end + redirect_if_not_current_path disco_app.new_subscription_charge_path(@shop.current_subscription) if @shop.current_subscription? && @shop.current_subscription.requires_active_charge? && !@shop.development? && !@shop.current_subscription.active_charge? end def redirect_if_not_current_path(target) - if request.path != target - redirect_to target - end + redirect_to target if request.path != target end def request_hmac_valid? @@ -71,9 +64,7 @@ def request_hmac_valid? def check_shop_whitelist if shop_session - if ENV['WHITELISTED_DOMAINS'].present? && !ENV['WHITELISTED_DOMAINS'].include?(shop_session.url) - redirect_to_login - end + redirect_to_login if ENV['WHITELISTED_DOMAINS'].present? && !ENV['WHITELISTED_DOMAINS'].include?(shop_session.url) end end diff --git a/app/controllers/disco_app/concerns/carrier_request_controller.rb b/app/controllers/disco_app/concerns/carrier_request_controller.rb index 52e52f1f..91a59d93 100644 --- a/app/controllers/disco_app/concerns/carrier_request_controller.rb +++ b/app/controllers/disco_app/concerns/carrier_request_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::CarrierRequestController + extend ActiveSupport::Concern included do @@ -10,26 +11,21 @@ module DiscoApp::Concerns::CarrierRequestController private def verify_carrier_request - unless carrier_request_signature_is_valid? - head :unauthorized - end + head :unauthorized unless carrier_request_signature_is_valid? end def carrier_request_signature_is_valid? - return true if Rails.env.development? and DiscoApp.configuration.skip_carrier_request_verification? + return true if Rails.env.development? && DiscoApp.configuration.skip_carrier_request_verification? + DiscoApp::CarrierRequestService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) end def find_shop - unless (@shop = DiscoApp::Shop.find_by_shopify_domain(request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN'])) - head :unauthorized - end + head :unauthorized unless (@shop = DiscoApp::Shop.find_by_shopify_domain(request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN'])) end def validate_rate_params - unless params[:rate].present? and params[:rate][:origin].present? and params[:rate][:destination].present? and params[:rate][:items].present? - head :bad_request - end + head :bad_request unless params[:rate].present? && params[:rate][:origin].present? && params[:rate][:destination].present? && params[:rate][:items].present? end end diff --git a/app/controllers/disco_app/concerns/user_authenticated_controller.rb b/app/controllers/disco_app/concerns/user_authenticated_controller.rb index 2ede3c45..629a6c8f 100644 --- a/app/controllers/disco_app/concerns/user_authenticated_controller.rb +++ b/app/controllers/disco_app/concerns/user_authenticated_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::UserAuthenticatedController + extend ActiveSupport::Concern include ShopifyApp::LoginProtection diff --git a/app/controllers/disco_app/concerns/webhooks_controller.rb b/app/controllers/disco_app/concerns/webhooks_controller.rb index 5e34242c..ffab245b 100644 --- a/app/controllers/disco_app/concerns/webhooks_controller.rb +++ b/app/controllers/disco_app/concerns/webhooks_controller.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::WebhooksController + extend ActiveSupport::Concern included do @@ -12,20 +13,16 @@ def process_webhook shopify_domain = request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN'] # Ensure a domain was provided in the headers. - unless shopify_domain - head :bad_request - end + head :bad_request unless shopify_domain # Try to find a matching background job task for the given topic using class name. job_class = DiscoApp::WebhookService.find_job_class(topic) # Return bad request if we couldn't match a job class. - unless job_class.present? - head :bad_request - end + head :bad_request unless job_class.present? # Decode the body data and enqueue the appropriate job. - data = ActiveSupport::JSON::decode(request.body.read).with_indifferent_access + data = ActiveSupport::JSON.decode(request.body.read).with_indifferent_access job_class.perform_later(shopify_domain, data) render body: nil @@ -34,14 +31,13 @@ def process_webhook private def verify_webhook - unless webhook_is_valid? - head :unauthorized - end + head :unauthorized unless webhook_is_valid? request.body.rewind end def webhook_is_valid? - return true if Rails.env.development? and DiscoApp.configuration.skip_webhook_verification? + return true if Rails.env.development? && DiscoApp.configuration.skip_webhook_verification? + DiscoApp::WebhookService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) end diff --git a/app/controllers/disco_app/flow/actions_controller.rb b/app/controllers/disco_app/flow/actions_controller.rb index 96215003..fe5f67a4 100644 --- a/app/controllers/disco_app/flow/actions_controller.rb +++ b/app/controllers/disco_app/flow/actions_controller.rb @@ -1,7 +1,9 @@ module DiscoApp module Flow class ActionsController < ActionController::Base + include DiscoApp::Flow::Concerns::ActionsController + end end end diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index 25976358..6d2e7bb7 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -25,9 +25,7 @@ def create_flow_action private def verify_flow_action - unless flow_action_is_valid? - head :unauthorized - end + head :unauthorized unless flow_action_is_valid? request.body.rewind end diff --git a/app/controllers/disco_app/frame_controller.rb b/app/controllers/disco_app/frame_controller.rb index 3d27d544..bfb70ab0 100644 --- a/app/controllers/disco_app/frame_controller.rb +++ b/app/controllers/disco_app/frame_controller.rb @@ -3,7 +3,6 @@ class DiscoApp::FrameController < ActionController::Base layout nil def frame - end end diff --git a/app/controllers/disco_app/install_controller.rb b/app/controllers/disco_app/install_controller.rb index fd895345..8a1dc5a5 100644 --- a/app/controllers/disco_app/install_controller.rb +++ b/app/controllers/disco_app/install_controller.rb @@ -1,4 +1,5 @@ class DiscoApp::InstallController < ApplicationController + include DiscoApp::Concerns::AuthenticatedController skip_before_action :check_current_subscription @@ -12,16 +13,12 @@ def install # Display an "installing" page. def installing - if @shop.installed? - redirect_to main_app.root_path - end + redirect_to main_app.root_path if @shop.installed? end # Display an "uninstalling" page. Should be almost never used. def uninstalling - if @shop.uninstalled? - redirect_to main_app.root_path - end + redirect_to main_app.root_path if @shop.uninstalled? end end diff --git a/app/controllers/disco_app/subscriptions_controller.rb b/app/controllers/disco_app/subscriptions_controller.rb index 29a97cac..2fe0f358 100644 --- a/app/controllers/disco_app/subscriptions_controller.rb +++ b/app/controllers/disco_app/subscriptions_controller.rb @@ -1,4 +1,5 @@ class DiscoApp::SubscriptionsController < ApplicationController + include DiscoApp::Concerns::AuthenticatedController skip_before_action :check_current_subscription @@ -10,13 +11,13 @@ def new def create # Get the selected plan. If it's not available or couldn't be found, # redirect back to the plan selection page. - if(plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan])).nil? - redirect_to action: :new and return + if (plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan])).nil? + redirect_to(action: :new) && return end # Subscribe the current shop to the selected plan. Pass along any cookied # plan code and source code. - if(subscription = DiscoApp::SubscriptionService.subscribe(@shop, plan, cookies[DiscoApp::CODE_COOKIE_KEY], cookies[DiscoApp::SOURCE_COOKIE_KEY])).nil? + if (subscription = DiscoApp::SubscriptionService.subscribe(@shop, plan, cookies[DiscoApp::CODE_COOKIE_KEY], cookies[DiscoApp::SOURCE_COOKIE_KEY])).nil? redirect_to action: :new else redirect_to main_app.root_path diff --git a/app/controllers/disco_app/user_sessions_controller.rb b/app/controllers/disco_app/user_sessions_controller.rb index 1f4b0d53..50487c1b 100644 --- a/app/controllers/disco_app/user_sessions_controller.rb +++ b/app/controllers/disco_app/user_sessions_controller.rb @@ -1,4 +1,5 @@ class DiscoApp::UserSessionsController < ApplicationController + include DiscoApp::Concerns::AuthenticatedController def new diff --git a/app/controllers/disco_app/webhooks_controller.rb b/app/controllers/disco_app/webhooks_controller.rb index 72616fd7..4a12f1f4 100644 --- a/app/controllers/disco_app/webhooks_controller.rb +++ b/app/controllers/disco_app/webhooks_controller.rb @@ -1,3 +1,5 @@ class DiscoApp::WebhooksController < ActionController::Base + include DiscoApp::Concerns::WebhooksController + end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index dbba900e..2c648c56 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,4 +1,5 @@ class SessionsController < ActionController::Base + include ShopifyApp::SessionsConcern def referral @@ -18,14 +19,14 @@ def failure # mode. Skipping OAuth still requires a shop with Shopify domain specified # by the `shop` parameter to be present in the local database. def authenticate - if Rails.env.development? and DiscoApp.configuration.skip_oauth? + if Rails.env.development? && DiscoApp.configuration.skip_oauth? shop = DiscoApp::Shop.find_by_shopify_domain!(sanitized_shop_name) sess = ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token) session[:shopify] = ShopifyApp::SessionRepository.store(sess) session[:shopify_domain] = sanitized_shop_name - redirect_to disco_app.frame_path and return + redirect_to(disco_app.frame_path) && return end super end diff --git a/app/helpers/disco_app/application_helper.rb b/app/helpers/disco_app/application_helper.rb index 4ab89274..11ff8142 100644 --- a/app/helpers/disco_app/application_helper.rb +++ b/app/helpers/disco_app/application_helper.rb @@ -18,7 +18,7 @@ def link_to_modal(name, path, options = {}) title: options.delete(:modal_title), width: options.delete(:modal_width), height: options.delete(:modal_height), - buttons: options.delete(:modal_buttons), + buttons: options.delete(:modal_buttons) } options[:onclick] = "ShopifyApp.Modal.open(#{modal_options.to_json}); return false;" options[:onclick].gsub!(/"function(.*?)"/, 'function\1') @@ -32,14 +32,14 @@ def react_component_with_content(name, args = {}, options = {}, &block) react_component(name, args, options) end - # Provide link to dynamically add a new nested fields association + # Provide link to dynamically add a new nested fields association def link_to_add_fields(name, f, association) new_object = f.object.send(association).klass.new id = new_object.object_id fields = f.fields_for(association, new_object, child_index: id) do |builder| - render(association.to_s.singularize + "_fields", f: builder) + render(association.to_s.singularize + '_fields', f: builder) end - link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")}) + link_to(name, '#', class: 'add_fields', data: { id: id, fields: fields.gsub("\n", '') }) end # Return the props required to instantiate a React ModelForm component for the diff --git a/app/jobs/disco_app/app_installed_job.rb b/app/jobs/disco_app/app_installed_job.rb index 5d3507f5..c6fdbc64 100644 --- a/app/jobs/disco_app/app_installed_job.rb +++ b/app/jobs/disco_app/app_installed_job.rb @@ -1,3 +1,5 @@ class DiscoApp::AppInstalledJob < DiscoApp::ShopJob + include DiscoApp::Concerns::AppInstalledJob + end diff --git a/app/jobs/disco_app/app_uninstalled_job.rb b/app/jobs/disco_app/app_uninstalled_job.rb index 16878d93..2b5a1e8e 100644 --- a/app/jobs/disco_app/app_uninstalled_job.rb +++ b/app/jobs/disco_app/app_uninstalled_job.rb @@ -1,3 +1,5 @@ class DiscoApp::AppUninstalledJob < DiscoApp::ShopJob + include DiscoApp::Concerns::AppUninstalledJob + end diff --git a/app/jobs/disco_app/concerns/app_installed_job.rb b/app/jobs/disco_app/concerns/app_installed_job.rb index 95ebe0e3..29f93271 100644 --- a/app/jobs/disco_app/concerns/app_installed_job.rb +++ b/app/jobs/disco_app/concerns/app_installed_job.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::AppInstalledJob + extend ActiveSupport::Concern included do @@ -21,9 +22,7 @@ def perform(_shop, plan_code = nil, source = nil) @shop.reload - if default_plan.present? - DiscoApp::SubscriptionService.subscribe(@shop, default_plan, plan_code, source) - end + DiscoApp::SubscriptionService.subscribe(@shop, default_plan, plan_code, source) if default_plan.present? end # Provide an overridable hook for applications to examine the @shop object diff --git a/app/jobs/disco_app/concerns/app_uninstalled_job.rb b/app/jobs/disco_app/concerns/app_uninstalled_job.rb index 382e8410..255820bd 100644 --- a/app/jobs/disco_app/concerns/app_uninstalled_job.rb +++ b/app/jobs/disco_app/concerns/app_uninstalled_job.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::AppUninstalledJob + extend ActiveSupport::Concern included do @@ -12,7 +13,7 @@ module DiscoApp::Concerns::AppUninstalledJob # - Mark any recurring application charges as cancelled. # - Remove any stored sessions for the shop. # - def perform(_shop, shop_data) + def perform(_shop, _shop_data) DiscoApp::ChargesService.cancel_recurring_charges(@shop) DiscoApp::SendSubscriptionJob.perform_later(@shop) @shop.sessions.delete_all diff --git a/app/jobs/disco_app/concerns/render_asset_group_job.rb b/app/jobs/disco_app/concerns/render_asset_group_job.rb index 2b9e7f04..656e595a 100644 --- a/app/jobs/disco_app/concerns/render_asset_group_job.rb +++ b/app/jobs/disco_app/concerns/render_asset_group_job.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::RenderAssetGroupJob + extend ActiveSupport::Concern def perform(_shop, instance, asset_group) diff --git a/app/jobs/disco_app/concerns/shop_update_job.rb b/app/jobs/disco_app/concerns/shop_update_job.rb index 6d0b9c26..c4ec3b23 100644 --- a/app/jobs/disco_app/concerns/shop_update_job.rb +++ b/app/jobs/disco_app/concerns/shop_update_job.rb @@ -1,10 +1,11 @@ module DiscoApp::Concerns::ShopUpdateJob + extend ActiveSupport::Concern # Perform an update of the current shop's information. def perform(_shop, shop_data = nil) # If we weren't provided with shop data (eg from a webhook), fetch it. - shop_data ||= ActiveSupport::JSON::decode(ShopifyAPI::Shop.current.to_json) + shop_data ||= ActiveSupport::JSON.decode(ShopifyAPI::Shop.current.to_json) # Update attributes stored directly on the Shop model, along with the data hash itself. @shop.update(shop_data.with_indifferent_access.slice(*DiscoApp::Shop.column_names).except(:id, :created_at).merge(data: shop_data)) diff --git a/app/jobs/disco_app/concerns/subscription_changed_job.rb b/app/jobs/disco_app/concerns/subscription_changed_job.rb index 1387d865..7de17a84 100644 --- a/app/jobs/disco_app/concerns/subscription_changed_job.rb +++ b/app/jobs/disco_app/concerns/subscription_changed_job.rb @@ -1,7 +1,8 @@ module DiscoApp::Concerns::SubscriptionChangedJob + extend ActiveSupport::Concern - def perform(_shop, subscription) + def perform(_shop, _subscription) DiscoApp::SendSubscriptionJob.perform_later(@shop) end diff --git a/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb b/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb index ca6093c1..8a86a6db 100644 --- a/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb @@ -1,10 +1,11 @@ module DiscoApp::Concerns::SynchroniseCarrierServiceJob + extend ActiveSupport::Concern # Ensure that any carrier service required by our app is registered. def perform(_shop) # Don't proceed unless we have a name and callback url. - return unless carrier_service_name and callback_url + return unless carrier_service_name && callback_url # Registered the carrier service if it hasn't been registered yet. unless current_carrier_service_names.include?(carrier_service_name) @@ -19,11 +20,11 @@ def perform(_shop) # Ensure any existing carrier services (with the correct name) are active # and have a current callback URL. current_carrier_services.each do |carrier_service| - if carrier_service.name == carrier_service_name - carrier_service.callback_url = callback_url - carrier_service.active = true - carrier_service.save - end + next unless carrier_service.name == carrier_service_name + + carrier_service.callback_url = callback_url + carrier_service.active = true + carrier_service.save end end diff --git a/app/jobs/disco_app/concerns/synchronise_resources_job.rb b/app/jobs/disco_app/concerns/synchronise_resources_job.rb index 63a159c4..a417a566 100644 --- a/app/jobs/disco_app/concerns/synchronise_resources_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_resources_job.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::SynchroniseResourcesJob + extend ActiveSupport::Concern def perform(_shop, class_name, params) diff --git a/app/jobs/disco_app/concerns/synchronise_users_job.rb b/app/jobs/disco_app/concerns/synchronise_users_job.rb index cefb3ba1..42b9c012 100644 --- a/app/jobs/disco_app/concerns/synchronise_users_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_users_job.rb @@ -1,13 +1,14 @@ module DiscoApp::Concerns::SynchroniseUsersJob + extend ActiveSupport::Concern def perform(_shop) begin - users = @shop.with_api_context { + users = @shop.with_api_context do ShopifyAPI::User.all - } + end rescue ActiveResource::UnauthorizedAccess => e - Rollbar.error(e) and return + Rollbar.error(e) && (return) end users.each { |user| DiscoApp::User.create_user(user, @shop) } end diff --git a/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb b/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb index c0a762b1..912db856 100644 --- a/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_webhooks_job.rb @@ -1,7 +1,8 @@ module DiscoApp::Concerns::SynchroniseWebhooksJob + extend ActiveSupport::Concern - COMMON_WEBHOOKS = [:'app/uninstalled', :'shop/update'] + COMMON_WEBHOOKS = [:'app/uninstalled', :'shop/update'].freeze # Ensure the webhooks registered with our shop are the same as those listed # in our application configuration. @@ -12,16 +13,14 @@ def perform(_shop) ShopifyAPI::Webhook.create( topic: topic, address: webhooks_url, - format: 'json', + format: 'json' ) end end # Remove any extraneous topics. current_webhooks.each do |webhook| - unless expected_topics.include?(webhook.topic.to_sym) - ShopifyAPI::Webhook.delete(webhook.id) - end + ShopifyAPI::Webhook.delete(webhook.id) unless expected_topics.include?(webhook.topic.to_sym) end # Ensure webhook addresses are current. diff --git a/app/jobs/disco_app/render_asset_group_job.rb b/app/jobs/disco_app/render_asset_group_job.rb index 7075b2ad..f2f7a5b9 100644 --- a/app/jobs/disco_app/render_asset_group_job.rb +++ b/app/jobs/disco_app/render_asset_group_job.rb @@ -1,3 +1,5 @@ class DiscoApp::RenderAssetGroupJob < DiscoApp::ShopJob + include DiscoApp::Concerns::RenderAssetGroupJob + end diff --git a/app/jobs/disco_app/shop_job.rb b/app/jobs/disco_app/shop_job.rb index fb02d51a..1f0af369 100644 --- a/app/jobs/disco_app/shop_job.rb +++ b/app/jobs/disco_app/shop_job.rb @@ -26,7 +26,7 @@ def shop_context(job, block) shop_id: @shop.id, shopify_domain: @shop.shopify_domain ) - + @shop.with_api_context { block.call(job.arguments) } end diff --git a/app/jobs/disco_app/shop_update_job.rb b/app/jobs/disco_app/shop_update_job.rb index 3ae4298c..dfc71610 100644 --- a/app/jobs/disco_app/shop_update_job.rb +++ b/app/jobs/disco_app/shop_update_job.rb @@ -1,3 +1,5 @@ class DiscoApp::ShopUpdateJob < DiscoApp::ShopJob + include DiscoApp::Concerns::ShopUpdateJob + end diff --git a/app/jobs/disco_app/subscription_changed_job.rb b/app/jobs/disco_app/subscription_changed_job.rb index e28beeaf..0800bee0 100644 --- a/app/jobs/disco_app/subscription_changed_job.rb +++ b/app/jobs/disco_app/subscription_changed_job.rb @@ -1,3 +1,5 @@ class DiscoApp::SubscriptionChangedJob < DiscoApp::ShopJob + include DiscoApp::Concerns::SubscriptionChangedJob + end diff --git a/app/jobs/disco_app/synchronise_carrier_service_job.rb b/app/jobs/disco_app/synchronise_carrier_service_job.rb index 543e1c01..4e4533aa 100644 --- a/app/jobs/disco_app/synchronise_carrier_service_job.rb +++ b/app/jobs/disco_app/synchronise_carrier_service_job.rb @@ -1,3 +1,5 @@ class DiscoApp::SynchroniseCarrierServiceJob < DiscoApp::ShopJob + include DiscoApp::Concerns::SynchroniseCarrierServiceJob + end diff --git a/app/jobs/disco_app/synchronise_resources_job.rb b/app/jobs/disco_app/synchronise_resources_job.rb index d97ee3cd..1af79436 100644 --- a/app/jobs/disco_app/synchronise_resources_job.rb +++ b/app/jobs/disco_app/synchronise_resources_job.rb @@ -1,3 +1,5 @@ class DiscoApp::SynchroniseResourcesJob < DiscoApp::ShopJob + include DiscoApp::Concerns::SynchroniseResourcesJob + end diff --git a/app/jobs/disco_app/synchronise_users_job.rb b/app/jobs/disco_app/synchronise_users_job.rb index 381962c9..4d03cf09 100644 --- a/app/jobs/disco_app/synchronise_users_job.rb +++ b/app/jobs/disco_app/synchronise_users_job.rb @@ -1,3 +1,5 @@ class DiscoApp::SynchroniseUsersJob < DiscoApp::ShopJob + include DiscoApp::Concerns::SynchroniseUsersJob + end diff --git a/app/jobs/disco_app/synchronise_webhooks_job.rb b/app/jobs/disco_app/synchronise_webhooks_job.rb index df5098a6..0920c62a 100644 --- a/app/jobs/disco_app/synchronise_webhooks_job.rb +++ b/app/jobs/disco_app/synchronise_webhooks_job.rb @@ -1,3 +1,5 @@ class DiscoApp::SynchroniseWebhooksJob < DiscoApp::ShopJob + include DiscoApp::Concerns::SynchroniseWebhooksJob + end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 10a4cba8..c6ae68f2 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,3 +1,5 @@ class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end diff --git a/app/models/disco_app/app_settings.rb b/app/models/disco_app/app_settings.rb index 70a19597..86817b00 100644 --- a/app/models/disco_app/app_settings.rb +++ b/app/models/disco_app/app_settings.rb @@ -1,3 +1,5 @@ class DiscoApp::AppSettings < ApplicationRecord + include DiscoApp::Concerns::AppSettings + end diff --git a/app/models/disco_app/application_charge.rb b/app/models/disco_app/application_charge.rb index 0fe3720c..c7656828 100644 --- a/app/models/disco_app/application_charge.rb +++ b/app/models/disco_app/application_charge.rb @@ -8,7 +8,7 @@ class DiscoApp::ApplicationCharge < ApplicationRecord accepted: 1, declined: 2, expired: 3, - active: 4, + active: 4 } scope :active, -> { where status: statuses[:active] } diff --git a/app/models/disco_app/concerns/app_settings.rb b/app/models/disco_app/concerns/app_settings.rb index 17a41bef..ce90afe7 100644 --- a/app/models/disco_app/concerns/app_settings.rb +++ b/app/models/disco_app/concerns/app_settings.rb @@ -1,7 +1,9 @@ module DiscoApp::Concerns::AppSettings + extend ActiveSupport::Concern included do acts_as_singleton end + end diff --git a/app/models/disco_app/concerns/can_be_liquified.rb b/app/models/disco_app/concerns/can_be_liquified.rb index 973f6581..23c547da 100644 --- a/app/models/disco_app/concerns/can_be_liquified.rb +++ b/app/models/disco_app/concerns/can_be_liquified.rb @@ -1,10 +1,10 @@ module DiscoApp::Concerns::CanBeLiquified + extend ActiveSupport::Concern - SPLIT_ARRAY_SEPARATOR = '@!@' + SPLIT_ARRAY_SEPARATOR = '@!@'.freeze included do - # Return this model as a hash for use with `to_liquid`. Returns `as_json` by default but is provided here as a hook # for potential overrides. def as_liquid @@ -26,21 +26,15 @@ def liquid_model_name # Return given value as a string expression that will be evaluated in Liquid to result in the correct value type. def as_liquid_value(key, value) - if value.is_a? Numeric or (!!value == value) - return value.to_s - end - if value.nil? - return 'nil' - end + return value.to_s if value.is_a?(Numeric) || (!!value == value) + return 'nil' if value.nil? if value.is_a? Array - return "'#{value.map { |e| (e.is_a? String) ? CGI::escapeHTML(e) : e }.join(SPLIT_ARRAY_SEPARATOR)}' | split: '#{SPLIT_ARRAY_SEPARATOR}'" + return "'#{value.map { |e| (e.is_a? String) ? CGI.escapeHTML(e) : e }.join(SPLIT_ARRAY_SEPARATOR)}' | split: '#{SPLIT_ARRAY_SEPARATOR}'" end - if value.is_a? String and key.end_with? '_html' - return "'#{value.to_s.gsub("'", "'")}'" - end - "'#{CGI::escapeHTML(value.to_s)}'" - end + return "'#{value.to_s.gsub("'", ''')}'" if value.is_a?(String) && key.end_with?('_html') + "'#{CGI.escapeHTML(value.to_s)}'" + end end end diff --git a/app/models/disco_app/concerns/has_metafields.rb b/app/models/disco_app/concerns/has_metafields.rb index 36423078..6db95511 100644 --- a/app/models/disco_app/concerns/has_metafields.rb +++ b/app/models/disco_app/concerns/has_metafields.rb @@ -1,8 +1,8 @@ module DiscoApp::Concerns::HasMetafields + extend ActiveSupport::Concern included do - # Write multiple metafields for this object in a single call. # # Expects an argument in a nested hash structure with :namespace => :key => @@ -42,7 +42,6 @@ def build_metafields(metafields) end end.flatten end - end end diff --git a/app/models/disco_app/concerns/plan.rb b/app/models/disco_app/concerns/plan.rb index b89cd5b1..bb1c3ee1 100644 --- a/app/models/disco_app/concerns/plan.rb +++ b/app/models/disco_app/concerns/plan.rb @@ -1,8 +1,8 @@ module DiscoApp::Concerns::Plan + extend ActiveSupport::Concern included do - has_many :subscriptions has_many :shops, through: :subscriptions has_many :plan_codes, dependent: :destroy @@ -25,11 +25,10 @@ module DiscoApp::Concerns::Plan scope :available, -> { where status: statuses[:available] } validates_presence_of :name - end def has_trial? - trial_period_days.present? and trial_period_days > 0 + trial_period_days.present? && (trial_period_days > 0) end end diff --git a/app/models/disco_app/concerns/plan_code.rb b/app/models/disco_app/concerns/plan_code.rb index b7892653..0e1b68a2 100644 --- a/app/models/disco_app/concerns/plan_code.rb +++ b/app/models/disco_app/concerns/plan_code.rb @@ -1,8 +1,8 @@ module DiscoApp::Concerns::PlanCode + extend ActiveSupport::Concern included do - belongs_to :plan enum status: { @@ -12,7 +12,6 @@ module DiscoApp::Concerns::PlanCode validates_presence_of :code validates_presence_of :amount - end end diff --git a/app/models/disco_app/concerns/renders_assets.rb b/app/models/disco_app/concerns/renders_assets.rb index b3333c48..0f780c6f 100644 --- a/app/models/disco_app/concerns/renders_assets.rb +++ b/app/models/disco_app/concerns/renders_assets.rb @@ -2,6 +2,7 @@ require 'uglifier' module DiscoApp::Concerns::RendersAssets + extend ActiveSupport::Concern included do @@ -10,7 +11,6 @@ module DiscoApp::Concerns::RendersAssets end class_methods do - # Ruby "macro" that allows the definition of a number of Shopify assets that # should be rendered and uploaded when certain attributes on the including # class change. This assumes that the including class (1) is an ActiveRecord @@ -70,7 +70,6 @@ def renders_assets_default_options minify: Rails.env.production? || Rails.env.staging? } end - end # Callback, triggered after a model save. Iterates through each asset group @@ -78,9 +77,7 @@ def renders_assets_default_options # attributes are found in the asset group's triggered_by list. def queue_render_asset_group_job renderable_asset_groups.each do |asset_group, options| - unless (previous_changes.keys & options[:triggered_by]).empty? - DiscoApp::RenderAssetGroupJob.perform_later(shop, self, asset_group.to_s) - end + DiscoApp::RenderAssetGroupJob.perform_later(shop, self, asset_group.to_s) unless (previous_changes.keys & options[:triggered_by]).empty? end end @@ -97,26 +94,22 @@ def render_asset_group(asset_group) options[:assets].each do |asset| # Create/replace the asset via the Shopify API. - shopify_asset = shop.with_api_context { + shopify_asset = shop.with_api_context do ShopifyAPI::Asset.create( key: asset, value: render_asset_group_asset(asset, public_urls, options) ) - } + end # Store the public URL to this asset, so that we're able to use it in # subsequent template renders. Adds a .css suffix to .scss assets, so that # we use the Shopify-compiled version of any SCSS stylesheets. - if shopify_asset.public_url.present? - public_urls[asset] = shopify_asset.public_url.gsub(/\.scss\?/, '.scss.css?') - end + public_urls[asset] = shopify_asset.public_url.gsub(/\.scss\?/, '.scss.css?') if shopify_asset.public_url.present? end # If we specified the creation of any script tags based on newly rendered # assets, do that now. - unless options[:script_tags].empty? - render_asset_script_tags(options, public_urls) - end + render_asset_script_tags(options, public_urls) unless options[:script_tags].empty? end private @@ -141,7 +134,7 @@ def render_asset_group_asset(asset, public_urls, options) # Return true if the given asset should be minified with Uglifier. def should_be_minified?(asset, options) - asset.to_s.end_with?('.js') and options[:minify] + asset.to_s.end_with?('.js') && options[:minify] end def render_asset_renderer @@ -157,7 +150,7 @@ def render_asset_script_tags(options, public_urls) # Iterate each script tag for which we have a known public URL and create # or update the corresponding script tag resource. public_urls.slice(*options[:script_tags]).each do |asset, public_url| - script_tag = current_script_tags.find(lambda { ShopifyAPI::ScriptTag.new(event: 'onload') }) { |script_tag| script_tag.src.include?("#{asset}?") } + script_tag = current_script_tags.find(-> { ShopifyAPI::ScriptTag.new(event: 'onload') }) { |script_tag| script_tag.src.include?("#{asset}?") } script_tag.src = public_url shop.with_api_context { script_tag.save } end diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index 96a4f858..b4ba6b5d 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::Shop + extend ActiveSupport::Concern included do @@ -31,7 +32,7 @@ module DiscoApp::Concerns::Shop } # Define some useful scopes. - scope :status, -> (status) { where status: status } + scope :status, ->(status) { where status: status } scope :installed, -> { where status: statuses[:installed] } scope :has_active_shopify_plan, -> { where.not(plan_name: [:cancelled, :frozen, :fraudulent]) } scope :shopify_plus, -> { where(plan_name: :shopify_plus) } @@ -86,8 +87,8 @@ def installed_duration def time_zone @time_zone ||= begin Time.find_zone!(data[:timezone].to_s.gsub(/^\(.+\)\s/, '')) - rescue ArgumentError - Time.zone + rescue ArgumentError + Time.zone end end @@ -106,7 +107,6 @@ def disco_api_client def data read_attribute(:data).with_indifferent_access end - end end diff --git a/app/models/disco_app/concerns/source.rb b/app/models/disco_app/concerns/source.rb index 37651091..08709ff6 100644 --- a/app/models/disco_app/concerns/source.rb +++ b/app/models/disco_app/concerns/source.rb @@ -1,14 +1,13 @@ module DiscoApp::Concerns::Source + extend ActiveSupport::Concern included do - has_many :subscriptions has_many :shops, through: :subscriptions validates_presence_of :source validates_presence_of :name - end end diff --git a/app/models/disco_app/concerns/subscription.rb b/app/models/disco_app/concerns/subscription.rb index dcd003ef..c9e44eb5 100644 --- a/app/models/disco_app/concerns/subscription.rb +++ b/app/models/disco_app/concerns/subscription.rb @@ -1,8 +1,8 @@ module DiscoApp::Concerns::Subscription + extend ActiveSupport::Concern included do - belongs_to :shop belongs_to :plan belongs_to :plan_code, optional: true @@ -23,7 +23,6 @@ module DiscoApp::Concerns::Subscription scope :current, -> { where status: [statuses[:trial], statuses[:active]] } after_commit :cancel_charge - end # Only require an active charge if the amount to be charged is > 0. @@ -67,6 +66,7 @@ def as_json(options = {}) def cancel_charge return if (previous_changes.keys & ['amount', 'trial_period_days']).empty? return unless active_charge? + active_charge.cancelled! end diff --git a/app/models/disco_app/concerns/synchronises.rb b/app/models/disco_app/concerns/synchronises.rb index 2cce9b05..8e5ca5c1 100644 --- a/app/models/disco_app/concerns/synchronises.rb +++ b/app/models/disco_app/concerns/synchronises.rb @@ -1,29 +1,27 @@ module DiscoApp::Concerns::Synchronises + extend ActiveSupport::Concern class_methods do - # Define the number of resources per page to fetch. SYNCHRONISES_PAGE_LIMIT = 250 - def should_synchronise?(shop, data) + def should_synchronise?(_shop, _data) true end - def synchronise_by(shop, data) + def synchronise_by(_shop, data) { id: data[:id] } end def synchronise(shop, data) - if data.is_a?(ShopifyAPI::Base) - data = ActiveSupport::JSON.decode(data.to_json) - end + data = ActiveSupport::JSON.decode(data.to_json) if data.is_a?(ShopifyAPI::Base) data = data.with_indifferent_access return unless should_synchronise?(shop, data) begin - instance = self.find_or_create_by!(self.synchronise_by(shop, data)) do |instance| + instance = find_or_create_by!(synchronise_by(shop, data)) do |instance| instance.shop = shop instance.data = data end @@ -36,7 +34,7 @@ def synchronise(shop, data) instance end - def should_synchronise_deletion?(shop, data) + def should_synchronise_deletion?(_shop, _data) true end @@ -45,26 +43,23 @@ def synchronise_deletion(shop, data) return unless should_synchronise_deletion?(shop, data) - self.where(shop: shop, id: data[:id]).destroy_all + where(shop: shop, id: data[:id]).destroy_all end def synchronise_all(shop, params = {}) resource_count = shop.with_api_context { self::SHOPIFY_API_CLASS.count(params) } (1..(resource_count / SYNCHRONISES_PAGE_LIMIT.to_f).ceil).each do |page| - DiscoApp::SynchroniseResourcesJob.perform_later(shop, self.name, params.merge(page: page, limit: SYNCHRONISES_PAGE_LIMIT)) + DiscoApp::SynchroniseResourcesJob.perform_later(shop, name, params.merge(page: page, limit: SYNCHRONISES_PAGE_LIMIT)) end end - end included do - # Override the "read" data attribute to allow indifferent access. def data read_attribute(:data).with_indifferent_access end - end end diff --git a/app/models/disco_app/concerns/taggable.rb b/app/models/disco_app/concerns/taggable.rb index 6ce067d5..737b62f3 100644 --- a/app/models/disco_app/concerns/taggable.rb +++ b/app/models/disco_app/concerns/taggable.rb @@ -1,4 +1,5 @@ module DiscoApp::Concerns::Taggable + extend ActiveSupport::Concern def tags diff --git a/app/models/disco_app/concerns/user.rb b/app/models/disco_app/concerns/user.rb index d0523c55..b524acf8 100644 --- a/app/models/disco_app/concerns/user.rb +++ b/app/models/disco_app/concerns/user.rb @@ -1,11 +1,12 @@ module DiscoApp::Concerns::User + extend ActiveSupport::Concern included do belongs_to :shop def self.create_user(shopify_user, shop) - user = self.find_or_create_by!(id: shopify_user.id, shop: shop) + user = find_or_create_by!(id: shopify_user.id, shop: shop) user.update( first_name: shopify_user.first_name || '', last_name: shopify_user.last_name || '', @@ -15,6 +16,6 @@ def self.create_user(shopify_user, shop) rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation retry end - end + end diff --git a/app/models/disco_app/flow/action.rb b/app/models/disco_app/flow/action.rb index 58c2c5c9..3ed97ac1 100644 --- a/app/models/disco_app/flow/action.rb +++ b/app/models/disco_app/flow/action.rb @@ -1,7 +1,9 @@ module DiscoApp module Flow class Action < ApplicationRecord + include DiscoApp::Flow::Concerns::Action + end end end diff --git a/app/models/disco_app/flow/concerns/action.rb b/app/models/disco_app/flow/concerns/action.rb index b4fae459..479fc550 100644 --- a/app/models/disco_app/flow/concerns/action.rb +++ b/app/models/disco_app/flow/concerns/action.rb @@ -2,10 +2,10 @@ module DiscoApp module Flow module Concerns module Action + extend ActiveSupport::Concern included do - belongs_to :shop self.table_name = :disco_app_flow_actions @@ -15,7 +15,6 @@ module Action succeeded: 1, failed: 2 } - end end diff --git a/app/models/disco_app/flow/concerns/trigger.rb b/app/models/disco_app/flow/concerns/trigger.rb index 49edc651..0ad422f7 100644 --- a/app/models/disco_app/flow/concerns/trigger.rb +++ b/app/models/disco_app/flow/concerns/trigger.rb @@ -2,10 +2,10 @@ module DiscoApp module Flow module Concerns module Trigger + extend ActiveSupport::Concern included do - belongs_to :shop self.table_name = :disco_app_flow_triggers @@ -15,7 +15,6 @@ module Trigger succeeded: 1, failed: 2 } - end end diff --git a/app/models/disco_app/flow/trigger.rb b/app/models/disco_app/flow/trigger.rb index 4b7a9b23..0e623fa4 100644 --- a/app/models/disco_app/flow/trigger.rb +++ b/app/models/disco_app/flow/trigger.rb @@ -1,7 +1,9 @@ module DiscoApp module Flow class Trigger < ApplicationRecord + include DiscoApp::Flow::Concerns::Trigger + end end end diff --git a/app/models/disco_app/plan.rb b/app/models/disco_app/plan.rb index 514202da..1c547879 100644 --- a/app/models/disco_app/plan.rb +++ b/app/models/disco_app/plan.rb @@ -1,3 +1,5 @@ class DiscoApp::Plan < ApplicationRecord + include DiscoApp::Concerns::Plan + end diff --git a/app/models/disco_app/plan_code.rb b/app/models/disco_app/plan_code.rb index 0ccf33e8..b51e5182 100644 --- a/app/models/disco_app/plan_code.rb +++ b/app/models/disco_app/plan_code.rb @@ -1,3 +1,5 @@ class DiscoApp::PlanCode < ApplicationRecord + include DiscoApp::Concerns::PlanCode + end diff --git a/app/models/disco_app/session_storage.rb b/app/models/disco_app/session_storage.rb index c60a3842..b0021feb 100644 --- a/app/models/disco_app/session_storage.rb +++ b/app/models/disco_app/session_storage.rb @@ -1,5 +1,6 @@ module DiscoApp class SessionStorage + def self.store(session) shop = Shop.find_or_initialize_by(shopify_domain: session.url) shop.shopify_token = session.token @@ -9,10 +10,12 @@ def self.store(session) def self.retrieve(id) return unless id + shop = Shop.find(id) ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token) rescue ActiveRecord::RecordNotFound nil end + end end diff --git a/app/models/disco_app/shop.rb b/app/models/disco_app/shop.rb index 1395c25a..74c55964 100644 --- a/app/models/disco_app/shop.rb +++ b/app/models/disco_app/shop.rb @@ -1,3 +1,5 @@ class DiscoApp::Shop < ApplicationRecord + include DiscoApp::Concerns::Shop + end diff --git a/app/models/disco_app/source.rb b/app/models/disco_app/source.rb index 2da69244..eebb0ec2 100644 --- a/app/models/disco_app/source.rb +++ b/app/models/disco_app/source.rb @@ -1,3 +1,5 @@ class DiscoApp::Source < ApplicationRecord + include DiscoApp::Concerns::Source + end diff --git a/app/models/disco_app/subscription.rb b/app/models/disco_app/subscription.rb index 8a598db6..38a6525e 100644 --- a/app/models/disco_app/subscription.rb +++ b/app/models/disco_app/subscription.rb @@ -1,3 +1,5 @@ class DiscoApp::Subscription < ApplicationRecord + include DiscoApp::Concerns::Subscription + end diff --git a/app/models/disco_app/user.rb b/app/models/disco_app/user.rb index e8d79be1..158d4366 100644 --- a/app/models/disco_app/user.rb +++ b/app/models/disco_app/user.rb @@ -1,3 +1,5 @@ class DiscoApp::User < ApplicationRecord + include DiscoApp::Concerns::User + end diff --git a/app/resources/disco_app/admin/resources/concerns/shop_resource.rb b/app/resources/disco_app/admin/resources/concerns/shop_resource.rb index 646f3f37..3dc4ac70 100644 --- a/app/resources/disco_app/admin/resources/concerns/shop_resource.rb +++ b/app/resources/disco_app/admin/resources/concerns/shop_resource.rb @@ -1,10 +1,10 @@ require 'jsonapi/resource' module DiscoApp::Admin::Resources::Concerns::ShopResource + extend ActiveSupport::Concern included do - attributes :domain, :status, :created_at attributes :email, :country_name, :currency, :plan_display_name attributes :current_subscription_id, :current_subscription_display_amount, :current_subscription_display_plan, :current_subscription_display_plan_code, :current_subscription_source @@ -15,33 +15,33 @@ module DiscoApp::Admin::Resources::Concerns::ShopResource filters :query, :status # Adjust the base records method to ensure only models for the authenticated domain are retrieved. - def self.records(options = {}) + def self.records(_options = {}) records = DiscoApp::Shop.order(created_at: :desc) records end # Apply filters. - def self.apply_filter(records, filter, value, options) + def self.apply_filter(records, filter, value, _options) return records if value.blank? # Perform appropriate filtering. case filter - when :query - return records.where('name LIKE ? OR shopify_domain LIKE ? OR domain LIKE ?', "%#{value.first}%", "%#{value.first}%", "%#{value.first}%") - when :status - return records.where(status: value.map { |v| DiscoApp::Shop.statuses[v.to_sym] } ) - else - return super(records, filter, value) + when :query + return records.where('name LIKE ? OR shopify_domain LIKE ? OR domain LIKE ?', "%#{value.first}%", "%#{value.first}%", "%#{value.first}%") + when :status + return records.where(status: value.map { |v| DiscoApp::Shop.statuses[v.to_sym] }) + else + return super(records, filter, value) end end # Don't allow the update of any fields via the API. - def self.updatable_fields(context) + def self.updatable_fields(_context) [] end # Don't allow the creation of any fields via the API. - def self.creatable_fields(context) + def self.creatable_fields(_context) [] end @@ -62,9 +62,7 @@ def plan_display_name end def current_subscription_id - if @model.current_subscription? - @model.current_subscription.id - end + @model.current_subscription.id if @model.current_subscription? end def current_subscription_display_amount @@ -94,7 +92,6 @@ def current_subscription_source '-' end end - end end diff --git a/app/resources/disco_app/admin/resources/shop_resource.rb b/app/resources/disco_app/admin/resources/shop_resource.rb index 22924431..117a9115 100644 --- a/app/resources/disco_app/admin/resources/shop_resource.rb +++ b/app/resources/disco_app/admin/resources/shop_resource.rb @@ -1,4 +1,5 @@ class DiscoApp::Admin::Resources::ShopResource < JSONAPI::Resource + include DiscoApp::Admin::Resources::Concerns::ShopResource end diff --git a/app/services/disco_app/carrier_request_service.rb b/app/services/disco_app/carrier_request_service.rb index 0fe3a4d9..3b777fb5 100644 --- a/app/services/disco_app/carrier_request_service.rb +++ b/app/services/disco_app/carrier_request_service.rb @@ -3,12 +3,12 @@ class DiscoApp::CarrierRequestService # Return true iff the provided hmac_to_verify matches that calculated from the # given data and secret. def self.is_valid_hmac?(body, secret, hmac_to_verify) - ActiveSupport::SecurityUtils.secure_compare(self.calculated_hmac(body, secret), hmac_to_verify.to_s) + ActiveSupport::SecurityUtils.secure_compare(calculated_hmac(body, secret), hmac_to_verify.to_s) end # Calculate the HMAC for the given data and secret. def self.calculated_hmac(body, secret) - digest = OpenSSL::Digest.new('sha256') + digest = OpenSSL::Digest.new('sha256') Base64.encode64(OpenSSL::HMAC.digest(digest, secret, body)).strip end diff --git a/app/services/disco_app/charges_service.rb b/app/services/disco_app/charges_service.rb index 97466b99..60065ede 100644 --- a/app/services/disco_app/charges_service.rb +++ b/app/services/disco_app/charges_service.rb @@ -6,24 +6,22 @@ def self.create(shop, subscription) # Create the charge object locally first. charge = subscription.charge_class.create!( shop: shop, - subscription: subscription, + subscription: subscription ) # Create the charge object on Shopify. - shopify_charge = shop.with_api_context { + shopify_charge = shop.with_api_context do subscription.shopify_charge_class.create( name: subscription.plan.name, - price: '%.2f' % (subscription.amount.to_f / 100.0), + price: format('%.2f', (subscription.amount.to_f / 100.0)), trial_days: subscription.plan.has_trial? ? subscription.trial_period_days : nil, return_url: charge.activate_url, test: !DiscoApp.configuration.real_charges? ) - } + end # If we couldn't create the charge on Shopify, return nil. - if shopify_charge.nil? - return nil - end + return nil if shopify_charge.nil? # Update the local record of the charge from Shopify's created charge, then # return it. @@ -37,44 +35,38 @@ def self.create(shop, subscription) # Attempt to activate the given Shopify charge for the given Shop using the # Shopify API. Returns true on successful activation, false otherwise. def self.activate(shop, charge) - begin - # Start by fetching the Shopify charge to check that it was accepted. - shopify_charge = shop.with_api_context { - charge.subscription.shopify_charge_class.find(charge.shopify_id) - } + # Start by fetching the Shopify charge to check that it was accepted. + shopify_charge = shop.with_api_context do + charge.subscription.shopify_charge_class.find(charge.shopify_id) + end - # Update the status of the local charge based on the Shopify charge. - charge.send("#{shopify_charge.status}!") if charge.respond_to? "#{shopify_charge.status}!" + # Update the status of the local charge based on the Shopify charge. + charge.send("#{shopify_charge.status}!") if charge.respond_to? "#{shopify_charge.status}!" - # If the charge wasn't accepted, fail and return. - return false unless charge.accepted? + # If the charge wasn't accepted, fail and return. + return false unless charge.accepted? - # If the charge was indeed accepted, activate it via Shopify. - charge.shop.with_api_context { - shopify_charge.activate - } + # If the charge was indeed accepted, activate it via Shopify. + charge.shop.with_api_context do + shopify_charge.activate + end - # If the charge was recurring, make sure that all other local recurring - # charges are marked inactive. - if charge.recurring? - self.cancel_recurring_charges(shop, charge) - end + # If the charge was recurring, make sure that all other local recurring + # charges are marked inactive. + cancel_recurring_charges(shop, charge) if charge.recurring? - charge.active! + charge.active! - true - rescue - false - end + true + rescue StandardError + false end # Cancel all recurring charges for the given shop. If the optional charge # parameter is given, it will be excluded from the cancellation. def self.cancel_recurring_charges(shop, charge = nil) charges = DiscoApp::RecurringApplicationCharge.where(shop: shop) - if charge.present? - charges = charges.where.not(id: charge) - end + charges = charges.where.not(id: charge) if charge.present? charges.update_all(status: DiscoApp::RecurringApplicationCharge.statuses[:cancelled]) end diff --git a/app/services/disco_app/flow/process_action.rb b/app/services/disco_app/flow/process_action.rb index c5e8765f..98410d73 100644 --- a/app/services/disco_app/flow/process_action.rb +++ b/app/services/disco_app/flow/process_action.rb @@ -24,12 +24,12 @@ def validate_action def find_action_service_class context.action_service_class = action.action_id.classify.safe_constantize || - %Q(Flow::Actions::#{("#{action.action_id}".classify)}).safe_constantize + %(Flow::Actions::#{action.action_id.to_s.classify}).safe_constantize - if action_service_class.nil? - update_action(false, ["Could not find service class for #{action.action_id}"]) - context.fail! - end + return if action_service_class.nil? + + update_action(false, ["Could not find service class for #{action.action_id}"]) + context.fail! end def execute_action_service_class diff --git a/app/services/disco_app/proxy_service.rb b/app/services/disco_app/proxy_service.rb index dda3aea2..71eb3459 100644 --- a/app/services/disco_app/proxy_service.rb +++ b/app/services/disco_app/proxy_service.rb @@ -5,12 +5,12 @@ class DiscoApp::ProxyService def self.proxy_signature_is_valid?(query_string, secret) query_hash = Rack::Utils.parse_query(query_string) signature = query_hash.delete('signature').to_s - ActiveSupport::SecurityUtils.secure_compare(self.calculated_signature(query_hash, secret), signature) + ActiveSupport::SecurityUtils.secure_compare(calculated_signature(query_hash, secret), signature) end # Return the calculated signature for the given query hash and secret. def self.calculated_signature(query_hash, secret) - sorted_params = query_hash.collect{ |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join + sorted_params = query_hash.map{ |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, sorted_params) end diff --git a/app/services/disco_app/request_validation_service.rb b/app/services/disco_app/request_validation_service.rb index b3ceedda..a9e79ba3 100644 --- a/app/services/disco_app/request_validation_service.rb +++ b/app/services/disco_app/request_validation_service.rb @@ -3,12 +3,12 @@ class DiscoApp::RequestValidationService def self.hmac_valid?(query_string, secret) query_hash = Rack::Utils.parse_query(query_string) hmac = query_hash.delete('hmac').to_s - ActiveSupport::SecurityUtils.secure_compare(self.calculated_hmac(query_hash, secret), hmac) + ActiveSupport::SecurityUtils.secure_compare(calculated_hmac(query_hash, secret), hmac) end # Return the calculated hmac for the given query hash and secret. def self.calculated_hmac(query_hash, secret) - sorted_params = query_hash.collect{ |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join('&') + sorted_params = query_hash.map{ |k, v| "#{k}=#{Array(v).join(',')}" }.sort.join('&') OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), secret, sorted_params) end diff --git a/app/services/disco_app/subscription_service.rb b/app/services/disco_app/subscription_service.rb index 50890806..11bce2a5 100644 --- a/app/services/disco_app/subscription_service.rb +++ b/app/services/disco_app/subscription_service.rb @@ -3,18 +3,13 @@ class DiscoApp::SubscriptionService # Subscribe the given shop to the given plan, optionally using the given plan # code and optionally tracking the subscription source. def self.subscribe(shop, plan, plan_code = nil, source_name = nil) - # If a plan code was provided, fetch it for the given plan. plan_code_instance = nil - if plan_code.present? - plan_code_instance = DiscoApp::PlanCode.available.find_by(plan: plan, code: plan_code) - end + plan_code_instance = DiscoApp::PlanCode.available.find_by(plan: plan, code: plan_code) if plan_code.present? # If a source name has been provided, fetch or create it source_instance = nil - if source_name.present? - source_instance = DiscoApp::Source.find_or_create_by(source: source_name) - end + source_instance = DiscoApp::Source.find_or_create_by(source: source_name) if source_name.present? # Cancel any existing current subscriptions. shop.subscriptions.current.update_all( diff --git a/app/services/disco_app/webhook_service.rb b/app/services/disco_app/webhook_service.rb index c6a3b8ca..21ff7abc 100644 --- a/app/services/disco_app/webhook_service.rb +++ b/app/services/disco_app/webhook_service.rb @@ -3,27 +3,25 @@ class DiscoApp::WebhookService # Return true iff the provided hmac_to_verify matches that calculated from the # given data and secret. def self.is_valid_hmac?(body, secret, hmac_to_verify) - ActiveSupport::SecurityUtils.secure_compare(self.calculated_hmac(body, secret), hmac_to_verify.to_s) + ActiveSupport::SecurityUtils.secure_compare(calculated_hmac(body, secret), hmac_to_verify.to_s) end # Calculate the HMAC for the given data and secret. def self.calculated_hmac(body, secret) - digest = OpenSSL::Digest.new('sha256') + digest = OpenSSL::Digest.new('sha256') Base64.encode64(OpenSSL::HMAC.digest(digest, secret, body)).strip end # Try to find a job class for the given webhook topic. def self.find_job_class(topic) + # First try to find a top-level matching job class. + "#{topic}_job".gsub('/', '_').classify.constantize + rescue NameError + # If that fails, try to find a DiscoApp:: prefixed job class. begin - # First try to find a top-level matching job class. - "#{topic}_job".gsub('/', '_').classify.constantize + %(DiscoApp::#{"#{topic}_job".gsub('/', '_').classify}).constantize rescue NameError - # If that fails, try to find a DiscoApp:: prefixed job class. - begin - %Q{DiscoApp::#{"#{topic}_job".gsub('/', '_').classify}}.constantize - rescue NameError - nil - end + nil end end diff --git a/config/routes.rb b/config/routes.rb index 0253fd18..f6b4d0ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,6 @@ require 'sidekiq/web' DiscoApp::Engine.routes.draw do - get 'ref', to: '/sessions#referral' get '/auth/failure', to: '/sessions#failure' @@ -70,5 +69,4 @@ get 'frame' => :frame, as: :frame end end - end diff --git a/disco_app.gemspec b/disco_app.gemspec index 3c84de5e..63b08449 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -1,62 +1,61 @@ -$:.push File.expand_path("../lib", __FILE__) +$LOAD_PATH.push File.expand_path('lib', __dir__) # Maintain your gem's version: -require "disco_app/version" +require 'disco_app/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "disco_app" + s.name = 'disco_app' s.version = DiscoApp::VERSION - s.authors = ["Gavin Ballard"] - s.email = ["gavin@gavinballard.com"] - s.homepage = "https://github.com/discolabs/disco_app/" - s.summary = "Rails engine for Shopify applications." - s.description = "Rails engine for Shopify applications." - s.license = "None" + s.authors = ['Gavin Ballard'] + s.email = ['gavin@gavinballard.com'] + s.homepage = 'https://github.com/discolabs/disco_app/' + s.summary = 'Rails engine for Shopify applications.' + s.description = 'Rails engine for Shopify applications.' + s.license = 'None' # To build up the list of files, we need to deviate from the standard .gemspec approach to ensure that # a number of "dotfiles"/"dotfolders" that we use as templates files are included in our gem package. - s.files = Dir.glob("{app,config,db,lib}/**/{*,.*}", File::FNM_DOTMATCH).reject { |d| d.end_with?('.') } - s.files += Dir["MIT-LICENSE", "Rakefile", "README.rdoc"] + s.files = Dir.glob('{app,config,db,lib}/**/{*,.*}', File::FNM_DOTMATCH).reject { |d| d.end_with?('.') } + s.files += Dir['MIT-LICENSE', 'Rakefile', 'README.rdoc'] - s.test_files = Dir["test/**/*"] + s.test_files = Dir['test/**/*'] - s.add_runtime_dependency 'rails', '~> 5.2.0' - s.add_runtime_dependency 'sass-rails', '~> 5.0' - s.add_runtime_dependency 'uglifier', '~> 3.2' + s.add_runtime_dependency 'active_link_to', '~> 1.0' + s.add_runtime_dependency 'active_utils', '~> 3.2' + s.add_runtime_dependency 'activemodel-serializers-xml', '~> 1.0' + s.add_runtime_dependency 'activerecord-session_store', '~> 1.0' + s.add_runtime_dependency 'acts_as_singleton', '~> 0.0.8' + s.add_runtime_dependency 'appsignal', '~> 2.7' + s.add_runtime_dependency 'classnames-rails', '~> 2.1' s.add_runtime_dependency 'coffee-rails', '~> 4.2' + s.add_runtime_dependency 'interactor' + s.add_runtime_dependency 'interactor-rails' s.add_runtime_dependency 'jquery-rails', '~> 4.3' - s.add_runtime_dependency 'turbolinks', '~> 5.0' - s.add_runtime_dependency 'shopify_app', '~> 7.2', '>= 7.2.3' - s.add_runtime_dependency 'shopify_api', '~> 6.0' - s.add_runtime_dependency 'puma', '~> 3.9' - s.add_runtime_dependency 'sidekiq', '~> 5.0' + s.add_runtime_dependency 'jsonapi-resources', '~> 0.8' + s.add_runtime_dependency 'mailgun_rails', '~> 0.8' + s.add_runtime_dependency 'newrelic_rpm', '~> 3.15' + s.add_runtime_dependency 'nokogiri', '~> 1.7' + s.add_runtime_dependency 'oj', '~> 2.14' s.add_runtime_dependency 'pg', '~> 0.21.0' + s.add_runtime_dependency 'premailer-rails', '~> 1.8' + s.add_runtime_dependency 'puma', '~> 3.9' + s.add_runtime_dependency 'rails', '~> 5.2.0' s.add_runtime_dependency 'rails_12factor', '~> 0.0.3' - s.add_runtime_dependency 'active_utils', '~> 3.2' - s.add_runtime_dependency 'activerecord-session_store', '~> 1.0' - s.add_runtime_dependency 'jsonapi-resources', '~> 0.8' - s.add_runtime_dependency 'acts_as_singleton', '~> 0.0.8' s.add_runtime_dependency 'react-rails', '~> 1.10' - s.add_runtime_dependency 'classnames-rails', '~> 2.1' s.add_runtime_dependency 'render_anywhere', '~> 0.0.12' + s.add_runtime_dependency 'sass-rails', '~> 5.0' + s.add_runtime_dependency 'shopify_api', '~> 6.0' + s.add_runtime_dependency 'shopify_app', '~> 7.2', '>= 7.2.3' + s.add_runtime_dependency 'sidekiq', '~> 5.0' s.add_runtime_dependency 'sinatra', '~> 2.0' - s.add_runtime_dependency 'active_link_to', '~> 1.0' - s.add_runtime_dependency 'premailer-rails', '~> 1.8' - s.add_runtime_dependency 'nokogiri', '~> 1.7' - s.add_runtime_dependency 'appsignal', '~> 2.7' - s.add_runtime_dependency 'oj', '~> 2.14' - s.add_runtime_dependency 'newrelic_rpm', '~> 3.15' - s.add_runtime_dependency 'mailgun_rails', '~> 0.8' - s.add_runtime_dependency 'activemodel-serializers-xml', '~> 1.0' - s.add_runtime_dependency 'interactor' - s.add_runtime_dependency 'interactor-rails' + s.add_runtime_dependency 'turbolinks', '~> 5.0' + s.add_runtime_dependency 'uglifier', '~> 3.2' s.add_development_dependency 'dotenv-rails', '~> 2.0' - s.add_development_dependency 'minitest-reporters', '1.1.9' s.add_development_dependency 'minitest', '5.10.1' - s.add_development_dependency 'webmock', '~> 2.3' + s.add_development_dependency 'minitest-reporters', '1.1.9' + s.add_development_dependency 'rubocop', '~> 0.70' s.add_development_dependency 'vcr', '~> 3.0' - s.add_development_dependency 'rubocop', '~> 0.50' + s.add_development_dependency 'webmock', '~> 2.3' end - diff --git a/lib/disco_app/configuration.rb b/lib/disco_app/configuration.rb index 2ef7ebda..7abd05bf 100644 --- a/lib/disco_app/configuration.rb +++ b/lib/disco_app/configuration.rb @@ -20,17 +20,17 @@ class Configuration # Set the below to create real Shopify charges. attr_accessor :real_charges - alias_method :real_charges?, :real_charges + alias real_charges? real_charges # Optional configuration, usually useful for development environments. attr_accessor :skip_proxy_verification - alias_method :skip_proxy_verification?, :skip_proxy_verification + alias skip_proxy_verification? skip_proxy_verification attr_accessor :skip_webhook_verification - alias_method :skip_webhook_verification?, :skip_webhook_verification + alias skip_webhook_verification? skip_webhook_verification attr_accessor :skip_carrier_request_verification - alias_method :skip_carrier_request_verification?, :skip_carrier_request_verification + alias skip_carrier_request_verification? skip_carrier_request_verification attr_accessor :skip_oauth - alias_method :skip_oauth?, :skip_oauth + alias skip_oauth? skip_oauth end diff --git a/lib/disco_app/constants.rb b/lib/disco_app/constants.rb index 68d27c43..8f7bf12f 100644 --- a/lib/disco_app/constants.rb +++ b/lib/disco_app/constants.rb @@ -1,4 +1,6 @@ module DiscoApp - SOURCE_COOKIE_KEY = '_disco_app_source' - CODE_COOKIE_KEY = '_disco_app_code' + + SOURCE_COOKIE_KEY = '_disco_app_source'.freeze + CODE_COOKIE_KEY = '_disco_app_code'.freeze + end diff --git a/lib/disco_app/engine.rb b/lib/disco_app/engine.rb index 1e3973d1..f800f89b 100644 --- a/lib/disco_app/engine.rb +++ b/lib/disco_app/engine.rb @@ -19,7 +19,7 @@ class Engine < ::Rails::Engine # Ensure our frame assets are included for precompilation. initializer 'disco_app.assets.precompile' do |app| - app.config.assets.precompile += %w(disco_app/icon.svg disco_app/admin.css disco_app/frame.css disco_app/frame.js) + app.config.assets.precompile += %w[disco_app/icon.svg disco_app/admin.css disco_app/frame.css disco_app/frame.js] end end diff --git a/lib/disco_app/session.rb b/lib/disco_app/session.rb index 0fa9c9ce..12b2af5d 100644 --- a/lib/disco_app/session.rb +++ b/lib/disco_app/session.rb @@ -7,6 +7,7 @@ class Session < ActiveRecord::SessionStore::Session def set_shop_id! return false unless loaded? + write_attribute(:shop_id, data[:shopify] || data['shopify']) end diff --git a/lib/disco_app/support/file_fixtures.rb b/lib/disco_app/support/file_fixtures.rb index ca37300b..79b1507f 100644 --- a/lib/disco_app/support/file_fixtures.rb +++ b/lib/disco_app/support/file_fixtures.rb @@ -11,6 +11,7 @@ def xml_fixture(path) def json_fixture(path, dir: 'json', parse: true) filename = Rails.root.join('test', 'fixtures', dir, "#{path}.json") return File.read(filename) unless parse + HashWithIndifferentAccess.new(ActiveSupport::JSON.decode(File.read(filename))) end diff --git a/lib/disco_app/version.rb b/lib/disco_app/version.rb index c1cc6d33..27ee18d0 100644 --- a/lib/disco_app/version.rb +++ b/lib/disco_app/version.rb @@ -1,3 +1,5 @@ module DiscoApp - VERSION = '0.16.1' + + VERSION = '0.16.1'.freeze + end diff --git a/lib/generators/disco_app/disco_app_generator.rb b/lib/generators/disco_app/disco_app_generator.rb index 1f658541..db9e7bd2 100644 --- a/lib/generators/disco_app/disco_app_generator.rb +++ b/lib/generators/disco_app/disco_app_generator.rb @@ -1,6 +1,6 @@ class DiscoAppGenerator < Rails::Generators::Base - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) # Copy a number of template files to the top-level directory of our application: # @@ -11,7 +11,7 @@ class DiscoAppGenerator < Rails::Generators::Base # - README/PULL REQUEST template # def copy_root_files - %w(.editorconfig .env .env.local .gitignore .rubocop.yml .codeclimate.yml Procfile CHECKS README.md).each do |file| + %w[.editorconfig .env .env.local .gitignore .rubocop.yml .codeclimate.yml Procfile CHECKS README.md].each do |file| copy_file "root/#{file}", file end directory 'root/.github' @@ -19,7 +19,7 @@ def copy_root_files # Remove a number of root files. def remove_root_files - %w(README.rdoc).each do |file| + %w[README.rdoc].each do |file| remove_file file end end @@ -88,54 +88,54 @@ def configure_application # Set time zone to UTC application "config.time_zone = 'UTC'" - application "# Ensure UTC is the default timezone" + application '# Ensure UTC is the default timezone' # Set server side rendereing for components.js application "config.react.server_renderer_options = {\nfiles: ['components.js'], # files to load for prerendering\n}" - application "# Enable server side react rendering" + application '# Enable server side react rendering' # Set defaults for various charge attributes. application "config.x.shopify_charges_default_trial_days = 14\n" - application "config.x.shopify_charges_default_price = 10.00" - application "config.x.shopify_charges_default_type = :recurring" - application "# Set defaults for charges created by the application" + application 'config.x.shopify_charges_default_price = 10.00' + application 'config.x.shopify_charges_default_type = :recurring' + application '# Set defaults for charges created by the application' # Set the "real charges" config variable to false explicitly by default. # Only in production do we read from the environment variable and # potentially have it become true. application "config.x.shopify_charges_real = false\n" - application "# Explicitly prevent real charges being created by default" + application '# Explicitly prevent real charges being created by default' application "config.x.shopify_charges_real = ENV['SHOPIFY_CHARGES_REAL'] == 'true'\n", env: :production - application "# Allow real charges in production with an ENV variable", env: :production + application '# Allow real charges in production with an ENV variable', env: :production # Configure session storage. application "ActiveRecord::SessionStore::Session.table_name = 'disco_app_sessions'" - application "ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session" - application "# Configure custom session storage" + application 'ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session' + application '# Configure custom session storage' # Set Sidekiq as the queue adapter in production. application "config.active_job.queue_adapter = :sidekiq\n", env: :production - application "# Use Sidekiq as the active job backend", env: :production + application '# Use Sidekiq as the active job backend', env: :production # Set Sidekiq as the queue adapter in staging. application "config.active_job.queue_adapter = :sidekiq\n", env: :staging - application "# Use Sidekiq as the active job backend", env: :staging + application '# Use Sidekiq as the active job backend', env: :staging # Ensure the application configuration uses the DEFAULT_HOST environment # variable to set up support for reverse routing absolute URLS (needed when # generating Webhook URLs for example). application "routes.default_url_options[:host] = ENV['DEFAULT_HOST']\n" - application "# Set the default host for absolute URL routing purposes" + application '# Set the default host for absolute URL routing purposes' # Configure React in development, staging, and production. - application "config.react.variant = :development", env: :development - application "# Use development variant of React in development.", env: :development + application 'config.react.variant = :development', env: :development + application '# Use development variant of React in development.', env: :development - application "config.react.variant = :production", env: :staging - application "# Use production variant of React in staging.", env: :staging + application 'config.react.variant = :production', env: :staging + application '# Use production variant of React in staging.', env: :staging - application "config.react.variant = :production", env: :production - application "# Use production variant of React in production.", env: :production + application 'config.react.variant = :production', env: :production + application '# Use production variant of React in production.', env: :production # Copy over the default puma configuration. copy_file 'config/puma.rb', 'config/puma.rb' @@ -211,7 +211,7 @@ def copy_and_remove_files # Add the Disco App test helper to test/test_helper.rb def add_test_helper - inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", { after: "require 'rails/test_help'\n" } + inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", after: "require 'rails/test_help'\n" end # Copy engine migrations over. diff --git a/lib/generators/disco_app/templates/root/.rubocop.yml b/lib/generators/disco_app/templates/root/.rubocop.yml index 251bc7df..847921c4 100644 --- a/lib/generators/disco_app/templates/root/.rubocop.yml +++ b/lib/generators/disco_app/templates/root/.rubocop.yml @@ -46,7 +46,6 @@ Layout/ClassStructure: ExpectedOrder: - module_inclusion - constants - - attributes - associations - validations - callbacks @@ -79,7 +78,7 @@ Layout/InitialIndentation: Checks the indentation of the first non-blank non-comment line in a file. Enabled: false -Layout/IndentHash: +Layout/IndentFirstHashElement: Enabled: true EnforcedStyle: consistent @@ -239,7 +238,7 @@ Style/FrozenStringLiteralComment: to help transition from Ruby 2.3.0 to Ruby 3.0. Enabled: false -Style/FlipFlop: +Lint/FlipFlop: Description: 'Checks for flip flops' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' Enabled: true @@ -433,7 +432,7 @@ Style/TrailingCommaInHashLiteral: Description: 'Checks for trailing comma in hash literals.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' EnforcedStyleForMultiline: no_comma - Enabled: false + Enabled: true Style/TrivialAccessors: Description: 'Prefer attr_* methods to trivial readers/writers.' @@ -480,6 +479,16 @@ Metrics/BlockLength: Enabled: true Exclude: - config/environments/* + ExcludedMethods: + - context + - define + - describe + - draw + - factory + - guard + - included + - namespace + - trait Metrics/BlockNesting: Description: 'Avoid excessive block nesting' @@ -645,7 +654,7 @@ Performance/ReverseEach: Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' Enabled: true -Performance/Sample: +Style/Sample: Description: >- Use `sample` instead of `shuffle.first`, `shuffle.last`, and `shuffle[Fixnum]`. diff --git a/lib/tasks/api.rake b/lib/tasks/api.rake index aeb1f2f3..e1f3365c 100644 --- a/lib/tasks/api.rake +++ b/lib/tasks/api.rake @@ -1,10 +1,8 @@ namespace :api do - desc 'Send all subscription information to the Disco API' task send_subscriptions: :environment do DiscoApp::Shop.find_each do |shop| DiscoApp::SendSubscriptionJob.perform_later(shop) end end - end diff --git a/lib/tasks/carrier_service.rake b/lib/tasks/carrier_service.rake index f06d39a1..1b4b23c7 100644 --- a/lib/tasks/carrier_service.rake +++ b/lib/tasks/carrier_service.rake @@ -1,10 +1,8 @@ namespace :carrier_service do - desc 'Synchronise carrier service across all installed shops.' task sync: :environment do DiscoApp::Shop.installed.has_active_shopify_plan.each do |shop| DiscoApp::SynchroniseCarrierServiceJob.perform_later(shop) end end - end diff --git a/lib/tasks/database.rake b/lib/tasks/database.rake index ecb767c1..281364ed 100644 --- a/lib/tasks/database.rake +++ b/lib/tasks/database.rake @@ -1,5 +1,5 @@ namespace :database do - desc "update postgres sequence numbers in case database has been migrated" + desc 'update postgres sequence numbers in case database has been migrated' task update_sequences: :environment do ActiveRecord::Base.connection.tables.each do |t| ActiveRecord::Base.connection.reset_pk_sequence!(t) diff --git a/lib/tasks/sessions.rake b/lib/tasks/sessions.rake index ce226508..42209c63 100644 --- a/lib/tasks/sessions.rake +++ b/lib/tasks/sessions.rake @@ -1,9 +1,7 @@ namespace :sessions do - desc 'Clean out any stale sessions.' task clean: [:environment, 'db:load_config'] do threshold = (ENV['SESSIONS_CLEAN_THRESHOLD_DAYS'] || 30).to_i.days.ago ActiveRecord::Base.connection.execute("DELETE FROM #{ActiveRecord::SessionStore::Session.table_name} WHERE updated_at < '#{threshold}'") end - end diff --git a/lib/tasks/shops.rake b/lib/tasks/shops.rake index 2d6e8e3c..93b492b6 100644 --- a/lib/tasks/shops.rake +++ b/lib/tasks/shops.rake @@ -1,10 +1,8 @@ namespace :shops do - desc 'Synchronise shop data across all installed shops.' task sync: :environment do DiscoApp::Shop.installed.has_active_shopify_plan.each do |shop| DiscoApp::ShopUpdateJob.perform_later(shop) end end - end diff --git a/lib/tasks/users.rake b/lib/tasks/users.rake index ce36bb23..b588c28c 100644 --- a/lib/tasks/users.rake +++ b/lib/tasks/users.rake @@ -1,10 +1,8 @@ namespace :users do - desc 'Synchronise user data accross all installed shops' task sync: :environment do DiscoApp::Shop.installed.has_active_shopify_plan.shopify_plus.each do |shop| DiscoApp::SynchroniseUsersJob.perform_later(shop) end end - end diff --git a/lib/tasks/webhooks.rake b/lib/tasks/webhooks.rake index 447a865e..168885f4 100644 --- a/lib/tasks/webhooks.rake +++ b/lib/tasks/webhooks.rake @@ -1,10 +1,8 @@ namespace :webhooks do - desc 'Synchronise webhooks across all installed shops.' task sync: :environment do DiscoApp::Shop.installed.has_active_shopify_plan.each do |shop| DiscoApp::SynchroniseWebhooksJob.perform_later(shop) end end - end diff --git a/test/clients/disco_app/api_client_test.rb b/test/clients/disco_app/api_client_test.rb index d0c6f4ca..7ffd8165 100644 --- a/test/clients/disco_app/api_client_test.rb +++ b/test/clients/disco_app/api_client_test.rb @@ -4,9 +4,9 @@ class ApiClientTest < ActiveSupport::TestCase def setup @shop = disco_app_shops(:widget_store) - stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json"). - with(body: api_fixture('subscriptions/valid_request').to_json). - to_return(status: 200, body: api_fixture('subscriptions/valid_request').to_json) + stub_request(:post, 'https://api.discolabs.com/v1/app_subscriptions.json') + .with(body: api_fixture('subscriptions/valid_request').to_json) + .to_return(status: 200, body: api_fixture('subscriptions/valid_request').to_json) end def teardown diff --git a/test/controllers/disco_app/admin/shops_controller_test.rb b/test/controllers/disco_app/admin/shops_controller_test.rb index 51e4acfb..3cff6735 100644 --- a/test/controllers/disco_app/admin/shops_controller_test.rb +++ b/test/controllers/disco_app/admin/shops_controller_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::Admin::ShopsControllerTest < ActionController::TestCase + include ActiveJob::TestHelper def setup diff --git a/test/controllers/disco_app/charges_controller_test.rb b/test/controllers/disco_app/charges_controller_test.rb index 649e62ab..d9fc3abe 100644 --- a/test/controllers/disco_app/charges_controller_test.rb +++ b/test/controllers/disco_app/charges_controller_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::ChargesControllerTest < ActionController::TestCase + include ActiveJob::TestHelper include DiscoApp::Test::ShopifyAPI @@ -43,15 +44,13 @@ def teardown end test 'user with unpaid current subscription can create new charge and is redirected to confirmation url' do - res = { "recurring_application_charge": { "name": "Basic", - "price": "9.99", - "trial_days": 14, - "return_url": /^https:\/\/test\.example\.com\/subscriptions\/304261385\/charges\/53297050(1|2)\/activate$/, - "test": true - } } + res = { "recurring_application_charge": { "name": 'Basic', + "price": '9.99', + "trial_days": 14, + "return_url": %r{^https://test\.example\.com/subscriptions/304261385/charges/53297050(1|2)/activate$}, + "test": true } } stub_request(:post, "#{@shop.admin_url}/recurring_application_charges.json") - .with(body: res - ).to_return(status: 201, body:api_fixture("widget_store/charges/create_second_recurring_application_charge_response").to_json) + .with(body: res).to_return(status: 201, body: api_fixture('widget_store/charges/create_second_recurring_application_charge_response').to_json) @current_subscription.active_charge.destroy post :create, params: { subscription_id: @current_subscription } diff --git a/test/controllers/disco_app/install_controller_test.rb b/test/controllers/disco_app/install_controller_test.rb index a8125f73..59b196a0 100644 --- a/test/controllers/disco_app/install_controller_test.rb +++ b/test/controllers/disco_app/install_controller_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::InstallControllerTest < ActionController::TestCase + include ActiveJob::TestHelper def setup @@ -23,7 +24,7 @@ def teardown test 'logged-in and installed user is redirected to installing url for install/uninstalling actions' do @shop.installed! - [:install, :uninstalling].each do |action| + [:install, :uninstalling].each do |_action| get(:install) assert_redirected_to :installing end diff --git a/test/controllers/disco_app/subscriptions_controller_test.rb b/test/controllers/disco_app/subscriptions_controller_test.rb index 416132bb..36ce0eb9 100644 --- a/test/controllers/disco_app/subscriptions_controller_test.rb +++ b/test/controllers/disco_app/subscriptions_controller_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::SubscriptionsControllerTest < ActionController::TestCase + include ActiveJob::TestHelper def setup diff --git a/test/controllers/disco_app/webhooks_controller_test.rb b/test/controllers/disco_app/webhooks_controller_test.rb index 02f85e9c..8a56d457 100644 --- a/test/controllers/disco_app/webhooks_controller_test.rb +++ b/test/controllers/disco_app/webhooks_controller_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::WebhooksControllerTest < ActionController::TestCase + include ActiveJob::TestHelper def setup diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb index 442665fe..0dcd1372 100644 --- a/test/controllers/home_controller_test.rb +++ b/test/controllers/home_controller_test.rb @@ -94,7 +94,7 @@ def teardown Timecop.freeze('2017-03-08 12:44:58 +1100') do hmac = 'eb49ba93a8daf8a11a04c66129faf98de1cd40f082b0ae78e79a2dfbbefb438d' get :index, params: { hmac: hmac, shop: 'widgets-dev.myshopify.com', timestamp: Time.now.to_i } - assert_response :success + assert_response :success end end diff --git a/test/disco_app_test.rb b/test/disco_app_test.rb index bb7e4898..daa54125 100644 --- a/test/disco_app_test.rb +++ b/test/disco_app_test.rb @@ -1,7 +1,9 @@ require 'test_helper' class DiscoAppTest < ActiveSupport::TestCase - test "truth" do + + test 'truth' do assert_kind_of Module, DiscoApp end + end diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile index ba6b733d..f7a26dda 100644 --- a/test/dummy/Rakefile +++ b/test/dummy/Rakefile @@ -1,6 +1,6 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require File.expand_path('config/application', __dir__) Rails.application.load_tasks diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb index c24cee2d..c57af07c 100644 --- a/test/dummy/app/controllers/application_controller.rb +++ b/test/dummy/app/controllers/application_controller.rb @@ -1,6 +1,8 @@ class ApplicationController < ActionController::Base + include ShopifyApp::LoginProtection # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + end diff --git a/test/dummy/app/controllers/carrier_request_controller.rb b/test/dummy/app/controllers/carrier_request_controller.rb index 568a4b6e..44bffefb 100644 --- a/test/dummy/app/controllers/carrier_request_controller.rb +++ b/test/dummy/app/controllers/carrier_request_controller.rb @@ -1,4 +1,5 @@ class CarrierRequestController < ActionController::Base + include DiscoApp::Concerns::CarrierRequestController def rates diff --git a/test/dummy/app/controllers/disco_app/admin/shops_controller.rb b/test/dummy/app/controllers/disco_app/admin/shops_controller.rb index b4e4aa5f..9069464b 100644 --- a/test/dummy/app/controllers/disco_app/admin/shops_controller.rb +++ b/test/dummy/app/controllers/disco_app/admin/shops_controller.rb @@ -1,4 +1,5 @@ class DiscoApp::Admin::ShopsController < DiscoApp::Admin::ApplicationController + include DiscoApp::Admin::Concerns::ShopsController def index diff --git a/test/dummy/app/controllers/home_controller.rb b/test/dummy/app/controllers/home_controller.rb index 1a20f94a..b8345c31 100644 --- a/test/dummy/app/controllers/home_controller.rb +++ b/test/dummy/app/controllers/home_controller.rb @@ -1,4 +1,5 @@ class HomeController < ApplicationController + include DiscoApp::Concerns::AuthenticatedController def index diff --git a/test/dummy/app/controllers/proxy_controller.rb b/test/dummy/app/controllers/proxy_controller.rb index 809b9dd6..124133fa 100644 --- a/test/dummy/app/controllers/proxy_controller.rb +++ b/test/dummy/app/controllers/proxy_controller.rb @@ -1,4 +1,5 @@ class ProxyController < ActionController::Base + include DiscoApp::Concerns::AppProxyController def index diff --git a/test/dummy/app/jobs/disco_app/app_installed_job.rb b/test/dummy/app/jobs/disco_app/app_installed_job.rb index 653dbb0b..0b415130 100644 --- a/test/dummy/app/jobs/disco_app/app_installed_job.rb +++ b/test/dummy/app/jobs/disco_app/app_installed_job.rb @@ -1,4 +1,5 @@ class DiscoApp::AppInstalledJob < DiscoApp::ShopJob + include DiscoApp::Concerns::AppInstalledJob DEVELOPMENT_PLAN_ID = 1 @@ -7,9 +8,7 @@ class DiscoApp::AppInstalledJob < DiscoApp::ShopJob # on their status. def default_plan @default_plan ||= begin - if @shop.plan_name === 'affiliate' - DiscoApp::Plan.find(DEVELOPMENT_PLAN_ID) - end + DiscoApp::Plan.find(DEVELOPMENT_PLAN_ID) if @shop.plan_name === 'affiliate' end end diff --git a/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb b/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb index bf303152..22f2917a 100644 --- a/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb +++ b/test/dummy/app/jobs/disco_app/app_uninstalled_job.rb @@ -1,4 +1,5 @@ class DiscoApp::AppUninstalledJob < DiscoApp::ShopJob + include DiscoApp::Concerns::AppUninstalledJob # Extend the perform method to change the country name of the shop to diff --git a/test/dummy/app/models/application_record.rb b/test/dummy/app/models/application_record.rb index 10a4cba8..c6ae68f2 100644 --- a/test/dummy/app/models/application_record.rb +++ b/test/dummy/app/models/application_record.rb @@ -1,3 +1,5 @@ class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end diff --git a/test/dummy/app/models/cart.rb b/test/dummy/app/models/cart.rb index b3f0b98d..1d3409ea 100644 --- a/test/dummy/app/models/cart.rb +++ b/test/dummy/app/models/cart.rb @@ -1,13 +1,14 @@ class Cart < ApplicationRecord - include DiscoApp::Concerns::Synchronises - belongs_to :shop, class_name: 'DiscoApp::Shop' + include DiscoApp::Concerns::Synchronises SHOPIFY_API_CLASS = ShopifyAPI::Cart + belongs_to :shop, class_name: 'DiscoApp::Shop' + before_save :set_token - def self.synchronise_by(shop, data) + def self.synchronise_by(_shop, data) { token: data[:token] } end diff --git a/test/dummy/app/models/disco_app/shop.rb b/test/dummy/app/models/disco_app/shop.rb index b0c6d3ac..00ca5065 100644 --- a/test/dummy/app/models/disco_app/shop.rb +++ b/test/dummy/app/models/disco_app/shop.rb @@ -1,6 +1,7 @@ require 'active_utils' class DiscoApp::Shop < ApplicationRecord + include DiscoApp::Concerns::Shop has_one :js_configuration @@ -10,11 +11,9 @@ class DiscoApp::Shop < ApplicationRecord # Extend the Shop model to return the Shop's country as an ActiveUtils country. def country - begin - ActiveUtils::Country.find(data[:country_name]) - rescue ActiveUtils::InvalidCountryCodeError - nil - end + ActiveUtils::Country.find(data[:country_name]) + rescue ActiveUtils::InvalidCountryCodeError + nil end end diff --git a/test/dummy/app/models/js_configuration.rb b/test/dummy/app/models/js_configuration.rb index 4a32ba5c..ee8e5cb2 100644 --- a/test/dummy/app/models/js_configuration.rb +++ b/test/dummy/app/models/js_configuration.rb @@ -1,4 +1,5 @@ class JsConfiguration < ApplicationRecord + include DiscoApp::Concerns::RendersAssets belongs_to :shop, class_name: 'DiscoApp::Shop' diff --git a/test/dummy/app/models/product.rb b/test/dummy/app/models/product.rb index 93410195..19819638 100644 --- a/test/dummy/app/models/product.rb +++ b/test/dummy/app/models/product.rb @@ -1,9 +1,10 @@ class Product < ApplicationRecord + include DiscoApp::Concerns::Synchronises + include DiscoApp::Concerns::HasMetafields + SHOPIFY_API_CLASS = ShopifyAPI::Product belongs_to :shop, class_name: 'DiscoApp::Shop' - SHOPIFY_API_CLASS = ShopifyAPI::Product - end diff --git a/test/dummy/app/models/widget_configuration.rb b/test/dummy/app/models/widget_configuration.rb index 54a14f68..1afa966f 100644 --- a/test/dummy/app/models/widget_configuration.rb +++ b/test/dummy/app/models/widget_configuration.rb @@ -1,4 +1,5 @@ class WidgetConfiguration < ApplicationRecord + include DiscoApp::Concerns::RendersAssets belongs_to :shop, class_name: 'DiscoApp::Shop' diff --git a/test/dummy/bin/bundle b/test/dummy/bin/bundle index 66e9889e..f19acf5b 100755 --- a/test/dummy/bin/bundle +++ b/test/dummy/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/test/dummy/bin/rails b/test/dummy/bin/rails index 5191e692..07396602 100755 --- a/test/dummy/bin/rails +++ b/test/dummy/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/test/dummy/bin/setup b/test/dummy/bin/setup index acdb2c13..6942b154 100755 --- a/test/dummy/bin/setup +++ b/test/dummy/bin/setup @@ -2,15 +2,15 @@ require 'pathname' # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = Pathname.new File.expand_path('..', __dir__) Dir.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 || bundle install" + puts '== Installing dependencies ==' + system 'gem install bundler --conservative' + system 'bundle check || bundle install' # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") @@ -18,12 +18,12 @@ Dir.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system "bin/rake db:setup" + system 'bin/rake db:setup' puts "\n== Removing old logs and tempfiles ==" - system "rm -f log/*" - system "rm -rf tmp/cache" + system 'rm -f log/*' + system 'rm -rf tmp/cache' puts "\n== Restarting application server ==" - system "touch tmp/restart.txt" + system 'touch tmp/restart.txt' end diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index 89da495c..ff785b6c 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -1,12 +1,13 @@ -require File.expand_path('../boot', __FILE__) +require File.expand_path('boot', __dir__) require 'rails/all' Bundler.require(*Rails.groups) -require "disco_app" +require 'disco_app' module Dummy class Application < Rails::Application + config.action_dispatch.default_headers['P3P'] = 'CP="Not used"' config.action_dispatch.default_headers.delete('X-Frame-Options') # Settings in config/environments/* take precedence over those specified here. @@ -30,6 +31,6 @@ class Application < Rails::Application # Explicitly prevent real charges being created by default config.x.shopify_charges_real = false + end end - diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb index 6266cfc5..c9aef85d 100644 --- a/test/dummy/config/boot.rb +++ b/test/dummy/config/boot.rb @@ -1,5 +1,5 @@ # 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.exist?(ENV['BUNDLE_GEMFILE']) -$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb index ee8d90dc..0b8bdd82 100644 --- a/test/dummy/config/environment.rb +++ b/test/dummy/config/environment.rb @@ -1,5 +1,5 @@ # Load the Rails application. -require File.expand_path('../application', __FILE__) +require File.expand_path('application', __dir__) # Initialize the Rails application. Rails.application.initialize! diff --git a/test/dummy/config/initializers/disco_app.rb b/test/dummy/config/initializers/disco_app.rb index 92c874e9..160c11b9 100644 --- a/test/dummy/config/initializers/disco_app.rb +++ b/test/dummy/config/initializers/disco_app.rb @@ -6,7 +6,7 @@ # Set a list of webhook topics to listen for. # See https://help.shopify.com/api/reference/webhook. - config.webhook_topics = [:'orders/create', :'orders/paid', :'carts/create', :'carts/update'] + config.webhook_topics = %i[orders/create orders/paid carts/create carts/update] # Set the below if using an application proxy. config.app_proxy_prefix = ENV['SHOPIFY_APP_PROXY_PREFIX'] diff --git a/test/dummy/config/initializers/omniauth.rb b/test/dummy/config/initializers/omniauth.rb index 25e9016c..ce579b45 100644 --- a/test/dummy/config/initializers/omniauth.rb +++ b/test/dummy/config/initializers/omniauth.rb @@ -1,7 +1,6 @@ Rails.application.config.middleware.use OmniAuth::Builder do provider :shopify, - ShopifyApp.configuration.api_key, - ShopifyApp.configuration.secret, - - :scope => ShopifyApp.configuration.scope + ShopifyApp.configuration.api_key, + ShopifyApp.configuration.secret, + scope: ShopifyApp.configuration.scope end diff --git a/test/dummy/config/initializers/session_store.rb b/test/dummy/config/initializers/session_store.rb index 647e382c..74aa173a 100644 --- a/test/dummy/config/initializers/session_store.rb +++ b/test/dummy/config/initializers/session_store.rb @@ -1,2 +1,2 @@ # Use an ActiveRecord-based session store. -Rails.application.config.session_store :active_record_store, :key => '_disco_app_session' +Rails.application.config.session_store :active_record_store, key: '_disco_app_session' diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index c67eea1a..f2501136 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -1,5 +1,4 @@ Rails.application.routes.draw do - root to: 'home#index' get '/proxy', to: 'proxy#index' @@ -7,5 +6,4 @@ mount ShopifyApp::Engine, at: '/' mount DiscoApp::Engine, at: '/' - end diff --git a/test/dummy/db/migrate/20160307182229_create_products.rb b/test/dummy/db/migrate/20160307182229_create_products.rb index 9b92598f..670729ab 100644 --- a/test/dummy/db/migrate/20160307182229_create_products.rb +++ b/test/dummy/db/migrate/20160307182229_create_products.rb @@ -1,5 +1,6 @@ class CreateProducts < ActiveRecord::Migration[4.2] -def change + + def change create_table :products do |t| t.integer :shop_id, limit: 8 t.jsonb :data @@ -8,4 +9,5 @@ def change end add_foreign_key :products, :disco_app_shops, column: :shop_id end + end diff --git a/test/dummy/db/migrate/20160530160739_create_asset_models.rb b/test/dummy/db/migrate/20160530160739_create_asset_models.rb index 99dfb16a..7d5afbae 100644 --- a/test/dummy/db/migrate/20160530160739_create_asset_models.rb +++ b/test/dummy/db/migrate/20160530160739_create_asset_models.rb @@ -1,5 +1,6 @@ class CreateAssetModels < ActiveRecord::Migration[4.2] -def change + + def change create_table :js_configurations do |t| t.integer :shop_id, limit: 8 t.string :label, default: 'Default' @@ -16,4 +17,5 @@ def change add_foreign_key :js_configurations, :disco_app_shops, column: :shop_id add_foreign_key :widget_configurations, :disco_app_shops, column: :shop_id end + end diff --git a/test/dummy/db/migrate/20161105054746_create_carts.rb b/test/dummy/db/migrate/20161105054746_create_carts.rb index 5ae0cdd7..220784ae 100644 --- a/test/dummy/db/migrate/20161105054746_create_carts.rb +++ b/test/dummy/db/migrate/20161105054746_create_carts.rb @@ -1,6 +1,6 @@ class CreateCarts < ActiveRecord::Migration[4.2] -def change + def change create_table :carts do |t| t.integer :shop_id, limit: 8 t.string :token @@ -11,4 +11,5 @@ def change add_foreign_key :carts, :disco_app_shops, column: :shop_id add_index :carts, :token, unique: true end + end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index e074d016..e6494a73 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -11,184 +11,183 @@ # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 2018_12_29_100327) do - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" - - create_table "carts", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.string "token" - t.jsonb "data" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["token"], name: "index_carts_on_token", unique: true - end - - create_table "disco_app_app_settings", id: :serial, force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "disco_app_application_charges", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.bigint "subscription_id" - t.integer "status", default: 0 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "shopify_id" - t.string "confirmation_url" - end - - create_table "disco_app_flow_actions", force: :cascade do |t| - t.bigint "shop_id" - t.string "action_id" - t.string "action_run_id" - t.jsonb "properties", default: {} - t.integer "status", default: 0 - t.datetime "processed_at" - t.jsonb "processing_errors", default: [] - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["action_run_id"], name: "index_disco_app_flow_actions_on_action_run_id", unique: true - end - - create_table "disco_app_flow_triggers", force: :cascade do |t| - t.bigint "shop_id" - t.string "title" - t.string "resource_name" - t.string "resource_url" - t.jsonb "properties", default: {} - t.integer "status", default: 0 - t.datetime "processed_at" - t.jsonb "processing_errors", default: [] - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "disco_app_plan_codes", id: :serial, force: :cascade do |t| - t.bigint "plan_id" - t.string "code" - t.integer "trial_period_days" - t.integer "amount" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "status", default: 0 - end - - create_table "disco_app_plans", id: :serial, force: :cascade do |t| - t.integer "status", default: 0 - t.string "name" - t.integer "plan_type", default: 0 - t.integer "trial_period_days" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "amount", default: 0 - t.string "currency", default: "USD" - t.integer "interval", default: 0 - t.integer "interval_count", default: 1 - end - - create_table "disco_app_recurring_application_charges", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.bigint "subscription_id" - t.integer "status", default: 0 - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.bigint "shopify_id" - t.string "confirmation_url" - end - - create_table "disco_app_sessions", id: :serial, force: :cascade do |t| - t.string "session_id", null: false - t.text "data" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "shop_id" - t.index ["session_id"], name: "index_disco_app_sessions_on_session_id", unique: true - t.index ["updated_at"], name: "index_disco_app_sessions_on_updated_at" - end - - create_table "disco_app_shops", id: :serial, force: :cascade do |t| - t.string "shopify_domain", null: false - t.string "shopify_token", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.integer "status", default: 0 - t.string "domain" - t.string "plan_name" - t.string "name" - t.jsonb "data", default: {} - t.index ["shopify_domain"], name: "index_disco_app_shops_on_shopify_domain", unique: true - end - - create_table "disco_app_sources", id: :serial, force: :cascade do |t| - t.string "source" - t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["source"], name: "index_disco_app_sources_on_source" - end - - create_table "disco_app_subscriptions", id: :serial, force: :cascade do |t| - t.integer "shop_id" - t.integer "plan_id" - t.integer "status" - t.integer "subscription_type" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.datetime "trial_start_at" - t.datetime "trial_end_at" - t.datetime "cancelled_at" - t.integer "amount", default: 0 - t.bigint "plan_code_id" - t.integer "trial_period_days" - t.bigint "source_id" - t.index ["plan_id"], name: "index_disco_app_subscriptions_on_plan_id" - t.index ["shop_id"], name: "index_disco_app_subscriptions_on_shop_id" - end - - create_table "disco_app_users", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.string "first_name" - t.string "last_name" - t.string "email" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["id", "shop_id"], name: "index_disco_app_users_on_id_and_shop_id", unique: true - end - - create_table "js_configurations", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.string "label", default: "Default" - t.string "locale", default: "en" - end - - create_table "products", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.jsonb "data" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "widget_configurations", id: :serial, force: :cascade do |t| - t.bigint "shop_id" - t.string "label", default: "Default" - t.string "locale", default: "en" - t.string "background_color", default: "#FFFFFF" - end - - add_foreign_key "carts", "disco_app_shops", column: "shop_id" - add_foreign_key "disco_app_application_charges", "disco_app_shops", column: "shop_id" - add_foreign_key "disco_app_application_charges", "disco_app_subscriptions", column: "subscription_id" - add_foreign_key "disco_app_flow_actions", "disco_app_shops", column: "shop_id", on_delete: :cascade - add_foreign_key "disco_app_flow_triggers", "disco_app_shops", column: "shop_id", on_delete: :cascade - add_foreign_key "disco_app_plan_codes", "disco_app_plans", column: "plan_id" - add_foreign_key "disco_app_recurring_application_charges", "disco_app_shops", column: "shop_id" - add_foreign_key "disco_app_recurring_application_charges", "disco_app_subscriptions", column: "subscription_id" - add_foreign_key "disco_app_sessions", "disco_app_shops", column: "shop_id", on_delete: :cascade - add_foreign_key "disco_app_subscriptions", "disco_app_plan_codes", column: "plan_code_id" - add_foreign_key "disco_app_subscriptions", "disco_app_sources", column: "source_id" - add_foreign_key "js_configurations", "disco_app_shops", column: "shop_id" - add_foreign_key "products", "disco_app_shops", column: "shop_id" - add_foreign_key "widget_configurations", "disco_app_shops", column: "shop_id" + enable_extension 'plpgsql' + + create_table 'carts', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.string 'token' + t.jsonb 'data' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['token'], name: 'index_carts_on_token', unique: true + end + + create_table 'disco_app_app_settings', id: :serial, force: :cascade do |t| + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'disco_app_application_charges', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.bigint 'subscription_id' + t.integer 'status', default: 0 + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.bigint 'shopify_id' + t.string 'confirmation_url' + end + + create_table 'disco_app_flow_actions', force: :cascade do |t| + t.bigint 'shop_id' + t.string 'action_id' + t.string 'action_run_id' + t.jsonb 'properties', default: {} + t.integer 'status', default: 0 + t.datetime 'processed_at' + t.jsonb 'processing_errors', default: [] + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['action_run_id'], name: 'index_disco_app_flow_actions_on_action_run_id', unique: true + end + + create_table 'disco_app_flow_triggers', force: :cascade do |t| + t.bigint 'shop_id' + t.string 'title' + t.string 'resource_name' + t.string 'resource_url' + t.jsonb 'properties', default: {} + t.integer 'status', default: 0 + t.datetime 'processed_at' + t.jsonb 'processing_errors', default: [] + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'disco_app_plan_codes', id: :serial, force: :cascade do |t| + t.bigint 'plan_id' + t.string 'code' + t.integer 'trial_period_days' + t.integer 'amount' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.integer 'status', default: 0 + end + + create_table 'disco_app_plans', id: :serial, force: :cascade do |t| + t.integer 'status', default: 0 + t.string 'name' + t.integer 'plan_type', default: 0 + t.integer 'trial_period_days' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.integer 'amount', default: 0 + t.string 'currency', default: 'USD' + t.integer 'interval', default: 0 + t.integer 'interval_count', default: 1 + end + + create_table 'disco_app_recurring_application_charges', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.bigint 'subscription_id' + t.integer 'status', default: 0 + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.bigint 'shopify_id' + t.string 'confirmation_url' + end + + create_table 'disco_app_sessions', id: :serial, force: :cascade do |t| + t.string 'session_id', null: false + t.text 'data' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.integer 'shop_id' + t.index ['session_id'], name: 'index_disco_app_sessions_on_session_id', unique: true + t.index ['updated_at'], name: 'index_disco_app_sessions_on_updated_at' + end + + create_table 'disco_app_shops', id: :serial, force: :cascade do |t| + t.string 'shopify_domain', null: false + t.string 'shopify_token', null: false + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.integer 'status', default: 0 + t.string 'domain' + t.string 'plan_name' + t.string 'name' + t.jsonb 'data', default: {} + t.index ['shopify_domain'], name: 'index_disco_app_shops_on_shopify_domain', unique: true + end + + create_table 'disco_app_sources', id: :serial, force: :cascade do |t| + t.string 'source' + t.string 'name' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['source'], name: 'index_disco_app_sources_on_source' + end + + create_table 'disco_app_subscriptions', id: :serial, force: :cascade do |t| + t.integer 'shop_id' + t.integer 'plan_id' + t.integer 'status' + t.integer 'subscription_type' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.datetime 'trial_start_at' + t.datetime 'trial_end_at' + t.datetime 'cancelled_at' + t.integer 'amount', default: 0 + t.bigint 'plan_code_id' + t.integer 'trial_period_days' + t.bigint 'source_id' + t.index ['plan_id'], name: 'index_disco_app_subscriptions_on_plan_id' + t.index ['shop_id'], name: 'index_disco_app_subscriptions_on_shop_id' + end + + create_table 'disco_app_users', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.string 'first_name' + t.string 'last_name' + t.string 'email' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + t.index ['id', 'shop_id'], name: 'index_disco_app_users_on_id_and_shop_id', unique: true + end + + create_table 'js_configurations', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.string 'label', default: 'Default' + t.string 'locale', default: 'en' + end + + create_table 'products', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.jsonb 'data' + t.datetime 'created_at', null: false + t.datetime 'updated_at', null: false + end + + create_table 'widget_configurations', id: :serial, force: :cascade do |t| + t.bigint 'shop_id' + t.string 'label', default: 'Default' + t.string 'locale', default: 'en' + t.string 'background_color', default: '#FFFFFF' + end + + add_foreign_key 'carts', 'disco_app_shops', column: 'shop_id' + add_foreign_key 'disco_app_application_charges', 'disco_app_shops', column: 'shop_id' + add_foreign_key 'disco_app_application_charges', 'disco_app_subscriptions', column: 'subscription_id' + add_foreign_key 'disco_app_flow_actions', 'disco_app_shops', column: 'shop_id', on_delete: :cascade + add_foreign_key 'disco_app_flow_triggers', 'disco_app_shops', column: 'shop_id', on_delete: :cascade + add_foreign_key 'disco_app_plan_codes', 'disco_app_plans', column: 'plan_id' + add_foreign_key 'disco_app_recurring_application_charges', 'disco_app_shops', column: 'shop_id' + add_foreign_key 'disco_app_recurring_application_charges', 'disco_app_subscriptions', column: 'subscription_id' + add_foreign_key 'disco_app_sessions', 'disco_app_shops', column: 'shop_id', on_delete: :cascade + add_foreign_key 'disco_app_subscriptions', 'disco_app_plan_codes', column: 'plan_code_id' + add_foreign_key 'disco_app_subscriptions', 'disco_app_sources', column: 'source_id' + add_foreign_key 'js_configurations', 'disco_app_shops', column: 'shop_id' + add_foreign_key 'products', 'disco_app_shops', column: 'shop_id' + add_foreign_key 'widget_configurations', 'disco_app_shops', column: 'shop_id' end diff --git a/test/integration/synchronises_test.rb b/test/integration/synchronises_test.rb index b02a4cf9..5b0408fe 100644 --- a/test/integration/synchronises_test.rb +++ b/test/integration/synchronises_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class SynchronisesTest < ActionDispatch::IntegrationTest + include ActiveJob::TestHelper fixtures :all diff --git a/test/jobs/disco_app/app_installed_job_test.rb b/test/jobs/disco_app/app_installed_job_test.rb index 043b017b..bdd550b0 100644 --- a/test/jobs/disco_app/app_installed_job_test.rb +++ b/test/jobs/disco_app/app_installed_job_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::AppInstalledJobTest < ActionController::TestCase + include ActiveJob::TestHelper def setup @@ -11,7 +12,7 @@ def setup stub_request(:get, "#{@shop.admin_url}/shop.json").to_return(status: 200, body: api_fixture('widget_store/shop').to_json) stub_request(:get, "#{@shop.admin_url}/carrier_services.json").to_return(status: 200, body: api_fixture('widget_store/carrier_services').to_json) stub_request(:post, "#{@shop.admin_url}/carrier_services.json").to_return(status: 200) - stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").to_return(status: 200) + stub_request(:post, 'https://api.discolabs.com/v1/app_subscriptions.json').to_return(status: 200) end def teardown @@ -61,7 +62,8 @@ def teardown end private - # Prevents the output from the webhook synchronisation from + + # Prevents the output from the webhook synchronisation from # printing to STDOUT and messing up the test output def with_suppressed_output original_stdout = $stdout.clone @@ -70,4 +72,5 @@ def with_suppressed_output ensure $stdout.reopen(original_stdout) end + end diff --git a/test/jobs/disco_app/app_uninstalled_job_test.rb b/test/jobs/disco_app/app_uninstalled_job_test.rb index 59f5abbe..997febf6 100644 --- a/test/jobs/disco_app/app_uninstalled_job_test.rb +++ b/test/jobs/disco_app/app_uninstalled_job_test.rb @@ -1,11 +1,12 @@ require 'test_helper' class DiscoApp::AppUninstalledJobTest < ActionController::TestCase + include ActiveJob::TestHelper def setup @shop = disco_app_shops(:widget_store) - stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").to_return(status: 200) + stub_request(:post, 'https://api.discolabs.com/v1/app_subscriptions.json').to_return(status: 200) perform_enqueued_jobs do DiscoApp::AppUninstalledJob.perform_later(@shop, {}) end @@ -25,6 +26,8 @@ def teardown test 'app uninstalled job can be extended using concerns' do assert_performed_jobs 2 @shop.reload - assert_equal 'Nowhere', @shop.data[:country_name] # Assert extended method called. + # Assert extended method called. + assert_equal 'Nowhere', @shop.data[:country_name] end + end diff --git a/test/jobs/disco_app/send_subscription_job_test.rb b/test/jobs/disco_app/send_subscription_job_test.rb index 482fc7e0..2a54f8c4 100644 --- a/test/jobs/disco_app/send_subscription_job_test.rb +++ b/test/jobs/disco_app/send_subscription_job_test.rb @@ -1,11 +1,12 @@ require 'test_helper' class DiscoApp::SendSubscriptionJobTest < ActionController::TestCase + include ActiveJob::TestHelper def setup @shop = disco_app_shops(:widget_store) - stub_request(:post, "https://api.discolabs.com/v1/app_subscriptions.json").to_return(status: 200) + stub_request(:post, 'https://api.discolabs.com/v1/app_subscriptions.json').to_return(status: 200) end def teardown @@ -18,7 +19,7 @@ def teardown perform_enqueued_jobs do DiscoApp::SendSubscriptionJob.perform_later(@shop) end - assert_requested(:post, "https://api.discolabs.com/v1/app_subscriptions.json", times: 1) + assert_requested(:post, 'https://api.discolabs.com/v1/app_subscriptions.json', times: 1) end end diff --git a/test/jobs/disco_app/synchronise_carrier_service_job_test.rb b/test/jobs/disco_app/synchronise_carrier_service_job_test.rb index 4924db54..0a82a0f2 100644 --- a/test/jobs/disco_app/synchronise_carrier_service_job_test.rb +++ b/test/jobs/disco_app/synchronise_carrier_service_job_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::SynchroniseCarrierServiceJobTest < ActionController::TestCase + include ActiveJob::TestHelper def setup diff --git a/test/jobs/disco_app/synchronise_users_job_test.rb b/test/jobs/disco_app/synchronise_users_job_test.rb index da3bc9e3..028c1a85 100644 --- a/test/jobs/disco_app/synchronise_users_job_test.rb +++ b/test/jobs/disco_app/synchronise_users_job_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::SynchroniseUsersJobTest < ActionController::TestCase + include ActiveJob::TestHelper def setup diff --git a/test/jobs/disco_app/synchronise_webhooks_job_test.rb b/test/jobs/disco_app/synchronise_webhooks_job_test.rb index 5305843e..99fe9481 100644 --- a/test/jobs/disco_app/synchronise_webhooks_job_test.rb +++ b/test/jobs/disco_app/synchronise_webhooks_job_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::SynchroniseWebhooksJobTest < ActionController::TestCase + include ActiveJob::TestHelper def setup @@ -40,11 +41,12 @@ def teardown assert output.first.include?('Invalid topic specified.') assert output.first.include?('orders/create - not registered') end - end + end end - + private - # Prevents the output from the webhook synchronisation from + + # Prevents the output from the webhook synchronisation from # printing to STDOUT and messing up the test output def with_suppressed_output original_stdout = $stdout.clone @@ -55,4 +57,3 @@ def with_suppressed_output end end - diff --git a/test/models/disco_app/can_be_liquified_test.rb b/test/models/disco_app/can_be_liquified_test.rb index c8656155..6894be9e 100644 --- a/test/models/disco_app/can_be_liquified_test.rb +++ b/test/models/disco_app/can_be_liquified_test.rb @@ -3,6 +3,7 @@ class DiscoApp::CanBeLiquifiedTest < ActiveSupport::TestCase class Model + include ActiveModel::Model include DiscoApp::Concerns::CanBeLiquified @@ -17,6 +18,7 @@ def as_json def liquid_model_name 'model' end + end def setup @@ -48,7 +50,7 @@ def teardown # Return an asset fixture as a string. def liquid_fixture(path) - filename = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'fixtures', 'liquid', "#{path}") + filename = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'fixtures', 'liquid', path.to_s) File.read(filename).strip end diff --git a/test/models/disco_app/has_metafields_test.rb b/test/models/disco_app/has_metafields_test.rb index dac96758..7c120840 100644 --- a/test/models/disco_app/has_metafields_test.rb +++ b/test/models/disco_app/has_metafields_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::HasMetafieldsTest < ActiveSupport::TestCase + include DiscoApp::Test::ShopifyAPI def setup @@ -14,27 +15,41 @@ def teardown end test 'can write metafields with a single namespace' do - stub_api_request(:put, "#{@shop.admin_url}/products/#{@product.id}.json", 'widget_store/products/write_metafields_single_namespace') - assert @shop.with_api_context { @product.write_metafields( - namespace1: { - key1: 'value1', - key2: 2 - } - ) } + stub_api_request( + :put, + "#{@shop.admin_url}/products/#{@product.id}.json", + 'widget_store/products/write_metafields_single_namespace' + ) + + assert @shop.with_api_context do + @product.write_metafields( + namespace1: { + key1: 'value1', + key2: 2 + } + ) + end end test 'can write metafields with multiple namespaces' do - stub_api_request(:put, "#{@shop.admin_url}/products/#{@product.id}.json", 'widget_store/products/write_metafields_multiple_namespaces') - assert @shop.with_api_context { @product.write_metafields( - namespace1: { - n1key1: 'value1', - n1key2: 2 - }, - namespace2: { - n2key3: 'value3', - n2key4: 2 - }, - ) } + stub_api_request( + :put, + "#{@shop.admin_url}/products/#{@product.id}.json", + 'widget_store/products/write_metafields_multiple_namespaces' + ) + + assert @shop.with_api_context do + @product.write_metafields( + namespace1: { + n1key1: 'value1', + n1key2: 2 + }, + namespace2: { + n2key3: 'value3', + n2key4: 2 + } + ) + end end end diff --git a/test/models/disco_app/renders_assets_test.rb b/test/models/disco_app/renders_assets_test.rb index aebe8bc5..4cddf9ee 100644 --- a/test/models/disco_app/renders_assets_test.rb +++ b/test/models/disco_app/renders_assets_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::RendersAssetsTest < ActiveSupport::TestCase + include ActiveJob::TestHelper include DiscoApp::Test::ShopifyAPI @@ -102,7 +103,7 @@ def teardown # Return an asset fixture as a string. def asset_fixture(path) - filename = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'fixtures', 'assets', "#{path}") + filename = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'fixtures', 'assets', path.to_s) File.read(filename) end diff --git a/test/models/disco_app/session_test.rb b/test/models/disco_app/session_test.rb index f3409be9..f1f1527f 100644 --- a/test/models/disco_app/session_test.rb +++ b/test/models/disco_app/session_test.rb @@ -7,8 +7,8 @@ def setup @session = DiscoApp::Session.create( session_id: 'a91bfc51fa79c9d09d43e2615d9345d4', data: { - :shopify => @shop.id, - :shopify_domain => @shop.shopify_domain + shopify: @shop.id, + shopify_domain: @shop.shopify_domain } ) end diff --git a/test/services/disco_app/charges_service_test.rb b/test/services/disco_app/charges_service_test.rb index 4b746f9a..c1248954 100644 --- a/test/services/disco_app/charges_service_test.rb +++ b/test/services/disco_app/charges_service_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::ChargesServiceTest < ActiveSupport::TestCase + include DiscoApp::Test::ShopifyAPI def setup @@ -30,15 +31,13 @@ def teardown end test 'creating a new charge for a recurring subscription is successful' do - res = { "recurring_application_charge": { "name": "Basic", - "price": "9.99", - "trial_days": 14, - "return_url": /^https:\/\/test\.example\.com\/subscriptions\/304261385\/charges\/53297050(1|2)\/activate$/, - "test": true - } } + res = { "recurring_application_charge": { "name": 'Basic', + "price": '9.99', + "trial_days": 14, + "return_url": %r{^https://test\.example\.com/subscriptions/304261385/charges/53297050(1|2)/activate$}, + "test": true } } stub_request(:post, "#{@shop.admin_url}/recurring_application_charges.json") - .with(body: res - ).to_return(status: 201, body:api_fixture("widget_store/charges/create_recurring_application_charge_response").to_json) + .with(body: res).to_return(status: 201, body: api_fixture('widget_store/charges/create_recurring_application_charge_response').to_json) new_charge = DiscoApp::ChargesService.create(@shop, @subscription) assert_equal 654381179, new_charge.shopify_id diff --git a/test/services/disco_app/subscription_service_test.rb b/test/services/disco_app/subscription_service_test.rb index fca8641d..6ac39f68 100644 --- a/test/services/disco_app/subscription_service_test.rb +++ b/test/services/disco_app/subscription_service_test.rb @@ -1,6 +1,7 @@ require 'test_helper' class DiscoApp::SubscriptionServiceTest < ActiveSupport::TestCase + include ActiveJob::TestHelper def setup diff --git a/test/support/test_shopify_api.rb b/test/support/test_shopify_api.rb index dda09221..b9c57fbc 100644 --- a/test/support/test_shopify_api.rb +++ b/test/support/test_shopify_api.rb @@ -5,7 +5,7 @@ def stub_api_request(method, endpoint, fixture_name) if method == :get stub_request(method, endpoint) .to_return(status: 200, body: api_fixture("#{fixture_name}_response").to_json) - elsif method == :post or method == :put + elsif (method == :post) || (method == :put) stub_request(method, endpoint) .with(body: api_fixture("#{fixture_name}_request").to_json) .to_return(status: 201, body: api_fixture("#{fixture_name}_response").to_json) diff --git a/test/test_helper.rb b/test/test_helper.rb index b52af53e..34316375 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,5 @@ # Prevent warnings from showing up during testing. -$VERBOSE=nil +$VERBOSE = nil # Configure Rails Environment ENV['RAILS_ENV'] = 'test' @@ -12,10 +12,10 @@ ENV['SHOPIFY_CHARGES_REAL'] = 'false' ENV['DISCO_API_URL'] = 'https://api.discolabs.com/v1/' -require File.expand_path("../../test/dummy/config/environment.rb", __FILE__) -ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)] -ActiveRecord::Migrator.migrations_paths << File.expand_path('../../db/migrate', __FILE__) -require "rails/test_help" +require File.expand_path('../test/dummy/config/environment.rb', __dir__) +ActiveRecord::Migrator.migrations_paths = [File.expand_path('../test/dummy/db/migrate', __dir__)] +ActiveRecord::Migrator.migrations_paths << File.expand_path('../db/migrate', __dir__) +require 'rails/test_help' # Require our additional test support helpers. require 'support/test_file_fixtures' @@ -33,7 +33,7 @@ # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_path=) - ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) + ActiveSupport::TestCase.fixture_path = File.expand_path('fixtures', __dir__) ActiveSupport::TestCase.fixtures :all end @@ -67,4 +67,5 @@ def log_out session[:shopify] = nil session[:shopify_domain] = nil end + end From 3d05ca392de356b102d01329f55508a434009cde Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 13:38:19 +1000 Subject: [PATCH 09/41] [rubocop] Fix up test for has_metafields --- test/models/disco_app/has_metafields_test.rb | 44 +++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/test/models/disco_app/has_metafields_test.rb b/test/models/disco_app/has_metafields_test.rb index 7c120840..1f83128d 100644 --- a/test/models/disco_app/has_metafields_test.rb +++ b/test/models/disco_app/has_metafields_test.rb @@ -21,14 +21,16 @@ def teardown 'widget_store/products/write_metafields_single_namespace' ) - assert @shop.with_api_context do - @product.write_metafields( - namespace1: { - key1: 'value1', - key2: 2 - } - ) - end + assert( + @shop.with_api_context do + @product.write_metafields( + namespace1: { + key1: 'value1', + key2: 2 + } + ) + end + ) end test 'can write metafields with multiple namespaces' do @@ -38,18 +40,20 @@ def teardown 'widget_store/products/write_metafields_multiple_namespaces' ) - assert @shop.with_api_context do - @product.write_metafields( - namespace1: { - n1key1: 'value1', - n1key2: 2 - }, - namespace2: { - n2key3: 'value3', - n2key4: 2 - } - ) - end + assert( + @shop.with_api_context do + @product.write_metafields( + namespace1: { + n1key1: 'value1', + n1key2: 2 + }, + namespace2: { + n2key3: 'value3', + n2key4: 2 + } + ) + end + ) end end From 097b853445180ccdb8d4f5038d8128f621420dc2 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 13:40:45 +1000 Subject: [PATCH 10/41] [rubocop] Fix memoized instance variable name issues --- .../disco_app/concerns/synchronise_carrier_service_job.rb | 2 +- app/jobs/disco_app/shop_job.rb | 4 +++- app/models/disco_app/concerns/shop.rb | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb b/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb index 8a86a6db..c2e84ed3 100644 --- a/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_carrier_service_job.rb @@ -50,7 +50,7 @@ def current_carrier_service_names # Return a list of currently registered carrier services. def current_carrier_services - @current_carrier_service ||= ShopifyAPI::CarrierService.find(:all) + @current_carrier_services ||= ShopifyAPI::CarrierService.find(:all) end end diff --git a/app/jobs/disco_app/shop_job.rb b/app/jobs/disco_app/shop_job.rb index 1f0af369..8046026b 100644 --- a/app/jobs/disco_app/shop_job.rb +++ b/app/jobs/disco_app/shop_job.rb @@ -18,7 +18,9 @@ class DiscoApp::ShopJob < ApplicationJob private def find_shop(job) - @shop ||= job.arguments.first.is_a?(DiscoApp::Shop) ? job.arguments.first : DiscoApp::Shop.find_by!(shopify_domain: job.arguments.first) + return @shop if @shop + + @shop = job.arguments.first.is_a?(DiscoApp::Shop) ? job.arguments.first : DiscoApp::Shop.find_by!(shopify_domain: job.arguments.first) end def shop_context(job, block) diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index b4ba6b5d..ddb6069f 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -100,7 +100,7 @@ def locale # Return an instance of the Disco API client. def disco_api_client - @api_client ||= DiscoApp::ApiClient.new(self, ENV['DISCO_API_URL']) + @disco_api_client ||= DiscoApp::ApiClient.new(self, ENV['DISCO_API_URL']) end # Override the "read" data attribute to allow indifferent access. From 5985b9cddc6273143f27f0ab764b304ef4c6f445 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:04:16 +1000 Subject: [PATCH 11/41] [rubocop] Prevent shadowing of outer instances --- app/models/disco_app/concerns/renders_assets.rb | 9 ++++++--- app/models/disco_app/concerns/synchronises.rb | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/models/disco_app/concerns/renders_assets.rb b/app/models/disco_app/concerns/renders_assets.rb index 0f780c6f..6c59229e 100644 --- a/app/models/disco_app/concerns/renders_assets.rb +++ b/app/models/disco_app/concerns/renders_assets.rb @@ -150,9 +150,12 @@ def render_asset_script_tags(options, public_urls) # Iterate each script tag for which we have a known public URL and create # or update the corresponding script tag resource. public_urls.slice(*options[:script_tags]).each do |asset, public_url| - script_tag = current_script_tags.find(-> { ShopifyAPI::ScriptTag.new(event: 'onload') }) { |script_tag| script_tag.src.include?("#{asset}?") } - script_tag.src = public_url - shop.with_api_context { script_tag.save } + generate_new_script_tag = -> { ShopifyAPI::ScriptTag.new(event: 'onload') } + + current_script_tags + .find(generate_new_script_tag) { |script_tag| script_tag.src.include?("#{asset}?") } + .tap { |script_tag| script_tag.src = public_url } + .yield_self { |script_tag| shop.with_api_context { script_tag.save } } end end diff --git a/app/models/disco_app/concerns/synchronises.rb b/app/models/disco_app/concerns/synchronises.rb index 8e5ca5c1..85426d0f 100644 --- a/app/models/disco_app/concerns/synchronises.rb +++ b/app/models/disco_app/concerns/synchronises.rb @@ -21,9 +21,9 @@ def synchronise(shop, data) return unless should_synchronise?(shop, data) begin - instance = find_or_create_by!(synchronise_by(shop, data)) do |instance| - instance.shop = shop - instance.data = data + instance = find_or_create_by!(synchronise_by(shop, data)) do |new_instance| + new_instance.shop = shop + new_instance.data = data end rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation retry From a4f6fe9e28f8be6fdfd9e548e9940db7acde9740 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:04:48 +1000 Subject: [PATCH 12/41] [rubocop] Use correct guard --- app/services/disco_app/flow/process_action.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/disco_app/flow/process_action.rb b/app/services/disco_app/flow/process_action.rb index 98410d73..cf64cfc3 100644 --- a/app/services/disco_app/flow/process_action.rb +++ b/app/services/disco_app/flow/process_action.rb @@ -26,7 +26,7 @@ def find_action_service_class action.action_id.classify.safe_constantize || %(Flow::Actions::#{action.action_id.to_s.classify}).safe_constantize - return if action_service_class.nil? + return unless action_service_class.nil? update_action(false, ["Could not find service class for #{action.action_id}"]) context.fail! From d0792fe4a3eb6d8e2c9614c4fae4b3ac6b7b4253 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:05:08 +1000 Subject: [PATCH 13/41] [rubocop] refactor complex methods --- .../disco_app/concerns/can_be_liquified.rb | 23 +++-- .../disco_app/subscription_service.rb | 88 +++++++++++++------ 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/app/models/disco_app/concerns/can_be_liquified.rb b/app/models/disco_app/concerns/can_be_liquified.rb index 23c547da..1c9efe1e 100644 --- a/app/models/disco_app/concerns/can_be_liquified.rb +++ b/app/models/disco_app/concerns/can_be_liquified.rb @@ -3,6 +3,7 @@ module DiscoApp::Concerns::CanBeLiquified extend ActiveSupport::Concern SPLIT_ARRAY_SEPARATOR = '@!@'.freeze + NIL_VALUE = 'nil'.freeze included do # Return this model as a hash for use with `to_liquid`. Returns `as_json` by default but is provided here as a hook @@ -27,14 +28,26 @@ def liquid_model_name # Return given value as a string expression that will be evaluated in Liquid to result in the correct value type. def as_liquid_value(key, value) return value.to_s if value.is_a?(Numeric) || (!!value == value) - return 'nil' if value.nil? - if value.is_a? Array - return "'#{value.map { |e| (e.is_a? String) ? CGI.escapeHTML(e) : e }.join(SPLIT_ARRAY_SEPARATOR)}' | split: '#{SPLIT_ARRAY_SEPARATOR}'" - end - return "'#{value.to_s.gsub("'", ''')}'" if value.is_a?(String) && key.end_with?('_html') + return NIL_VALUE if value.nil? + return converted_array_value(value) if value.is_a? Array + return converted_html(value) if value.is_a?(String) && key.end_with?('_html') "'#{CGI.escapeHTML(value.to_s)}'" end + + def converted_array_value(value) + split_array = value.map do |element| + CGI.escapeHTML(element) if element.is_a? String + + element + end.join(SPLIT_ARRAY_SEPARATOR) + + "'#{split_array}' | split: '#{SPLIT_ARRAY_SEPARATOR}'" + end + + def converted_html(value) + "'#{value.to_s.gsub("'", ''')}'" + end end end diff --git a/app/services/disco_app/subscription_service.rb b/app/services/disco_app/subscription_service.rb index 11bce2a5..5ea2bd65 100644 --- a/app/services/disco_app/subscription_service.rb +++ b/app/services/disco_app/subscription_service.rb @@ -3,39 +3,23 @@ class DiscoApp::SubscriptionService # Subscribe the given shop to the given plan, optionally using the given plan # code and optionally tracking the subscription source. def self.subscribe(shop, plan, plan_code = nil, source_name = nil) - # If a plan code was provided, fetch it for the given plan. - plan_code_instance = nil - plan_code_instance = DiscoApp::PlanCode.available.find_by(plan: plan, code: plan_code) if plan_code.present? - - # If a source name has been provided, fetch or create it - source_instance = nil - source_instance = DiscoApp::Source.find_or_create_by(source: source_name) if source_name.present? + new(shop, plan, plan_code, source_name).subscribe + end - # Cancel any existing current subscriptions. - shop.subscriptions.current.update_all( - status: DiscoApp::Subscription.statuses[:cancelled], - cancelled_at: Time.now - ) + attr_reader :shop, :plan, :plan_code, :source_name - # Get the amount that should be charged for the subscription. - subscription_amount = plan_code_instance.present? ? plan_code_instance.amount : plan.amount + def initialize(shop, plan, plan_code = nil, source_name = nil) + @shop = shop + @plan = plan + @plan_code = plan_code + @source_name = source_name + end - # Get the date the subscription trial should end. - subscription_trial_period_days = plan_code_instance.present? ? plan_code_instance.trial_period_days : plan.trial_period_days + def subscribe + cancel_existing_subscriptions # Create the new subscription. - new_subscription = DiscoApp::Subscription.create!( - shop: shop, - plan: plan, - plan_code: plan_code_instance, - status: DiscoApp::Subscription.statuses[plan.has_trial? ? :trial : :active], - subscription_type: plan.plan_type, - amount: subscription_amount, - trial_period_days: plan.has_trial? ? subscription_trial_period_days : nil, - trial_start_at: plan.has_trial? ? Time.now : nil, - trial_end_at: plan.has_trial? ? subscription_trial_period_days.days.from_now : nil, - source: source_instance - ) + new_subscription = create_new_subscription # Enqueue the subscription changed background job. DiscoApp::SubscriptionChangedJob.perform_later(shop, new_subscription) @@ -44,4 +28,52 @@ def self.subscribe(shop, plan, plan_code = nil, source_name = nil) new_subscription end + private + + # If a plan code was provided, fetch it for the given plan. + def plan_code_instance + return unless plan_code.present? + + @plan_code_instance ||= DiscoApp::PlanCode.available.find_by(plan: plan, code: plan_code) + end + + # If a source name has been provided, fetch or create it + def source_instance + return unless source_name.present? + + @source_instance ||= DiscoApp::Source.find_or_create_by(source: source_name) + end + + # Cancel any existing current subscriptions. + def cancel_existing_subscriptions + shop.subscriptions.current.update_all( + status: DiscoApp::Subscription.statuses[:cancelled], + cancelled_at: Time.now + ) + end + + # Get the amount that should be charged for the subscription. + def subscription_amount + plan_code_instance.present? ? plan_code_instance.amount : plan.amount + end + + # Get the date the subscription trial should end. + def subscription_trial_period_days + plan_code_instance.present? ? plan_code_instance.trial_period_days : plan.trial_period_days + end + + def create_new_subscription + DiscoApp::Subscription.create!( + shop: shop, + plan: plan, + plan_code: plan_code_instance, + status: DiscoApp::Subscription.statuses[plan.has_trial? ? :trial : :active], + subscription_type: plan.plan_type, + amount: subscription_amount, + trial_period_days: plan.has_trial? ? subscription_trial_period_days : nil, + trial_start_at: plan.has_trial? ? Time.now : nil, + trial_end_at: plan.has_trial? ? subscription_trial_period_days.days.from_now : nil, + source: source_instance + ) + end end From d7a9298ed932f372bc805d6fcb3f02a741a1ee3c Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:07:57 +1000 Subject: [PATCH 14/41] [rubocop] Use .positive? instead of > 0 --- app/models/disco_app/concerns/plan.rb | 2 +- app/models/disco_app/concerns/subscription.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/disco_app/concerns/plan.rb b/app/models/disco_app/concerns/plan.rb index bb1c3ee1..f60f10b5 100644 --- a/app/models/disco_app/concerns/plan.rb +++ b/app/models/disco_app/concerns/plan.rb @@ -28,7 +28,7 @@ module DiscoApp::Concerns::Plan end def has_trial? - trial_period_days.present? && (trial_period_days > 0) + trial_period_days.present? && trial_period_days.positive? end end diff --git a/app/models/disco_app/concerns/subscription.rb b/app/models/disco_app/concerns/subscription.rb index c9e44eb5..eeb7490c 100644 --- a/app/models/disco_app/concerns/subscription.rb +++ b/app/models/disco_app/concerns/subscription.rb @@ -27,7 +27,7 @@ module DiscoApp::Concerns::Subscription # Only require an active charge if the amount to be charged is > 0. def requires_active_charge? - amount > 0 + amount.positive? end # Convenience method to check if this subscription has an active charge. From adbc18b858e3bc66641e47a86d2f25a34a7eb00a Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:10:32 +1000 Subject: [PATCH 15/41] [rubocop] remove predicate name method --- .../disco_app/concerns/carrier_request_controller.rb | 2 +- app/controllers/disco_app/concerns/webhooks_controller.rb | 2 +- app/controllers/disco_app/flow/concerns/actions_controller.rb | 2 +- app/services/disco_app/carrier_request_service.rb | 2 +- app/services/disco_app/webhook_service.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/disco_app/concerns/carrier_request_controller.rb b/app/controllers/disco_app/concerns/carrier_request_controller.rb index 91a59d93..1be1d431 100644 --- a/app/controllers/disco_app/concerns/carrier_request_controller.rb +++ b/app/controllers/disco_app/concerns/carrier_request_controller.rb @@ -17,7 +17,7 @@ def verify_carrier_request def carrier_request_signature_is_valid? return true if Rails.env.development? && DiscoApp.configuration.skip_carrier_request_verification? - DiscoApp::CarrierRequestService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) + DiscoApp::CarrierRequestService.valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) end def find_shop diff --git a/app/controllers/disco_app/concerns/webhooks_controller.rb b/app/controllers/disco_app/concerns/webhooks_controller.rb index ffab245b..7649b833 100644 --- a/app/controllers/disco_app/concerns/webhooks_controller.rb +++ b/app/controllers/disco_app/concerns/webhooks_controller.rb @@ -38,7 +38,7 @@ def verify_webhook def webhook_is_valid? return true if Rails.env.development? && DiscoApp.configuration.skip_webhook_verification? - DiscoApp::WebhookService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) + DiscoApp::WebhookService.valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) end end diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index 6d2e7bb7..f2302452 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -32,7 +32,7 @@ def verify_flow_action # Shopify Flow action endpoints use the same verification method as webhooks, which is why we reuse this # service method here. def flow_action_is_valid? - DiscoApp::WebhookService.is_valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) + DiscoApp::WebhookService.valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) end def find_shop diff --git a/app/services/disco_app/carrier_request_service.rb b/app/services/disco_app/carrier_request_service.rb index 3b777fb5..ad0b98f1 100644 --- a/app/services/disco_app/carrier_request_service.rb +++ b/app/services/disco_app/carrier_request_service.rb @@ -2,7 +2,7 @@ class DiscoApp::CarrierRequestService # Return true iff the provided hmac_to_verify matches that calculated from the # given data and secret. - def self.is_valid_hmac?(body, secret, hmac_to_verify) + def self.valid_hmac?(body, secret, hmac_to_verify) ActiveSupport::SecurityUtils.secure_compare(calculated_hmac(body, secret), hmac_to_verify.to_s) end diff --git a/app/services/disco_app/webhook_service.rb b/app/services/disco_app/webhook_service.rb index 21ff7abc..d0257a62 100644 --- a/app/services/disco_app/webhook_service.rb +++ b/app/services/disco_app/webhook_service.rb @@ -2,7 +2,7 @@ class DiscoApp::WebhookService # Return true iff the provided hmac_to_verify matches that calculated from the # given data and secret. - def self.is_valid_hmac?(body, secret, hmac_to_verify) + def self.valid_hmac?(body, secret, hmac_to_verify) ActiveSupport::SecurityUtils.secure_compare(calculated_hmac(body, secret), hmac_to_verify.to_s) end From 56fd1e195b83bf3759445674449bc7a5bebd0356 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:20:31 +1000 Subject: [PATCH 16/41] [rubocop] use guard clauses not conditional --- .../concerns/authenticated_controller.rb | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/app/controllers/disco_app/concerns/authenticated_controller.rb b/app/controllers/disco_app/concerns/authenticated_controller.rb index 59ae0503..b63c6f45 100644 --- a/app/controllers/disco_app/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/concerns/authenticated_controller.rb @@ -18,12 +18,13 @@ module DiscoApp::Concerns::AuthenticatedController private def auto_login - if shop_session.nil? && request_hmac_valid? - if (shop = DiscoApp::Shop.find_by_shopify_domain(sanitized_shop_name)).present? - session[:shopify] = shop.id - session[:shopify_domain] = sanitized_shop_name - end - end + return unless shop_session.nil? && request_hmac_valid? + + shop = DiscoApp::Shop.find_by_shopify_domain(sanitized_shop_name) + return unless shop.present? + + session[:shopify] = shop.id + session[:shopify_domain] = sanitized_shop_name end def shopify_shop @@ -63,9 +64,11 @@ def request_hmac_valid? end def check_shop_whitelist - if shop_session - redirect_to_login if ENV['WHITELISTED_DOMAINS'].present? && !ENV['WHITELISTED_DOMAINS'].include?(shop_session.url) - end + return unless shop_session + return unless ENV['WHITELISTED_DOMAINS'].present? + return if ENV['WHITELISTED_DOMAINS'].include?(shop_session.url) + + redirect_to_login end end From bb7c90c6c2ef07b851f1da34fe649d2f1ad8c134 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:25:34 +1000 Subject: [PATCH 17/41] [rubocop] remove uesless assignment --- .../disco_app/subscriptions_controller.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/controllers/disco_app/subscriptions_controller.rb b/app/controllers/disco_app/subscriptions_controller.rb index 2fe0f358..6d15b252 100644 --- a/app/controllers/disco_app/subscriptions_controller.rb +++ b/app/controllers/disco_app/subscriptions_controller.rb @@ -11,13 +11,20 @@ def new def create # Get the selected plan. If it's not available or couldn't be found, # redirect back to the plan selection page. - if (plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan])).nil? - redirect_to(action: :new) && return - end + plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan]) + + redirect_to(action: :new) && return unless plan # Subscribe the current shop to the selected plan. Pass along any cookied # plan code and source code. - if (subscription = DiscoApp::SubscriptionService.subscribe(@shop, plan, cookies[DiscoApp::CODE_COOKIE_KEY], cookies[DiscoApp::SOURCE_COOKIE_KEY])).nil? + subscription = DiscoApp::SubscriptionService.subscribe( + @shop, + plan, + cookies[DiscoApp::CODE_COOKIE_KEY], + cookies[DiscoApp::SOURCE_COOKIE_KEY] + ) + + if subscription.nil? redirect_to action: :new else redirect_to main_app.root_path From 4fccb145ede636055eff7d536fd48dd5bf424ad3 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:26:28 +1000 Subject: [PATCH 18/41] [rubocop] add space at end of class body --- app/services/disco_app/subscription_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/disco_app/subscription_service.rb b/app/services/disco_app/subscription_service.rb index 5ea2bd65..2132607c 100644 --- a/app/services/disco_app/subscription_service.rb +++ b/app/services/disco_app/subscription_service.rb @@ -76,4 +76,5 @@ def create_new_subscription source: source_instance ) end + end From 6b9f3174febcc2ffb7f7dd6b19645d28a0cc212b Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Fri, 14 Jun 2019 15:29:29 +1000 Subject: [PATCH 19/41] [rubocop] remove uncomunicative variable name --- app/helpers/disco_app/application_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/helpers/disco_app/application_helper.rb b/app/helpers/disco_app/application_helper.rb index 11ff8142..f8c32423 100644 --- a/app/helpers/disco_app/application_helper.rb +++ b/app/helpers/disco_app/application_helper.rb @@ -33,10 +33,10 @@ def react_component_with_content(name, args = {}, options = {}, &block) end # Provide link to dynamically add a new nested fields association - def link_to_add_fields(name, f, association) - new_object = f.object.send(association).klass.new + def link_to_add_fields(name, form, association) + new_object = form.object.send(association).klass.new id = new_object.object_id - fields = f.fields_for(association, new_object, child_index: id) do |builder| + fields = form.fields_for(association, new_object, child_index: id) do |builder| render(association.to_s.singularize + '_fields', f: builder) end link_to(name, '#', class: 'add_fields', data: { id: id, fields: fields.gsub("\n", '') }) From 0e7f067f7a23c88c9cb4d4deea742a20f4c7f4ac Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Sat, 15 Jun 2019 21:10:00 -0400 Subject: [PATCH 20/41] [rubocop] disable block length for class_methods --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index e0d1cbb8..21fadbfd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -491,6 +491,7 @@ Metrics/BlockLength: - included - namespace - trait + - class_methods Metrics/BlockNesting: Description: 'Avoid excessive block nesting' From 8db146e3e0f42c41639170e67a0633290301de4e Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Sat, 15 Jun 2019 21:38:53 -0400 Subject: [PATCH 21/41] [rubocop] avoid double negation using case --- .../disco_app/concerns/can_be_liquified.rb | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/models/disco_app/concerns/can_be_liquified.rb b/app/models/disco_app/concerns/can_be_liquified.rb index 1c9efe1e..bdc9c1a7 100644 --- a/app/models/disco_app/concerns/can_be_liquified.rb +++ b/app/models/disco_app/concerns/can_be_liquified.rb @@ -27,12 +27,20 @@ def liquid_model_name # Return given value as a string expression that will be evaluated in Liquid to result in the correct value type. def as_liquid_value(key, value) - return value.to_s if value.is_a?(Numeric) || (!!value == value) - return NIL_VALUE if value.nil? - return converted_array_value(value) if value.is_a? Array - return converted_html(value) if value.is_a?(String) && key.end_with?('_html') - - "'#{CGI.escapeHTML(value.to_s)}'" + html_string = ->(val) { val.is_a?(String) && key.end_with?('_html') } + + case value + when Numeric, TrueClass, FalseClass + value.to_s + when NilClass + NIL_VALUE + when Array + converted_array_value(value) + when html_string + converted_html(value) + else + "'#{CGI.escapeHTML(value.to_s)}'" + end end def converted_array_value(value) From 1f41eff7e2f402a00e2089822d24162c03a0910d Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:04:48 +1000 Subject: [PATCH 22/41] [rubocop] Clear up authenticated controller logic --- .../disco_app/admin/concerns/authenticated_controller.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/disco_app/admin/concerns/authenticated_controller.rb b/app/controllers/disco_app/admin/concerns/authenticated_controller.rb index 3528164e..4c560ffd 100644 --- a/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/admin/concerns/authenticated_controller.rb @@ -11,9 +11,12 @@ module DiscoApp::Admin::Concerns::AuthenticatedController private def authenticate_administrator - authenticate_or_request_with_http_basic do |username, password| - !username.blank? && !password.blank? && username == ENV['ADMIN_APP_USERNAME'] && password == ENV['ADMIN_APP_PASSWORD'] - end + authenticate_or_request_with_http_basic do |username, password| + username.present? && + password.present? && + username == ENV['ADMIN_APP_USERNAME'] && + password == ENV['ADMIN_APP_PASSWORD'] + end end end From a1e401f3eda585e0d43ea94dfac8c218c380e824 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:17:03 +1000 Subject: [PATCH 23/41] Refactor authenticated controller method --- .../disco_app/concerns/authenticated_controller.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/controllers/disco_app/concerns/authenticated_controller.rb b/app/controllers/disco_app/concerns/authenticated_controller.rb index b63c6f45..dda06475 100644 --- a/app/controllers/disco_app/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/concerns/authenticated_controller.rb @@ -52,7 +52,12 @@ def check_current_subscription end def check_active_charge - redirect_if_not_current_path disco_app.new_subscription_charge_path(@shop.current_subscription) if @shop.current_subscription? && @shop.current_subscription.requires_active_charge? && !@shop.development? && !@shop.current_subscription.active_charge? + return unless @shop.current_subscription? + return unless @shop.current_subscription.requires_active_charge? + return if @shop.development? + return if @shop.current_subscription.active_charge? + + redirect_if_not_current_path disco_app.new_subscription_charge_path(@shop.current_subscription) end def redirect_if_not_current_path(target) From 56a0fbefe5065800446b33a8541f58d1fef532e4 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:41:13 +1000 Subject: [PATCH 24/41] Use JSON.parse --- app/controllers/disco_app/concerns/webhooks_controller.rb | 2 +- app/jobs/disco_app/concerns/shop_update_job.rb | 2 +- app/models/disco_app/concerns/synchronises.rb | 2 +- lib/disco_app/support/file_fixtures.rb | 2 +- test/integration/synchronises_test.rb | 2 +- test/support/test_file_fixtures.rb | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/disco_app/concerns/webhooks_controller.rb b/app/controllers/disco_app/concerns/webhooks_controller.rb index 7649b833..06976a9a 100644 --- a/app/controllers/disco_app/concerns/webhooks_controller.rb +++ b/app/controllers/disco_app/concerns/webhooks_controller.rb @@ -22,7 +22,7 @@ def process_webhook head :bad_request unless job_class.present? # Decode the body data and enqueue the appropriate job. - data = ActiveSupport::JSON.decode(request.body.read).with_indifferent_access + data = JSON.parse(request.body.read).with_indifferent_access job_class.perform_later(shopify_domain, data) render body: nil diff --git a/app/jobs/disco_app/concerns/shop_update_job.rb b/app/jobs/disco_app/concerns/shop_update_job.rb index c4ec3b23..39ee557f 100644 --- a/app/jobs/disco_app/concerns/shop_update_job.rb +++ b/app/jobs/disco_app/concerns/shop_update_job.rb @@ -5,7 +5,7 @@ module DiscoApp::Concerns::ShopUpdateJob # Perform an update of the current shop's information. def perform(_shop, shop_data = nil) # If we weren't provided with shop data (eg from a webhook), fetch it. - shop_data ||= ActiveSupport::JSON.decode(ShopifyAPI::Shop.current.to_json) + shop_data ||= JSON.parse(ShopifyAPI::Shop.current.to_json) # Update attributes stored directly on the Shop model, along with the data hash itself. @shop.update(shop_data.with_indifferent_access.slice(*DiscoApp::Shop.column_names).except(:id, :created_at).merge(data: shop_data)) diff --git a/app/models/disco_app/concerns/synchronises.rb b/app/models/disco_app/concerns/synchronises.rb index 85426d0f..7ec4ad30 100644 --- a/app/models/disco_app/concerns/synchronises.rb +++ b/app/models/disco_app/concerns/synchronises.rb @@ -15,7 +15,7 @@ def synchronise_by(_shop, data) end def synchronise(shop, data) - data = ActiveSupport::JSON.decode(data.to_json) if data.is_a?(ShopifyAPI::Base) + data = JSON.parse(data.to_json) if data.is_a?(ShopifyAPI::Base) data = data.with_indifferent_access return unless should_synchronise?(shop, data) diff --git a/lib/disco_app/support/file_fixtures.rb b/lib/disco_app/support/file_fixtures.rb index 79b1507f..a7d6809a 100644 --- a/lib/disco_app/support/file_fixtures.rb +++ b/lib/disco_app/support/file_fixtures.rb @@ -12,7 +12,7 @@ def json_fixture(path, dir: 'json', parse: true) filename = Rails.root.join('test', 'fixtures', dir, "#{path}.json") return File.read(filename) unless parse - HashWithIndifferentAccess.new(ActiveSupport::JSON.decode(File.read(filename))) + HashWithIndifferentAccess.new(JSON.parse(File.read(filename))) end # Webhook fixtures are special-case JSON fixtures. diff --git a/test/integration/synchronises_test.rb b/test/integration/synchronises_test.rb index 5b0408fe..15082334 100644 --- a/test/integration/synchronises_test.rb +++ b/test/integration/synchronises_test.rb @@ -58,7 +58,7 @@ def teardown test 'shopify api model still allows synchronisation' do assert_equal({}, @product.data) - shopify_product = ShopifyAPI::Product.new(ActiveSupport::JSON.decode(webhook_fixture('product_updated'))) + shopify_product = ShopifyAPI::Product.new(JSON.parse(webhook_fixture('product_updated'))) Product.synchronise(@shop, shopify_product) # Assert the product was updated locally, with the correct attributes. diff --git a/test/support/test_file_fixtures.rb b/test/support/test_file_fixtures.rb index 3df86566..0994665d 100644 --- a/test/support/test_file_fixtures.rb +++ b/test/support/test_file_fixtures.rb @@ -10,13 +10,13 @@ def xml_fixture(path) # Return a JSON fixture as an indifferent hash. def json_fixture(path) filename = File.join(File.dirname(File.dirname(__FILE__)), 'fixtures', 'json', "#{path}.json") - HashWithIndifferentAccess.new(ActiveSupport::JSON.decode(File.read(filename))) + HashWithIndifferentAccess.new(JSON.parse(File.read(filename))) end # API fixtures are special-case JSON fixtures. def api_fixture(path) filename = File.join(File.dirname(File.dirname(__FILE__)), 'fixtures', 'api', "#{path}.json") - HashWithIndifferentAccess.new(ActiveSupport::JSON.decode(File.read(filename))) + HashWithIndifferentAccess.new(JSON.parse(File.read(filename))) end # Webhook fixtures are special-case JSON fixtures. From b42b46ad4fbb05631aa6b0728cab6aea3d151979 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:41:37 +1000 Subject: [PATCH 25/41] Rename liquid methods --- app/models/disco_app/concerns/can_be_liquified.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/disco_app/concerns/can_be_liquified.rb b/app/models/disco_app/concerns/can_be_liquified.rb index bdc9c1a7..b93a6870 100644 --- a/app/models/disco_app/concerns/can_be_liquified.rb +++ b/app/models/disco_app/concerns/can_be_liquified.rb @@ -35,15 +35,15 @@ def as_liquid_value(key, value) when NilClass NIL_VALUE when Array - converted_array_value(value) - when html_string - converted_html(value) + as_liquid_array_value(value) + when html_string + as_liquid_html_value(value) else "'#{CGI.escapeHTML(value.to_s)}'" end end - def converted_array_value(value) + def as_liquid_array_value(value) split_array = value.map do |element| CGI.escapeHTML(element) if element.is_a? String @@ -53,7 +53,7 @@ def converted_array_value(value) "'#{split_array}' | split: '#{SPLIT_ARRAY_SEPARATOR}'" end - def converted_html(value) + def as_liquid_html_value(value) "'#{value.to_s.gsub("'", ''')}'" end end From 6e37deab6e7d5ca8ffaf03a7e05fc4a0f31bfa64 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:41:51 +1000 Subject: [PATCH 26/41] Use more natural return statement --- app/jobs/disco_app/concerns/synchronise_users_job.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/jobs/disco_app/concerns/synchronise_users_job.rb b/app/jobs/disco_app/concerns/synchronise_users_job.rb index 42b9c012..e8232844 100644 --- a/app/jobs/disco_app/concerns/synchronise_users_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_users_job.rb @@ -8,8 +8,10 @@ def perform(_shop) ShopifyAPI::User.all end rescue ActiveResource::UnauthorizedAccess => e - Rollbar.error(e) && (return) + Rollbar.error(e) + return end + users.each { |user| DiscoApp::User.create_user(user, @shop) } end From 80a30167ba447659675e07224e640b903cc37f5a Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:45:45 +1000 Subject: [PATCH 27/41] Refactor long lines --- .../concerns/carrier_request_controller.rb | 19 ++++++++++++++++--- .../disco_app/concerns/webhooks_controller.rb | 6 +++++- .../flow/concerns/actions_controller.rb | 6 +++++- .../disco_app/concerns/shop_update_job.rb | 8 +++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/controllers/disco_app/concerns/carrier_request_controller.rb b/app/controllers/disco_app/concerns/carrier_request_controller.rb index 1be1d431..395df17d 100644 --- a/app/controllers/disco_app/concerns/carrier_request_controller.rb +++ b/app/controllers/disco_app/concerns/carrier_request_controller.rb @@ -17,15 +17,28 @@ def verify_carrier_request def carrier_request_signature_is_valid? return true if Rails.env.development? && DiscoApp.configuration.skip_carrier_request_verification? - DiscoApp::CarrierRequestService.valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) + DiscoApp::CarrierRequestService.valid_hmac?( + request.body.read.to_s, + ShopifyApp.configuration.secret, + request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] + ) end def find_shop - head :unauthorized unless (@shop = DiscoApp::Shop.find_by_shopify_domain(request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN'])) + @shop = DiscoApp::Shop.find_by_shopify_domain(request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']) + + head :unauthorized unless @shop end def validate_rate_params - head :bad_request unless params[:rate].present? && params[:rate][:origin].present? && params[:rate][:destination].present? && params[:rate][:items].present? + head :bad_request unless request_is_valid? + end + + def request_is_valid? + return false unless params[:rate].present? + return false unless params[:rate][:origin].present? + return false unless params[:rate][:destination].present? + return false unless params[:rate][:items].present? end end diff --git a/app/controllers/disco_app/concerns/webhooks_controller.rb b/app/controllers/disco_app/concerns/webhooks_controller.rb index 06976a9a..bea4d4d0 100644 --- a/app/controllers/disco_app/concerns/webhooks_controller.rb +++ b/app/controllers/disco_app/concerns/webhooks_controller.rb @@ -38,7 +38,11 @@ def verify_webhook def webhook_is_valid? return true if Rails.env.development? && DiscoApp.configuration.skip_webhook_verification? - DiscoApp::WebhookService.valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) + DiscoApp::WebhookService.valid_hmac?( + request.body.read.to_s, + ShopifyApp.configuration.secret, + request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] + ) end end diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index f2302452..153a8540 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -32,7 +32,11 @@ def verify_flow_action # Shopify Flow action endpoints use the same verification method as webhooks, which is why we reuse this # service method here. def flow_action_is_valid? - DiscoApp::WebhookService.valid_hmac?(request.body.read.to_s, ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256']) + DiscoApp::WebhookService.valid_hmac?( + request.body.read.to_s, + ShopifyApp.configuration.secret, + request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] + ) end def find_shop diff --git a/app/jobs/disco_app/concerns/shop_update_job.rb b/app/jobs/disco_app/concerns/shop_update_job.rb index 39ee557f..37b4f8cb 100644 --- a/app/jobs/disco_app/concerns/shop_update_job.rb +++ b/app/jobs/disco_app/concerns/shop_update_job.rb @@ -8,7 +8,13 @@ def perform(_shop, shop_data = nil) shop_data ||= JSON.parse(ShopifyAPI::Shop.current.to_json) # Update attributes stored directly on the Shop model, along with the data hash itself. - @shop.update(shop_data.with_indifferent_access.slice(*DiscoApp::Shop.column_names).except(:id, :created_at).merge(data: shop_data)) + @shop.update( + shop_data + .with_indifferent_access + .slice(*DiscoApp::Shop.column_names) + .except(:id, :created_at) + .merge(data: shop_data) + ) end end From ba200359d2e35c9a90b122dc8bc4e61f6474e3e5 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:46:00 +1000 Subject: [PATCH 28/41] Return head where necessary --- app/controllers/disco_app/concerns/webhooks_controller.rb | 7 ++++--- .../disco_app/flow/concerns/actions_controller.rb | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/controllers/disco_app/concerns/webhooks_controller.rb b/app/controllers/disco_app/concerns/webhooks_controller.rb index bea4d4d0..f7db6381 100644 --- a/app/controllers/disco_app/concerns/webhooks_controller.rb +++ b/app/controllers/disco_app/concerns/webhooks_controller.rb @@ -13,13 +13,13 @@ def process_webhook shopify_domain = request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN'] # Ensure a domain was provided in the headers. - head :bad_request unless shopify_domain + return head :bad_request unless shopify_domain # Try to find a matching background job task for the given topic using class name. job_class = DiscoApp::WebhookService.find_job_class(topic) # Return bad request if we couldn't match a job class. - head :bad_request unless job_class.present? + return head :bad_request unless job_class.present? # Decode the body data and enqueue the appropriate job. data = JSON.parse(request.body.read).with_indifferent_access @@ -31,7 +31,8 @@ def process_webhook private def verify_webhook - head :unauthorized unless webhook_is_valid? + return head :unauthorized unless webhook_is_valid? + request.body.rewind end diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index 153a8540..af48b7c0 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -25,7 +25,8 @@ def create_flow_action private def verify_flow_action - head :unauthorized unless flow_action_is_valid? + return head :unauthorized unless flow_action_is_valid? + request.body.rewind end From 4a261fede0281eb01ec67de99c414399fb5ded90 Mon Sep 17 00:00:00 2001 From: Tom Gamon Date: Tue, 25 Jun 2019 09:46:14 +1000 Subject: [PATCH 29/41] [rubocop] whitespace & indent --- .../admin/concerns/authenticated_controller.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/disco_app/admin/concerns/authenticated_controller.rb b/app/controllers/disco_app/admin/concerns/authenticated_controller.rb index 4c560ffd..af9f55a1 100644 --- a/app/controllers/disco_app/admin/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/admin/concerns/authenticated_controller.rb @@ -11,12 +11,12 @@ module DiscoApp::Admin::Concerns::AuthenticatedController private def authenticate_administrator - authenticate_or_request_with_http_basic do |username, password| - username.present? && - password.present? && - username == ENV['ADMIN_APP_USERNAME'] && - password == ENV['ADMIN_APP_PASSWORD'] - end + authenticate_or_request_with_http_basic do |username, password| + username.present? && + password.present? && + username == ENV['ADMIN_APP_USERNAME'] && + password == ENV['ADMIN_APP_PASSWORD'] + end end end From d70e2051cb9f494db6c0a33a5dba6a13965b72bb Mon Sep 17 00:00:00 2001 From: David Bird Date: Tue, 2 Jul 2019 17:28:37 +1000 Subject: [PATCH 30/41] CH5263: upgrade Polaris support --- disco_app.gemspec | 2 +- .../disco_app/install/install_generator.rb | 4 - .../install/templates/config/newrelic.yml | 29 ----- .../install/templates/initializers/rollbar.rb | 0 .../install/templates/root/.gitignore | 6 + .../disco_app/react/react_generator.rb | 11 +- .../embedded/api/base_controller.rb | 2 +- .../templates/app/models/api_response.rb | 107 ++++++++++++++++++ .../serializers/disco_app/shop_serializer.rb | 13 +++ .../serializers/disco_app/user_serializer.rb | 13 +++ .../app/serializers/empty_serializer.rb | 5 + .../app/serializers/error_serializer.rb | 77 +++++++++++++ .../app/views/embedded/home/index.html.erb | 2 +- .../javascripts/embedded/components/App.jsx | 20 ++-- .../components/Shared/EmbeddedPage.jsx | 30 ++++- .../components/Shared/ErrorBanner.jsx | 5 +- .../embedded/components/withApi.jsx | 2 +- .../app/webpack/javascripts/embedded/utils.js | 8 +- .../app/webpack/stylesheets/embedded.scss | 1 + .../webpack/stylesheets/shared/banners.scss | 7 ++ .../app/webpack/stylesheets/shared/busy.scss | 3 + .../app/webpack/stylesheets/shared/index.scss | 3 + .../stylesheets/shared/pagination.scss | 5 + .../react/templates/config/webpack/test.js | 5 + .../react/templates/config/webpacker.yml | 30 ++++- .../disco_app/react/templates/root/.babelrc | 33 ------ .../react/templates/root/.eslintignore | 5 + .../disco_app/react/templates/root/.eslintrc | 37 ++++-- .../react/templates/root/.postcssrc.yml | 6 - .../react/templates/root/babel.config.js | 72 ++++++++++++ .../react/templates/root/package.json.tt | 100 +++++++++++----- .../react/templates/root/postcss.config.js | 14 +++ 32 files changed, 525 insertions(+), 132 deletions(-) delete mode 100644 lib/generators/disco_app/install/templates/config/newrelic.yml create mode 100644 lib/generators/disco_app/install/templates/initializers/rollbar.rb create mode 100644 lib/generators/disco_app/react/templates/app/models/api_response.rb create mode 100644 lib/generators/disco_app/react/templates/app/serializers/disco_app/shop_serializer.rb create mode 100644 lib/generators/disco_app/react/templates/app/serializers/disco_app/user_serializer.rb create mode 100644 lib/generators/disco_app/react/templates/app/serializers/empty_serializer.rb create mode 100644 lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb create mode 100644 lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/banners.scss create mode 100644 lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/busy.scss create mode 100644 lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/index.scss create mode 100644 lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/pagination.scss create mode 100644 lib/generators/disco_app/react/templates/config/webpack/test.js delete mode 100644 lib/generators/disco_app/react/templates/root/.babelrc create mode 100644 lib/generators/disco_app/react/templates/root/.eslintignore delete mode 100644 lib/generators/disco_app/react/templates/root/.postcssrc.yml create mode 100644 lib/generators/disco_app/react/templates/root/babel.config.js create mode 100644 lib/generators/disco_app/react/templates/root/postcss.config.js diff --git a/disco_app.gemspec b/disco_app.gemspec index 25c8deea..37cf42c5 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'rails', '~> 5.2.0' s.add_runtime_dependency 'sass-rails', '~> 5.0' - s.add_runtime_dependency 'uglifier', '~> 3.2' + s.add_runtime_dependency 'uglifier', '>= 3.2' s.add_runtime_dependency 'coffee-rails', '~> 4.2' s.add_runtime_dependency 'jquery-rails', '~> 4.3' s.add_runtime_dependency 'turbolinks', '~> 5.0' diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb index fd232a9d..c52f974e 100644 --- a/lib/generators/disco_app/install/install_generator.rb +++ b/lib/generators/disco_app/install/install_generator.rb @@ -139,10 +139,6 @@ def configure_application end CONFIG application configuration, env: :production - - # Monitoring configuration - copy_file 'initializers/rollbar.rb', 'config/initializers/rollbar.rb' - copy_file 'config/newrelic.yml', 'config/newrelic.yml' end diff --git a/lib/generators/disco_app/install/templates/config/newrelic.yml b/lib/generators/disco_app/install/templates/config/newrelic.yml deleted file mode 100644 index f5eb2596..00000000 --- a/lib/generators/disco_app/install/templates/config/newrelic.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This file configures the New Relic Agent. -# -# For full documentation of agent configuration options, please refer to -# https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration - -common: &default_settings - license_key: <%= ENV['NEW_RELIC_LICENSE_KEY'] %> - app_name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> - - # To disable the agent regardless of other settings, uncomment the following: - # agent_enabled: false - - # Logging level for log/newrelic_agent.log - log_level: info - -development: - <<: *default_settings - app_name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> (Development) - developer_mode: true - -test: - <<: *default_settings - monitor_mode: false - -staging: - <<: *default_settings - -production: - <<: *default_settings diff --git a/lib/generators/disco_app/install/templates/initializers/rollbar.rb b/lib/generators/disco_app/install/templates/initializers/rollbar.rb new file mode 100644 index 00000000..e69de29b diff --git a/lib/generators/disco_app/install/templates/root/.gitignore b/lib/generators/disco_app/install/templates/root/.gitignore index 4079c4af..e7f7ea5a 100644 --- a/lib/generators/disco_app/install/templates/root/.gitignore +++ b/lib/generators/disco_app/install/templates/root/.gitignore @@ -15,6 +15,11 @@ pickle-email-*.html .byebug_history .DS_Store .disco_app +/public/packs +/public/packs-test +/node_modules +yarn-debug.log* +.yarn-integrity ## Environment normalisation: /.bundle @@ -22,6 +27,7 @@ pickle-email-*.html /vendor/ruby /.env.local .envrc +.pryrc # these should all be checked in to normalise the environment: # Gemfile.lock, .ruby-version, .ruby-gemset diff --git a/lib/generators/disco_app/react/react_generator.rb b/lib/generators/disco_app/react/react_generator.rb index 564c3308..5fcbe9df 100644 --- a/lib/generators/disco_app/react/react_generator.rb +++ b/lib/generators/disco_app/react/react_generator.rb @@ -62,7 +62,7 @@ def install_webpacker end def configure_webpack - %w[.babelrc .eslintrc .postcssrc.yml .prettierrc].each do |file| + %w[.eslintignore .eslintrc .prettierrc babel.config.js postcss.config.js].each do |file| copy_file "root/#{file}", file end @@ -70,6 +70,7 @@ def configure_webpack copy_file 'config/webpacker.yml' copy_file 'config/webpack/staging.js' + copy_file 'config/webpack/test.js' run "if [ -d 'app/javascript' ]; then mv -f app/javascript app/webpack; fi" end @@ -87,6 +88,14 @@ def configure_views copy_file 'app/views/layouts/embedded.html.erb' end + def configure_serializers + directory 'app/serializers' + end + + def configure_api_response + copy_file 'app/models/api_response.rb' + end + def configure_react directory 'app/webpack/javascripts' directory 'app/webpack/stylesheets' diff --git a/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb index 4d3842af..3e95fe67 100644 --- a/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb +++ b/lib/generators/disco_app/react/templates/app/controllers/embedded/api/base_controller.rb @@ -10,7 +10,7 @@ class BaseController < ApplicationController private def unprocessable_entity(exception) - render json: ApiResponse.serialize(exception.record.errors), status: 422 + render json: ApiResponse.serialize(exception.record.errors), status: :unprocessable_entity end end diff --git a/lib/generators/disco_app/react/templates/app/models/api_response.rb b/lib/generators/disco_app/react/templates/app/models/api_response.rb new file mode 100644 index 00000000..46591106 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/models/api_response.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +class ApiResponse + + EMPTY_SERIALIZER = 'Empty' + ERROR_SERIALIZER = 'Error' + SERIALIZER_SUFFIX = 'Serializer' + SENSITIVE_REQUEST_PARAMS = ['timestamp', 'signature'].freeze + + def initialize(result, custom_serializer = nil) + @result = result + @custom_serializer = custom_serializer + end + + def serialize(options = {}) + request = options.delete(:request) + + serializer.new( + result, options.merge(collection_options(request)) + ).serializable_hash + end + + def self.serialize(result, options = {}) + serializer = options.delete(:serializer) + + new(result, serializer).serialize(options) + end + + private + + attr_accessor :result, :custom_serializer + + def array? + result.is_a?(Array) + end + + def collection? + result.is_a?(ActiveRecord::Relation) + end + + def error? + [ + result.is_a?(ActiveModel::Errors), + result.is_a?(ActiveResource::Errors), + result.is_a?(StandardError), + result.is_a?(String) + ].any? + end + + def empty? + (array? || collection?) && result.empty? + end + + def resource? + !array && !collection? && !error? + end + + def resource_type + return custom_serializer.to_s.classify if custom_serializer + return ERROR_SERIALIZER if error? + return EMPTY_SERIALIZER if empty? + return result.first.class.name if array? + return result.class.to_s.deconstantize if collection? + + result.class.name + end + + def serializer + "#{resource_type}#{SERIALIZER_SUFFIX}".constantize + end + + def collection_options(request = nil) + options = {} + + return options if error? + + options[:is_collection] = array? || collection? + + if collection? && result.respond_to?(:total_count) + options[:meta] = { + page_size: result.limit_value, + total_count: result.total_count, + total_pages: result.total_pages + } + + options[:links] = { + prev: link(result.prev_page, request), + next: link(result.next_page, request) + } + end + + options + end + + def link(page, request) + return page if page.blank? || request.blank? + + uri = URI.parse(request) + params = Rack::Utils.parse_query(uri.query) + params.delete_if{ |key| SENSITIVE_REQUEST_PARAMS.include?(key) } + params['page[number]'] = page + parsed_params = params.map{ |key, value| "#{key}=#{value}" }.join('&') + + "#{uri.scheme}:://#{uri.host}#{uri.path}?#{parsed_params}" + end + +end diff --git a/lib/generators/disco_app/react/templates/app/serializers/disco_app/shop_serializer.rb b/lib/generators/disco_app/react/templates/app/serializers/disco_app/shop_serializer.rb new file mode 100644 index 00000000..0be8594a --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/serializers/disco_app/shop_serializer.rb @@ -0,0 +1,13 @@ +module DiscoApp + class ShopSerializer + + include FastJsonapi::ObjectSerializer + + attributes :id, :name, :shopify_url + + attribute :time_zone do |shop| + shop.time_zone.tzinfo.name + end + + end +end diff --git a/lib/generators/disco_app/react/templates/app/serializers/disco_app/user_serializer.rb b/lib/generators/disco_app/react/templates/app/serializers/disco_app/user_serializer.rb new file mode 100644 index 00000000..7190103a --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/serializers/disco_app/user_serializer.rb @@ -0,0 +1,13 @@ +module DiscoApp + class UserSerializer + + include FastJsonapi::ObjectSerializer + + attributes :email, :first_name, :id, :last_name + + attribute :initials do |user| + [user.first_name, user.last_name].compact.map(&:first).join + end + + end +end diff --git a/lib/generators/disco_app/react/templates/app/serializers/empty_serializer.rb b/lib/generators/disco_app/react/templates/app/serializers/empty_serializer.rb new file mode 100644 index 00000000..e4da4f5b --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/serializers/empty_serializer.rb @@ -0,0 +1,5 @@ +class EmptySerializer + + include FastJsonapi::ObjectSerializer + +end diff --git a/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb b/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb new file mode 100644 index 00000000..bb454940 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb @@ -0,0 +1,77 @@ +class ErrorSerializer + + attr_reader :errors, :source, :title + + def initialize(errors, source: nil, title: nil) + @errors = errors + @source = source + @title = title + end + + def serialized_json + serializable_hash.to_json + end + + def serializable_hash + { + errors: formatted_errors + } + end + + private + + def formatted_errors + return errors_from_exception if exception_error? + return errors_from_string if string_error? + + errors_from_active_model + end + + def errors_from_active_model + error_array = [] + + errors.keys.each do |field| + errors.full_messages_for(field).each do |error| + error_array << { + source: { pointer: "/data/attributes/#{field}" }, + title: title || 'Unprocessable entity', + detail: error + } + end + end + + error_array + end + + def errors_from_exception + error = { + title: title || errors.class.name.demodulize || 'Unknown error', + detail: errors.message + } + + error[:source] = { pointer: source } if source + + [error] + end + + def errors_from_string + error = { + title: title || 'Unknown error', + detail: errors + } + + error[:source] = { pointer: source } if source + + [error] + end + + def exception_error? + errors.is_a?(StandardError) + end + + + def string_error? + errors.is_a?(String) + end + +end diff --git a/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb b/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb index b2e3ea20..dacc1bb5 100644 --- a/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb +++ b/lib/generators/disco_app/react/templates/app/views/embedded/home/index.html.erb @@ -5,7 +5,7 @@ data-debug="<%= Rails.env.development? ? 'true' : 'false' %>" data-environment="<%= Rails.env %>" data-force-redirect="<%= Rails.env.development? ? 'false' : 'true' %>" - data-shop-origin="https://<%= @shop_session&.url %>" + data-shop-origin="<%= @shop_session&.url %>" data-version="<%= Rails.application.class::VERSION %>" >
<%= javascript_pack_tag 'embedded' %> diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx index ccf348da..85747a4f 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/App.jsx @@ -5,6 +5,8 @@ import { Route, Switch } from 'react-router-dom'; import HomePage from './HomePage'; import withApi from './withApi'; +const HomePageWithApi = withApi(HomePage); + class App extends React.Component { static propTypes = { api: PropTypes.func.isRequired, @@ -15,15 +17,15 @@ class App extends React.Component { shop: PropTypes.shape({ id: PropTypes.string, name: PropTypes.string, - plan: PropTypes.string, - shopifyDomain: PropTypes.string + shopifyDomain: PropTypes.string, + timeZone: PropTypes.string }), user: PropTypes.shape({ id: PropTypes.string, email: PropTypes.string, firstName: PropTypes.string, - lastName: PropTypes.string, - initials: PropTypes.string + initials: PropTypes.string, + lastName: PropTypes.string }) }; @@ -40,7 +42,7 @@ class App extends React.Component { } componentWillMount() { - const { api } = this.props; + const { api, parseApiResponse } = this.props; axios .all([ @@ -51,8 +53,8 @@ class App extends React.Component { axios.spread( async (shopResponse, usersResponse) => { this.setState({ - shop: await this.props.parseApiResponse(shopResponse), - user: await this.props.parseApiResponse(usersResponse) + shop: await parseApiResponse(shopResponse), + user: await parseApiResponse(usersResponse) }); } ) @@ -60,9 +62,9 @@ class App extends React.Component { } render() { - if (!this.state.user) return
; + const { user } = this.state; - const HomePageWithApi = withApi(HomePage); + if (!user) return
; return ( diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx index 7f5ef4c0..62372b4a 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/EmbeddedPage.jsx @@ -2,29 +2,43 @@ import _ from 'lodash'; import * as PropTypes from 'prop-types'; import React from 'react'; import ReactRouterPropTypes from 'react-router-prop-types'; +import { History } from '@shopify/app-bridge/actions'; import { Page } from '@shopify/polaris'; class EmbeddedPage extends React.Component { + static contextTypes = { + polaris: PropTypes.object + }; + static propTypes = { children: PropTypes.node.isRequired, history: ReactRouterPropTypes.history.isRequired, + location: ReactRouterPropTypes.location.isRequired, title: PropTypes.string.isRequired }; - static contextTypes = { - easdk: PropTypes.object - }; - componentDidMount() { window.addEventListener('message', this.handleMessage); - this.context.easdk.pushState(this.props.history.location.pathname); + this.pushHistory(); + } + + componentDidUpdate(prevProps) { + if (prevProps.location.pathname !== this.props.location.pathname) { + this.pushHistory(); + } } componentWillUnmount() { window.removeEventListener('message', this.handleMessage); } + pushHistory = () => { + const history = History.create(this.context.polaris.appBridge); + + history.dispatch(History.Action.PUSH, this.props.history.location.pathname); + }; + handleMessage = e => { if (e.isTrusted) { if (_.isString(e.data)) { @@ -38,6 +52,12 @@ class EmbeddedPage extends React.Component { } }; + pushHistory = () => { + const history = History.create(this.context.polaris.appBridge); + + history.dispatch(History.Action.PUSH, this.props.history.location.pathname); + }; + render() { return ( diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx index d198af6e..5734957b 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/Shared/ErrorBanner.jsx @@ -30,7 +30,7 @@ const ErrorBanner = ({ errors, prologue }) => { return (

- {prologue}, {errorMessage()}: + {prologue},{errorMessage()}: {errorKeys().map((key, index) => ( {separator(index)} @@ -38,7 +38,8 @@ const ErrorBanner = ({ errors, prologue }) => { {key} - ))}. + ))} + .

); diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx index b6d72620..c339095d 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/components/withApi.jsx @@ -110,8 +110,8 @@ function withApi(WrappedComponent) { diff --git a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js index 1f58aea6..7ba64922 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js +++ b/lib/generators/disco_app/react/templates/app/webpack/javascripts/embedded/utils.js @@ -1,4 +1,4 @@ -import * as dateFormat from 'dateformat'; +import { DateTime } from 'luxon'; import numeral from 'numeral'; export const numberToCurrency = amount => { @@ -12,4 +12,8 @@ export const numberToCurrency = amount => { return numeral(floatAmount).format(format); }; -export const strftime = (date, format) => dateFormat(date, format); +export const formatTime = time => { + if (!time) return 'N/A'; + + return DateTime.fromISO(time).toLocaleString(DateTime.DATETIME_SHORT); +}; diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss index 0c02ec95..320921dd 100644 --- a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss +++ b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded.scss @@ -1 +1,2 @@ @import '@shopify/polaris/styles.css'; +@import 'embedded/shared/index'; diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/banners.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/banners.scss new file mode 100644 index 00000000..200c8f0e --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/banners.scss @@ -0,0 +1,7 @@ +.Polaris-Page__Content > .Polaris-Banner--withinPage { + margin-bottom: 2rem; +} + +.Polaris-DisplayText--sizeLarge + .Polaris-Banner--withinPage { + margin-top: 16px; +} diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/busy.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/busy.scss new file mode 100644 index 00000000..5f1b768c --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/busy.scss @@ -0,0 +1,3 @@ +.Disco-ResourceListWrapper.busy .Polaris-ResourceList__ResourceListWrapper { + opacity: 0.5; +} diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/index.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/index.scss new file mode 100644 index 00000000..5e0cef8d --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/index.scss @@ -0,0 +1,3 @@ +@import 'banners'; +@import 'busy'; +@import 'pagination'; diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/pagination.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/pagination.scss new file mode 100644 index 00000000..9b506f50 --- /dev/null +++ b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/pagination.scss @@ -0,0 +1,5 @@ +.Disco-PaginationWrapper { + align-items: center; + display: flex; + justify-content: center; +} diff --git a/lib/generators/disco_app/react/templates/config/webpack/test.js b/lib/generators/disco_app/react/templates/config/webpack/test.js new file mode 100644 index 00000000..89d35d93 --- /dev/null +++ b/lib/generators/disco_app/react/templates/config/webpack/test.js @@ -0,0 +1,5 @@ +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +const environment = require('./environment'); + +module.exports = environment.toWebpackConfig(); diff --git a/lib/generators/disco_app/react/templates/config/webpacker.yml b/lib/generators/disco_app/react/templates/config/webpacker.yml index b70f13f2..24b5a57b 100644 --- a/lib/generators/disco_app/react/templates/config/webpacker.yml +++ b/lib/generators/disco_app/react/templates/config/webpacker.yml @@ -3,8 +3,11 @@ default: &default source_path: app/webpack source_entry_path: packs + public_root_path: public public_output_path: packs cache_path: tmp/cache/webpacker + check_yarn_integrity: false + webpack_compile_output: false # Additional paths webpack should lookup modules # ['app/assets', 'engine/foo/app/assets'] @@ -13,8 +16,26 @@ default: &default # Reload manifest.json on all requests so we reload latest compiled packs cache_manifest: false + # Extract and emit a css file + extract_css: true + + static_assets_extensions: + - .jpg + - .jpeg + - .png + - .gif + - .tiff + - .ico + - .svg + - .eot + - .otf + - .ttf + - .woff + - .woff2 + extensions: - .jsx + - .mjs - .js - .sass - .scss @@ -32,6 +53,9 @@ development: <<: *default compile: true + # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules + check_yarn_integrity: true + # Reference: https://webpack.js.org/configuration/dev-server/ dev_server: https: false @@ -49,7 +73,8 @@ development: headers: 'Access-Control-Allow-Origin': '*' watch_options: - ignored: /node_modules/ + ignored: '**/node_modules/**' + test: <<: *default @@ -64,5 +89,8 @@ production: # Production depends on precompilation of packs prior to booting for performance. compile: false + # Extract and emit a css file + extract_css: true + # Cache manifest.json for performance cache_manifest: true diff --git a/lib/generators/disco_app/react/templates/root/.babelrc b/lib/generators/disco_app/react/templates/root/.babelrc deleted file mode 100644 index 4f6a0ea4..00000000 --- a/lib/generators/disco_app/react/templates/root/.babelrc +++ /dev/null @@ -1,33 +0,0 @@ -{ - "presets": [ - [ - "env", - { - "modules": false, - "targets": { - "browsers": "> 1%", - "uglify": true - }, - "useBuiltIns": true - } - ], - "react" - ], - "plugins": [ - "syntax-dynamic-import", - "transform-object-rest-spread", - [ - "transform-class-properties", - { - "spec": true - } - ], - [ - "transform-runtime", - { - "polyfill": false, - "regenerator": true - } - ] - ] -} diff --git a/lib/generators/disco_app/react/templates/root/.eslintignore b/lib/generators/disco_app/react/templates/root/.eslintignore new file mode 100644 index 00000000..01fcec5a --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/.eslintignore @@ -0,0 +1,5 @@ +app/assets/javascripts/* +config/webpack/* +node_modules/* +public/packs/* +vendor/ruby/* diff --git a/lib/generators/disco_app/react/templates/root/.eslintrc b/lib/generators/disco_app/react/templates/root/.eslintrc index 4a6be233..00c8d226 100644 --- a/lib/generators/disco_app/react/templates/root/.eslintrc +++ b/lib/generators/disco_app/react/templates/root/.eslintrc @@ -1,24 +1,40 @@ { - "root": true, + "env": { + "browser": true, + "es6": true, + "jest": true + }, "extends": [ "airbnb", "plugin:prettier/recommended", - "plugin:import/warnings" + "plugin:import/errors", + "plugin:import/warnings", + "plugin:jest/recommended" ], - "env": { - "browser": true, - "es6": true - }, "parser": "babel-eslint", "parserOptions": { "ecmaFeatures": { - "jsx": true + "jsx": true, + "modules": true }, "ecmaVersion": 8, "sourceType": "module" }, + "plugins": [ + "jest" + ], + "root": true, "rules": { "import/first": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": [ + "app/webpack/**/*.spec.*", + "spec/javascripts/setupTests.js" + ] + } + ], "import/order": [ "error", { @@ -43,6 +59,11 @@ "no-return-assign": [ "error", "except-parens" - ] + ], + "react/destructuring-assignment": "off", + "react/forbid-prop-types": "off", + "react/jsx-one-expression-per-line": "off", + "react/no-access-state-in-setstate": "off", + "react/no-danger": "off" } } diff --git a/lib/generators/disco_app/react/templates/root/.postcssrc.yml b/lib/generators/disco_app/react/templates/root/.postcssrc.yml deleted file mode 100644 index 460b9da8..00000000 --- a/lib/generators/disco_app/react/templates/root/.postcssrc.yml +++ /dev/null @@ -1,6 +0,0 @@ -plugins: - postcss-import: {} - postcss-cssnext: - features: - customProperties: - warnings: false diff --git a/lib/generators/disco_app/react/templates/root/babel.config.js b/lib/generators/disco_app/react/templates/root/babel.config.js new file mode 100644 index 00000000..9f0a8d36 --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/babel.config.js @@ -0,0 +1,72 @@ +module.exports = api => { + const validEnv = ['development', 'test', 'production']; + const currentEnv = api.env(); + const isDevelopmentEnv = api.env('development'); + const isProductionEnv = api.env('production'); + const isTestEnv = api.env('test'); + + if (!validEnv.includes(currentEnv)) { + throw new Error( + `${'Please specify a valid `NODE_ENV` or ' + + '`BABEL_ENV` environment variables. Valid values are "development", ' + + '"test", and "production". Instead, received: '}${JSON.stringify( + currentEnv + )}.` + ); + } + + return { + presets: [ + isTestEnv && [ + '@babel/preset-env', + { + modules: 'commonjs', + targets: { + node: 'current' + } + } + ], + (isProductionEnv || isDevelopmentEnv) && [ + '@babel/preset-env', + { + forceAllTransforms: true, + useBuiltIns: 'entry', + modules: false, + exclude: ['transform-typeof-symbol'] + } + ], + '@babel/preset-react' + ].filter(Boolean), + plugins: [ + 'babel-plugin-macros', + '@babel/plugin-syntax-dynamic-import', + isTestEnv && 'babel-plugin-dynamic-import-node', + '@babel/plugin-transform-destructuring', + [ + '@babel/plugin-proposal-class-properties', + { + loose: true + } + ], + [ + '@babel/plugin-proposal-object-rest-spread', + { + useBuiltIns: true + } + ], + [ + '@babel/plugin-transform-runtime', + { + helpers: false, + regenerator: true + } + ], + [ + '@babel/plugin-transform-regenerator', + { + async: false + } + ] + ].filter(Boolean) + }; +}; diff --git a/lib/generators/disco_app/react/templates/root/package.json.tt b/lib/generators/disco_app/react/templates/root/package.json.tt index db13b25c..994973e0 100644 --- a/lib/generators/disco_app/react/templates/root/package.json.tt +++ b/lib/generators/disco_app/react/templates/root/package.json.tt @@ -1,40 +1,84 @@ { "name": "<%= Rails.application.class.parent_name.underscore.dasherize %>", "private": true, + "scripts": { + "lint": "eslint app/webpack -c .eslintrc --ext js,jsx", + "test": "jest", + "test-watch": "jest --watch" + }, + "jest": { + "moduleDirectories": [ + "app/webpack/javascripts", + "node_modules" + ], + "roots": [ + "app/webpack/javascripts" + ], + "setupFilesAfterEnv": [ + "spec/javascripts/setupTests.js" + ], + "testPathIgnorePatterns": [ + "/config/", + "/node_modules/", + "/vendor/" + ], + "transform": { + "^.+\\.jsx?$": "babel-jest" + } + }, "dependencies": { - "@rails/webpacker": "3.5", - "@shopify/polaris": "2.1.2", + "@babel/core": "^7.3.4", + "@babel/plugin-proposal-class-properties": "^7.3.4", + "@babel/plugin-proposal-object-rest-spread": "^7.3.4", + "@babel/plugin-syntax-dynamic-import": "^7.2.0", + "@babel/plugin-transform-destructuring": "^7.3.2", + "@babel/plugin-transform-regenerator": "^7.3.4", + "@babel/plugin-transform-runtime": "^7.3.4", + "@babel/preset-env": "^7.3.4", + "@babel/preset-react": "^7.0.0", + "@rails/webpacker": "^4.0.2", + "@shopify/app-bridge": "^1.2.0-0", + "@shopify/polaris": "^3.10.0", "axios": "^0.18.0", - "babel-preset-react": "^6.24.1", - "bugsnag-js": "^4.7.2", + "babel-plugin-dynamic-import-node": "^2.2.0", + "babel-plugin-macros": "^2.5.0", + "bugsnag-js": "^4.7.3", "bugsnag-react": "^1.1.1", - "dateformat": "^3.0.3", - "kitsu-core": "^5.1.1", - "lodash": "^4.17.10", - "moment": "^2.22.2", + "kitsu-core": "^7.0.0", + "lodash": "^4.17.11", + "luxon": "^1.11.4", "numeral": "^2.0.6", "pluralize": "^7.0.0", - "prop-types": "^15.6.1", - "qs": "^6.5.2", - "query-string": "^6.1.0", - "react": "^16.4.0", - "react-dom": "^16.4.0", - "react-router-dom": "^4.2.2", - "react-router-prop-types": "^1.0.3", - "regenerator": "^0.13.2", - "url-parse": "^1.4.1" + "postcss-flexbugs-fixes": "^4.1.0", + "postcss-import": "^12.0.1", + "postcss-preset-env": "^6.6.0", + "prop-types": "^15.7.2", + "qs": "^6.6.0", + "query-string": "^6.4.0", + "react": "^16.8.4", + "react-dom": "^16.6.3", + "react-router-dom": "^4.3.1", + "react-router-prop-types": "^1.0.4", + "regenerator": "^0.13.3", + "url-parse": "^1.4.4" }, "devDependencies": { - "babel-eslint": "8.2.3", - "babel-plugin-transform-runtime": "^6.23.0", - "eslint": "4.19.1", - "eslint-config-airbnb": "^16.1.0", - "eslint-config-prettier": "^2.9.0", - "eslint-plugin-import": "^2.12.0", - "eslint-plugin-jsx-a11y": "^6.0.3", - "eslint-plugin-prettier": "^2.6.0", - "eslint-plugin-react": "^7.9.1", - "prettier": "^1.13.4", - "webpack-dev-server": "2.11.2" + "babel-eslint": "^10.0.1", + "babel-jest": "^24.8.0", + "enzyme": "^3.10.0", + "enzyme-adapter-react-16": "^1.14.0", + "eslint": "^5.15.3", + "eslint-config-airbnb": "^17.1.0", + "eslint-config-prettier": "^4.1.0", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-jest": "^22.6.4", + "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-prettier": "^3.0.1", + "eslint-plugin-react": "^7.12.4", + "jest": "^24.8.0", + "jest-enzyme": "^7.0.2", + "prettier": "^1.16.4", + "react-test-renderer": "^16.8.6", + "webpack-dev-server": "^3.2.1" } } diff --git a/lib/generators/disco_app/react/templates/root/postcss.config.js b/lib/generators/disco_app/react/templates/root/postcss.config.js new file mode 100644 index 00000000..a4063b20 --- /dev/null +++ b/lib/generators/disco_app/react/templates/root/postcss.config.js @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + 'postcss-import': {}, + 'postcss-flexbugs-fixes': {}, + 'postcss-preset-env': [ + { + autoprefixer: { + flexbox: 'no-2009' + }, + stage: 3 + } + ] + } +}; From d3d003bf1acbb1942cc98070977d517452517f82 Mon Sep 17 00:00:00 2001 From: David Bird Date: Tue, 2 Jul 2019 20:22:01 +1000 Subject: [PATCH 31/41] CH5263: fix merge conflict --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b28d6420..d98b4246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. -<<<<<<< HEAD -======= ## 0.16.1 - 2019-01-01 ### Added - Support for Shopify Flow triggers and actions ->>>>>>> develop ## 0.16.0 - 2018-10-01 ### Changed - Update to Rails 5.2 From 260e20065c936bf76bd30d42a6424a4864a586c6 Mon Sep 17 00:00:00 2001 From: David Bird Date: Wed, 3 Jul 2019 08:09:40 +1000 Subject: [PATCH 32/41] CH5263: post-merge cleanup --- .../disco_app/disco_app_generator.rb | 252 ------------------ .../disco_app/install/install_generator.rb | 24 +- .../templates/config/cable.yml.tt | 0 .../templates/config/environments/staging.rb | 0 .../install/templates/initializers/rollbar.rb | 0 .../install/templates/root/.codeclimate.yml | 7 - .../disco_app/templates/config/appsignal.yml | 12 - .../disco_app/templates/config/newrelic.yml | 29 -- .../templates/initializers/session_store.rb | 2 - 9 files changed, 20 insertions(+), 306 deletions(-) delete mode 100644 lib/generators/disco_app/disco_app_generator.rb rename lib/generators/disco_app/{ => install}/templates/config/cable.yml.tt (100%) rename lib/generators/disco_app/{ => install}/templates/config/environments/staging.rb (100%) delete mode 100644 lib/generators/disco_app/install/templates/initializers/rollbar.rb delete mode 100644 lib/generators/disco_app/install/templates/root/.codeclimate.yml delete mode 100644 lib/generators/disco_app/templates/config/appsignal.yml delete mode 100644 lib/generators/disco_app/templates/config/newrelic.yml delete mode 100644 lib/generators/disco_app/templates/initializers/session_store.rb diff --git a/lib/generators/disco_app/disco_app_generator.rb b/lib/generators/disco_app/disco_app_generator.rb deleted file mode 100644 index db9e7bd2..00000000 --- a/lib/generators/disco_app/disco_app_generator.rb +++ /dev/null @@ -1,252 +0,0 @@ -class DiscoAppGenerator < Rails::Generators::Base - - source_root File.expand_path('templates', __dir__) - - # Copy a number of template files to the top-level directory of our application: - # - # - .env and .env.local for settings environment variables in development with dotenv-rails; - # - Slightly customised version of the default Rails .gitignore; - # - Default simple Procfile for Heroku; - # - .editorconfig to help enforce 2-space tabs, newlines and truncated whitespace for editors that support it. - # - README/PULL REQUEST template - # - def copy_root_files - %w[.editorconfig .env .env.local .gitignore .rubocop.yml .codeclimate.yml Procfile CHECKS README.md].each do |file| - copy_file "root/#{file}", file - end - directory 'root/.github' - end - - # Remove a number of root files. - def remove_root_files - %w[README.rdoc].each do |file| - remove_file file - end - end - - # Configure the application's Gemfile. - def configure_gemfile - # Remove sqlite. - gsub_file 'Gemfile', /^# Use sqlite3 as the database for Active Record\ngem 'sqlite3'/m, '' - - # Add gem requirements. - gem 'active_link_to' - gem 'activeresource' - gem 'acts_as_singleton' - gem 'classnames-rails' - gem 'newrelic_rpm' - gem 'nokogiri' - gem 'oj' - gem 'pg' - gem 'premailer-rails' - gem 'react-rails' - gem 'render_anywhere' - gem 'appsignal' - gem 'shopify_app' - gem 'sidekiq' - - # Indicate which gems should only be used in staging and production. - gem_group :staging, :production do - gem 'mailgun_rails' - gem 'rails_12factor' - end - - # Indicate which gems should only be used in development and test. - gem_group :development, :test do - gem 'dotenv-rails' - gem 'mechanize' - gem 'minitest-reporters' - gem 'webmock' - end - end - - # copy template for pg configuration - def update_database_config - template 'config/database.yml.tt' - end - - def update_cable_config - template 'config/cable.yml.tt' - end - - # Run bundle install to add our new gems before running tasks. - def bundle_install - Bundler.with_clean_env do - run 'bundle install' - end - end - - def support_staging_environment - copy_file 'config/environments/staging.rb', 'config/environments/staging.rb' - end - - # Make any required adjustments to the application configuration. - def configure_application - # The force_ssl flag is commented by default for production. - # Uncomment to ensure config.force_ssl = true in production. - uncomment_lines 'config/environments/production.rb', /force_ssl/ - - # Set time zone to UTC - application "config.time_zone = 'UTC'" - application '# Ensure UTC is the default timezone' - - # Set server side rendereing for components.js - application "config.react.server_renderer_options = {\nfiles: ['components.js'], # files to load for prerendering\n}" - application '# Enable server side react rendering' - - # Set defaults for various charge attributes. - application "config.x.shopify_charges_default_trial_days = 14\n" - application 'config.x.shopify_charges_default_price = 10.00' - application 'config.x.shopify_charges_default_type = :recurring' - application '# Set defaults for charges created by the application' - - # Set the "real charges" config variable to false explicitly by default. - # Only in production do we read from the environment variable and - # potentially have it become true. - application "config.x.shopify_charges_real = false\n" - application '# Explicitly prevent real charges being created by default' - application "config.x.shopify_charges_real = ENV['SHOPIFY_CHARGES_REAL'] == 'true'\n", env: :production - application '# Allow real charges in production with an ENV variable', env: :production - - # Configure session storage. - application "ActiveRecord::SessionStore::Session.table_name = 'disco_app_sessions'" - application 'ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session' - application '# Configure custom session storage' - - # Set Sidekiq as the queue adapter in production. - application "config.active_job.queue_adapter = :sidekiq\n", env: :production - application '# Use Sidekiq as the active job backend', env: :production - - # Set Sidekiq as the queue adapter in staging. - application "config.active_job.queue_adapter = :sidekiq\n", env: :staging - application '# Use Sidekiq as the active job backend', env: :staging - - # Ensure the application configuration uses the DEFAULT_HOST environment - # variable to set up support for reverse routing absolute URLS (needed when - # generating Webhook URLs for example). - application "routes.default_url_options[:host] = ENV['DEFAULT_HOST']\n" - application '# Set the default host for absolute URL routing purposes' - - # Configure React in development, staging, and production. - application 'config.react.variant = :development', env: :development - application '# Use development variant of React in development.', env: :development - - application 'config.react.variant = :production', env: :staging - application '# Use production variant of React in staging.', env: :staging - - application 'config.react.variant = :production', env: :production - application '# Use production variant of React in production.', env: :production - - # Copy over the default puma configuration. - copy_file 'config/puma.rb', 'config/puma.rb' - - # Mail configuration - configuration = <<-CONFIG.strip_heredoc - - # Configure ActionMailer to use MailGun - if ENV['MAILGUN_API_KEY'] - config.action_mailer.delivery_method = :mailgun - config.action_mailer.mailgun_settings = { - api_key: ENV['MAILGUN_API_KEY'], - domain: ENV['MAILGUN_API_DOMAIN'] - } - end - CONFIG - application configuration, env: :production - application configuration, env: :staging - - # Monitoring configuration - copy_file 'config/appsignal.yml', 'config/appsignal.yml' - copy_file 'config/newrelic.yml', 'config/newrelic.yml' - end - - # Add entries to .env and .env.local - def add_env_variables - configuration = <<-CONFIG.strip_heredoc - - MAILGUN_API_KEY= - MAILGUN_API_DOMAIN= - CONFIG - append_to_file '.env', configuration - append_to_file '.env.local', configuration - end - - # Set up routes. - def setup_routes - route "mount DiscoApp::Engine, at: '/'" - end - - # Run generators. - def run_generators - generate 'shopify_app:install' - generate 'shopify_app:home_controller' - generate 'react:install' - end - - # Copy template files to the appropriate location. In some cases, we'll be - # overwriting or removing existing files or those created by ShopifyApp. - def copy_and_remove_files - # Copy initializers - copy_file 'initializers/shopify_app.rb', 'config/initializers/shopify_app.rb' - copy_file 'initializers/disco_app.rb', 'config/initializers/disco_app.rb' - copy_file 'initializers/shopify_session_repository.rb', 'config/initializers/shopify_session_repository.rb' - copy_file 'initializers/session_store.rb', 'config/initializers/session_store.rb' - - # Copy default home controller and view - copy_file 'controllers/home_controller.rb', 'app/controllers/home_controller.rb' - copy_file 'views/home/index.html.erb', 'app/views/home/index.html.erb' - - # Copy assets - copy_file 'assets/javascripts/application.js', 'app/assets/javascripts/application.js' - copy_file 'assets/javascripts/components.js', 'app/assets/javascripts/components.js' - copy_file 'assets/stylesheets/application.scss', 'app/assets/stylesheets/application.scss' - - # Remove application.css - remove_file 'app/assets/stylesheets/application.css' - - # Remove the layout files created by ShopifyApp - remove_file 'app/views/layouts/application.html.erb' - remove_file 'app/views/layouts/embedded_app.html.erb' - end - - # Add the Disco App test helper to test/test_helper.rb - def add_test_helper - inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", after: "require 'rails/test_help'\n" - end - - # Copy engine migrations over. - def install_migrations - rake 'disco_app:install:migrations' - end - - # Create PG database - def create_database - rake 'db:create' - end - - # Run migrations. - def migrate - rake 'db:migrate' - end - - # Lock down the application to a specific Ruby version: - # - # - Via .ruby-version file for rbenv in development; - # - Via a Gemfile line in production. - # - # This should be the last operation, to allow all other operations to run in the initial Ruby version. - def set_ruby_version - copy_file 'root/.ruby-version', '.ruby-version' - prepend_to_file 'Gemfile', "ruby '2.5.0'\n" - end - - private - - # This method of finding the component.js manifest taken from the - # install generator in react-rails. - # See https://github.com/reactjs/react-rails/blob/3f0af13fa755d6e95969c17728d0354c234f3a37/lib/generators/react/install_generator.rb#L53-L55 - def components - Pathname.new(destination_root).join('app/assets/javascripts', 'components.js') - end - -end diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb index c52f974e..d121a87c 100644 --- a/lib/generators/disco_app/install/install_generator.rb +++ b/lib/generators/disco_app/install/install_generator.rb @@ -13,7 +13,7 @@ class InstallGenerator < Rails::Generators::Base # - README/PULL REQUEST template # def copy_root_files - %w(.editorconfig .env .env.local .gitignore .rubocop.yml .codeclimate.yml Procfile CHECKS README.md).each do |file| + %w(.editorconfig .env .env.local .gitignore .rubocop.yml Procfile CHECKS README.md).each do |file| copy_file "root/#{file}", file end directory 'root/.github' @@ -33,6 +33,7 @@ def configure_gemfile # Add gem requirements. gem 'active_link_to' + gem 'activeresource' gem 'acts_as_singleton' gem 'classnames-rails' gem 'newrelic_rpm' @@ -45,7 +46,6 @@ def configure_gemfile gem 'rollbar' gem 'shopify_app' gem 'sidekiq' - gem 'activeresource' # Indicate which gems should only be used in production. gem_group :production do @@ -53,12 +53,17 @@ def configure_gemfile gem 'rails_12factor' end + # Indicate which gems should only be used in development. + gem_group :production do + gem 'rb-readline' + end + # Indicate which gems should only be used in development and test. gem_group :development, :test do gem 'dotenv-rails' + gem 'mechanize' gem 'minitest-reporters' gem 'webmock' - gem 'mechanize' end end @@ -74,6 +79,10 @@ def bundle_install end end + def support_staging_environment + copy_file 'config/environments/staging.rb', 'config/environments/staging.rb' + end + # Make any required adjustments to the application configuration. def configure_application # The force_ssl flag is commented by default for production. @@ -111,15 +120,21 @@ def configure_application application "config.active_job.queue_adapter = :sidekiq\n", env: :production application "# Use Sidekiq as the active job backend", env: :production + # Set Sidekiq as the queue adapter in staging. + application "config.active_job.queue_adapter = :sidekiq\n", env: :staging + application '# Use Sidekiq as the active job backend', env: :staging + # Ensure the application configuration uses the DEFAULT_HOST environment # variable to set up support for reverse routing absolute URLS (needed when # generating Webhook URLs for example). application "routes.default_url_options[:host] = ENV['DEFAULT_HOST']\n" application "# Set the default host for absolute URL routing purposes" - # Configure React in development and production. + # Configure React in development, staging and production. application "config.react.variant = :development", env: :development application "# Use development variant of React in development.", env: :development + application 'config.react.variant = :production', env: :staging + application '# Use production variant of React in staging.', env: :staging application "config.react.variant = :production", env: :production application "# Use production variant of React in production.", env: :production @@ -139,6 +154,7 @@ def configure_application end CONFIG application configuration, env: :production + application configuration, env: :staging end diff --git a/lib/generators/disco_app/templates/config/cable.yml.tt b/lib/generators/disco_app/install/templates/config/cable.yml.tt similarity index 100% rename from lib/generators/disco_app/templates/config/cable.yml.tt rename to lib/generators/disco_app/install/templates/config/cable.yml.tt diff --git a/lib/generators/disco_app/templates/config/environments/staging.rb b/lib/generators/disco_app/install/templates/config/environments/staging.rb similarity index 100% rename from lib/generators/disco_app/templates/config/environments/staging.rb rename to lib/generators/disco_app/install/templates/config/environments/staging.rb diff --git a/lib/generators/disco_app/install/templates/initializers/rollbar.rb b/lib/generators/disco_app/install/templates/initializers/rollbar.rb deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/generators/disco_app/install/templates/root/.codeclimate.yml b/lib/generators/disco_app/install/templates/root/.codeclimate.yml deleted file mode 100644 index 183e3390..00000000 --- a/lib/generators/disco_app/install/templates/root/.codeclimate.yml +++ /dev/null @@ -1,7 +0,0 @@ -engines: - rubocop: - enabled: true - -ratings: - paths: - - "**.rb" diff --git a/lib/generators/disco_app/templates/config/appsignal.yml b/lib/generators/disco_app/templates/config/appsignal.yml deleted file mode 100644 index f0fbd1b7..00000000 --- a/lib/generators/disco_app/templates/config/appsignal.yml +++ /dev/null @@ -1,12 +0,0 @@ -default: &defaults - name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> - push_api_key: <%= ENV['APPSIGNAL_PUSH_API_KEY'] %> - development: - <<: *defaults - active: false - staging: - <<: *defaults - active: true - production: - <<: *defaults - active: true diff --git a/lib/generators/disco_app/templates/config/newrelic.yml b/lib/generators/disco_app/templates/config/newrelic.yml deleted file mode 100644 index f5eb2596..00000000 --- a/lib/generators/disco_app/templates/config/newrelic.yml +++ /dev/null @@ -1,29 +0,0 @@ -# This file configures the New Relic Agent. -# -# For full documentation of agent configuration options, please refer to -# https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration - -common: &default_settings - license_key: <%= ENV['NEW_RELIC_LICENSE_KEY'] %> - app_name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> - - # To disable the agent regardless of other settings, uncomment the following: - # agent_enabled: false - - # Logging level for log/newrelic_agent.log - log_level: info - -development: - <<: *default_settings - app_name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> (Development) - developer_mode: true - -test: - <<: *default_settings - monitor_mode: false - -staging: - <<: *default_settings - -production: - <<: *default_settings diff --git a/lib/generators/disco_app/templates/initializers/session_store.rb b/lib/generators/disco_app/templates/initializers/session_store.rb deleted file mode 100644 index 74aa173a..00000000 --- a/lib/generators/disco_app/templates/initializers/session_store.rb +++ /dev/null @@ -1,2 +0,0 @@ -# Use an ActiveRecord-based session store. -Rails.application.config.session_store :active_record_store, key: '_disco_app_session' From 2d4a2d52b8c861c703e3b571b0e77cedc27953dc Mon Sep 17 00:00:00 2001 From: David Bird Date: Wed, 3 Jul 2019 08:23:05 +1000 Subject: [PATCH 33/41] CH5263: AppSignal support --- .../disco_app/install/install_generator.rb | 6 ++++-- .../disco_app/install/templates/config/appsignal.yml | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 lib/generators/disco_app/install/templates/config/appsignal.yml diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb index d121a87c..d2f8238d 100644 --- a/lib/generators/disco_app/install/install_generator.rb +++ b/lib/generators/disco_app/install/install_generator.rb @@ -35,15 +35,14 @@ def configure_gemfile gem 'active_link_to' gem 'activeresource' gem 'acts_as_singleton' + gem 'appsignal' gem 'classnames-rails' - gem 'newrelic_rpm' gem 'nokogiri' gem 'oj' gem 'pg' gem 'premailer-rails' gem 'react-rails' gem 'render_anywhere' - gem 'rollbar' gem 'shopify_app' gem 'sidekiq' @@ -155,6 +154,9 @@ def configure_application CONFIG application configuration, env: :production application configuration, env: :staging + + # Monitoring configuration + copy_file 'config/appsignal.yml', 'config/appsignal.yml' end diff --git a/lib/generators/disco_app/install/templates/config/appsignal.yml b/lib/generators/disco_app/install/templates/config/appsignal.yml new file mode 100644 index 00000000..f0fbd1b7 --- /dev/null +++ b/lib/generators/disco_app/install/templates/config/appsignal.yml @@ -0,0 +1,12 @@ +default: &defaults + name: <%= ENV['SHOPIFY_APP_NAME'] || 'Unknown App' %> + push_api_key: <%= ENV['APPSIGNAL_PUSH_API_KEY'] %> + development: + <<: *defaults + active: false + staging: + <<: *defaults + active: true + production: + <<: *defaults + active: true From d07d6da0593edfa85fdc32772e5ae6a2fff46b78 Mon Sep 17 00:00:00 2001 From: Gavin Ballard Date: Tue, 9 Jul 2019 14:54:28 +1000 Subject: [PATCH 34/41] Use with_indifferent_access when reading Flow properties --- app/models/disco_app/flow/concerns/action.rb | 4 ++++ app/models/disco_app/flow/concerns/trigger.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/models/disco_app/flow/concerns/action.rb b/app/models/disco_app/flow/concerns/action.rb index 479fc550..820be067 100644 --- a/app/models/disco_app/flow/concerns/action.rb +++ b/app/models/disco_app/flow/concerns/action.rb @@ -15,6 +15,10 @@ module Action succeeded: 1, failed: 2 } + + def properties + read_attribute(:properties).with_indifferent_access + end end end diff --git a/app/models/disco_app/flow/concerns/trigger.rb b/app/models/disco_app/flow/concerns/trigger.rb index 0ad422f7..a1c48ca1 100644 --- a/app/models/disco_app/flow/concerns/trigger.rb +++ b/app/models/disco_app/flow/concerns/trigger.rb @@ -15,6 +15,10 @@ module Trigger succeeded: 1, failed: 2 } + + def properties + read_attribute(:properties).with_indifferent_access + end end end From 7c2595e555b4d1703de6b95cadc67414411204fe Mon Sep 17 00:00:00 2001 From: John Voon Date: Thu, 11 Jul 2019 10:48:04 +1000 Subject: [PATCH 35/41] CH5345: Switch disco_app from minitest to RSpec - Remove `test` directory generated by `rails new` command - Remove `minitest-reports` gem - Add `.env.local` file - Add `rspec-rails` gem - Add .rspec, `rails_helper.rb` and `spec_helper.rb` files - Add `timber` and `timber-rails` gems with config - Add `vcr` gem with config - Add `faker` gem - Add `factory_bot_rails` gem with config and generator - Add `coveralls` gem with config - Add `database_cleaner` gem with config - Add `shoulda-matchers` gem with config - Add `webmock` config - Add `active_job` config - Add `json_helper.rb` in `spec/support/helpers` - Add `a_synchronise_job.rb` in `spec/support/shared_examples` - Fix existing rubocop complaints --- .rubocop.yml | 2 +- .../flow/concerns/actions_controller.rb | 4 +- .../disco_app/install/install_generator.rb | 69 +++++++++++++------ .../install/templates/initializers/timber.rb | 6 ++ .../install/templates/root/.env.local | 28 ++++++++ .../disco_app/install/templates/root/.rspec | 1 + .../install/templates/spec/rails_helper.rb | 40 +++++++++++ .../install/templates/spec/spec_helper.rb | 24 +++++++ .../templates/spec/support/active_job.rb | 13 ++++ .../templates/spec/support/coveralls.rb | 3 + .../spec/support/database_cleaner.rb | 17 +++++ .../templates/spec/support/factory_bot.rb | 3 + .../spec/support/helpers/json_helper.rb | 13 ++++ .../shared_examples/a_synchronise_job.rb | 12 ++++ .../install/templates/spec/support/shoulda.rb | 6 ++ .../install/templates/spec/support/vcr.rb | 14 ++++ .../install/templates/spec/support/webmock.rb | 8 +++ .../disco_app/react/react_generator.rb | 2 +- .../app/serializers/error_serializer.rb | 1 - .../templates/config/initializers/omniauth.rb | 14 ++-- 20 files changed, 247 insertions(+), 33 deletions(-) create mode 100644 lib/generators/disco_app/install/templates/initializers/timber.rb create mode 100644 lib/generators/disco_app/install/templates/root/.env.local create mode 100644 lib/generators/disco_app/install/templates/root/.rspec create mode 100644 lib/generators/disco_app/install/templates/spec/rails_helper.rb create mode 100644 lib/generators/disco_app/install/templates/spec/spec_helper.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/active_job.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/coveralls.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/database_cleaner.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/factory_bot.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/helpers/json_helper.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/shared_examples/a_synchronise_job.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/shoulda.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/vcr.rb create mode 100644 lib/generators/disco_app/install/templates/spec/support/webmock.rb diff --git a/.rubocop.yml b/.rubocop.yml index 21fadbfd..ce2de8b7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -480,7 +480,7 @@ Metrics/BlockLength: Exclude: - config/environments/* - ./*.gemspec - - lib/generators/disco_app/templates/config/environments/* + - lib/generators/disco_app/install/templates/config/environments/* ExcludedMethods: - context - define diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index af48b7c0..da76e92f 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -34,8 +34,8 @@ def verify_flow_action # service method here. def flow_action_is_valid? DiscoApp::WebhookService.valid_hmac?( - request.body.read.to_s, - ShopifyApp.configuration.secret, + request.body.read.to_s, + ShopifyApp.configuration.secret, request.headers['HTTP_X_SHOPIFY_HMAC_SHA256'] ) end diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb index d2f8238d..7bbb5bca 100644 --- a/lib/generators/disco_app/install/install_generator.rb +++ b/lib/generators/disco_app/install/install_generator.rb @@ -13,7 +13,7 @@ class InstallGenerator < Rails::Generators::Base # - README/PULL REQUEST template # def copy_root_files - %w(.editorconfig .env .env.local .gitignore .rubocop.yml Procfile CHECKS README.md).each do |file| + %w[.editorconfig .env .env.local .gitignore .rubocop.yml Procfile CHECKS README.md].each do |file| copy_file "root/#{file}", file end directory 'root/.github' @@ -21,7 +21,7 @@ def copy_root_files # Remove a number of root files. def remove_root_files - %w(README.rdoc).each do |file| + %w[README.rdoc].each do |file| remove_file file end end @@ -45,6 +45,8 @@ def configure_gemfile gem 'render_anywhere' gem 'shopify_app' gem 'sidekiq' + gem 'timber', '~> 3.0' + gem 'timber-rails', '~> 1.0' # Indicate which gems should only be used in production. gem_group :production do @@ -59,11 +61,20 @@ def configure_gemfile # Indicate which gems should only be used in development and test. gem_group :development, :test do + gem 'coveralls' gem 'dotenv-rails' + gem 'factory_bot_rails' + gem 'faker' gem 'mechanize' - gem 'minitest-reporters' + gem 'rspec-rails' + gem 'vcr' gem 'webmock' end + + gem_group :test do + gem 'database_cleaner' + gem 'shoulda-matchers' + end end # copy template for pg configuration @@ -90,34 +101,34 @@ def configure_application # Set time zone to UTC application "config.time_zone = 'UTC'" - application "# Ensure UTC is the default timezone" + application '# Ensure UTC is the default timezone' # Set server side rendereing for components.js application "config.react.server_renderer_options = {\nfiles: ['components.js'], # files to load for prerendering\n}" - application "# Enable server side react rendering" + application '# Enable server side react rendering' # Set defaults for various charge attributes. application "config.x.shopify_charges_default_trial_days = 14\n" - application "config.x.shopify_charges_default_price = 10.00" - application "config.x.shopify_charges_default_type = :recurring" - application "# Set defaults for charges created by the application" + application 'config.x.shopify_charges_default_price = 10.00' + application 'config.x.shopify_charges_default_type = :recurring' + application '# Set defaults for charges created by the application' # Set the "real charges" config variable to false explicitly by default. # Only in production do we read from the environment variable and # potentially have it become true. application "config.x.shopify_charges_real = false\n" - application "# Explicitly prevent real charges being created by default" + application '# Explicitly prevent real charges being created by default' application "config.x.shopify_charges_real = ENV['SHOPIFY_CHARGES_REAL'] == 'true'\n", env: :production - application "# Allow real charges in production with an ENV variable", env: :production + application '# Allow real charges in production with an ENV variable', env: :production # Configure session storage. application "ActiveRecord::SessionStore::Session.table_name = 'disco_app_sessions'" - application "ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session" - application "# Configure custom session storage" + application 'ActionDispatch::Session::ActiveRecordStore.session_class = DiscoApp::Session' + application '# Configure custom session storage' # Set Sidekiq as the queue adapter in production. application "config.active_job.queue_adapter = :sidekiq\n", env: :production - application "# Use Sidekiq as the active job backend", env: :production + application '# Use Sidekiq as the active job backend', env: :production # Set Sidekiq as the queue adapter in staging. application "config.active_job.queue_adapter = :sidekiq\n", env: :staging @@ -127,15 +138,23 @@ def configure_application # variable to set up support for reverse routing absolute URLS (needed when # generating Webhook URLs for example). application "routes.default_url_options[:host] = ENV['DEFAULT_HOST']\n" - application "# Set the default host for absolute URL routing purposes" + application '# Set the default host for absolute URL routing purposes' # Configure React in development, staging and production. - application "config.react.variant = :development", env: :development - application "# Use development variant of React in development.", env: :development + application 'config.react.variant = :development', env: :development + application '# Use development variant of React in development.', env: :development application 'config.react.variant = :production', env: :staging application '# Use production variant of React in staging.', env: :staging - application "config.react.variant = :production", env: :production - application "# Use production variant of React in production.", env: :production + application 'config.react.variant = :production', env: :production + application '# Use production variant of React in production.', env: :production + + # Configure Factory Bot as the Rails testing fixture replacement + application <<~CONFIG + config.generators do |g| + g.test_framework :rspec, fixtures: true, view_specs: false, helper_specs: false, routing_specs: false + g.fixture_replacement :factory_bot, dir: 'spec/factories' + end + CONFIG # Copy over the default puma configuration. copy_file 'config/puma.rb', 'config/puma.rb' @@ -159,7 +178,6 @@ def configure_application copy_file 'config/appsignal.yml', 'config/appsignal.yml' end - # Add entries to .env and .env.local def add_env_variables configuration = <<-CONFIG.strip_heredoc @@ -183,6 +201,11 @@ def run_generators generate 'react:install' end + def configure_rspec + directory 'spec' + copy_file 'root/.rspec', '.rspec' + end + # Copy template files to the appropriate location. In some cases, we'll be # overwriting or removing existing files or those created by ShopifyApp. def copy_and_remove_files @@ -191,6 +214,7 @@ def copy_and_remove_files copy_file 'initializers/disco_app.rb', 'config/initializers/disco_app.rb' copy_file 'initializers/shopify_session_repository.rb', 'config/initializers/shopify_session_repository.rb' copy_file 'initializers/session_store.rb', 'config/initializers/session_store.rb' + copy_file 'initializers/timber.rb', 'config/initializers/timber.rb' # Copy default home controller and view copy_file 'controllers/home_controller.rb', 'app/controllers/home_controller.rb' @@ -207,11 +231,14 @@ def copy_and_remove_files # Remove the layout files created by ShopifyApp remove_file 'app/views/layouts/application.html.erb' remove_file 'app/views/layouts/embedded_app.html.erb' + + # Remove the test directory generated by rails new + remove_dir 'test' end # Add the Disco App test helper to test/test_helper.rb def add_test_helper - inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", { after: "require 'rails/test_help'\n" } + inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", after: "require 'rails/test_help'\n" end # Copy engine migrations over. @@ -251,4 +278,4 @@ def components end end -end \ No newline at end of file +end diff --git a/lib/generators/disco_app/install/templates/initializers/timber.rb b/lib/generators/disco_app/install/templates/initializers/timber.rb new file mode 100644 index 00000000..28aa2711 --- /dev/null +++ b/lib/generators/disco_app/install/templates/initializers/timber.rb @@ -0,0 +1,6 @@ +if Rails.env.production? + http_device = Timber::LogDevices::HTTP.new('YOUR_API_KEY', 'YOUR_SOURCE_ID') + Rails.logger = Timber::Logger.new(http_device) +else + Rails.logger = Timber::Logger.new(STDOUT) +end diff --git a/lib/generators/disco_app/install/templates/root/.env.local b/lib/generators/disco_app/install/templates/root/.env.local new file mode 100644 index 00000000..62058f99 --- /dev/null +++ b/lib/generators/disco_app/install/templates/root/.env.local @@ -0,0 +1,28 @@ +DEFAULT_HOST= + +SHOPIFY_APP_NAME= +SHOPIFY_APP_API_KEY= +SHOPIFY_APP_SECRET= +SHOPIFY_APP_SCOPE= +SHOPIFY_APP_PROXY_PREFIX= + +ADMIN_APP_USERNAME= +ADMIN_APP_PASSWORD= + +SHOPIFY_REAL_CHARGES= + +SKIP_PROXY_VERIFICATION= +SKIP_WEBHOOK_VERIFICATION= +SKIP_CARRIER_REQUEST_VERIFICATION= +SKIP_OAUTH= + +SECRET_KEY_BASE= + +REDIS_PROVIDER= + +DISCO_API_URL= + +WHITELISTED_DOMAINS= + +# You can find this listed in 1Password +APPSIGNAL_PUSH_API_KEY= \ No newline at end of file diff --git a/lib/generators/disco_app/install/templates/root/.rspec b/lib/generators/disco_app/install/templates/root/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/lib/generators/disco_app/install/templates/root/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/lib/generators/disco_app/install/templates/spec/rails_helper.rb b/lib/generators/disco_app/install/templates/spec/rails_helper.rb new file mode 100644 index 00000000..1e39dda2 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/rails_helper.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../config/environment', __dir__) + +abort('The Rails environment is running in production mode!') if Rails.env.production? + +require 'rspec/rails' + +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip + exit 1 +end + +RSpec.configure do |config| + config.filter_rails_from_backtrace! + config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.infer_spec_type_from_file_location! + config.use_transactional_fixtures = false + config.include Helpers::JsonHelper +end diff --git a/lib/generators/disco_app/install/templates/spec/spec_helper.rb b/lib/generators/disco_app/install/templates/spec/spec_helper.rb new file mode 100644 index 00000000..7d531957 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/spec_helper.rb @@ -0,0 +1,24 @@ +require 'support/coveralls' +require 'support/webmock' + +RSpec.configure do |config| + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.default_formatter = 'doc' if config.files_to_run.one? + config.disable_monkey_patching! + config.example_status_persistence_file_path = 'spec/examples.txt' + config.filter_run_when_matching :focus + config.order = :random + config.shared_context_metadata_behavior = :apply_to_host_groups + + config.filter_run focus: true + config.run_all_when_everything_filtered = true + + Kernel.srand config.seed +end diff --git a/lib/generators/disco_app/install/templates/spec/support/active_job.rb b/lib/generators/disco_app/install/templates/spec/support/active_job.rb new file mode 100644 index 00000000..edff539c --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/active_job.rb @@ -0,0 +1,13 @@ +require 'sidekiq/testing' + +RSpec.configure do |config| + config.include ActiveJob::TestHelper + + config.before(:all) do + Sidekiq::Testing.fake! + end + + config.before(:each) do + Sidekiq::Worker.clear_all + end +end diff --git a/lib/generators/disco_app/install/templates/spec/support/coveralls.rb b/lib/generators/disco_app/install/templates/spec/support/coveralls.rb new file mode 100644 index 00000000..7a960894 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/coveralls.rb @@ -0,0 +1,3 @@ +require 'coveralls' + +Coveralls.wear!('rails') diff --git a/lib/generators/disco_app/install/templates/spec/support/database_cleaner.rb b/lib/generators/disco_app/install/templates/spec/support/database_cleaner.rb new file mode 100644 index 00000000..32ba6918 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/database_cleaner.rb @@ -0,0 +1,17 @@ +RSpec.configure do |config| + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.after(:each) do + DatabaseCleaner.clean + end +end diff --git a/lib/generators/disco_app/install/templates/spec/support/factory_bot.rb b/lib/generators/disco_app/install/templates/spec/support/factory_bot.rb new file mode 100644 index 00000000..c7890e49 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/factory_bot.rb @@ -0,0 +1,3 @@ +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods +end diff --git a/lib/generators/disco_app/install/templates/spec/support/helpers/json_helper.rb b/lib/generators/disco_app/install/templates/spec/support/helpers/json_helper.rb new file mode 100644 index 00000000..1dc22891 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/helpers/json_helper.rb @@ -0,0 +1,13 @@ +module Helpers + module JsonHelper + + # Return a JSON fixture as an indifferent hash. + def json_fixture(path, dir: 'json', parse: true) + filename = Rails.root.join('spec', 'fixtures', 'files', dir, "#{path}.json") + return File.read(filename) unless parse + + HashWithIndifferentAccess.new(ActiveSupport::JSON.decode(File.read(filename))) + end + + end +end diff --git a/lib/generators/disco_app/install/templates/spec/support/shared_examples/a_synchronise_job.rb b/lib/generators/disco_app/install/templates/spec/support/shared_examples/a_synchronise_job.rb new file mode 100644 index 00000000..c9c9291d --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/shared_examples/a_synchronise_job.rb @@ -0,0 +1,12 @@ +RSpec.shared_examples :a_synchronise_job do |model| + subject(:job) { described_class.perform_later(shop, data) } + + let(:shop) { create(:shop) } + let(:data) { 'data' } + + it "synchronises #{model}" do + expect(model).to receive(:synchronise).with(shop, data, any_args) + + perform_enqueued_jobs { job } + end +end diff --git a/lib/generators/disco_app/install/templates/spec/support/shoulda.rb b/lib/generators/disco_app/install/templates/spec/support/shoulda.rb new file mode 100644 index 00000000..7d045f35 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/shoulda.rb @@ -0,0 +1,6 @@ +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/lib/generators/disco_app/install/templates/spec/support/vcr.rb b/lib/generators/disco_app/install/templates/spec/support/vcr.rb new file mode 100644 index 00000000..88935c9c --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/vcr.rb @@ -0,0 +1,14 @@ +VCR.configure do |config| + config.before_record do |interaction| + interaction.request.headers['X-Shopify-Access-Token'] = [''] + interaction.request.headers['Authorization'] = [''] + end + config.cassette_library_dir = 'spec/vcr' + config.configure_rspec_metadata! + config.default_cassette_options = { + decode_compressed_response: true, + match_requests_on: %i[method uri body], + record: :once + } + config.hook_into :webmock +end diff --git a/lib/generators/disco_app/install/templates/spec/support/webmock.rb b/lib/generators/disco_app/install/templates/spec/support/webmock.rb new file mode 100644 index 00000000..e6cac2a5 --- /dev/null +++ b/lib/generators/disco_app/install/templates/spec/support/webmock.rb @@ -0,0 +1,8 @@ +require 'webmock/rspec' + +RSpec.configure do |config| + config.before(:each) do + WebMock.reset! + WebMock.disable_net_connect! + end +end diff --git a/lib/generators/disco_app/react/react_generator.rb b/lib/generators/disco_app/react/react_generator.rb index 5fcbe9df..3aa953e7 100644 --- a/lib/generators/disco_app/react/react_generator.rb +++ b/lib/generators/disco_app/react/react_generator.rb @@ -105,4 +105,4 @@ def configure_react end end -end \ No newline at end of file +end diff --git a/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb b/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb index bb454940..1078a8d8 100644 --- a/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb +++ b/lib/generators/disco_app/react/templates/app/serializers/error_serializer.rb @@ -69,7 +69,6 @@ def exception_error? errors.is_a?(StandardError) end - def string_error? errors.is_a?(String) end diff --git a/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb b/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb index f08f6036..230b57d8 100644 --- a/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb +++ b/lib/generators/disco_app/react/templates/config/initializers/omniauth.rb @@ -16,12 +16,12 @@ def name Rails.application.config.middleware.use OmniAuth::Builder do provider :shopify, - ShopifyApp.configuration.api_key, - ShopifyApp.configuration.secret, - scope: ShopifyApp.configuration.scope + ShopifyApp.configuration.api_key, + ShopifyApp.configuration.secret, + scope: ShopifyApp.configuration.scope provider :shopify_user, - ShopifyApp.configuration.api_key, - ShopifyApp.configuration.secret, - scope: ShopifyApp.configuration.scope, - setup: SETUP_PROC + ShopifyApp.configuration.api_key, + ShopifyApp.configuration.secret, + scope: ShopifyApp.configuration.scope, + setup: SETUP_PROC end From 8b3406de3d9081de452e06b3abcf72cd610c4660 Mon Sep 17 00:00:00 2001 From: John Voon Date: Thu, 11 Jul 2019 16:09:57 +1000 Subject: [PATCH 36/41] CH5345: Switch disco_app from minitest to RSpec - Remove `add_test_helper` from `install_generator.rb` --- lib/generators/disco_app/install/install_generator.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb index 7bbb5bca..b624e4bd 100644 --- a/lib/generators/disco_app/install/install_generator.rb +++ b/lib/generators/disco_app/install/install_generator.rb @@ -236,11 +236,6 @@ def copy_and_remove_files remove_dir 'test' end - # Add the Disco App test helper to test/test_helper.rb - def add_test_helper - inject_into_file 'test/test_helper.rb', "require 'disco_app/test_help'\n", after: "require 'rails/test_help'\n" - end - # Copy engine migrations over. def install_migrations rake 'disco_app:install:migrations' From 7dba28763d9cdf3f3ce71dc4b388bd175d1d062c Mon Sep 17 00:00:00 2001 From: John Voon Date: Mon, 15 Jul 2019 12:24:43 +1000 Subject: [PATCH 37/41] CH5429: Investigate Rav build error - Restructure stylesheets/shared to stylesheets/embedded/shared --- .../app/webpack/stylesheets/{ => embedded}/shared/banners.scss | 0 .../app/webpack/stylesheets/{ => embedded}/shared/busy.scss | 0 .../app/webpack/stylesheets/{ => embedded}/shared/index.scss | 0 .../app/webpack/stylesheets/{ => embedded}/shared/pagination.scss | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename lib/generators/disco_app/react/templates/app/webpack/stylesheets/{ => embedded}/shared/banners.scss (100%) rename lib/generators/disco_app/react/templates/app/webpack/stylesheets/{ => embedded}/shared/busy.scss (100%) rename lib/generators/disco_app/react/templates/app/webpack/stylesheets/{ => embedded}/shared/index.scss (100%) rename lib/generators/disco_app/react/templates/app/webpack/stylesheets/{ => embedded}/shared/pagination.scss (100%) diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/banners.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/banners.scss similarity index 100% rename from lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/banners.scss rename to lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/banners.scss diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/busy.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/busy.scss similarity index 100% rename from lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/busy.scss rename to lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/busy.scss diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/index.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/index.scss similarity index 100% rename from lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/index.scss rename to lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/index.scss diff --git a/lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/pagination.scss b/lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/pagination.scss similarity index 100% rename from lib/generators/disco_app/react/templates/app/webpack/stylesheets/shared/pagination.scss rename to lib/generators/disco_app/react/templates/app/webpack/stylesheets/embedded/shared/pagination.scss From f6ccf59a26924240cbdf9ed76867f2ab5c6dbb56 Mon Sep 17 00:00:00 2001 From: John Voon Date: Mon, 15 Jul 2019 16:23:37 +1000 Subject: [PATCH 38/41] CH5426: Update disco_app to use latest Rubocop - Add rubocop gems to `install_generator.rb` and `disco_app.gemspec` - Add rake task for running rubocop and auto-correct with extensions - `EnforcedStyle: rails` => `EnforcedStyle: indented_internal_methods` - `Performance/Sample` => `Style/Sample` - `Style/FlipFlop` => `Lint/FlipFlop` - Exclude `rails_helper.rb` from `Rails/Exit` rule - Specify environments for `Rails/UnknownEnv` rule, e.g. staging - Disable `Rails/Output` rule for `.rubocop.yml` file in this repo - Leave `Rails/Output` rule enabled for generated `.rubocop.yml` - Run rubocop command to perform safe auto-corrections - Change from compact to nested style for generated files only - Add `dependent: :restrict_with_exception` to `has_many` where blank - Disable Rails/SkipsModelValidations rule for `update_all` method calls - Add type to `remove_column` method call in migration - Disable `Rails/HttpPositionalArguments` for this repo's `rubocop.yml` - Disable Style/ClassAndModuleChildren for this repo's `rubocop.yml` - Enable Style/ClassAndModuleChildren for generated `rubocop.yml` - Add public_attributes and private_attributes to ExpectedOrder --- .rubocop.yml | 52 +++++++++++++------ app/clients/disco_app/api_client.rb | 2 +- .../admin/concerns/app_settings_controller.rb | 2 +- .../admin/concerns/plans_controller.rb | 2 +- .../admin/concerns/sources_controller.rb | 2 +- .../concerns/subscriptions_controller.rb | 4 +- .../disco_app/charges_controller.rb | 2 +- .../concerns/app_proxy_controller.rb | 2 +- .../concerns/authenticated_controller.rb | 6 +-- .../concerns/carrier_request_controller.rb | 10 ++-- .../disco_app/concerns/webhooks_controller.rb | 2 +- .../flow/concerns/actions_controller.rb | 2 +- .../disco_app/subscriptions_controller.rb | 2 +- app/controllers/sessions_controller.rb | 2 +- app/helpers/disco_app/application_helper.rb | 2 +- app/models/disco_app/concerns/plan.rb | 2 +- app/models/disco_app/concerns/shop.rb | 4 +- app/models/disco_app/concerns/source.rb | 2 +- app/services/disco_app/charges_service.rb | 2 +- app/services/disco_app/partner_app_service.rb | 2 +- .../disco_app/subscription_service.rb | 10 ++-- app/services/disco_app/webhook_service.rb | 4 +- ...62629_add_sources_to_shop_subscriptions.rb | 2 +- disco_app.gemspec | 4 +- .../disco_app/install/install_generator.rb | 15 +++++- .../install/templates/root/.rubocop.yml | 48 +++++++++++------ .../disco_app/flow/process_action_test.rb | 2 +- .../disco_app/flow/process_trigger_test.rb | 2 +- .../disco_app/subscription_service_test.rb | 4 +- 29 files changed, 125 insertions(+), 72 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ce2de8b7..b4d777b5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +1,13 @@ +require: + - rubocop-rails + - rubocop-performance + AllCops: Exclude: - bin/* - db/schema.rb - vendor/ruby/**/* + - node_modules/**/* TargetRubyVersion: 2.5 # Layout @@ -46,6 +51,7 @@ Layout/ClassStructure: ExpectedOrder: - module_inclusion - constants + - public_attributes - associations - validations - callbacks @@ -53,6 +59,7 @@ Layout/ClassStructure: - initializer - public_methods - protected_methods + - private_attributes - private_methods Enabled: true @@ -83,7 +90,7 @@ Layout/IndentFirstHashElement: EnforcedStyle: consistent Layout/IndentationConsistency: - EnforcedStyle: rails + EnforcedStyle: indented_internal_methods Enabled: true Layout/MultilineBlockLayout: @@ -238,11 +245,6 @@ Style/FrozenStringLiteralComment: to help transition from Ruby 2.3.0 to Ruby 3.0. Enabled: false -Lint/FlipFlop: - Description: 'Checks for flip flops' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' - Enabled: true - Style/FormatString: Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' @@ -383,6 +385,13 @@ Style/RegexpLiteral: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' Enabled: true +Style/Sample: + Description: >- + Use `sample` instead of `shuffle.first`, + `shuffle.last`, and `shuffle[Fixnum]`. + Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' + Enabled: true + Style/SelfAssignment: Description: >- Checks for places where self-assignment shorthand should have @@ -568,6 +577,11 @@ Lint/ElseLayout: Description: 'Check for odd code arrangement in an else block.' Enabled: true +Lint/FlipFlop: + Description: 'Checks for flip flops' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' + Enabled: true + Lint/FormatParameterMismatch: Description: 'The number of parameters to format/sprint must match the fields.' Enabled: true @@ -657,13 +671,6 @@ Performance/ReverseEach: Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' Enabled: true -Style/Sample: - Description: >- - Use `sample` instead of `shuffle.first`, - `shuffle.last`, and `shuffle[Fixnum]`. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' - Enabled: true - Performance/Size: Description: >- Use `size` instead of `count` for counting @@ -695,6 +702,10 @@ Rails/Delegate: Description: 'Prefer delegate method for delegations.' Enabled: false +Rails/Exit: + Exclude: + - lib/generators/disco_app/install/templates/spec/rails_helper.rb + Rails/FindBy: Description: 'Prefer find_by over where.first.' Enabled: true @@ -709,7 +720,11 @@ Rails/HasAndBelongsToMany: Rails/Output: Description: 'Checks for calls to puts, print, etc.' - Enabled: true + Enabled: false + +Rails/HttpPositionalArguments: + Description: 'Use keyword arguments instead of positional arguments for http calls' + Enabled: false Rails/ReadWriteAttribute: Description: >- @@ -727,6 +742,13 @@ Rails/TimeZone: Reference: 'http://danilenko.org/2012/7/6/rails_timezones' Enabled: true +Rails/UnknownEnv: + Environments: + - development + - production + - staging + - test + Rails/Validation: Description: 'Use validates :attribute, hash of validations.' Enabled: false @@ -734,4 +756,4 @@ Rails/Validation: # Bundler Bundler/OrderedGems: - Enabled: true + Enabled: true \ No newline at end of file diff --git a/app/clients/disco_app/api_client.rb b/app/clients/disco_app/api_client.rb index 9a9a645f..3c236588 100644 --- a/app/clients/disco_app/api_client.rb +++ b/app/clients/disco_app/api_client.rb @@ -10,7 +10,7 @@ def initialize(shop, url) end def create_app_subscription - return unless @url.present? + return if @url.blank? url = @url + SUBSCRIPTION_ENDPOINT begin diff --git a/app/controllers/disco_app/admin/concerns/app_settings_controller.rb b/app/controllers/disco_app/admin/concerns/app_settings_controller.rb index 789251a3..f4d01703 100644 --- a/app/controllers/disco_app/admin/concerns/app_settings_controller.rb +++ b/app/controllers/disco_app/admin/concerns/app_settings_controller.rb @@ -8,7 +8,7 @@ def edit def update @app_settings = DiscoApp::AppSettings.instance - if @app_settings.update_attributes(app_settings_params) + if @app_settings.update(app_settings_params) flash[:success] = 'Settings updated.' redirect_to edit_admin_app_settings_path else diff --git a/app/controllers/disco_app/admin/concerns/plans_controller.rb b/app/controllers/disco_app/admin/concerns/plans_controller.rb index ec52d1e1..6ed16149 100644 --- a/app/controllers/disco_app/admin/concerns/plans_controller.rb +++ b/app/controllers/disco_app/admin/concerns/plans_controller.rb @@ -27,7 +27,7 @@ def edit end def update - if @plan.update_attributes(plan_params) + if @plan.update(plan_params) redirect_to edit_admin_plan_path(@plan) else render 'edit' diff --git a/app/controllers/disco_app/admin/concerns/sources_controller.rb b/app/controllers/disco_app/admin/concerns/sources_controller.rb index fcf218cc..8d689113 100644 --- a/app/controllers/disco_app/admin/concerns/sources_controller.rb +++ b/app/controllers/disco_app/admin/concerns/sources_controller.rb @@ -27,7 +27,7 @@ def edit end def update - if @source.update_attributes(source_params) + if @source.update(source_params) redirect_to edit_admin_plan_path(@source) else render 'edit' diff --git a/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb b/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb index 097876bc..c990cb35 100644 --- a/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb +++ b/app/controllers/disco_app/admin/concerns/subscriptions_controller.rb @@ -10,7 +10,7 @@ def edit end def update - if @subscription.update_attributes(subscription_params) + if @subscription.update(subscription_params) redirect_to edit_admin_shop_subscription_path(@subscription.shop, @subscription) else render 'edit' @@ -20,7 +20,7 @@ def update private def find_subscription - @subscription = DiscoApp::Subscription.find_by_id(params[:id]) + @subscription = DiscoApp::Subscription.find_by(id: params[:id]) end def subscription_params diff --git a/app/controllers/disco_app/charges_controller.rb b/app/controllers/disco_app/charges_controller.rb index 565007e6..fad18b3e 100644 --- a/app/controllers/disco_app/charges_controller.rb +++ b/app/controllers/disco_app/charges_controller.rb @@ -39,7 +39,7 @@ def activate private def find_subscription - @subscription = @shop.subscriptions.find_by_id!(params[:subscription_id]) + @subscription = @shop.subscriptions.find_by!(id: params[:subscription_id]) redirect_to main_app.root_url unless @subscription.requires_active_charge? && !@subscription.active_charge? end diff --git a/app/controllers/disco_app/concerns/app_proxy_controller.rb b/app/controllers/disco_app/concerns/app_proxy_controller.rb index 051038de..237078dd 100644 --- a/app/controllers/disco_app/concerns/app_proxy_controller.rb +++ b/app/controllers/disco_app/concerns/app_proxy_controller.rb @@ -25,7 +25,7 @@ def proxy_signature_is_valid? end def shopify_shop - @shop = DiscoApp::Shop.find_by_shopify_domain!(params[:shop]) + @shop = DiscoApp::Shop.find_by!(shopify_domain: params[:shop]) end def add_liquid_header diff --git a/app/controllers/disco_app/concerns/authenticated_controller.rb b/app/controllers/disco_app/concerns/authenticated_controller.rb index dda06475..218436b7 100644 --- a/app/controllers/disco_app/concerns/authenticated_controller.rb +++ b/app/controllers/disco_app/concerns/authenticated_controller.rb @@ -20,8 +20,8 @@ module DiscoApp::Concerns::AuthenticatedController def auto_login return unless shop_session.nil? && request_hmac_valid? - shop = DiscoApp::Shop.find_by_shopify_domain(sanitized_shop_name) - return unless shop.present? + shop = DiscoApp::Shop.find_by(shopify_domain: sanitized_shop_name) + return if shop.blank? session[:shopify] = shop.id session[:shopify_domain] = sanitized_shop_name @@ -70,7 +70,7 @@ def request_hmac_valid? def check_shop_whitelist return unless shop_session - return unless ENV['WHITELISTED_DOMAINS'].present? + return if ENV['WHITELISTED_DOMAINS'].blank? return if ENV['WHITELISTED_DOMAINS'].include?(shop_session.url) redirect_to_login diff --git a/app/controllers/disco_app/concerns/carrier_request_controller.rb b/app/controllers/disco_app/concerns/carrier_request_controller.rb index 395df17d..af0464bc 100644 --- a/app/controllers/disco_app/concerns/carrier_request_controller.rb +++ b/app/controllers/disco_app/concerns/carrier_request_controller.rb @@ -25,7 +25,7 @@ def carrier_request_signature_is_valid? end def find_shop - @shop = DiscoApp::Shop.find_by_shopify_domain(request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']) + @shop = DiscoApp::Shop.find_by(shopify_domain: request.headers['HTTP_X_SHOPIFY_SHOP_DOMAIN']) head :unauthorized unless @shop end @@ -35,10 +35,10 @@ def validate_rate_params end def request_is_valid? - return false unless params[:rate].present? - return false unless params[:rate][:origin].present? - return false unless params[:rate][:destination].present? - return false unless params[:rate][:items].present? + return false if params[:rate].blank? + return false if params[:rate][:origin].blank? + return false if params[:rate][:destination].blank? + return false if params[:rate][:items].blank? end end diff --git a/app/controllers/disco_app/concerns/webhooks_controller.rb b/app/controllers/disco_app/concerns/webhooks_controller.rb index f7db6381..d4cfc244 100644 --- a/app/controllers/disco_app/concerns/webhooks_controller.rb +++ b/app/controllers/disco_app/concerns/webhooks_controller.rb @@ -19,7 +19,7 @@ def process_webhook job_class = DiscoApp::WebhookService.find_job_class(topic) # Return bad request if we couldn't match a job class. - return head :bad_request unless job_class.present? + return head :bad_request if job_class.blank? # Decode the body data and enqueue the appropriate job. data = JSON.parse(request.body.read).with_indifferent_access diff --git a/app/controllers/disco_app/flow/concerns/actions_controller.rb b/app/controllers/disco_app/flow/concerns/actions_controller.rb index da76e92f..d06342e3 100644 --- a/app/controllers/disco_app/flow/concerns/actions_controller.rb +++ b/app/controllers/disco_app/flow/concerns/actions_controller.rb @@ -41,7 +41,7 @@ def flow_action_is_valid? end def find_shop - @shop = DiscoApp::Shop.find_by_shopify_domain!(params[:shopify_domain]) + @shop = DiscoApp::Shop.find_by!(shopify_domain: params[:shopify_domain]) end end diff --git a/app/controllers/disco_app/subscriptions_controller.rb b/app/controllers/disco_app/subscriptions_controller.rb index 6d15b252..b02281d8 100644 --- a/app/controllers/disco_app/subscriptions_controller.rb +++ b/app/controllers/disco_app/subscriptions_controller.rb @@ -11,7 +11,7 @@ def new def create # Get the selected plan. If it's not available or couldn't be found, # redirect back to the plan selection page. - plan = DiscoApp::Plan.available.find_by_id(subscription_params[:plan]) + plan = DiscoApp::Plan.available.find_by(id: subscription_params[:plan]) redirect_to(action: :new) && return unless plan diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 2c648c56..5514d77e 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -20,7 +20,7 @@ def failure # by the `shop` parameter to be present in the local database. def authenticate if Rails.env.development? && DiscoApp.configuration.skip_oauth? - shop = DiscoApp::Shop.find_by_shopify_domain!(sanitized_shop_name) + shop = DiscoApp::Shop.find_by!(shopify_domain: sanitized_shop_name) sess = ShopifyAPI::Session.new(shop.shopify_domain, shop.shopify_token) session[:shopify] = ShopifyApp::SessionRepository.store(sess) diff --git a/app/helpers/disco_app/application_helper.rb b/app/helpers/disco_app/application_helper.rb index f8c32423..3eed6024 100644 --- a/app/helpers/disco_app/application_helper.rb +++ b/app/helpers/disco_app/application_helper.rb @@ -39,7 +39,7 @@ def link_to_add_fields(name, form, association) fields = form.fields_for(association, new_object, child_index: id) do |builder| render(association.to_s.singularize + '_fields', f: builder) end - link_to(name, '#', class: 'add_fields', data: { id: id, fields: fields.gsub("\n", '') }) + link_to(name, '#', class: 'add_fields', data: { id: id, fields: fields.delete("\n") }) end # Return the props required to instantiate a React ModelForm component for the diff --git a/app/models/disco_app/concerns/plan.rb b/app/models/disco_app/concerns/plan.rb index f60f10b5..c5280f19 100644 --- a/app/models/disco_app/concerns/plan.rb +++ b/app/models/disco_app/concerns/plan.rb @@ -3,7 +3,7 @@ module DiscoApp::Concerns::Plan extend ActiveSupport::Concern included do - has_many :subscriptions + has_many :subscriptions, dependent: :restrict_with_exception has_many :shops, through: :subscriptions has_many :plan_codes, dependent: :destroy diff --git a/app/models/disco_app/concerns/shop.rb b/app/models/disco_app/concerns/shop.rb index ddb6069f..2c1713ee 100644 --- a/app/models/disco_app/concerns/shop.rb +++ b/app/models/disco_app/concerns/shop.rb @@ -7,11 +7,11 @@ module DiscoApp::Concerns::Shop include ActionView::Helpers::DateHelper # Define relationships to plans and subscriptions. - has_many :subscriptions + has_many :subscriptions, dependent: :restrict_with_exception has_many :plans, through: :subscriptions # Define relationship to users. - has_many :users + has_many :users, dependent: :restrict_with_exception # Define relationship to sessions. has_many :sessions, class_name: 'DiscoApp::Session', dependent: :destroy diff --git a/app/models/disco_app/concerns/source.rb b/app/models/disco_app/concerns/source.rb index 08709ff6..be0290f2 100644 --- a/app/models/disco_app/concerns/source.rb +++ b/app/models/disco_app/concerns/source.rb @@ -3,7 +3,7 @@ module DiscoApp::Concerns::Source extend ActiveSupport::Concern included do - has_many :subscriptions + has_many :subscriptions, dependent: :restrict_with_exception has_many :shops, through: :subscriptions validates_presence_of :source diff --git a/app/services/disco_app/charges_service.rb b/app/services/disco_app/charges_service.rb index 60065ede..935c271a 100644 --- a/app/services/disco_app/charges_service.rb +++ b/app/services/disco_app/charges_service.rb @@ -67,7 +67,7 @@ def self.activate(shop, charge) def self.cancel_recurring_charges(shop, charge = nil) charges = DiscoApp::RecurringApplicationCharge.where(shop: shop) charges = charges.where.not(id: charge) if charge.present? - charges.update_all(status: DiscoApp::RecurringApplicationCharge.statuses[:cancelled]) + charges.update_all(status: DiscoApp::RecurringApplicationCharge.statuses[:cancelled]) # rubocop:disable Rails/SkipsModelValidations end end diff --git a/app/services/disco_app/partner_app_service.rb b/app/services/disco_app/partner_app_service.rb index 30ec6f96..bc4060b3 100644 --- a/app/services/disco_app/partner_app_service.rb +++ b/app/services/disco_app/partner_app_service.rb @@ -79,7 +79,7 @@ def create_partner_app(apps_page) form['create_form[application_url]'] = @app_url # Accept TOS - unless form['create_form[accepted]'].blank? + if form['create_form[accepted]'].present? form['create_form[accepted]'] = '1' form.hiddens.last.value = 1 end diff --git a/app/services/disco_app/subscription_service.rb b/app/services/disco_app/subscription_service.rb index 2132607c..f7e85508 100644 --- a/app/services/disco_app/subscription_service.rb +++ b/app/services/disco_app/subscription_service.rb @@ -32,23 +32,23 @@ def subscribe # If a plan code was provided, fetch it for the given plan. def plan_code_instance - return unless plan_code.present? + return if plan_code.blank? @plan_code_instance ||= DiscoApp::PlanCode.available.find_by(plan: plan, code: plan_code) end # If a source name has been provided, fetch or create it def source_instance - return unless source_name.present? + return if source_name.blank? @source_instance ||= DiscoApp::Source.find_or_create_by(source: source_name) end # Cancel any existing current subscriptions. def cancel_existing_subscriptions - shop.subscriptions.current.update_all( + shop.subscriptions.current.update_all( # rubocop:disable Rails/SkipsModelValidations status: DiscoApp::Subscription.statuses[:cancelled], - cancelled_at: Time.now + cancelled_at: Time.zone.now ) end @@ -71,7 +71,7 @@ def create_new_subscription subscription_type: plan.plan_type, amount: subscription_amount, trial_period_days: plan.has_trial? ? subscription_trial_period_days : nil, - trial_start_at: plan.has_trial? ? Time.now : nil, + trial_start_at: plan.has_trial? ? Time.zone.now : nil, trial_end_at: plan.has_trial? ? subscription_trial_period_days.days.from_now : nil, source: source_instance ) diff --git a/app/services/disco_app/webhook_service.rb b/app/services/disco_app/webhook_service.rb index d0257a62..75970527 100644 --- a/app/services/disco_app/webhook_service.rb +++ b/app/services/disco_app/webhook_service.rb @@ -15,11 +15,11 @@ def self.calculated_hmac(body, secret) # Try to find a job class for the given webhook topic. def self.find_job_class(topic) # First try to find a top-level matching job class. - "#{topic}_job".gsub('/', '_').classify.constantize + "#{topic}_job".tr('/', '_').classify.constantize rescue NameError # If that fails, try to find a DiscoApp:: prefixed job class. begin - %(DiscoApp::#{"#{topic}_job".gsub('/', '_').classify}).constantize + %(DiscoApp::#{"#{topic}_job".tr('/', '_').classify}).constantize rescue NameError nil end diff --git a/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb b/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb index 870c6e97..352252d3 100644 --- a/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb +++ b/db/migrate/20170315062629_add_sources_to_shop_subscriptions.rb @@ -10,7 +10,7 @@ def change end end - remove_column :disco_app_subscriptions, :source + remove_column :disco_app_subscriptions, :source, :string end end diff --git a/disco_app.gemspec b/disco_app.gemspec index 3fb0b260..a6e20d3c 100644 --- a/disco_app.gemspec +++ b/disco_app.gemspec @@ -55,7 +55,9 @@ Gem::Specification.new do |s| s.add_development_dependency 'dotenv-rails', '~> 2.0' s.add_development_dependency 'minitest', '5.10.1' s.add_development_dependency 'minitest-reporters', '1.1.9' - s.add_development_dependency 'rubocop', '~> 0.70' + s.add_development_dependency 'rubocop', '~> 0.72' + s.add_development_dependency 'rubocop-performance', '~> 1.4.0' + s.add_development_dependency 'rubocop-rails', '~> 2.2.0' s.add_development_dependency 'vcr', '~> 3.0' s.add_development_dependency 'webmock', '~> 2.3' end diff --git a/lib/generators/disco_app/install/install_generator.rb b/lib/generators/disco_app/install/install_generator.rb index b624e4bd..35d4e4fc 100644 --- a/lib/generators/disco_app/install/install_generator.rb +++ b/lib/generators/disco_app/install/install_generator.rb @@ -59,6 +59,12 @@ def configure_gemfile gem 'rb-readline' end + gem_group :development do + gem 'rubocop' + gem 'rubocop-performance' + gem 'rubocop-rails' + end + # Indicate which gems should only be used in development and test. gem_group :development, :test do gem 'coveralls' @@ -104,8 +110,13 @@ def configure_application application '# Ensure UTC is the default timezone' # Set server side rendereing for components.js - application "config.react.server_renderer_options = {\nfiles: ['components.js'], # files to load for prerendering\n}" - application '# Enable server side react rendering' + application <<~CONFIG + # Enable server side react rendering + config.react.server_renderer_options = { + # files to load for prerendering + files: ['components.js'] + } + CONFIG # Set defaults for various charge attributes. application "config.x.shopify_charges_default_trial_days = 14\n" diff --git a/lib/generators/disco_app/install/templates/root/.rubocop.yml b/lib/generators/disco_app/install/templates/root/.rubocop.yml index 25ff354f..f5507fbe 100644 --- a/lib/generators/disco_app/install/templates/root/.rubocop.yml +++ b/lib/generators/disco_app/install/templates/root/.rubocop.yml @@ -1,8 +1,13 @@ +require: + - rubocop-rails + - rubocop-performance + AllCops: Exclude: - bin/* - db/schema.rb - vendor/ruby/**/* + - node_modules/**/* TargetRubyVersion: 2.5 # Layout @@ -46,7 +51,7 @@ Layout/ClassStructure: ExpectedOrder: - module_inclusion - constants - - attributes + - public_attributes - associations - validations - callbacks @@ -54,6 +59,7 @@ Layout/ClassStructure: - initializer - public_methods - protected_methods + - private_attributes - private_methods Enabled: true @@ -84,7 +90,7 @@ Layout/IndentFirstHashElement: EnforcedStyle: consistent Layout/IndentationConsistency: - EnforcedStyle: rails + EnforcedStyle: indented_internal_methods Enabled: true Layout/MultilineBlockLayout: @@ -239,11 +245,6 @@ Style/FrozenStringLiteralComment: to help transition from Ruby 2.3.0 to Ruby 3.0. Enabled: false -Lint/FlipFlop: - Description: 'Checks for flip flops' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' - Enabled: true - Style/FormatString: Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' @@ -384,6 +385,13 @@ Style/RegexpLiteral: StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' Enabled: true +Style/Sample: + Description: >- + Use `sample` instead of `shuffle.first`, + `shuffle.last`, and `shuffle[Fixnum]`. + Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' + Enabled: true + Style/SelfAssignment: Description: >- Checks for places where self-assignment shorthand should have @@ -491,6 +499,7 @@ Metrics/BlockLength: - namespace - trait + Metrics/BlockNesting: Description: 'Avoid excessive block nesting' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' @@ -566,6 +575,11 @@ Lint/ElseLayout: Description: 'Check for odd code arrangement in an else block.' Enabled: true +Lint/FlipFlop: + Description: 'Checks for flip flops' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' + Enabled: true + Lint/FormatParameterMismatch: Description: 'The number of parameters to format/sprint must match the fields.' Enabled: true @@ -655,13 +669,6 @@ Performance/ReverseEach: Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' Enabled: true -Style/Sample: - Description: >- - Use `sample` instead of `shuffle.first`, - `shuffle.last`, and `shuffle[Fixnum]`. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' - Enabled: true - Performance/Size: Description: >- Use `size` instead of `count` for counting @@ -693,6 +700,10 @@ Rails/Delegate: Description: 'Prefer delegate method for delegations.' Enabled: false +Rails/Exit: + Exclude: + - lib/generators/disco_app/install/templates/spec/rails_helper.rb + Rails/FindBy: Description: 'Prefer find_by over where.first.' Enabled: true @@ -725,6 +736,13 @@ Rails/TimeZone: Reference: 'http://danilenko.org/2012/7/6/rails_timezones' Enabled: true +Rails/UnknownEnv: + Environments: + - development + - production + - staging + - test + Rails/Validation: Description: 'Use validates :attribute, hash of validations.' Enabled: false @@ -732,4 +750,4 @@ Rails/Validation: # Bundler Bundler/OrderedGems: - Enabled: true + Enabled: true \ No newline at end of file diff --git a/test/services/disco_app/flow/process_action_test.rb b/test/services/disco_app/flow/process_action_test.rb index ccb1db97..0bae5ab3 100644 --- a/test/services/disco_app/flow/process_action_test.rb +++ b/test/services/disco_app/flow/process_action_test.rb @@ -24,7 +24,7 @@ def setup action_run_id: 'bdb15e45-4f9d-4c80-88c8-7b43a24edaac-30892-cc8eb62a-14db-43fc-bc33-d6dea41ae623', properties: { 'customer_email' => 'name@example.com' } ) - @now = Time.parse('2018-12-29T00:00:00Z') + @now = Time.zone.parse('2018-12-29T00:00:00Z') Timecop.freeze(@now) end diff --git a/test/services/disco_app/flow/process_trigger_test.rb b/test/services/disco_app/flow/process_trigger_test.rb index 15b25c55..ae9f113e 100644 --- a/test/services/disco_app/flow/process_trigger_test.rb +++ b/test/services/disco_app/flow/process_trigger_test.rb @@ -14,7 +14,7 @@ def setup resource_url: 'https://example.com/test-resource-url', properties: { 'Customer email' => 'name@example.com' } ) - @now = Time.parse('2018-12-29T00:00:00Z') + @now = Time.zone.parse('2018-12-29T00:00:00Z') Timecop.freeze(@now) end diff --git a/test/services/disco_app/subscription_service_test.rb b/test/services/disco_app/subscription_service_test.rb index 6ac39f68..233f9063 100644 --- a/test/services/disco_app/subscription_service_test.rb +++ b/test/services/disco_app/subscription_service_test.rb @@ -45,14 +45,14 @@ def teardown test 'new subscription for a plan with a trial period created correctly' do new_subscription = DiscoApp::SubscriptionService.subscribe(@shop, disco_app_plans(:premium)) assert new_subscription.trial? - assert_equal Time.now, new_subscription.trial_start_at + assert_equal Time.zone.now, new_subscription.trial_start_at assert_equal 28.days.from_now, new_subscription.trial_end_at end test 'new subscription for a plan with a plan code created correctly' do new_subscription = DiscoApp::SubscriptionService.subscribe(@shop, disco_app_plans(:premium), 'PODCAST') assert new_subscription.trial? - assert_equal Time.now, new_subscription.trial_start_at + assert_equal Time.zone.now, new_subscription.trial_start_at assert_equal 60.days.from_now, new_subscription.trial_end_at assert_equal 60, new_subscription.trial_period_days assert_equal 8999, new_subscription.amount From 78f0f04d5e6e1aaba790df921eec5dcf24a9841e Mon Sep 17 00:00:00 2001 From: John Voon Date: Tue, 16 Jul 2019 13:55:19 +1000 Subject: [PATCH 39/41] Bump version --- CHANGELOG.md | 16 ++++++++++++++++ UPGRADING.md | 9 +++++++++ VERSION | 2 +- initialise.sh | 4 ++-- lib/disco_app/version.rb | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d98b4246..768f7868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Change Log All notable changes to this project will be documented in this file. +## 0.17.0 - 2019-07-16 +### Added +- Timber logging for generated apps +- rubocop-performance and rubocop-rails plugins +- Support for AppSignal +- Generator for React applications + +### Changed +- Switch from Minitest to RSpec as the testing library for generated apps +- Update rubocop.yml file +- Lock shopify_api version to avoid breaking changes +- Upgrade Rails to 5.2.2 to avoid gem vulnerabilities +- Run rubocop auto-corrections +- Use with_indifferent_access when reading Flow properties +- Renaming of methods and general refactoring + ## 0.16.1 - 2019-01-01 ### Added - Support for Shopify Flow triggers and actions diff --git a/UPGRADING.md b/UPGRADING.md index 3fb14840..d6dc37b8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,15 @@ This file contains more detailed instructions on what's required when updating an application between one release version of the gem to the next. It's intended as more in-depth accompaniment to the notes in `CHANGELOG.md` for each version. +## Upgrading from 0.16.1 to 0.17.1 +Upgrade your app to Rails version 5.2.2. + +Set your `shopify_api` version to 6.0: + +```ruby +gem 'shopify_api', '~> 6.0' +``` + ## Upgrading from 0.16.0 to 0.16.1 Ensure new Shopify Flow database migrations are brought across and run: diff --git a/VERSION b/VERSION index 2a0970ca..c5523bd0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.16.1 +0.17.0 diff --git a/initialise.sh b/initialise.sh index 05128212..e8d4981b 100755 --- a/initialise.sh +++ b/initialise.sh @@ -4,7 +4,7 @@ APP_NAME="$1" RAILS_VERSION="${RAILS_VERSION:-5.2.0}" RUBY_VERSION="${RUBY_VERSION:-2.5.0}" -DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.16.1}" +DISCO_APP_VERSION="${DISCO_APP_VERSION:-0.17.0}" if [ -z $APP_NAME ]; then echo "Usage: ./initialise.sh app_name (rails_version) (ruby_version) (disco_app_version)" @@ -21,4 +21,4 @@ bundle install bundle exec rails _"$RAILS_VERSION"_ new . --force --skip-bundle echo "gem 'disco_app', '$DISCO_APP_VERSION', source: \"https://gem.fury.io/discolabs/\"" >> Gemfile bundle update -bundle exec rails generate disco_app --force +bundle exec rails generate disco_app:install --force diff --git a/lib/disco_app/version.rb b/lib/disco_app/version.rb index 27ee18d0..d0a19251 100644 --- a/lib/disco_app/version.rb +++ b/lib/disco_app/version.rb @@ -1,5 +1,5 @@ module DiscoApp - VERSION = '0.16.1'.freeze + VERSION = '0.17.0'.freeze end From a0d72cc7a816680a72f6c8e9412307d9fd6f0f53 Mon Sep 17 00:00:00 2001 From: John Voon Date: Thu, 18 Jul 2019 15:11:19 +1000 Subject: [PATCH 40/41] CH6320: Fix 400 error on carrier request --- .../disco_app/concerns/carrier_request_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/disco_app/concerns/carrier_request_controller.rb b/app/controllers/disco_app/concerns/carrier_request_controller.rb index af0464bc..9253f0b4 100644 --- a/app/controllers/disco_app/concerns/carrier_request_controller.rb +++ b/app/controllers/disco_app/concerns/carrier_request_controller.rb @@ -39,6 +39,8 @@ def request_is_valid? return false if params[:rate][:origin].blank? return false if params[:rate][:destination].blank? return false if params[:rate][:items].blank? + + true end end From 1892ba946ae393a318f4cac11cb85f87cb45f74e Mon Sep 17 00:00:00 2001 From: John Voon Date: Thu, 18 Jul 2019 15:32:56 +1000 Subject: [PATCH 41/41] CH6321: Remove Rollbar calls and refs in README --- README.md | 22 ++++--------------- .../concerns/synchronise_users_job.rb | 2 +- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 415be280..c4cfd845 100644 --- a/README.md +++ b/README.md @@ -78,22 +78,15 @@ app via the partner dashboard. Before you can do this, you need to configure a couple of things. #### Create a DiscoApp configuration file in your home directory -First, you'll need to add your partner dashboard and Rollbar credentials to a DiscoApp -configuration file in your home directory, `~/.disco_app.yml`: +First, you'll need to add your partner dashboard credentials to a DiscoApp configuration file in your home directory, `~/.disco_app.yml`: ``` params: PARTNER_EMAIL: "hello@discolabs.com" PARTNER_PASSWORD: "***********" PARTNER_ORGANIZATION: "Disco" - ROLLBAR_ACCOUNT_ACCESS_TOKEN_WRITE: "******************************" - ROLLBAR_ACCOUNT_ACCESS_TOKEN_READ: "******************************" ``` -You can find your tokens in the Rollbar settings under 'Project Access Tokens'. -If you don't yet have a Rollbar account you can leave out the bottom two lines for now. -You'll only need to set this up the one time on your local machine. - #### Configure initial values in local ENV file Next, you'll need to set a few of the basic configuration parameters for your app in `.env.local` in the application directory. The command line utility @@ -349,7 +342,6 @@ There's a number of useful Rake tasks that are baked into the app. They are: - `rake shops:sync`: Synchronises shop data across all installed shops. - `rake users:sync`: Synchronises user data across all installed shops. - `rake generate:partner_app`: Generates an app on the Disco Partner Dashboard -- `rake generate:rollbar_project`: Generates a Rollbar Project ### Background Tasks The `DiscoApp::ShopJob` class inherits from `ActiveJob::Base`, and can be used @@ -955,21 +947,15 @@ Mailgun API in production for sending email. Adds the `MAILGUN_API_KEY` and DiscoApp has support for both exception reporting and application performance monitoring to the application. -[Rollbar][] is used for exception tracking, and will be activated when a -`ROLLBAR_ACCESS_TOKEN` environment variable is present. Rollbar access tokens -are unique to each app. In order to generate a new token run -`rake generate:rollbar_project`. -Make sure you have configured your `~/.disco_app.yml` as per -[the setup guide](#create-a-discoapp-configuration-file-in-your-home-directory) -and that you have the necessary Rollbar permissions to create a project. You can -specify an app name by adding APP_NAME='App Name'. +[Appsignal](https://github.com/appsignal/appsignal-ruby) is used for exception tracking, and will be activated when a +`APPSIGNAL_PUSH_API_KEY` environment variable is present. The Appsignal Push API Key can be found in 1Password. [New Relic][] is used for application performance monitoring, and will be activated when a `NEW_RELIC_LICENSE_KEY` environment variable is present. There is a single New Relic license key across all Disco apps - contact Gavin if you need it to deploy a new application. -[Rollbar]: https://www.rollbar.com +[Appsignal]: https://www.appsignal.com [New Relic]: https://www.newrelic.com diff --git a/app/jobs/disco_app/concerns/synchronise_users_job.rb b/app/jobs/disco_app/concerns/synchronise_users_job.rb index e8232844..0d25647a 100644 --- a/app/jobs/disco_app/concerns/synchronise_users_job.rb +++ b/app/jobs/disco_app/concerns/synchronise_users_job.rb @@ -8,7 +8,7 @@ def perform(_shop) ShopifyAPI::User.all end rescue ActiveResource::UnauthorizedAccess => e - Rollbar.error(e) + Appsignal.set_error(e) return end