Compare commits

..

No commits in common. "main" and "store-website" have entirely different histories.

36 changed files with 136 additions and 620 deletions

View File

@ -23,7 +23,7 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref }} # Checkout the actual branch, not the result if merged into the base ref: ${{ github.head_ref }} # Checkout the actual branch, not the result if merged into the base
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1.220.0
- run: bundle install - run: bundle install
- &postgres_wait - &postgres_wait
name: Wait until Postgres is ready to accept connections name: Wait until Postgres is ready to accept connections
@ -65,29 +65,26 @@ jobs:
if: failure() if: failure()
run: docker ps --filter network=$JOB_CONTAINER_NAME-$GITHUB_JOB-network --filter name=$JOB_CONTAINER_NAME-* --format "{{.ID}}" | xargs docker rm -f run: docker ps --filter network=$JOB_CONTAINER_NAME-$GITHUB_JOB-network --filter name=$JOB_CONTAINER_NAME-* --format "{{.ID}}" | xargs docker rm -f
rubocop: rubocop:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1.220.0
- run: bundle install - run: bundle install
- run: bundle exec rubocop --force-exclusion --parallel - run: bundle exec rubocop --force-exclusion --parallel
check-licenses: check-licenses:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1.220.0
- name: Install project dependencies - name: Install project dependencies
run: bundle install --jobs `getconf _NPROCESSORS_ONLN` run: bundle install --jobs `getconf _NPROCESSORS_ONLN`
- name: Run license finder - name: Run license finder
run: license_finder run: license_finder
copyright_notice: copyright_notice:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -124,6 +121,9 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
needs: needs:
- unit_tests - unit_tests
- rubocop
- check-licenses
- copyright_notice
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:

View File

@ -1 +1 @@
ruby-3.4.3 ruby-3.4.2

View File

@ -1,7 +1,7 @@
# syntax = docker/dockerfile:1 # syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.4.3 ARG RUBY_VERSION=3.4.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here # Rails app lives here
@ -13,7 +13,7 @@ ENV RAILS_ENV="production" \
BUNDLE_PATH="/usr/local/bundle" \ BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development" BUNDLE_WITHOUT="development"
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf RUN apt-get update && apt-get install -y nodejs
# Throw-away build stage to reduce size of final image # Throw-away build stage to reduce size of final image
FROM base AS build FROM base AS build

View File

@ -1,13 +1,13 @@
# syntax = docker/dockerfile:1 # syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.4.3 ARG RUBY_VERSION=3.4.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here # Rails app lives here
WORKDIR /rails WORKDIR /rails
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf RUN apt-get update && apt-get install -y nodejs
FROM base as build FROM base as build

View File

@ -2,7 +2,7 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '3.4.3' ruby '3.4.2'
gem 'bootsnap', require: false gem 'bootsnap', require: false
gem 'csv' gem 'csv'
gem 'importmap-rails' gem 'importmap-rails'
@ -51,7 +51,3 @@ gem 'chroma'
gem 'solid_queue', '~> 1.0' gem 'solid_queue', '~> 1.0'
gem 'devise', '~> 4.9' gem 'devise', '~> 4.9'
gem 'wicked_pdf', '~> 2.8'
gem 'rqrcode', '~> 3.1'

View File

