diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..25f5ca5 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,5 @@ +# Omakase Ruby styling for Rails +inherit_gem: + rubocop-rails-omakase: rubocop.yml + +# Your own specialized rules go here diff --git a/Dockerfile b/Dockerfile index f19c5f1..0639c80 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,36 @@ # syntax = docker/dockerfile:1 -# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile -ARG RUBY_VERSION=3.2.2 -FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base +# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: +# docker build -t my-app . +# docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY= my-app + +# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version +ARG RUBY_VERSION=3.3.1 +FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base # Rails app lives here WORKDIR /rails +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libsqlite3-0 libvips && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + # Set production environment ENV RAILS_ENV="production" \ BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ BUNDLE_WITHOUT="development" - # Throw-away build stage to reduce size of final image -FROM base as build +FROM base AS build # Install packages needed to build gems RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git libvips pkg-config + apt-get install --no-install-recommends -y build-essential git pkg-config && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems COPY Gemfile Gemfile.lock ./ @@ -27,7 +38,6 @@ RUN bundle install && \ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ bundle exec bootsnap precompile --gemfile - # Copy application code COPY . . @@ -38,24 +48,20 @@ RUN bundle exec bootsnap precompile app/ lib/ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile + + # Final stage for app image FROM base -# Install packages needed for deployment -RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libsqlite3-0 libvips libjemalloc2 && \ - rm -rf /var/lib/apt/lists /var/cache/apt/archives - # Copy built artifacts: gems, application -COPY --from=build /usr/local/bundle /usr/local/bundle +COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" COPY --from=build /rails /rails # Run and own only the runtime files as a non-root user for security -RUN useradd rails --create-home --shell /bin/bash && \ +RUN groupadd --system --gid 1000 rails && \ + useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ chown -R rails:rails db log storage tmp -USER rails:rails - -ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 +USER 1000:1000 # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] diff --git a/Gemfile b/Gemfile index 033ab42..ef1c2ba 100644 --- a/Gemfile +++ b/Gemfile @@ -1,32 +1,61 @@ source "https://rubygems.org" -git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "rails", github: "rails/rails", branch: "main" -gem "sprockets-rails" -gem "sqlite3" -gem "puma" +# The modern asset pipeline for Rails [https://github.com/rails/propshaft] +gem "propshaft" +# Use sqlite3 as the database for Active Record +gem "sqlite3", ">= 1.4" +# Use the Puma web server [https://github.com/puma/puma] +gem "puma", ">= 5.0" +# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] gem "importmap-rails" +# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] gem "turbo-rails" +# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] gem "stimulus-rails" +# Build JSON APIs with ease [https://github.com/rails/jbuilder] gem "jbuilder" +# Use Redis adapter to run Action Cable in production +# gem "redis", ">= 4.0.1" +# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] +# gem "kredis" + +# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] +# gem "bcrypt", "~> 3.1.7" + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem "tzinfo-data", platforms: %i[ windows jruby ] + +# Reduces boot times through caching; required in config/boot.rb gem "bootsnap", require: false +# Deploy this application anywhere as a Docker container [https://kamal-deploy.org] +# gem "kamal", require: false + +# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # gem "image_processing", "~> 1.2" group :development, :test do - gem "debug", platforms: %i[mri mingw x64_mingw] + # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem + gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" + + # Static analysis for security vulnerabilities [https://brakemanscanner.org/] + gem "brakeman", require: false + + # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] + gem "rubocop-rails-omakase", require: false end group :development do - gem "standard" + # Use console on exceptions pages [https://github.com/rails/web-console] gem "web-console" end group :test do + # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] gem "capybara" gem "selenium-webdriver" - gem "webdrivers" end gem "tailwindcss-rails", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index f34018e..5b0b297 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,8 @@ GEM bindex (0.8.1) bootsnap (1.18.3) msgpack (~> 1.2) + brakeman (6.1.2) + racc builder (3.3.0) capybara (3.40.0) addressable @@ -142,7 +144,6 @@ GEM activesupport (>= 5.0.0) json (2.7.2) language_server-protocol (3.17.0.3) - lint_roller (1.1.0) logger (1.6.0) loofah (2.22.0) crass (~> 1.0.2) @@ -167,6 +168,8 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) + nokogiri (1.16.6-aarch64-linux) + racc (~> 1.4) nokogiri (1.16.6-arm64-darwin) racc (~> 1.4) nokogiri (1.16.6-x86_64-linux) @@ -175,6 +178,11 @@ GEM parser (3.3.3.0) ast (~> 2.4.1) racc + propshaft (0.9.0) + actionpack (>= 7.0.0) + activesupport (>= 7.0.0) + rack + railties (>= 7.0.0) psych (5.1.2) stringio public_suffix (6.0.0) @@ -218,40 +226,39 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.31.3) parser (>= 3.3.1.0) + rubocop-minitest (0.35.0) + rubocop (>= 1.61, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails (2.25.0) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-ast (>= 1.31.1, < 2.0) + rubocop-rails-omakase (1.0.0) + rubocop + rubocop-minitest + rubocop-performance + rubocop-rails ruby-progressbar (1.13.0) rubyzip (2.3.2) - selenium-webdriver (4.10.0) + selenium-webdriver (4.22.0) + base64 (~> 0.2) + logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) - sprockets (4.2.1) - concurrent-ruby (~> 1.0) - rack (>= 2.2.4, < 4) - sprockets-rails (3.5.1) - actionpack (>= 6.1) - activesupport (>= 6.1) - sprockets (>= 3.0.0) + sqlite3 (2.0.2-aarch64-linux-gnu) sqlite3 (2.0.2-arm64-darwin) sqlite3 (2.0.2-x86_64-linux-gnu) - standard (1.37.0) - language_server-protocol (~> 3.17.0.2) - lint_roller (~> 1.0) - rubocop (~> 1.64.0) - standard-custom (~> 1.0.0) - standard-performance (~> 1.4) - standard-custom (1.0.2) - lint_roller (~> 1.0) - rubocop (~> 1.50) - standard-performance (1.4.0) - lint_roller (~> 1.1) - rubocop-performance (~> 1.21.0) stimulus-rails (1.3.3) railties (>= 6.0.0) stringio (3.1.1) strscan (3.1.0) + tailwindcss-rails (2.6.1-aarch64-linux) + railties (>= 7.0.0) tailwindcss-rails (2.6.1-arm64-darwin) railties (>= 7.0.0) tailwindcss-rails (2.6.1-x86_64-linux) @@ -271,10 +278,6 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webdrivers (5.3.1) - nokogiri (~> 1.6) - rubyzip (>= 1.3.0) - selenium-webdriver (~> 4.0, < 4.11) webrick (1.8.1) websocket (1.2.10) websocket-driver (0.7.6) @@ -285,26 +288,28 @@ GEM zeitwerk (2.6.16) PLATFORMS + aarch64-linux arm64-darwin-23 x86_64-linux DEPENDENCIES bootsnap + brakeman capybara debug importmap-rails jbuilder - puma + propshaft + puma (>= 5.0) rails! + rubocop-rails-omakase selenium-webdriver - sprockets-rails - sqlite3 - standard + sqlite3 (>= 1.4) stimulus-rails tailwindcss-rails (~> 2.0) turbo-rails + tzinfo-data web-console - webdrivers BUNDLED WITH 2.5.9 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js deleted file mode 100644 index b06fc42..0000000 --- a/app/assets/config/manifest.js +++ /dev/null @@ -1,5 +0,0 @@ -//= link_tree ../images -//= link_directory ../stylesheets .css -//= link_tree ../../javascript .js -//= link_tree ../../../vendor/javascript .js -//= link_tree ../builds diff --git a/app/helpers/expenses_helper.rb b/app/helpers/expenses_helper.rb index 739166e..263c8f3 100644 --- a/app/helpers/expenses_helper.rb +++ b/app/helpers/expenses_helper.rb @@ -2,6 +2,6 @@ module ExpensesHelper def expense_periods Expense .periods - .map { |key, value| [key.titleize, Expense.periods.key(value)] } + .map { |key, value| [ key.titleize, Expense.periods.key(value) ] } end end diff --git a/app/helpers/incomes_helper.rb b/app/helpers/incomes_helper.rb index 9d6b84f..8275806 100644 --- a/app/helpers/incomes_helper.rb +++ b/app/helpers/incomes_helper.rb @@ -2,6 +2,6 @@ module IncomesHelper def members Member .all - .map { |member, value| [member.name, member.id] } + .map { |member, value| [ member.name, member.id ] } end end diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index dffd4ba..840d093 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -1,7 +1,12 @@ #!/bin/bash -e +# Enable jemalloc for reduced memory usage and latency. +if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then + export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)" +fi + # If running the rails server then create or migrate existing database -if [ "${*}" == "./bin/rails server" ]; then +if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then ./bin/rails db:prepare fi diff --git a/config/application.rb b/config/application.rb index 3526226..36fd491 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,7 +9,7 @@ Bundler.require(*Rails.groups) module FamilyBudget class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 7.1 + config.load_defaults 8.0 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. diff --git a/config/environments/development.rb b/config/environments/development.rb index 61c8aa6..d969821 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -24,7 +24,7 @@ Rails.application.configure do config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store - config.public_file_server.headers = {"Cache-Control" => "public, max-age=#{2.days.to_i}"} + config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -39,7 +39,7 @@ Rails.application.configure do config.action_mailer.perform_caching = false - config.action_mailer.default_url_options = {host: "localhost", port: 3000} + config.action_mailer.default_url_options = { host: "localhost", port: 3000 } # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log diff --git a/config/environments/production.rb b/config/environments/production.rb index e0ae758..8f42c8c 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -60,7 +60,7 @@ Rails.application.configure do .then { |logger| ActiveSupport::TaggedLogging.new(logger) } # Prepend all log lines with the following tags. - config.log_tags = [:request_id] + config.log_tags = [ :request_id ] # "info" includes generic and useful information about system operation, but avoids logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). If you diff --git a/config/environments/test.rb b/config/environments/test.rb index 3064700..83d1fa9 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -18,7 +18,7 @@ Rails.application.configure do config.eager_load = ENV["CI"].present? # Configure public file server for tests with Cache-Control for performance. - config.public_file_server.headers = {"Cache-Control" => "public, max-age=#{1.hour.to_i}"} + config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true @@ -43,7 +43,7 @@ Rails.application.configure do # Unlike controllers, the mailer instance doesn't have any context about the # incoming request so you'll need to provide the :host parameter yourself. - config.action_mailer.default_url_options = {host: "www.example.com"} + config.action_mailer.default_url_options = { host: "www.example.com" } # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 2ac80b6..e2db3a5 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -3,5 +3,5 @@ require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: ENV["VISIBLE_SYSTEM_TESTS"].present? ? :chrome : :headless_chrome, - screen_size: [1400, 1400] + screen_size: [ 1400, 1400 ] end