@ -76,9 +76,7 @@ GEM
rails (>= 6.0) rails (>= 6.0)
addressable (2.8.7) addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 7.0)
annotaterb (4.17.0) annotaterb (4.15.0)
activerecord (>= 6.0.0)
activesupport (>= 6.0.0)
ast (2.4.3) ast (2.4.3)
babel-source (5.8.35) babel-source (5.8.35)
babel-transpiler (0.7.0) babel-transpiler (0.7.0)
@ -87,7 +85,7 @@ GEM
base64 (0.3.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.20)
benchmark (0.4.1) benchmark (0.4.1)
bigdecimal (3.2.2) bigdecimal (3.2.1)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.6) bootsnap (1.18.6)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -95,14 +93,13 @@ GEM
childprocess (5.1.0) childprocess (5.1.0)
logger (~> 1.5) logger (~> 1.5)
chroma (0.2.0) chroma (0.2.0)
chunky_png (1.4.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
connection_pool (2.5.3) connection_pool (2.5.3)
crass (1.0.6) crass (1.0.6)
csv (3.3.5) csv (3.3.5)
date (3.4.1) date (3.4.1)
debug (1.11.0) debug (1.10.0)
irb (~> 1.10) irb (~> 1.10)
reline (>= 0.3.8) reline (>= 0.3.8)
devise (4.9.4) devise (4.9.4)
@ -113,17 +110,17 @@ GEM
warden (~> 1.2.3) warden (~> 1.2.3)
diff-lcs (1.6.2) diff-lcs (1.6.2)
drb (2.2.3) drb (2.2.3)
erb (5.0.2) erb (5.0.1)
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.2.11) et-orbi (1.2.11)
tzinfo tzinfo
execjs (2.9.1) execjs (2.9.1)
factory_bot (6.5.4) factory_bot (6.4.6)
activesupport (>= 6.1.0) activesupport (>= 5.0.0)
factory_bot_rails (6.5.0) factory_bot_rails (6.4.3)
factory_bot (~> 6.5) factory_bot (~> 6.4)
railties (>= 6.1.0) railties (>= 5.0.0)
faker (3.5.2) faker (3.5.1)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
fugit (1.11.1) fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1, >= 1.2.11)
@ -140,7 +137,7 @@ GEM
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
io-console (0.8.1) io-console (0.8.0)
irb (1.15.2) irb (1.15.2)
pp (>= 0.6.0) pp (>= 0.6.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
@ -211,21 +208,20 @@ GEM
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.4)
nokogiri (1.18.9) nokogiri (1.18.8)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-aarch64-linux-gnu) nokogiri (1.18.8-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-arm-linux-gnu) nokogiri (1.18.8-arm-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-arm64-darwin) nokogiri (1.18.8-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-x86_64-darwin) nokogiri (1.18.8-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-gnu) nokogiri (1.18.8-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostruct (0.6.2)
parallel (1.27.0) parallel (1.27.0)
parser (3.3.8.0) parser (3.3.8.0)
ast (~> 2.4.1) ast (~> 2.4.1)
@ -249,7 +245,7 @@ GEM
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rack (3.1.16) rack (3.1.15)
rack-cors (3.0.0) rack-cors (3.0.0)
logger logger
rack (>= 3.0.14) rack (>= 3.0.14)
@ -291,7 +287,7 @@ GEM
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.3.0) rake (13.3.0)
rdoc (6.14.2) rdoc (6.14.0)
erb erb
psych (>= 4.0.0) psych (>= 4.0.0)
react-rails (3.2.1) react-rails (3.2.1)
@ -300,21 +296,17 @@ GEM
execjs execjs
railties (>= 3.2) railties (>= 3.2)
tilt tilt
redis (5.4.1) redis (5.4.0)
redis-client (>= 0.22.0) redis-client (>= 0.22.0)
redis-client (0.23.2) redis-client (0.23.2)
connection_pool connection_pool
regexp_parser (2.10.0) regexp_parser (2.10.0)
reline (0.6.2) reline (0.6.1)
io-console (~> 0.5) io-console (~> 0.5)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.3.9) rexml (3.3.9)
rqrcode (3.1.0)
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.0.0)
rspec-core (3.13.4) rspec-core (3.13.4)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5) rspec-expectations (3.13.5)
@ -323,7 +315,7 @@ GEM
rspec-mocks (3.13.5) rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (8.0.1) rspec-rails (8.0.0)
actionpack (>= 7.2) actionpack (>= 7.2)
activesupport (>= 7.2) activesupport (>= 7.2)
railties (>= 7.2) railties (>= 7.2)
@ -347,7 +339,7 @@ GEM
rswag-ui (2.16.0) rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1) actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.1)
rubocop (1.78.0) rubocop (1.76.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
@ -355,10 +347,10 @@ GEM
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.45.1, < 2.0) rubocop-ast (>= 1.45.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.45.1) rubocop-ast (1.45.0)
parser (>= 3.3.7.2) parser (>= 3.3.7.2)
prism (~> 1.4) prism (~> 1.4)
rubocop-factory_bot (2.27.1) rubocop-factory_bot (2.27.1)
@ -384,13 +376,13 @@ GEM
securerandom (0.4.1) securerandom (0.4.1)
shoulda-matchers (6.5.0) shoulda-matchers (6.5.0)
activesupport (>= 5.2.0) activesupport (>= 5.2.0)
solid_queue (1.2.1) solid_queue (1.1.5)
activejob (>= 7.1) activejob (>= 7.1)
activerecord (>= 7.1) activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1) concurrent-ruby (>= 1.3.1)
fugit (~> 1.11.0) fugit (~> 1.11.0)
railties (>= 7.1) railties (>= 7.1)
thor (>= 1.3.1) thor (~> 1.3.1)
sprockets (4.2.1) sprockets (4.2.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4) rack (>= 2.2.4, < 4)
@ -424,9 +416,6 @@ GEM
base64 base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
wicked_pdf (2.8.2)
activesupport
ostruct
with_env (1.1.0) with_env (1.1.0)
xml-simple (1.1.9) xml-simple (1.1.9)
rexml rexml
@ -465,7 +454,6 @@ DEPENDENCIES
rails (~> 8.0.0, >= 8.0.0) rails (~> 8.0.0, >= 8.0.0)
react-rails react-rails
redis (>= 4.0.1) redis (>= 4.0.1)
rqrcode (~> 3.1)
rspec-rails (~> 8.0.0) rspec-rails (~> 8.0.0)
rswag rswag
rubocop rubocop
@ -481,7 +469,6 @@ DEPENDENCIES
turbo-rails turbo-rails
tzinfo-data tzinfo-data
web-console web-console
wicked_pdf (~> 2.8)
CHECKSUMS CHECKSUMS
actioncable (8.0.2) sha256=7bcce2df62e91a80143592600e16583c273e98aab50ae40a9f6a2604bb3289a0 actioncable (8.0.2) sha256=7bcce2df62e91a80143592600e16583c273e98aab50ae40a9f6a2604bb3289a0
@ -497,43 +484,42 @@ CHECKSUMS
activesupport (8.0.2) sha256=8565cddba31b900cdc17682fd66ecd020441e3eef320a9930285394e8c07a45e activesupport (8.0.2) sha256=8565cddba31b900cdc17682fd66ecd020441e3eef320a9930285394e8c07a45e
acts_as_tenant (1.0.1) sha256=6944e4d64533337938a8817a6b4ff9b11189c9dcc0b1333bb89f3821a4c14c53 acts_as_tenant (1.0.1) sha256=6944e4d64533337938a8817a6b4ff9b11189c9dcc0b1333bb89f3821a4c14c53
addressable (2.8.7) sha256=462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232 addressable (2.8.7) sha256=462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232
annotaterb (4.17.0) sha256=f0338f8aaadd5c47fa3deaccb560a54abcdde29aca6f69f4b94726ea9256b4bd annotaterb (4.15.0) sha256=fb871b5a3f96d1a3195ab0f0f51adb9105ab00492f10e97dd6f35d321e3410b0
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
babel-source (5.8.35) sha256=79ef222a9dcb867ac2efa3b0da35b4bcb15a4bfa67b6b2dcbf1e9a29104498d9 babel-source (5.8.35) sha256=79ef222a9dcb867ac2efa3b0da35b4bcb15a4bfa67b6b2dcbf1e9a29104498d9
babel-transpiler (0.7.0) sha256=4c06f4ad9e8e1cabe94f99e11df2f140bb72aca9ba067dbb49dc14d9b98d1570 babel-transpiler (0.7.0) sha256=4c06f4ad9e8e1cabe94f99e11df2f140bb72aca9ba067dbb49dc14d9b98d1570
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099
benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce
bigdecimal (3.2.2) sha256=39085f76b495eb39a79ce07af716f3a6829bc35eb44f2195e2753749f2fa5adc bigdecimal (3.2.1) sha256=1f68631e876c6aba8fe9b84b36983c55ad3293ff2d1ad4c6f115bde1e9d802e3
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
bootsnap (1.18.6) sha256=0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00 bootsnap (1.18.6) sha256=0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec
chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54 chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54
chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f
debug (1.11.0) sha256=1425db64cfa0130c952684e3dc974985be201dd62899bf4bbe3f8b5d6cf1aef2 debug (1.10.0) sha256=11e28ca74875979e612444104f3972bd5ffb9e79179907d7ad46dba44bd2e7a4
devise (4.9.4) sha256=920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8 devise (4.9.4) sha256=920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
erb (5.0.2) sha256=d30f258143d4300fb4ecf430042ac12970c9bb4b33c974a545b8f58c1ec26c0f erb (5.0.1) sha256=760439803b36cc93eca8a266aab614614e588024a89bc30a62e78d98ff452c23
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
et-orbi (1.2.11) sha256=d26e868cc21db88280a9ec1a50aa3da5d267eb9b2037ba7b831d6c2731f5df64 et-orbi (1.2.11) sha256=d26e868cc21db88280a9ec1a50aa3da5d267eb9b2037ba7b831d6c2731f5df64
execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb
factory_bot (6.5.4) sha256=4707fb7d80a7c14d71feb069460587bfc342e4ff1ef28097e0ad69d5ddfce613 factory_bot (6.4.6) sha256=1a9486ce98d318d740d8f5804b885a8265a28f326ecf2bcd4ce9fb27a71a6e04
factory_bot_rails (6.5.0) sha256=4a7b61635424a57cc60412a18b72b9dcfb02fabfce2c930447a01dce8b37c0a2 factory_bot_rails (6.4.3) sha256=ea73ceac1c0ff3dc11fff390bf2ea8a2604066525ed8ecd3b3bc2c267226dcc8
faker (3.5.2) sha256=f9a80291b2e3f259801d1dd552f0732fe04dce5d1f74e798365bc0413789c473 faker (3.5.1) sha256=1ad1fbea279d882f486059c23fe3ddb816ccd1d7052c05a45014b4450d859bfc
fugit (1.11.1) sha256=e89485e7be22226d8e9c6da411664d0660284b4b1c08cacb540f505907869868 fugit (1.11.1) sha256=e89485e7be22226d8e9c6da411664d0660284b4b1c08cacb540f505907869868
globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9
httparty (0.23.1) sha256=3ac1dd62f2010f6ece551716f5ceec2b2012011d89f1751917ab7f724e966b55 httparty (0.23.1) sha256=3ac1dd62f2010f6ece551716f5ceec2b2012011d89f1751917ab7f724e966b55
i18n (1.14.7) sha256=ceba573f8138ff2c0915427f1fc5bdf4aa3ab8ae88c8ce255eb3ecf0a11a5d0f i18n (1.14.7) sha256=ceba573f8138ff2c0915427f1fc5bdf4aa3ab8ae88c8ce255eb3ecf0a11a5d0f
importmap-rails (2.1.0) sha256=9f10c67d60651a547579f448100d033df311c5d5db578301374aeb774faae741 importmap-rails (2.1.0) sha256=9f10c67d60651a547579f448100d033df311c5d5db578301374aeb774faae741
io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb io-console (0.8.0) sha256=cd6a9facbc69871d69b2cb8b926fc6ea7ef06f06e505e81a64f14a470fddefa2
irb (1.15.2) sha256=222f32952e278da34b58ffe45e8634bf4afc2dc7aa9da23fed67e581aa50fdba irb (1.15.2) sha256=222f32952e278da34b58ffe45e8634bf4afc2dc7aa9da23fed67e581aa50fdba
jbuilder (2.13.0) sha256=7200a38a1c0081aa81b7a9757e7a299db75bc58cf1fd45ca7919a91627d227d6 jbuilder (2.13.0) sha256=7200a38a1c0081aa81b7a9757e7a299db75bc58cf1fd45ca7919a91627d227d6
json (2.12.2) sha256=ba94a48ad265605c8fa9a50a5892f3ba6a02661aa010f638211f3cb36f44abf4 json (2.12.2) sha256=ba94a48ad265605c8fa9a50a5892f3ba6a02661aa010f638211f3cb36f44abf4
@ -566,14 +552,13 @@ CHECKSUMS
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736 net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9 nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9
nokogiri (1.18.9) sha256=ac5a7d93fd0e3cef388800b037407890882413feccca79eb0272a2715a82fa33 nokogiri (1.18.8) sha256=8c7464875d9ca7f71080c24c0db7bcaa3940e8be3c6fc4bcebccf8b9a0016365
nokogiri (1.18.9-aarch64-linux-gnu) sha256=5bcfdf7aa8d1056a7ad5e52e1adffc64ef53d12d0724fbc6f458a3af1a4b9e32 nokogiri (1.18.8-aarch64-linux-gnu) sha256=36badd2eb281fca6214a5188e24a34399b15d89730639a068d12931e2adc210e
nokogiri (1.18.9-arm-linux-gnu) sha256=fe611ae65880e445a9c0f650d52327db239f3488626df4173c05beafd161d46e nokogiri (1.18.8-arm-linux-gnu) sha256=17de01ca3adf9f8e187883ed73c672344d3dbb3c260f88ffa1008e8dc255a28e
nokogiri (1.18.9-arm64-darwin) sha256=eea3f1f06463ff6309d3ff5b88033c4948d0da1ab3cc0a3a24f63c4d4a763979 nokogiri (1.18.8-arm64-darwin) sha256=483b5b9fb33653f6f05cbe00d09ea315f268f0e707cfc809aa39b62993008212
nokogiri (1.18.9-x86_64-darwin) sha256=e0d2deb03d3d7af8016e8c9df5ff4a7d692159cefb135cbb6a4109f265652348 nokogiri (1.18.8-x86_64-darwin) sha256=024cdfe7d9ae3466bba6c06f348fb2a8395d9426b66a3c82f1961b907945cc0c
nokogiri (1.18.9-x86_64-linux-gnu) sha256=b52f5defedc53d14f71eeaaf990da66b077e1918a2e13088b6a96d0230f44360 nokogiri (1.18.8-x86_64-linux-gnu) sha256=4a747875db873d18a2985ee2c320a6070c4a414ad629da625fbc58d1a20e5ecc
orm_adapter (0.5.0) sha256=aa5d0be5d540cbb46d3a93e88061f4ece6a25f6e97d6a47122beb84fe595e9b9 orm_adapter (0.5.0) sha256=aa5d0be5d540cbb46d3a93e88061f4ece6a25f6e97d6a47122beb84fe595e9b9
ostruct (0.6.2) sha256=6d7302a299e400a2c248d6ce0dad18fc3a5714e8096facc25ffd0c54ee57cfc0
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parser (3.3.8.0) sha256=2476364142b307fa5a1b1ece44f260728be23858a9c71078e956131a75453c45 parser (3.3.8.0) sha256=2476364142b307fa5a1b1ece44f260728be23858a9c71078e956131a75453c45
pg (1.5.9) sha256=761efbdf73b66516f0c26fcbe6515dc7500c3f0aa1a1b853feae245433c64fdc pg (1.5.9) sha256=761efbdf73b66516f0c26fcbe6515dc7500c3f0aa1a1b853feae245433c64fdc
@ -587,7 +572,7 @@ CHECKSUMS
puma (6.6.0) sha256=f25c06873eb3d5de5f0a4ebc783acc81a4ccfe580c760cfe323497798018ad87 puma (6.6.0) sha256=f25c06873eb3d5de5f0a4ebc783acc81a4ccfe580c760cfe323497798018ad87
raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rack (3.1.16) sha256=efb5606c351efc56b85b10c3493055d0d35209d23f44792ec4e1183eb0234635 rack (3.1.15) sha256=d12b3e9960d18a26ded961250f2c0e3b375b49ff40dbe6786e9c3b160cbffca4
rack-cors (3.0.0) sha256=7b95be61db39606906b61b83bd7203fa802b0ceaaad8fcb2fef39e097bf53f68 rack-cors (3.0.0) sha256=7b95be61db39606906b61b83bd7203fa802b0ceaaad8fcb2fef39e097bf53f68
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9 rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
@ -598,27 +583,25 @@ CHECKSUMS
railties (8.0.2) sha256=0d7c3f40c49ba74980f1bac1d4bb153a9331c5ee8a9631d89c7bf79db82e5cf9 railties (8.0.2) sha256=0d7c3f40c49ba74980f1bac1d4bb153a9331c5ee8a9631d89c7bf79db82e5cf9
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493
rdoc (6.14.2) sha256=9fdd44df130f856ae70cc9a264dfd659b9b40de369b16581f4ab746e42439226 rdoc (6.14.0) sha256=2c46de58d7129b8743fcf6d76e3db971bdc914150e15ac06b386549bd82ed7db
react-rails (3.2.1) sha256=2235db0b240517596b1cb3e26177ab5bc64d3a56579b0415ee242b1691f81f64 react-rails (3.2.1) sha256=2235db0b240517596b1cb3e26177ab5bc64d3a56579b0415ee242b1691f81f64
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae redis (5.4.0) sha256=798900d869418a9fc3977f916578375b45c38247a556b61d58cba6bb02f7d06b
redis-client (0.23.2) sha256=e33bab6682c8155cfef95e6dd296936bb9c2981a89fb578ace27a076fa2836fa redis-client (0.23.2) sha256=e33bab6682c8155cfef95e6dd296936bb9c2981a89fb578ace27a076fa2836fa
regexp_parser (2.10.0) sha256=cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61 regexp_parser (2.10.0) sha256=cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61
reline (0.6.2) sha256=1dad26a6008872d59c8e05244b119347c9f2ddaf4a53dce97856cd5f30a02846 reline (0.6.1) sha256=1afcc9d7cb1029cdbe780d72f2f09251ce46d3780050f3ec39c3ccc6b60675fb
responders (3.1.1) sha256=92f2a87e09028347368639cfb468f5fefa745cb0dc2377ef060db1cdd79a341a responders (3.1.1) sha256=92f2a87e09028347368639cfb468f5fefa745cb0dc2377ef060db1cdd79a341a
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9 rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
rqrcode (3.1.0) sha256=e2d5996375f6e9a013823c289ed575dbea678b8e0388574302c1fac563f098af
rqrcode_core (2.0.0) sha256=1e40b823ab57a96482a417fff5dd5c33645a00cea6ef5d9e342fecc5ef91d9ab
rspec-core (3.13.4) sha256=f9da156b7b775c82610a7b580624df51a55102f8c8e4a103b98f5d7a9fa23958 rspec-core (3.13.4) sha256=f9da156b7b775c82610a7b580624df51a55102f8c8e4a103b98f5d7a9fa23958
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81 rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81
rspec-rails (8.0.1) sha256=0c3700b10ab6d7c648c4cd554023d8c2b5b07e7f01205f7608f0c511cf686505 rspec-rails (8.0.0) sha256=977a508cd94d152db2068c6585470db5d0cd47eef56d5410b9531034fb9d97bf
rspec-support (3.13.4) sha256=184b1814f6a968102b57df631892c7f1990a91c9a3b9e80ef892a0fc2a71a3f7 rspec-support (3.13.4) sha256=184b1814f6a968102b57df631892c7f1990a91c9a3b9e80ef892a0fc2a71a3f7
rswag (2.16.0) sha256=f07ce41548b9bb51464c38bc7b95af22fee84b90f2d1197a515a623906353086 rswag (2.16.0) sha256=f07ce41548b9bb51464c38bc7b95af22fee84b90f2d1197a515a623906353086
rswag-api (2.16.0) sha256=b653f7bd92e98be18b01ab4525d88950d7b0960e293a99f856b9efcee3ae6074 rswag-api (2.16.0) sha256=b653f7bd92e98be18b01ab4525d88950d7b0960e293a99f856b9efcee3ae6074
rswag-specs (2.16.0) sha256=8ba26085c408b0bd2ed21dc8015c80f417c7d34c63720ab7133c2549b5bd2a91 rswag-specs (2.16.0) sha256=8ba26085c408b0bd2ed21dc8015c80f417c7d34c63720ab7133c2549b5bd2a91
rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f
rubocop (1.78.0) sha256=8b74a6f912eb4fd3e6878851f7f7f45dcad8c7185c34250d4f952b0ee80d6bc0 rubocop (1.76.0) sha256=b7515398e1280b3cb7e3e0c429933ca3597ea43b7d0f03cb3c2d97719851c411
rubocop-ast (1.45.1) sha256=94042e49adc17f187ba037b33f941ba7398fede77cdf4bffafba95190a473a3e rubocop-ast (1.45.0) sha256=0b4ade77d15f25b9e07214fb42fa98164f5316accea525e14e44bbb8f06f78d7
rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe
rubocop-rails (2.32.0) sha256=9fcc623c8722fe71e835e99c4a18b740b5b0d3fb69915d7f0777f00794b30490 rubocop-rails (2.32.0) sha256=9fcc623c8722fe71e835e99c4a18b740b5b0d3fb69915d7f0777f00794b30490
rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479 rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479
@ -628,7 +611,7 @@ CHECKSUMS
rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
shoulda-matchers (6.5.0) sha256=ef6b572b2bed1ac4aba6ab2c5ff345a24b6d055a93a3d1c3bfc86d9d499e3f44 shoulda-matchers (6.5.0) sha256=ef6b572b2bed1ac4aba6ab2c5ff345a24b6d055a93a3d1c3bfc86d9d499e3f44
solid_queue (1.2.1) sha256=7976b3690a08080ef63d1b11281f0b77398f7697dbeda0e2c5532682639d4b15 solid_queue (1.1.5) sha256=bae0c9d76310f4953ebc57466f2e8c78703a0fbf4b89d25756c23c88f9b6df9b
sprockets (4.2.1) sha256=951b13dd2f2fcae840a7184722689a803e0ff9d2702d902bd844b196da773f97 sprockets (4.2.1) sha256=951b13dd2f2fcae840a7184722689a803e0ff9d2702d902bd844b196da773f97
sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
@ -646,13 +629,12 @@ CHECKSUMS
web-console (4.2.1) sha256=e7bcf37a10ea2b4ec4281649d1cee461b32232d0a447e82c786e6841fd22fe20 web-console (4.2.1) sha256=e7bcf37a10ea2b4ec4281649d1cee461b32232d0a447e82c786e6841fd22fe20
websocket-driver (0.7.7) sha256=056d99f2cd545712cfb1291650fde7478e4f2661dc1db6a0fa3b966231a146b4 websocket-driver (0.7.7) sha256=056d99f2cd545712cfb1291650fde7478e4f2661dc1db6a0fa3b966231a146b4
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
wicked_pdf (2.8.2) sha256=648d9b0cec5a34adbc9bbf809731052a78119e2d6d323b9e4aa1383e1d683824
with_env (1.1.0) sha256=50b3e4f0a6cda8f90d8a6bd87a6261f6c381429abafb161c4c69ad4a0cd0b6e4 with_env (1.1.0) sha256=50b3e4f0a6cda8f90d8a6bd87a6261f6c381429abafb161c4c69ad4a0cd0b6e4
xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d
zeitwerk (2.7.3) sha256=b2e86b4a9b57d26ba68a15230dcc7fe6f040f06831ce64417b0621ad96ba3e85 zeitwerk (2.7.3) sha256=b2e86b4a9b57d26ba68a15230dcc7fe6f040f06831ce64417b0621ad96ba3e85
RUBY VERSION RUBY VERSION
ruby 3.4.3p32 ruby 3.4.2p28
BUNDLED WITH BUNDLED WITH
2.6.1 2.6.1

View File

@ -5,31 +5,21 @@
require 'csv' require 'csv'
class GuestsController < ApplicationController class GuestsController < ApplicationController
GUEST_PARAMS = { only: %i[id name status], include: { group: { only: %i[id name] } } }.freeze
skip_before_action :authenticate_user!, only: :update
def index def index
render json: Guest.includes(:group) render json: Guest.includes(:group)
.left_joins(:group) .left_joins(:group)
.order('groups.name' => :asc, invitation_id: :asc, name: :asc) .order('groups.name' => :asc, name: :asc)
.as_json(GUEST_PARAMS) .as_json(only: %i[id name status], include: { group: { only: %i[id name] } })
end end
def create def create
guest = Guest.create!(guest_params) Guest.create!(guest_params)
render json: guest.as_json(GUEST_PARAMS), status: :created render json: {}, status: :created
end end
def update def update
guest = Guest.find(params[:id]) Guest.find(params[:id]).update!(guest_params)
guest.update!(guest_params) render json: {}, status: :ok
if !user_signed_in? && guest.saved_change_to_status?
AdminMailer.with(guest_id: guest.id).attendance_change_email.deliver_later
end
render json: guest.as_json(GUEST_PARAMS), status: :ok
end end
def destroy def destroy
@ -40,6 +30,6 @@ class GuestsController < ApplicationController
private private
def guest_params def guest_params
user_signed_in? ? params.expect(guest: %i[name group_id status]) : params.expect(guest: %i[status]) params.expect(guest: %i[name group_id status])
end end
end end

View File

@ -2,46 +2,18 @@
# frozen_string_literal: true # frozen_string_literal: true
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
class InvitationsController < ApplicationController class InvitationsController < ApplicationController
skip_before_action :authenticate_user!, only: :show
def index def index
@invitations = Invitation.includes(:guests).all render json: Invitation.includes(:guests).as_json(
respond_to do |format| only: :id,
format.json do include: {
render json: @invitations.as_json( guests: {
only: :id, only: %i[id name]
include: { guests: { only: %i[id name] } } }
) }
end )
format.pdf do
pdf_html = ActionController::Base.new.render_to_string(
template: 'invitations/sheet',
layout: 'pdf',
locals: { invitations: @invitations }
)
pdf = WickedPdf.new.pdf_from_string(pdf_html)
send_data pdf, filename: "invitations_#{Time.current.strftime('%Y%m%d_%H%M%S')}.pdf"
end
end
end
def email
AdminMailer.with(wedding_id: ActsAsTenant.current_tenant.id).invitations_pdf_email.deliver_later
head :ok
end
def sheet; end
def show
invitation = Invitation.includes(:guests).find(params[:id])
if invitation
render json: invitation, only: :id, include: { guests: { only: %i[id name status] } }, status: :ok
else
render json: { error: 'Invitation not found' }, status: :not_found
end
end end
def create def create

View File

@ -3,8 +3,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class WebsitesController < ApplicationController class WebsitesController < ApplicationController
skip_before_action :authenticate_user!, only: :show
def show def show
render json: current_tenant.website.as_json(only: %i[content]) || {}, status: :ok render json: current_tenant.website.as_json(only: %i[content]) || {}, status: :ok
end end

View File

@ -12,8 +12,8 @@ class TableSimulatorJob < ApplicationJob
ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do
engine = VNS::Engine.new engine = VNS::Engine.new
engine.add_optimization(Tables::Swap) engine.add_perturbation(Tables::Swap)
engine.add_optimization(Tables::Shift) engine.add_perturbation(Tables::Shift)
initial_solution = Tables::Distribution.new(min_per_table: MIN_PER_TABLE, max_per_table: MAX_PER_TABLE) initial_solution = Tables::Distribution.new(min_per_table: MIN_PER_TABLE, max_per_table: MAX_PER_TABLE)
initial_solution.random_distribution(Guest.potential.shuffle) initial_solution.random_distribution(Guest.potential.shuffle)

View File

@ -1,45 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class AdminMailer < ApplicationMailer
def attendance_change_email
@guest = Guest.find(params[:guest_id])
ActsAsTenant.with_tenant(@guest.wedding) do
mail(
to: recipients,
subject: I18n.t(
'admin_mailer.attendance_change_email.subject',
name: @guest.name,
status: I18n.t("active_record.attributes.guest/status.#{@guest.status}")
)
)
end
end
def invitations_pdf_email
ActsAsTenant.with_tenant(Wedding.find(params[:wedding_id])) do
invitations = Invitation.includes(:guests).all
pdf_html = ActionController::Base.new.render_to_string(
template: 'invitations/sheet',
layout: 'pdf',
locals: { invitations: }
)
pdf = WickedPdf.new.pdf_from_string(pdf_html)
attachments["invitations_#{Time.current.strftime('%Y%m%d_%H%M%S')}.pdf"] = pdf
mail(
to: recipients,
subject: I18n.t('admin_mailer.invitations_pdf_email.subject')
)
end
end
private
def recipients
ActsAsTenant.current_tenant.users.pluck(:email)
end
end

View File

@ -3,16 +3,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
class << self default from: 'from@example.com'
private
def default_from
File.read('/run/secrets/smtp_user_name').strip
rescue Errno::ENOENT
'development@example.com'
end
end
default from: default_from
layout 'mailer' layout 'mailer'
end end

View File

@ -22,8 +22,4 @@
class Invitation < ApplicationRecord class Invitation < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding
has_many :guests, dependent: :nullify has_many :guests, dependent: :nullify
def url
"#{Rails.application.routes.url_helpers.root_url(slug: ActsAsTenant.current_tenant.slug)}/site/invitation/#{id}"
end
end end

View File

@ -23,6 +23,5 @@ class Wedding < ApplicationRecord
has_many :guests, dependent: :delete_all has_many :guests, dependent: :delete_all
has_many :groups, dependent: :delete_all has_many :groups, dependent: :delete_all
has_many :invitations, dependent: :delete_all has_many :invitations, dependent: :delete_all
has_many :users, dependent: :delete_all
has_one :website, dependent: :destroy has_one :website, dependent: :destroy
end end

View File

@ -5,12 +5,16 @@
class SerializableGuest < JSONAPI::Serializable::Resource class SerializableGuest < JSONAPI::Serializable::Resource
type 'guest' type 'guest'
attributes :id, :status attributes :id, :group_id, :status
attribute :name do attribute :name do
@object.name @object.name
end end
attribute :group_name do
@object.group.name
end
attribute :status do attribute :status do
@object.status.capitalize @object.status.capitalize
end end

View File

@ -16,7 +16,6 @@ class AffinityGroupsHierarchy < Array
end end
discomforts discomforts
invitation_counts
freeze freeze
end end
@ -55,16 +54,8 @@ class AffinityGroupsHierarchy < Array
Rational(dist, dist + 1) Rational(dist, dist + 1)
end end
def guest_count(invitation_id)
@invitation_counts[invitation_id] || 0
end
private private
def invitation_counts
@invitation_counts = Guest.where.not(invitation_id: nil).group(:invitation_id).count
end
def discomforts def discomforts
@discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id, @discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id,
:discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc| :discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc|

View File

@ -15,7 +15,7 @@ module Tables
end end
def breakdown def breakdown
@breakdown ||= { table_size_penalty:, cohesion_penalty:, invitations_penalty: } @breakdown ||= { table_size_penalty:, cohesion_penalty: }
end end
private private
@ -39,12 +39,6 @@ module Tables
10 * (cohesion_discomfort * 1.0 / table.size) 10 * (cohesion_discomfort * 1.0 / table.size)
end end
def invitations_penalty
2 * table.map(&:invitation_id)
.tally
.sum { |invitation_id, guests_in_table| hierarchy.guest_count(invitation_id) - guests_in_table }
end
# #
# Calculates the discomfort of the table based on the cohesion of the guests. The total discomfort # Calculates the discomfort of the table based on the cohesion of the guests. The total discomfort
# is calculated as the sum of the discomfort of each pair of guests. The discomfort of a pair of # is calculated as the sum of the discomfort of each pair of guests. The discomfort of a pair of

View File

@ -14,18 +14,17 @@ module Tables
attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy
def initialize(min_per_table:, max_per_table:, hierarchy: AffinityGroupsHierarchy.new) def initialize(min_per_table:, max_per_table:)
@min_per_table = min_per_table @min_per_table = min_per_table
@max_per_table = max_per_table @max_per_table = max_per_table
@hierarchy = hierarchy @hierarchy = AffinityGroupsHierarchy.new
@tables = [] @tables = []
end end
def random_distribution(people, random: Random.new) def random_distribution(people)
min_tables = (people.count * 1.0 / @max_per_table).ceil min_tables = (people.count * 1.0 / @max_per_table).ceil
max_tables = (people.count * 1.0 / @min_per_table).ceil max_tables = (people.count * 1.0 / @min_per_table).ceil
table_size = random.rand(min_tables..max_tables) @tables = people.in_groups(rand(min_tables..max_tables), false)
@tables = people.in_groups(table_size, false)
.map { |group| Table.new(group) } .map { |group| Table.new(group) }
.each { |table| table.min_per_table = @min_per_table } .each { |table| table.min_per_table = @min_per_table }
.each { |table| table.max_per_table = @max_per_table } .each { |table| table.max_per_table = @max_per_table }
@ -42,8 +41,7 @@ module Tables
end end
def deep_dup def deep_dup
self.class.new(min_per_table: @min_per_table, max_per_table: @max_per_table, self.class.new(min_per_table: @min_per_table, max_per_table: @max_per_table).tap do |new_distribution|
hierarchy: @hierarchy).tap do |new_distribution|
new_distribution.tables = @tables.map(&:dup) new_distribution.tables = @tables.map(&:dup)
end end
end end

View File

@ -1,33 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Tables
class WheelSwap
private attr_reader :initial_solution
def initialize(initial_solution)
@initial_solution = initial_solution
end
def call(size = 1)
Rails.logger.debug { "WheelSwap with size: #{size}" }
new_solution = @initial_solution.deep_dup
selected_guests = []
size.times do
selected_guests += new_solution.tables.map(&:pop)
end
selected_guests.shuffle!
tables = new_solution.tables.cycle
tables.next << selected_guests.pop while selected_guests.any?
new_solution.tables.each(&:reset)
new_solution
end
end
end

View File

@ -4,7 +4,6 @@
module VNS module VNS
class Engine class Engine
PERTURBATION_SIZES = [1, 1, 1, 2, 2, 3].freeze
class << self class << self
def sequence(elements) def sequence(elements)
elements = elements.to_a elements = elements.to_a
@ -16,11 +15,6 @@ module VNS
@target_function = function @target_function = function
end end
def add_optimization(klass)
@optimizations ||= Set.new
@optimizations << klass
end
def add_perturbation(klass) def add_perturbation(klass)
@perturbations ||= Set.new @perturbations ||= Set.new
@perturbations << klass @perturbations << klass
@ -30,58 +24,32 @@ module VNS
def run def run
raise 'No target function defined' unless @target_function raise 'No target function defined' unless @target_function
raise 'No optimizations defined' unless @optimizations raise 'No perturbations defined' unless @perturbations
raise 'No initial solution defined' unless @initial_solution raise 'No initial solution defined' unless @initial_solution
@perturbations ||= Set.new @best_solution = @initial_solution
@best_score = @target_function.call(@best_solution)
@current_solution = @initial_solution self.class.sequence(@perturbations).each do |perturbation|
@best_score = @target_function.call(@current_solution) optimize(perturbation)
run_all_optimizations
best_solution = @current_solution
50.times do
@current_solution = Tables::WheelSwap.new(best_solution).call(PERTURBATION_SIZES.sample)
@best_score = @target_function.call(@current_solution)
Rails.logger.debug { "After perturbation: #{@best_score}" }
run_all_optimizations
next unless best_solution.discomfort > @current_solution.discomfort
best_solution = @current_solution
Rails.logger.debug do
"Found better solution after perturbation optimization: #{@current_solution.discomfort}"
end
end end
best_solution @best_solution
end end
private private
def run_all_optimizations def optimize(perturbation_klass)
self.class.sequence(@optimizations).each do |optimization|
optimize(optimization)
Rails.logger.debug { "Finished optimization phase: #{optimization}" }
end
Rails.logger.debug { 'Finished all optimization phases' }
end
def optimize(optimization_klass)
loop do loop do
optimized = false optimized = false
optimization_klass.new(@current_solution).each do |alternative_solution| perturbation_klass.new(@best_solution).each do |alternative_solution|
score = @target_function.call(alternative_solution) score = @target_function.call(alternative_solution)
next if score >= @best_score next if score >= @best_score
@current_solution = alternative_solution.deep_dup @best_solution = alternative_solution.deep_dup
@best_score = score @best_score = score
optimized = true optimized = true
Rails.logger.debug { "[#{optimization_klass}] Found better solution with score: #{score}" }
break break
end end

View File

@ -1,17 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<p><%= I18n.t('admin_mailer.greeting') %>,</p>
<p>
<%= I18n.t('admin_mailer.attendance_change_email.paragraph_1', name: @guest.name) %>
</p>
<ul>
<li>
<strong><%= I18n.t("active_record.attributes.guest.status") %>:</strong> <%= I18n.t("active_record.attributes.guest/status.#{@guest.status}") %>
</li>
</ul>
<p>
<%= I18n.t("admin_mailer.attendance_change_email.notify_on_updates") %>
</p>

View File

@ -1,9 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<%= I18n.t('admin_mailer.greeting') %>,
<%= I18n.t('admin_mailer.attendance_change_email.paragraph_1', name: @guest.name) %>
- <%= I18n.t("active_record.attributes.guest.status") %>: <%= I18n.t("active_record.attributes.guest/status.#{@guest.status}") %>
<%= I18n.t("admin_mailer.attendance_change_email.notify_on_updates") %>

View File

@ -1,7 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<p><%= I18n.t('admin_mailer.greeting') %>,</p>
<p>
<%= I18n.t('admin_mailer.invitations_pdf_email.paragraph_1') %>
</p>

View File

@ -1,5 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<%= I18n.t('admin_mailer.greeting') %>,
<%= I18n.t('admin_mailer.invitations_pdf_email.paragraph_1') %>

View File

@ -1,33 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<% invitations.each_slice(4) do |invitation_group| %>
<table style="width: 100%; border-collapse: separate; border-spacing: 0 20px; margin-bottom: 40px;">
<% invitation_group.each do |invitation| %>
<tr>
<td style="width: 270px; height: 270px; text-align: center; vertical-align: middle; padding: 10px;">
<%= image_tag(RQRCode::QRCode.new(invitation.url).as_png(
bit_depth: 1,
border_modules: 4,
color_mode: ChunkyPNG::COLOR_GRAYSCALE,
color: "black",
file: nil,
fill: "white",
module_px_size: 6,
resize_exactly_to: false,
resize_gte_to: false,
size: 250
).to_data_url)
%>
</td>
<td style="vertical-align: middle; padding: 10px;">
<ul style="margin: 0; padding-left: 20px;">
<% invitation.guests.each do |guest| %>
<%= content_tag(:li, guest.name) %>
<% end %>
</ul>
</td>
</tr>
<% end %>
</table>
<div style="page-break-after: always;"></div>
<% end %>

View File

@ -1,12 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<!doctype html>
<html>
<head>
<meta charset='utf-8' />
</head>
<div id="content">
<%= yield %>
</div>
</body>
</html>

View File

@ -79,5 +79,4 @@ Rails.application.configure do
config.action_controller.raise_on_missing_callback_actions = true config.action_controller.raise_on_missing_callback_actions = true
config.hosts << "libre-wedding-planner.app.localhost" config.hosts << "libre-wedding-planner.app.localhost"
Rails.application.routes.default_url_options[:host] = "libre-wedding-planner.app.localhost"
end end

View File

@ -72,20 +72,6 @@ Rails.application.configure do
# config.active_job.queue_name_prefix = "wedding_planner_production" # config.active_job.queue_name_prefix = "wedding_planner_production"
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = begin
{
address: File.read("/run/secrets/smtp_address").strip,
port: File.read("/run/secrets/smtp_port").strip.to_i,
user_name: File.read("/run/secrets/smtp_user_name").strip,
password: File.read("/run/secrets/smtp_password").strip,
authentication: File.read("/run/secrets/smtp_authentication").strip.to_sym,
tls: true
}
rescue Errno::ENOENT
{}
end
# Ignore bad email addresses and do not raise email delivery errors. # 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. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
@ -108,7 +94,6 @@ Rails.application.configure do
# ] # ]
config.hosts << "app.libreweddingplanner.org" config.hosts << "app.libreweddingplanner.org"
Rails.application.routes.default_url_options[:host] = "app.libreweddingplanner.org"
# Skip DNS rebinding protection for the default health check endpoint. # Skip DNS rebinding protection for the default health check endpoint.
config.host_authorization = { exclude: ->(request) { request.path == "/up" } } config.host_authorization = { exclude: ->(request) { request.path == "/up" } }

View File

@ -10,7 +10,7 @@ Rswag::Ui.configure do |c|
# (under openapi_root) as JSON or YAML endpoints, then the list below should # (under openapi_root) as JSON or YAML endpoints, then the list below should
# correspond to the relative paths for those endpoints. # correspond to the relative paths for those endpoints.
c.openapi_endpoint '/api/api-docs/v1/swagger.yaml', 'API V1 Docs' c.swagger_endpoint '/api/api-docs/v1/swagger.yaml', 'API V1 Docs'
# Add Basic Auth in case your API is private # Add Basic Auth in case your API is private
# c.basic_auth_enabled = true # c.basic_auth_enabled = true

View File

@ -10,10 +10,4 @@ class Set
def to_table def to_table
Tables::Table.new(self) Tables::Table.new(self)
end end
def pop
element = self.to_a.sample
self.delete(element)
element
end
end end

View File

@ -1,32 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# WickedPDF Global Configuration
#
# Use this to set up shared configuration options for your entire application.
# Any of the configuration options shown here can also be applied to single
# models by passing arguments to the `render :pdf` call.
#
# To learn more, check out the README:
#
# https://github.com/mileszs/wicked_pdf/blob/master/README.md
WickedPdf.configure do |config|
# Path to the wkhtmltopdf executable: This usually isn't needed if using
# one of the wkhtmltopdf-binary family of gems.
# config.exe_path = '/usr/local/bin/wkhtmltopdf'
# or
# config.exe_path = Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf')
# Needed for wkhtmltopdf 0.12.6+ to use many wicked_pdf asset helpers
# config.enable_local_file_access = true
# Layout file to be used for all PDFs
# (but can be overridden in `render :pdf` calls)
# config.layout = 'pdf.html'
# Using wkhtmltopdf without an X server can be achieved by enabling the
# 'use_xvfb' flag. This will wrap all wkhtmltopdf commands around the
# 'xvfb-run' command, in order to simulate an X server.
#
# config.use_xvfb = true
end

View File

@ -1,22 +1,31 @@
# Files in the config/locales directory are used for internationalization and
# are automatically loaded by Rails. If you want to use locales other than
# English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
# <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more about the API, please read the Rails Internationalization guide
# at https://guides.rubyonrails.org/i18n.html.
#
# Be aware that YAML interprets the following case-insensitive strings as
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
# must be quoted to be interpreted as strings. For example:
#
# en:
# "yes": yup
# enabled: "ON"
en: en:
hello: "Hello world" hello: "Hello world"
active_record:
attributes:
guest:
status: Status
guest/status:
considered: Considered
invited: Invited
confirmed: Confirmed
declined: Declined
tentative: Tentative
admin_mailer:
greeting: "Dear user"
attendance_change_email:
subject: "%{name} has changed their attendance status: %{status}"
paragraph_1: "The guest %{name} has changed their attendance for the wedding."
notify_on_updates: "You will be notified of any further changes to their attendance status."
invitations_pdf_email:
subject: "Your wedding invitations are ready"
paragraph_1: "Your wedding invitations are ready. Please, find them attached to this email."

View File

@ -42,9 +42,7 @@ Rails.application.routes.draw do
resources :tables_arrangements, only: %i[index show create] resources :tables_arrangements, only: %i[index show create]
resources :summary, only: :index resources :summary, only: :index
resources :invitations, only: %i[show index create update destroy] do resources :invitations, only: %i[index create update destroy]
post :email, on: :collection
end
root to: redirect("/%{slug}") root to: redirect("/%{slug}")
end end

View File

@ -1,29 +0,0 @@
# frozen_string_literal: true
namespace :vns do
desc 'Benchmarks the efficiency of the VNS implementation'
task benchmark: :environment do
ActsAsTenant.with_tenant(Wedding.first) do
Rails.logger.info "There are #{Guest.potential.count} potential guests"
engine = VNS::Engine.new
engine.add_optimization(Tables::Swap)
engine.add_optimization(Tables::Shift)
hierarchy = AffinityGroupsHierarchy.new
initial_solution = Tables::Distribution.new(min_per_table: 8, max_per_table: 10, hierarchy:)
random = Random.new(561_163)
initial_solution.random_distribution(Guest.potential.shuffle(random:), random:)
engine.initial_solution = initial_solution
engine.target_function(&:discomfort)
solution = Rails.benchmark('VNS Benchmarking') { engine.run }
Rails.logger.info "Best solution found with discomfort: #{solution.discomfort}"
end
end
end

View File

@ -14,14 +14,14 @@ module Tables
describe '#calculate' do describe '#calculate' do
before do before do
allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_penalty: 5, invitations_penalty: 4) allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_discomfort: 3)
end end
let(:table) { Table.new(create_list(:guest, 6)) } let(:table) { Table.new(create_list(:guest, 6)) }
it 'returns the sum of the table size penalty and the average cohesion penalty', :aggregate_failures do it 'returns the sum of the table size penalty and the average cohesion penalty', :aggregate_failures do
expect(calculator.calculate).to eq(11) expect(calculator.calculate).to eq(7)
expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5, invitations_penalty: 4) expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5)
end end
end end
@ -73,102 +73,5 @@ module Tables
it { expect(calculator.send(:table_size_penalty)).to eq(10) } it { expect(calculator.send(:table_size_penalty)).to eq(10) }
end end
end end
describe '#cohesion_penalty' do
let(:calculator) { described_class.new(table:, hierarchy:) }
let(:table) { Table.new(guests) }
context 'when all guests belong to the same group' do
let(:guests) { create_list(:guest, 6, group: family) }
let(:hierarchy) { instance_double(AffinityGroupsHierarchy, discomfort: 0) }
it 'returns 0 as cohesion penalty' do
expect(calculator.send(:cohesion_penalty)).to eq(0)
end
end
context 'when guests belong to two different groups with discomfort 1' do
let(:guests) do
create_list(:guest, 3, group: family) +
create_list(:guest, 3, group: friends)
end
let(:hierarchy) do
instance_double(AffinityGroupsHierarchy, discomfort: 1)
end
it 'calculates the correct cohesion penalty' do
# 3 from family, 3 from friends: 3*3*1 = 9 discomfort
expect(calculator.send(:cohesion_penalty)).to eq(10 * (9.0 / 6))
end
end
context 'when guests belong to three groups with different discomforts' do
let(:guests) do
create_list(:guest, 2, group: family) +
create_list(:guest, 2, group: friends) +
create_list(:guest, 2, group: work)
end
let(:hierarchy) do
instance_double(AffinityGroupsHierarchy)
end
before do
allow(hierarchy).to receive(:discomfort).with(family.id, friends.id).and_return(0.5)
allow(hierarchy).to receive(:discomfort).with(family.id, work.id).and_return(1)
allow(hierarchy).to receive(:discomfort).with(friends.id, work.id).and_return(0.2)
allow(hierarchy).to receive(:discomfort).with(friends.id, family.id).and_return(0.5)
allow(hierarchy).to receive(:discomfort).with(work.id, family.id).and_return(1)
allow(hierarchy).to receive(:discomfort).with(work.id, friends.id).and_return(0.2)
end
it 'calculates the correct cohesion penalty' do
# pairs: (family, friends): 2*2*0.5 = 2
# (family, work): 2*2*1 = 4
# (friends, work): 2*2*0.2 = 0.8
# total discomfort = 2 + 4 + 0.8 = 6.8
expect(calculator.send(:cohesion_penalty)).to eq(10 * (6.8 / 6))
end
end
end
describe '#invitations_penalty' do
let(:invitation_a) { create(:invitation) }
let(:invitation_b) { create(:invitation) }
let(:invitation_c) { create(:invitation) }
let(:table) do
create_list(:guest, 2, invitation: invitation_a) +
create_list(:guest, 3, invitation: invitation_b) +
create_list(:guest, 4, invitation: invitation_c)
end
context 'when the table contains all members of an invitation' do
it 'returns 0 as penalty' do
expect(calculator.send(:invitations_penalty)).to eq(0)
end
end
context 'when there is an additional guest of one of the invitations that is not included' do
before do
create(:guest, invitation: invitation_a)
end
it 'returns the penalty for the missing guest' do
expect(calculator.send(:invitations_penalty)).to eq(2)
end
end
context 'when there are multiple guests missing from different invitations' do
before do
create(:guest, invitation: invitation_b)
create(:guest, invitation: invitation_c)
end
it 'returns 2x # of guests left out as the total penalty for all missing guests' do
expect(calculator.send(:invitations_penalty)).to eq(4)
end
end
end
end end
end end

View File

@ -1,28 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
require 'rails_helper'
module Tables
RSpec.describe WheelSwap do
context 'when the solution has three tables' do
let(:initial_solution) do
Distribution.new(min_per_table: 3, max_per_table: 3).tap do |distribution|
distribution.tables << Set[:a, :b, :c].to_table
distribution.tables << Set[:d, :e, :f].to_table
distribution.tables << Set[:g, :h, :i].to_table
end
end
it 'swaps a random guest from each table with a guest from another table', :aggregate_failures do
result = described_class.new(initial_solution).call
expect(result.tables.size).to eq(3)
expect(result.tables.map(&:size)).to all(eq(3))
expect(result.tables.map(&:to_a).flatten).to match_array((:a..:i).to_a)
end
end
end
end