├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── rails-hotwire ├── .dockerignore ├── .gitattributes ├── .gitignore ├── .kamal │ └── secrets ├── .rubocop.yml ├── .ruby-version ├── .yarnrc.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app │ ├── channels │ │ └── application_cable │ │ │ └── channel.rb │ ├── components │ │ ├── application_component.rb │ │ ├── badge_component.rb │ │ ├── tab_component.rb │ │ └── tab_item_component.rb │ ├── controllers │ │ ├── application_controller.rb │ │ ├── comments_controller.rb │ │ └── todos_controller.rb │ ├── helpers │ │ ├── application_helper.rb │ │ └── components_helper.rb │ ├── javascript │ │ ├── config │ │ │ └── tailwind.config.js │ │ ├── controllers │ │ │ └── index.js │ │ ├── entrypoints │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.scss │ ├── jobs │ │ ├── application_job.rb │ │ └── todos_cleanup_job.rb │ ├── models │ │ ├── application_record.rb │ │ ├── comment.rb │ │ ├── concerns │ │ │ └── sortable.rb │ │ └── todo.rb │ └── views │ │ ├── comments │ │ ├── _comment.html.erb │ │ ├── _form.html.erb │ │ └── _list.html.erb │ │ ├── layouts │ │ ├── _footer.html.erb │ │ └── application.html.erb │ │ ├── shared │ │ ├── _no_content.html.erb │ │ └── pagy │ │ │ └── _nav.html.erb │ │ └── todos │ │ ├── _form.html.erb │ │ ├── _list.html.erb │ │ ├── _tabs.html.erb │ │ ├── _todo.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── new.html.erb │ │ └── toggle_completed.turbo_stream.erb ├── bin │ ├── bundle │ ├── docker-entrypoint │ ├── jobs │ ├── rails │ ├── rake │ ├── setup │ ├── vite │ └── yarn ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── cache.yml │ ├── credentials.yml.enc │ ├── database.yml │ ├── deploy.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── application_controller_renderer.rb │ │ ├── backtrace_silencers.rb │ │ ├── content_security_policy.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── pagy.rb │ │ ├── permissions_policy.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── master.key │ ├── puma.rb │ ├── queue.yml │ ├── recurring.yml │ ├── routes.rb │ └── vite.json ├── data │ └── .keep ├── db │ ├── cache_schema.rb │ ├── migrate │ │ ├── 20200903175607_create_todos.rb │ │ └── 20240216030624_create_comment.rb │ ├── queue_schema.rb │ ├── schema.rb │ └── seeds.rb ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep ├── log │ └── .keep ├── package.json ├── postcss.config.js ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── favicon.ico │ └── robots.txt ├── test │ ├── controllers │ │ └── .keep │ ├── fixtures │ │ └── files │ │ │ └── .keep │ ├── helpers │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── models │ │ └── .keep │ └── test_helper.rb ├── vite.config.mts └── yarn.lock └── rails-vuejs ├── .gitignore ├── .kamal └── secrets ├── .rubocop.yml ├── .ruby-version ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── controllers │ ├── api │ │ └── v1 │ │ │ ├── base_controller.rb │ │ │ └── todos_controller.rb │ └── application_controller.rb ├── models │ ├── application_record.rb │ └── todo.rb ├── serializers │ └── todo_serializer.rb └── views │ └── .keep ├── bin ├── bundle ├── docker-entrypoint ├── rails ├── rake └── setup ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── deploy.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── pagy.rb │ ├── permissions_policy.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── master.key ├── puma.rb └── routes.rb ├── data └── .keep ├── db ├── migrate │ └── 20200908202034_create_todos.rb ├── schema.rb └── seeds.rb ├── frontend ├── .eslintrc.js ├── .gitignore ├── .node-version ├── .yarnrc.yml ├── index.html ├── jsconfig.json ├── package.json ├── postcss.config.js ├── src │ ├── components │ │ ├── App.vue │ │ ├── Shared │ │ │ ├── Footer.vue │ │ │ └── Pagination.vue │ │ └── Todos │ │ │ ├── Empty.vue │ │ │ ├── Form.vue │ │ │ ├── List.vue │ │ │ └── Search.vue │ ├── main.js │ ├── pages │ │ └── Todos │ │ │ ├── Edit.vue │ │ │ ├── Index.vue │ │ │ └── New.vue │ ├── router │ │ └── index.js │ ├── store │ │ ├── index.js │ │ └── modules │ │ │ └── todos.js │ ├── style.css │ └── utils │ │ └── fetch.js ├── tailwind.config.js ├── vite.config.mjs └── yarn.lock ├── log └── .keep └── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: guillaumebriday 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Guillaume Briday 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern datatables 2 | 3 | ## Concept Overview 4 | 5 | This project shows how to build a **modern** real life application with in two different ways. 6 | 7 | They are many ways to build **reactive** web interfaces but do we really need to add the complexity of JavaScript frameworks like [Vue.js](https://vuejs.org/) or [React](https://reactjs.org/)? 8 | 9 | These tools are very powerful but the complexity they add to a full-stack developer's workflow is **INSANE**. 10 | 11 | In this repo, you'll find two apps: 12 | 13 | 👉 One is build with [Rails](https://rubyonrails.org/) and [Hotwire](https://hotwire.dev/) aka NEW MAGIC. 14 | 15 | 👉 The other with Rails as a backend API and Vue.js as a full static SPA in the frontend. 16 | 17 | They have the exact same features and level of responsiveness. 18 | 19 | 👉 Here is a blog post with some details of how it works under the hood: https://guillaumebriday.fr/hotwire-reactive-search-form-without-javascript 20 | 21 | 👉 You can already use [Stimulus Rails Auto Submit](https://www.stimulus-components.com/docs/stimulus-auto-submit) to auto-submit your forms with Stimulus without writing a single line of custom JavaScript. 22 | 23 | ## Demo 24 | 25 | Both example are available online 🥳: 26 | 27 | - [https://rails-hotwire.guillaumebriday.fr](https://rails-hotwire.guillaumebriday.fr) 28 | - [https://rails-vuejs.guillaumebriday.fr](https://rails-vuejs.guillaumebriday.fr) 29 | 30 | ## Resources 31 | 32 | More and more developers come back from building dedicated SPAs and just want to use the power of Vue or React in a classic Backend application. 33 | 34 | Many projects help you in multiples frameworks: 35 | 36 | - [Hotwire](https://hotwire.dev/) 37 | - [Laravel Livewire](https://laravel-livewire.com/) 38 | - [htmx](https://github.com/bigskysoftware/htmx) 39 | - [Inertiajs](https://inertiajs.com/) 40 | - [Stimulus Reflex](https://docs.stimulusreflex.com/) 41 | - [Phoenix Live_View](https://github.com/phoenixframework/phoenix_live_view) 42 | - [Turbolinks](https://github.com/turbolinks/turbolinks) 43 | 44 | If you're still perplex about this old school approach, you should watch this video from Sam Stephenson: 45 | 46 | [Turbolinks 5: I Can’t Believe It’s Not Native! ](https://www.youtube.com/watch?v=SWEts0rlezA). 47 | 48 | ## Contributing 49 | 50 | Do not hesitate to contribute to the project by adapting or adding features ! Bug reports or pull requests are welcome. 51 | 52 | ## License 53 | 54 | This project is released under the [MIT](http://opensource.org/licenses/MIT) license. 55 | -------------------------------------------------------------------------------- /rails-hotwire/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | 6 | # Ignore bundler config. 7 | /.bundle 8 | 9 | # Ignore all environment files (except templates). 10 | /.env* 11 | !/.env*.erb 12 | 13 | # Ignore all default key files. 14 | /config/master.key 15 | /config/credentials/*.key 16 | 17 | # Ignore all logfiles and tempfiles. 18 | /log/* 19 | /tmp/* 20 | !/log/.keep 21 | !/tmp/.keep 22 | 23 | # Ignore pidfiles, but keep the directory. 24 | /tmp/pids/* 25 | !/tmp/pids/.keep 26 | 27 | # Ignore storage (uploaded files in development and any SQLite databases). 28 | /storage/* 29 | !/storage/.keep 30 | /tmp/storage/* 31 | !/tmp/storage/.keep 32 | 33 | # Ignore assets. 34 | /node_modules/ 35 | /app/assets/builds/* 36 | !/app/assets/builds/.keep 37 | /public/assets 38 | -------------------------------------------------------------------------------- /rails-hotwire/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | 7 | # Mark any vendored files as having been vendored. 8 | vendor/* linguist-vendored 9 | -------------------------------------------------------------------------------- /rails-hotwire/.gitignore: -------------------------------------------------------------------------------- 1 | rerun.txt 2 | *.swp 3 | *.swo 4 | *.DS_Store 5 | .bundle 6 | *.sqlite3* 7 | log/*.log 8 | .sass-cache/ 9 | tmp/* 10 | public/tmp/* 11 | public/uploads 12 | public/packs/ 13 | dump.rdb 14 | .byebug_history 15 | routes.txt 16 | .idea 17 | /public/packs 18 | /public/packs-test 19 | /public/assets 20 | /node_modules 21 | /coverage 22 | /storage 23 | yarn-error.log 24 | 25 | /public/packs 26 | /public/packs-test 27 | /node_modules 28 | /yarn-error.log 29 | yarn-debug.log* 30 | .yarn-integrity 31 | .yarn/* 32 | !.yarn/patches 33 | !.yarn/plugins 34 | !.yarn/releases 35 | !.yarn/sdks 36 | !.yarn/versions 37 | 38 | .env 39 | 40 | /app/assets/builds/* 41 | !/app/assets/builds/.keep 42 | 43 | # Vite Ruby 44 | /public/vite* 45 | -------------------------------------------------------------------------------- /rails-hotwire/.kamal/secrets: -------------------------------------------------------------------------------- 1 | KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD 2 | RAILS_MASTER_KEY=$RAILS_MASTER_KEY 3 | -------------------------------------------------------------------------------- /rails-hotwire/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-performance 3 | - rubocop-rspec 4 | - rubocop-rails 5 | 6 | AllCops: 7 | NewCops: enable 8 | Exclude: 9 | - db/schema.rb 10 | - node_modules/**/* 11 | - bin/**/* 12 | - tmp/**/* 13 | 14 | Rails/UnknownEnv: 15 | Environments: 16 | - production 17 | - development 18 | - test 19 | 20 | Style/Documentation: 21 | Enabled: false 22 | 23 | Style/EmptyMethod: 24 | Enabled: false 25 | 26 | Style/Lambda: 27 | Enabled: false 28 | 29 | Metrics/AbcSize: 30 | Enabled: false 31 | 32 | Metrics/MethodLength: 33 | Enabled: false 34 | 35 | Naming/MemoizedInstanceVariableName: 36 | Enabled: false 37 | 38 | Rails/UniqueValidationWithoutIndex: 39 | Enabled: false 40 | 41 | Style/ClassAndModuleChildren: 42 | Enabled: false 43 | 44 | Rails/I18nLocaleTexts: 45 | Enabled: false 46 | 47 | Style/HashSyntax: 48 | EnforcedShorthandSyntax: never 49 | -------------------------------------------------------------------------------- /rails-hotwire/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.1 2 | -------------------------------------------------------------------------------- /rails-hotwire/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /rails-hotwire/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: 4 | # docker build -t my-app . 5 | # docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY= my-app 6 | 7 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version 8 | ARG RUBY_VERSION=3.4.1 9 | FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base 10 | 11 | # Rails app lives here 12 | WORKDIR /rails 13 | 14 | # Install base packages 15 | RUN apt-get update -qq && \ 16 | apt-get install --no-install-recommends -y curl libjemalloc2 sqlite3 && \ 17 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 18 | 19 | # Set production environment 20 | ENV RAILS_ENV="production" \ 21 | BUNDLE_DEPLOYMENT="1" \ 22 | BUNDLE_PATH="/usr/local/bundle" \ 23 | BUNDLE_WITHOUT="development" 24 | 25 | # Throw-away build stage to reduce size of final image 26 | FROM base AS build 27 | 28 | # Install packages needed to build gems and node modules 29 | RUN apt-get update -qq && \ 30 | apt-get install --no-install-recommends -y build-essential git node-gyp pkg-config python-is-python3 && \ 31 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 32 | 33 | # Install JavaScript dependencies 34 | ARG NODE_VERSION=20.17.0 35 | ENV PATH=/usr/local/node/bin:$PATH 36 | RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ 37 | /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ 38 | corepack enable && \ 39 | rm -rf /tmp/node-build-master 40 | 41 | # Install application gems 42 | COPY Gemfile Gemfile.lock ./ 43 | RUN bundle install && \ 44 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ 45 | bundle exec bootsnap precompile --gemfile 46 | 47 | # Install node modules 48 | COPY package.json yarn.lock ./ 49 | RUN yarn install --immutable 50 | 51 | # Copy application code 52 | COPY . . 53 | 54 | # Precompile bootsnap code for faster boot times 55 | RUN bundle exec bootsnap precompile app/ lib/ 56 | 57 | # Precompiling assets for production without requiring secret RAILS_MASTER_KEY 58 | RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile 59 | 60 | 61 | RUN rm -rf node_modules 62 | 63 | 64 | # Final stage for app image 65 | FROM base 66 | 67 | # Copy built artifacts: gems, application 68 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 69 | COPY --from=build /rails /rails 70 | 71 | # Run and own only the runtime files as a non-root user for security 72 | RUN groupadd --system --gid 1000 rails && \ 73 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 74 | chown -R rails:rails db log tmp data 75 | USER 1000:1000 76 | 77 | # Entrypoint prepares the database. 78 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 79 | 80 | # Start the server by default, this can be overwritten at runtime 81 | EXPOSE 3000 82 | CMD ["./bin/rails", "server"] 83 | -------------------------------------------------------------------------------- /rails-hotwire/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gem 'bootsnap', require: false 6 | gem 'mission_control-jobs' 7 | gem 'propshaft' 8 | gem 'puma' 9 | gem 'rails' 10 | gem 'solid_cache' 11 | gem 'solid_queue' 12 | gem 'sqlite3' 13 | 14 | gem 'pagy' 15 | gem 'stimulus-rails' 16 | gem 'turbo-rails' 17 | gem 'view_component' 18 | gem 'vite_rails' 19 | 20 | group :development, :test do 21 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 22 | gem 'byebug' 23 | gem 'rubocop' 24 | gem 'rubocop-performance' 25 | gem 'rubocop-rails' 26 | gem 'rubocop-rspec' 27 | 28 | gem 'bcrypt_pbkdf' 29 | gem 'ed25519' 30 | gem 'kamal' 31 | end 32 | 33 | group :development do 34 | gem 'listen', '~> 3.3' 35 | end 36 | -------------------------------------------------------------------------------- /rails-hotwire/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (8.0.1) 5 | actionpack (= 8.0.1) 6 | activesupport (= 8.0.1) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | zeitwerk (~> 2.6) 10 | actionmailbox (8.0.1) 11 | actionpack (= 8.0.1) 12 | activejob (= 8.0.1) 13 | activerecord (= 8.0.1) 14 | activestorage (= 8.0.1) 15 | activesupport (= 8.0.1) 16 | mail (>= 2.8.0) 17 | actionmailer (8.0.1) 18 | actionpack (= 8.0.1) 19 | actionview (= 8.0.1) 20 | activejob (= 8.0.1) 21 | activesupport (= 8.0.1) 22 | mail (>= 2.8.0) 23 | rails-dom-testing (~> 2.2) 24 | actionpack (8.0.1) 25 | actionview (= 8.0.1) 26 | activesupport (= 8.0.1) 27 | nokogiri (>= 1.8.5) 28 | rack (>= 2.2.4) 29 | rack-session (>= 1.0.1) 30 | rack-test (>= 0.6.3) 31 | rails-dom-testing (~> 2.2) 32 | rails-html-sanitizer (~> 1.6) 33 | useragent (~> 0.16) 34 | actiontext (8.0.1) 35 | actionpack (= 8.0.1) 36 | activerecord (= 8.0.1) 37 | activestorage (= 8.0.1) 38 | activesupport (= 8.0.1) 39 | globalid (>= 0.6.0) 40 | nokogiri (>= 1.8.5) 41 | actionview (8.0.1) 42 | activesupport (= 8.0.1) 43 | builder (~> 3.1) 44 | erubi (~> 1.11) 45 | rails-dom-testing (~> 2.2) 46 | rails-html-sanitizer (~> 1.6) 47 | activejob (8.0.1) 48 | activesupport (= 8.0.1) 49 | globalid (>= 0.3.6) 50 | activemodel (8.0.1) 51 | activesupport (= 8.0.1) 52 | activerecord (8.0.1) 53 | activemodel (= 8.0.1) 54 | activesupport (= 8.0.1) 55 | timeout (>= 0.4.0) 56 | activestorage (8.0.1) 57 | actionpack (= 8.0.1) 58 | activejob (= 8.0.1) 59 | activerecord (= 8.0.1) 60 | activesupport (= 8.0.1) 61 | marcel (~> 1.0) 62 | activesupport (8.0.1) 63 | base64 64 | benchmark (>= 0.3) 65 | bigdecimal 66 | concurrent-ruby (~> 1.0, >= 1.3.1) 67 | connection_pool (>= 2.2.5) 68 | drb 69 | i18n (>= 1.6, < 2) 70 | logger (>= 1.4.2) 71 | minitest (>= 5.1) 72 | securerandom (>= 0.3) 73 | tzinfo (~> 2.0, >= 2.0.5) 74 | uri (>= 0.13.1) 75 | ast (2.4.2) 76 | base64 (0.2.0) 77 | bcrypt_pbkdf (1.1.1) 78 | benchmark (0.4.0) 79 | bigdecimal (3.1.9) 80 | bootsnap (1.18.4) 81 | msgpack (~> 1.2) 82 | builder (3.3.0) 83 | byebug (11.1.3) 84 | concurrent-ruby (1.3.4) 85 | connection_pool (2.4.1) 86 | crass (1.0.6) 87 | date (3.4.1) 88 | dotenv (3.1.7) 89 | drb (2.2.1) 90 | dry-cli (1.2.0) 91 | ed25519 (1.3.0) 92 | erubi (1.13.1) 93 | et-orbi (1.2.11) 94 | tzinfo 95 | ffi (1.17.0) 96 | fugit (1.11.1) 97 | et-orbi (~> 1, >= 1.2.11) 98 | raabro (~> 1.4) 99 | globalid (1.2.1) 100 | activesupport (>= 6.1) 101 | i18n (1.14.6) 102 | concurrent-ruby (~> 1.0) 103 | importmap-rails (2.1.0) 104 | actionpack (>= 6.0.0) 105 | activesupport (>= 6.0.0) 106 | railties (>= 6.0.0) 107 | io-console (0.8.0) 108 | irb (1.14.3) 109 | rdoc (>= 4.0.0) 110 | reline (>= 0.4.2) 111 | json (2.9.1) 112 | kamal (2.4.0) 113 | activesupport (>= 7.0) 114 | base64 (~> 0.2) 115 | bcrypt_pbkdf (~> 1.0) 116 | concurrent-ruby (~> 1.2) 117 | dotenv (~> 3.1) 118 | ed25519 (~> 1.2) 119 | net-ssh (~> 7.3) 120 | sshkit (>= 1.23.0, < 2.0) 121 | thor (~> 1.3) 122 | zeitwerk (>= 2.6.18, < 3.0) 123 | language_server-protocol (3.17.0.3) 124 | listen (3.9.0) 125 | rb-fsevent (~> 0.10, >= 0.10.3) 126 | rb-inotify (~> 0.9, >= 0.9.10) 127 | logger (1.6.4) 128 | loofah (2.23.1) 129 | crass (~> 1.0.2) 130 | nokogiri (>= 1.12.0) 131 | mail (2.8.1) 132 | mini_mime (>= 0.1.1) 133 | net-imap 134 | net-pop 135 | net-smtp 136 | marcel (1.0.4) 137 | method_source (1.1.0) 138 | mini_mime (1.1.5) 139 | minitest (5.25.4) 140 | mission_control-jobs (1.0.1) 141 | actioncable (>= 7.1) 142 | actionpack (>= 7.1) 143 | activejob (>= 7.1) 144 | activerecord (>= 7.1) 145 | importmap-rails (>= 1.2.1) 146 | irb (~> 1.13) 147 | railties (>= 7.1) 148 | stimulus-rails 149 | turbo-rails 150 | msgpack (1.7.5) 151 | mutex_m (0.3.0) 152 | net-imap (0.5.4) 153 | date 154 | net-protocol 155 | net-pop (0.1.2) 156 | net-protocol 157 | net-protocol (0.2.2) 158 | timeout 159 | net-scp (4.0.0) 160 | net-ssh (>= 2.6.5, < 8.0.0) 161 | net-sftp (4.0.0) 162 | net-ssh (>= 5.0.0, < 8.0.0) 163 | net-smtp (0.5.0) 164 | net-protocol 165 | net-ssh (7.3.0) 166 | nio4r (2.7.4) 167 | nokogiri (1.18.0-aarch64-linux-gnu) 168 | racc (~> 1.4) 169 | nokogiri (1.18.0-arm64-darwin) 170 | racc (~> 1.4) 171 | nokogiri (1.18.0-x86_64-linux-gnu) 172 | racc (~> 1.4) 173 | ostruct (0.6.1) 174 | pagy (9.3.3) 175 | parallel (1.26.3) 176 | parser (3.3.6.0) 177 | ast (~> 2.4.1) 178 | racc 179 | propshaft (1.1.0) 180 | actionpack (>= 7.0.0) 181 | activesupport (>= 7.0.0) 182 | rack 183 | railties (>= 7.0.0) 184 | psych (5.2.2) 185 | date 186 | stringio 187 | puma (6.5.0) 188 | nio4r (~> 2.0) 189 | raabro (1.4.0) 190 | racc (1.8.1) 191 | rack (3.1.8) 192 | rack-proxy (0.7.7) 193 | rack 194 | rack-session (2.0.0) 195 | rack (>= 3.0.0) 196 | rack-test (2.2.0) 197 | rack (>= 1.3) 198 | rackup (2.2.1) 199 | rack (>= 3) 200 | rails (8.0.1) 201 | actioncable (= 8.0.1) 202 | actionmailbox (= 8.0.1) 203 | actionmailer (= 8.0.1) 204 | actionpack (= 8.0.1) 205 | actiontext (= 8.0.1) 206 | actionview (= 8.0.1) 207 | activejob (= 8.0.1) 208 | activemodel (= 8.0.1) 209 | activerecord (= 8.0.1) 210 | activestorage (= 8.0.1) 211 | activesupport (= 8.0.1) 212 | bundler (>= 1.15.0) 213 | railties (= 8.0.1) 214 | rails-dom-testing (2.2.0) 215 | activesupport (>= 5.0.0) 216 | minitest 217 | nokogiri (>= 1.6) 218 | rails-html-sanitizer (1.6.2) 219 | loofah (~> 2.21) 220 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 221 | railties (8.0.1) 222 | actionpack (= 8.0.1) 223 | activesupport (= 8.0.1) 224 | irb (~> 1.13) 225 | rackup (>= 1.0.0) 226 | rake (>= 12.2) 227 | thor (~> 1.0, >= 1.2.2) 228 | zeitwerk (~> 2.6) 229 | rainbow (3.1.1) 230 | rake (13.2.1) 231 | rb-fsevent (0.11.2) 232 | rb-inotify (0.11.1) 233 | ffi (~> 1.0) 234 | rdoc (6.10.0) 235 | psych (>= 4.0.0) 236 | regexp_parser (2.10.0) 237 | reline (0.6.0) 238 | io-console (~> 0.5) 239 | rubocop (1.69.2) 240 | json (~> 2.3) 241 | language_server-protocol (>= 3.17.0) 242 | parallel (~> 1.10) 243 | parser (>= 3.3.0.2) 244 | rainbow (>= 2.2.2, < 4.0) 245 | regexp_parser (>= 2.9.3, < 3.0) 246 | rubocop-ast (>= 1.36.2, < 2.0) 247 | ruby-progressbar (~> 1.7) 248 | unicode-display_width (>= 2.4.0, < 4.0) 249 | rubocop-ast (1.37.0) 250 | parser (>= 3.3.1.0) 251 | rubocop-performance (1.23.0) 252 | rubocop (>= 1.48.1, < 2.0) 253 | rubocop-ast (>= 1.31.1, < 2.0) 254 | rubocop-rails (2.28.0) 255 | activesupport (>= 4.2.0) 256 | rack (>= 1.1) 257 | rubocop (>= 1.52.0, < 2.0) 258 | rubocop-ast (>= 1.31.1, < 2.0) 259 | rubocop-rspec (3.3.0) 260 | rubocop (~> 1.61) 261 | ruby-progressbar (1.13.0) 262 | securerandom (0.4.1) 263 | solid_cache (1.0.6) 264 | activejob (>= 7.2) 265 | activerecord (>= 7.2) 266 | railties (>= 7.2) 267 | solid_queue (1.1.2) 268 | activejob (>= 7.1) 269 | activerecord (>= 7.1) 270 | concurrent-ruby (>= 1.3.1) 271 | fugit (~> 1.11.0) 272 | railties (>= 7.1) 273 | thor (~> 1.3.1) 274 | sqlite3 (2.5.0-aarch64-linux-gnu) 275 | sqlite3 (2.5.0-arm64-darwin) 276 | sqlite3 (2.5.0-x86_64-linux-gnu) 277 | sshkit (1.23.2) 278 | base64 279 | net-scp (>= 1.1.2) 280 | net-sftp (>= 2.1.2) 281 | net-ssh (>= 2.8.0) 282 | ostruct 283 | stimulus-rails (1.3.4) 284 | railties (>= 6.0.0) 285 | stringio (3.1.2) 286 | thor (1.3.2) 287 | timeout (0.4.3) 288 | turbo-rails (2.0.11) 289 | actionpack (>= 6.0.0) 290 | railties (>= 6.0.0) 291 | tzinfo (2.0.6) 292 | concurrent-ruby (~> 1.0) 293 | unicode-display_width (3.1.3) 294 | unicode-emoji (~> 4.0, >= 4.0.4) 295 | unicode-emoji (4.0.4) 296 | uri (1.0.2) 297 | useragent (0.16.11) 298 | view_component (3.21.0) 299 | activesupport (>= 5.2.0, < 8.1) 300 | concurrent-ruby (~> 1.0) 301 | method_source (~> 1.0) 302 | vite_rails (3.0.19) 303 | railties (>= 5.1, < 9) 304 | vite_ruby (~> 3.0, >= 3.2.2) 305 | vite_ruby (3.9.1) 306 | dry-cli (>= 0.7, < 2) 307 | logger (~> 1.6) 308 | mutex_m 309 | rack-proxy (~> 0.6, >= 0.6.1) 310 | zeitwerk (~> 2.2) 311 | websocket-driver (0.7.6) 312 | websocket-extensions (>= 0.1.0) 313 | websocket-extensions (0.1.5) 314 | zeitwerk (2.7.1) 315 | 316 | PLATFORMS 317 | aarch64-linux 318 | arm64-darwin-22 319 | arm64-darwin-23 320 | arm64-darwin-24 321 | x86_64-linux 322 | 323 | DEPENDENCIES 324 | bcrypt_pbkdf 325 | bootsnap 326 | byebug 327 | ed25519 328 | kamal 329 | listen (~> 3.3) 330 | mission_control-jobs 331 | pagy 332 | propshaft 333 | puma 334 | rails 335 | rubocop 336 | rubocop-performance 337 | rubocop-rails 338 | rubocop-rspec 339 | solid_cache 340 | solid_queue 341 | sqlite3 342 | stimulus-rails 343 | turbo-rails 344 | view_component 345 | vite_rails 346 | 347 | BUNDLED WITH 348 | 2.5.23 349 | -------------------------------------------------------------------------------- /rails-hotwire/README.md: -------------------------------------------------------------------------------- 1 | # Rails with Hotwire 2 | 3 | This project is designed to use Rails in his all glory, [as a monolith](https://m.signalvnoise.com/the-majestic-monolith/) with [Hotwire](https://hotwired.dev/) aka the NEW MAGIC. 4 | 5 | ## Getting started 6 | 7 | ### Install 8 | 9 | Development environment requirements: 10 | - [Ruby](https://www.ruby-lang.org/en/) 11 | - [Node](https://nodejs.org/en/) 12 | 13 | ```bash 14 | $ git clone git@github.com:guillaumebriday/modern-datatables.git && cd modern-datatables/rails-hotwire 15 | $ bundle 16 | $ bundle exec rails db:migrate 17 | $ bundle exec rails s 18 | ``` 19 | 20 | ```bash 21 | $ yarn 22 | $ yarn dev 23 | ``` 24 | 25 | Open [http://localhost:3000](http://localhost:3000) in your browser. 26 | -------------------------------------------------------------------------------- /rails-hotwire/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | 5 | require_relative 'config/application' 6 | 7 | Rails.application.load_tasks 8 | -------------------------------------------------------------------------------- /rails-hotwire/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /rails-hotwire/app/components/application_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationComponent < ViewComponent::Base 4 | end 5 | -------------------------------------------------------------------------------- /rails-hotwire/app/components/badge_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class BadgeComponent < ApplicationComponent 4 | def initialize(id: nil, classes: nil, value: nil, variant: nil) # rubocop:disable Lint/MissingSuper 5 | @id = id 6 | @classes = classes 7 | @value = value 8 | @variant = variant 9 | end 10 | 11 | def call 12 | tag.span( 13 | @value, 14 | id: @id, 15 | class: class_names( 16 | 'inline-block text-xs text-center px-2 py-1 align-middle rounded-md font-semibold ring-1 ring-inset', 17 | @classes, 18 | colors 19 | ) 20 | ) 21 | end 22 | 23 | private 24 | 25 | def colors 26 | return if @variant.blank? 27 | 28 | { 29 | secondary: 'bg-gray-100 text-gray-800 ring-gray-200', 30 | danger: 'bg-red-100 text-red-800 ring-red-200', 31 | warning: 'bg-yellow-100 text-yellow-800 ring-yellow-200', 32 | success: 'bg-green-100 text-green-800 ring-green-200', 33 | info: 'bg-blue-100 text-blue-800 ring-blue-200' 34 | }[@variant.to_sym] 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /rails-hotwire/app/components/tab_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TabComponent < ApplicationComponent 4 | renders_many :items, TabItemComponent 5 | 6 | def render? 7 | items.any? 8 | end 9 | 10 | def call 11 | tag.div(class: 'border-b border-gray-200 mb-3') do 12 | tag.nav(class: '-mb-px flex gap-8') do 13 | safe_join(items) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /rails-hotwire/app/components/tab_item_component.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TabItemComponent < ApplicationComponent 4 | def initialize(options = {}) # rubocop:disable Lint/MissingSuper 5 | @options = options 6 | 7 | @path = @options.delete(:path) 8 | @name = @options.delete(:name) 9 | @count = @options.delete(:count) 10 | @active = @options.delete(:active) { true } 11 | end 12 | 13 | def call 14 | helpers.link_to( 15 | @path, 16 | class: class_names('flex gap-2 whitespace-nowrap border-b-2 py-3 px-1 text-sm font-medium', extra_class), 17 | data: { turbo_action: 'replace' } 18 | ) do 19 | concat(@name) 20 | concat(helpers.badge_tag(variant: @active ? :info : :secondary, value: @count)) 21 | end 22 | end 23 | 24 | private 25 | 26 | def extra_class 27 | return 'border-blue-500 text-blue-600' if @active 28 | 29 | 'border-transparent text-gray-500 hover:border-gray-200 hover:text-gray-700' 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /rails-hotwire/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | include Pagy::Backend 5 | end 6 | -------------------------------------------------------------------------------- /rails-hotwire/app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CommentsController < ApplicationController 4 | before_action :set_todo, only: %i[index] 5 | before_action :set_comment, only: %i[destroy] 6 | 7 | def index 8 | pagy, comments = pagy @todo.comments.order(id: :desc) 9 | 10 | render partial: 'comments/list', 11 | locals: { 12 | todo: @todo, 13 | pagy: pagy, 14 | comments: comments 15 | } 16 | end 17 | 18 | def create 19 | comment = Comment.new(comment_params) 20 | 21 | if comment.save 22 | render turbo_stream: [ 23 | turbo_stream.prepend('comments', partial: 'comments/comment', locals: { comment: comment }), 24 | turbo_stream.replace('comment_form', partial: 'comments/form', 25 | locals: { comment: Comment.new(todo: comment.todo) }), 26 | turbo_stream.update('comments_count', comment.todo.comments.count), 27 | turbo_stream.remove('no_content') 28 | ] 29 | else 30 | render turbo_stream: turbo_stream.replace('comment_form', partial: 'comments/form', locals: { comment: comment }) 31 | end 32 | end 33 | 34 | def destroy 35 | return unless @comment.destroy 36 | 37 | render turbo_stream: [ 38 | turbo_stream.remove(@comment), 39 | turbo_stream.update('comments_count', @comment.todo.comments.count) 40 | ] 41 | end 42 | 43 | private 44 | 45 | def set_todo 46 | @todo = Todo.find(params[:todo_id]) 47 | end 48 | 49 | def set_comment 50 | @comment = Comment.find(params[:id]) 51 | end 52 | 53 | def comment_params 54 | params.require(:comment).permit(:description, :todo_id) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /rails-hotwire/app/controllers/todos_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TodosController < ApplicationController 4 | before_action :set_todo, only: %i[edit update toggle_completed] 5 | 6 | def index 7 | params[:sort] ||= '-created_at' 8 | 9 | @title = 'Todos' 10 | @pagy, @todos = pagy Todo 11 | .by_description(params[:description]) 12 | .by_completed(params[:completed]) 13 | .apply_sort(params) 14 | 15 | set_counts 16 | end 17 | 18 | def new 19 | @title = 'Create todo' 20 | @todo = Todo.new 21 | end 22 | 23 | def edit 24 | @title = 'Edit todo' 25 | end 26 | 27 | def create 28 | @todo = Todo.new(todo_params) 29 | 30 | respond_to do |format| 31 | if @todo.save 32 | format.html { redirect_to todos_path, notice: 'Todo was successfully created.' } 33 | else 34 | format.turbo_stream { render turbo_stream: turbo_stream.replace(@todo, partial: 'todos/form') } 35 | format.html { render :new } 36 | end 37 | end 38 | end 39 | 40 | def update 41 | respond_to do |format| 42 | if @todo.update(todo_params) 43 | format.html { redirect_to todos_path notice: 'Todo was successfully updated.' } 44 | else 45 | format.turbo_stream { render turbo_stream: turbo_stream.replace(@todo, partial: 'todos/form') } 46 | format.html { render :edit } 47 | end 48 | end 49 | end 50 | 51 | def toggle_completed 52 | @todo.update(completed: !@todo.completed?) 53 | 54 | set_counts 55 | end 56 | 57 | private 58 | 59 | def set_todo 60 | @todo = Todo.find(params[:id]) 61 | end 62 | 63 | def set_counts 64 | @all_count = Todo.async_count 65 | @completed_count = Todo.by_completed(true).async_count 66 | @in_progress_count = Todo.by_completed(false).async_count 67 | end 68 | 69 | def todo_params 70 | params 71 | .require(:todo) 72 | .permit( 73 | :description, 74 | :completed 75 | ) 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /rails-hotwire/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | include Pagy::Frontend 5 | 6 | def sort_link_to(name = nil, sort_name = nil, &block) # rubocop:disable Metrics/PerceivedComplexity,Metrics/CyclomaticComplexity 7 | if block.present? 8 | sort_name = name 9 | name = capture(&block) 10 | end 11 | 12 | current_sort = params[:sort] 13 | current_sort = params[:sort][1..] if params[:sort].present? && params[:sort].start_with?('-') 14 | 15 | icon = if current_sort != sort_name || current_sort.blank? 16 | <<~HTML.html_safe # rubocop:disable Rails/OutputSafety 17 | 18 | 19 | 20 | HTML 21 | elsif params[:sort].start_with?('-') && current_sort == sort_name 22 | <<~HTML.html_safe # rubocop:disable Rails/OutputSafety 23 | 24 | 25 | 26 | HTML 27 | else 28 | <<~HTML.html_safe # rubocop:disable Rails/OutputSafety 29 | 30 | 31 | 32 | HTML 33 | end 34 | 35 | sort = if current_sort != sort_name || !params[:sort].start_with?('-') 36 | "-#{sort_name}" 37 | else 38 | sort_name 39 | end 40 | 41 | link_to url_for(permitted_params.merge(sort: sort)), data: { turbo_action: 'replace' } do 42 | [ 43 | name, 44 | icon 45 | ].join.html_safe # rubocop:disable Rails/OutputSafety 46 | end 47 | end 48 | 49 | def permitted_params 50 | params.slice(:page, :per_page, :completed).permit!.to_h 51 | end 52 | 53 | def timeago(date, format: :long) 54 | return if date.blank? 55 | 56 | content = I18n.l(date, format: format) 57 | 58 | tag.time(content, 59 | title: content, 60 | data: { 61 | controller: 'timeago', 62 | timeago_datetime_value: date.iso8601, 63 | timeago_add_suffix_value: true 64 | }) 65 | end 66 | 67 | def loading_tag 68 | <<~HTML.html_safe # rubocop:disable Rails/OutputSafety 69 |
70 | 74 | 75 | Loading... 76 |
77 | HTML 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /rails-hotwire/app/helpers/components_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ComponentsHelper 4 | UI_HELPERS = { 5 | tab_component: TabComponent, 6 | badge_tag: BadgeComponent 7 | }.freeze 8 | 9 | UI_HELPERS.each do |name, component| 10 | define_method name do |*args, **kwargs, &block| 11 | render component.new(*args, **kwargs), &block 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /rails-hotwire/app/javascript/config/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './app/views/**/*.html.erb', 5 | './app/helpers/**/*.rb', 6 | './app/components/**/*.rb', 7 | './app/assets/stylesheets/**/*.css', 8 | './app/javascript/**/*.js' 9 | ], 10 | plugins: [ 11 | require('@tailwindcss/forms') 12 | ], 13 | theme: { 14 | extend: { 15 | boxShadow: { 16 | 'outline-blue': `0 0 0 3px rgba(191, 219, 254, .5)`, 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /rails-hotwire/app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | import { Application } from '@hotwired/stimulus' 2 | import Timeago from "@stimulus-components/timeago" 3 | import AutoSubmit from '@stimulus-components/auto-submit' 4 | 5 | const application = Application.start() 6 | 7 | application.register("timeago", Timeago) 8 | application.register("auto-submit", AutoSubmit) 9 | -------------------------------------------------------------------------------- /rails-hotwire/app/javascript/entrypoints/application.js: -------------------------------------------------------------------------------- 1 | import "@hotwired/turbo-rails" 2 | import "@/controllers" 3 | 4 | import '../stylesheets/application.scss' 5 | -------------------------------------------------------------------------------- /rails-hotwire/app/javascript/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @use "tailwindcss/base"; 2 | @use "tailwindcss/components"; 3 | @use "tailwindcss/utilities"; 4 | 5 | .page { 6 | @apply relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white transition ease-in-out duration-150; 7 | 8 | &:hover { 9 | @apply text-gray-500; 10 | } 11 | 12 | &:focus { 13 | @apply outline-none shadow-outline-blue border-blue-300; 14 | } 15 | 16 | &:active { 17 | @apply bg-gray-100 text-gray-700; 18 | } 19 | } 20 | 21 | .page--link { 22 | @apply -ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm leading-5 font-medium text-gray-700 transition ease-in-out duration-150; 23 | 24 | &:focus { 25 | @apply z-10 outline-none border-blue-300 shadow-outline-blue; 26 | } 27 | 28 | &:active { 29 | @apply bg-gray-100 text-gray-700; 30 | } 31 | 32 | &:hover, 33 | &.active { 34 | @apply bg-blue-100 text-blue-800; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rails-hotwire/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | queue_as :default 5 | end 6 | -------------------------------------------------------------------------------- /rails-hotwire/app/jobs/todos_cleanup_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TodosCleanupJob < ApplicationJob 4 | def perform 5 | Todo.where(completed: true, created_at: ..2.years.ago).destroy_all 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /rails-hotwire/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | -------------------------------------------------------------------------------- /rails-hotwire/app/models/comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Comment < ApplicationRecord 4 | belongs_to :todo, touch: true 5 | 6 | validates :description, 7 | presence: true 8 | end 9 | -------------------------------------------------------------------------------- /rails-hotwire/app/models/concerns/sortable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Sortable 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | def self.apply_sort(params) 8 | sort = params[:sort] 9 | 10 | return all if sort.blank? 11 | 12 | direction = :asc 13 | 14 | if sort.start_with?('-') 15 | sort = sort[1..] 16 | direction = :desc 17 | end 18 | 19 | scope = "order_by_#{sort}" 20 | 21 | return send(scope, direction) if respond_to?(scope) 22 | 23 | all 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /rails-hotwire/app/models/todo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Todo < ApplicationRecord 4 | include Sortable 5 | 6 | after_update_commit { broadcast_replace_to 'todos', partial: 'todos/todo', locals: { todo: self } } 7 | 8 | has_many :comments, dependent: :destroy 9 | 10 | validates :description, 11 | uniqueness: true, 12 | presence: true 13 | 14 | scope :order_by_created_at, ->(direction = :asc) { order(created_at: direction) } 15 | scope :order_by_description, ->(direction = :asc) { order(description: direction) } 16 | scope :order_by_completed, ->(direction = :asc) { order(completed: direction) } 17 | scope :by_description, ->(description) { 18 | return if description.blank? 19 | 20 | where(arel_table[:description].matches("%#{description}%")) 21 | } 22 | 23 | scope :by_completed, ->(completed) { 24 | return if completed.nil? 25 | 26 | where(completed: completed) 27 | } 28 | end 29 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag comment do %> 2 |
3 |
4 |
5 |

6 | Profile picture 11 | Guillaume Briday 12 |

13 | 14 |

15 | <%= timeago(comment.created_at) %> 16 |

17 | 18 | <%= button_to comment_path(comment), method: :delete, data: { turbo_confirm: 'Are you sure?' } do %> 19 | 20 | 21 | 22 | <% end %> 23 |
24 |
25 | 26 |

27 | <%= comment.description %> 28 |

29 |
30 | <% end %> 31 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/comments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: comment, id: 'comment_form', class: 'mb-8' do |f| %> 2 | <%= f.hidden_field :todo_id, value: comment.todo_id %> 3 | 4 |
5 | <%= f.label :description, class: "block text-sm font-medium leading-5 text-gray-700" %> 6 | 7 |
8 | <%= f.text_area :description, 9 | placeholder: 'Your comment', 10 | class: class_names("mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50", 'border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red': f.object.errors[:description].present?) %> 11 |
12 | 13 |

14 | <%= f.object.errors.full_messages_for(:description).first %> 15 |

16 |
17 | 18 | 19 | <%= f.submit 'Submit', class: "inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-500 focus:outline-none focus:border-blue-700 focus:shadow-outline-blue active:bg-blue-700 transition duration-150 ease-in-out" %> 20 | 21 | <% end %> 22 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/comments/_list.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag 'comments' do %> 2 | <%= turbo_frame_tag "comments_#{pagy.page}" do %> 3 | <% if comments.present? %> 4 | <%= render partial: 'comments/comment', 5 | collection: comments, 6 | cached: true %> 7 | <% else %> 8 | <%= render 'shared/no_content' %> 9 | <% end %> 10 | 11 | <% if pagy.next %> 12 | <%= turbo_frame_tag "comments_#{pagy.next}", src: comments_path(todo_id: todo.id, page: pagy.next), loading: :lazy do %> 13 | <%= loading_tag %> 14 | <% end %> 15 | <% end %> 16 | <% end %> 17 | <% end %> 18 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/layouts/_footer.html.erb: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= @title %> - Modern Datatables 7 | 8 | <%= csrf_meta_tags %> 9 | <%= csp_meta_tag %> 10 | 11 | <%= turbo_refreshes_with method: :morph, scroll: :preserve %> 12 | 13 | 14 | 15 | <%= vite_client_tag %> 16 | <%= vite_javascript_tag 'application', 'data-turbo-track': 'reload' %> 17 | 18 | 19 | 20 |
21 |
22 | <%= yield %> 23 |
24 | 25 | <%= render "layouts/footer" %> 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/shared/_no_content.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |
8 | 9 |
10 |

11 | There is nothing here. 12 |

13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/shared/pagy/_nav.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | link = pagy_anchor(pagy, anchor_string: "data-turbo-action='advance'") 3 | %> 4 | 5 | <% content_for :prev do %> 6 |
7 | 8 | 9 | 10 |
11 | <% end %> 12 | 13 | <% content_for :next do %> 14 |
15 | 16 | 17 | 18 |
19 | <% end %> 20 | 21 | 91 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: @todo, id: dom_id(@todo) do |f| %> 2 |
3 |
4 | <%= f.label :description, class: "block text-sm font-medium leading-5 text-gray-700" %> 5 | 6 |
7 | <%= f.text_field :description, 8 | placeholder: 'Your description', 9 | class: class_names("mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50", 'border-red-300 text-red-900 placeholder-red-300 focus:border-red-300 focus:shadow-outline-red': f.object.errors[:description].present?) %> 10 |
11 | 12 |

13 | <%= f.object.errors.full_messages_for(:description).first %> 14 |

15 |
16 |
17 | 18 |
19 |
20 | <%= f.check_box :completed, class: "rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-offset-0 focus:ring-blue-200 focus:ring-opacity-50" %> 21 |
22 | 23 |
24 | <%= f.label :completed, class: "font-medium text-gray-700" %> 25 |
26 |
27 | 28 |
29 |
30 | 31 | <%= link_to 'Back', todos_path, class: "py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition duration-150 ease-in-out" %> 32 | 33 | 34 | 35 | <%= f.submit 'Save', class: "inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-500 focus:outline-none focus:border-blue-700 focus:shadow-outline-blue active:bg-blue-700 transition duration-150 ease-in-out" %> 36 | 37 |
38 |
39 | <% end %> 40 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/_list.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag 'todos_list' do %> 2 | <%= render 'todos/tabs' %> 3 | 4 | <% if @todos.present? %> 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | <%= sort_link_to 'Description', 'description' %> 13 |
14 | 15 |
16 | <%= sort_link_to 'Completed?', 'completed' %> 17 |
18 | 19 |
20 |
21 | 22 | <%= render partial: 'todos/todo', 23 | collection: @todos, 24 | cached: true %> 25 |
26 |
27 |
28 |
29 |
30 | 31 | <%= render 'shared/pagy/nav', pagy: @pagy %> 32 | <% else %> 33 | <%= render 'shared/no_content' %> 34 | <% end %> 35 | <% end %> 36 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/_tabs.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% 3 | links = [ 4 | { 5 | name: 'All', 6 | path: todos_path(permitted_params.merge(completed: nil)), 7 | count: @all_count.value, 8 | active: params[:completed].blank? 9 | }, 10 | { 11 | name: 'Completed', 12 | path: todos_path(permitted_params.merge(completed: true)), 13 | count: @completed_count.value, 14 | active: params[:completed] == "true" 15 | }, 16 | { 17 | name: 'In progress', 18 | path: todos_path(permitted_params.merge(completed: false)), 19 | count: @in_progress_count.value, 20 | active: params[:completed] == "false" 21 | } 22 | ] 23 | %> 24 | 25 | <%= tab_component do |tabs| %> 26 | <% links.each do |link| %> 27 | <%= tabs.with_item(link) %> 28 | <% end %> 29 | <% end %> 30 |
31 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/_todo.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_frame_tag todo, class: 'table-row-group' do %> 2 |
3 |
4 | <%= todo.description %> 5 |
6 | 7 |
8 | <%= button_to "#{todo.completed?}", 9 | toggle_completed_todo_path(todo, todo_counter: local_assigns[:todo_counter]), 10 | method: :patch, 11 | class: "cursor-pointer inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 #{todo.completed? ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}" %> 12 |
13 | 14 |
15 | <%= link_to edit_todo_path(todo), class: 'text-blue-600 hover:text-blue-900', data: { turbo_frame: '_top' } do %> 16 | 17 | 18 | 19 | <% end %> 20 |
21 |
22 | <% end %> 23 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | Editing Todo 4 |

5 |
6 | 7 | <%= render 'todos/form' %> 8 | 9 |

10 | Comments <%= badge_tag(value: @todo.comments.count, variant: :info, id: 'comments_count') %> 11 |

12 | 13 | <%= render 'comments/form', comment: Comment.new(todo: @todo) %> 14 | 15 | <%= turbo_frame_tag "comments", src: comments_path(todo_id: @todo.id), loading: 'lazy' do %> 16 | <%= loading_tag %> 17 | <% end %> 18 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream_from 'todos' %> 2 | 3 |
4 |
5 |
6 |

7 | Modern Datatables 8 |

9 | 10 |

11 | This demo is build with 12 | <%= link_to "Hotwire", 'https://hotwire.dev/', class: 'underline hover:text-gray-900' %> 13 | and 14 | <%= link_to "Rails", 'https://rubyonrails.org/', class: 'underline hover:text-gray-900' %>. 15 |

16 | 17 |

18 | Why this project? 19 | Find out more on 20 | <%= link_to "Github", 'https://github.com/guillaumebriday/modern-datatables', class: 'underline hover:text-gray-900' %>. 21 |

22 |
23 |
24 |
25 | 26 |
27 |

28 | Todos 29 |

30 | 31 |
32 | 33 | <%= link_to 'Create todo', new_todo_path, class: 'inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-700 active:bg-blue-700 transition duration-150 ease-in-out' %> 34 | 35 |
36 |
37 | 38 | <%= form_for todos_url, 39 | method: :get, 40 | html: { 41 | class: "pb-5 flex mb-0 gap-2", 42 | data: { 43 | controller: 'auto-submit', 44 | turbo_frame: 'todos_list', 45 | turbo_action: 'advance' 46 | } 47 | } do %> 48 |
49 |
50 | 51 | 52 | 53 |
54 | 55 | <%= search_field_tag :description, 56 | params[:description], 57 | class: "pl-10 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50", 58 | placeholder: "Search...", 59 | data: { action: 'keyup->auto-submit#save' } %> 60 |
61 | <% end %> 62 | 63 | <%= render 'todos/list' %> 64 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | New Todo 4 |

5 |
6 | 7 | <%= render 'form' %> 8 | -------------------------------------------------------------------------------- /rails-hotwire/app/views/todos/toggle_completed.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.replace( 2 | @todo, 3 | partial: 'todos/todo', 4 | locals: { 5 | todo: @todo, 6 | todo_counter: params[:todo_counter]&.to_i 7 | }) %> 8 | 9 | <%= turbo_stream.replace('todos_tabs', partial: 'todos/tabs') %> 10 | -------------------------------------------------------------------------------- /rails-hotwire/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /rails-hotwire/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -f /usr/lib/*/libjemalloc.so.2 ]; then 5 | LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2) $LD_PRELOAD" 6 | fi 7 | 8 | # If running the rails server then create or migrate existing database 9 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 10 | ./bin/rails db:prepare 11 | fi 12 | 13 | exec "${@}" 14 | -------------------------------------------------------------------------------- /rails-hotwire/bin/jobs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/environment" 4 | require "solid_queue/cli" 5 | 6 | SolidQueue::Cli.start(ARGV) 7 | -------------------------------------------------------------------------------- /rails-hotwire/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /rails-hotwire/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /rails-hotwire/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | 6 | def system!(*args) 7 | system(*args, exception: true) 8 | end 9 | 10 | FileUtils.chdir APP_ROOT do 11 | # This script is a way to set up or update your development environment automatically. 12 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 13 | # Add necessary setup steps to this file. 14 | 15 | puts "== Installing dependencies ==" 16 | system("bundle check") || system!("bundle install") 17 | 18 | # puts "\n== Copying sample files ==" 19 | # unless File.exist?("config/database.yml") 20 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 21 | # end 22 | 23 | puts "\n== Preparing database ==" 24 | system! "bin/rails db:prepare" 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! "bin/rails log:clear tmp:clear" 28 | 29 | unless ARGV.include?("--skip-server") 30 | puts "\n== Starting development server ==" 31 | STDOUT.flush # flush the output before exec(2) so that it displays 32 | exec "bin/dev" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /rails-hotwire/bin/vite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'vite' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("vite_ruby", "vite") 28 | -------------------------------------------------------------------------------- /rails-hotwire/bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | APP_ROOT = File.expand_path('..', __dir__) 5 | Dir.chdir(APP_ROOT) do 6 | executable_path = ENV["PATH"].split(File::PATH_SEPARATOR).find do |path| 7 | normalized_path = File.expand_path(path) 8 | 9 | normalized_path != __dir__ && File.executable?(Pathname.new(normalized_path).join('yarn')) 10 | end 11 | 12 | if executable_path 13 | exec File.expand_path(Pathname.new(executable_path).join('yarn')), *ARGV 14 | else 15 | $stderr.puts "Yarn executable was not detected in the system." 16 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 17 | exit 1 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /rails-hotwire/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | Rails.application.load_server 9 | -------------------------------------------------------------------------------- /rails-hotwire/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails' 6 | # Pick the frameworks you want: 7 | require 'active_model/railtie' 8 | require 'active_job/railtie' 9 | require 'active_record/railtie' 10 | # require "active_storage/engine" 11 | require 'action_controller/railtie' 12 | # require "action_mailer/railtie" 13 | # require "action_mailbox/engine" 14 | # require "action_text/engine" 15 | require 'action_view/railtie' 16 | require 'action_cable/engine' 17 | require 'rails/test_unit/railtie' 18 | 19 | # Require the gems listed in Gemfile, including any gems 20 | # you've limited to :test, :development, or :production. 21 | Bundler.require(*Rails.groups) 22 | 23 | module RailsHotwire 24 | class Application < Rails::Application 25 | # Initialize configuration defaults for originally generated Rails version. 26 | config.load_defaults 8.0 27 | 28 | # Please, add to the `ignore` list any other `lib` subdirectories that do 29 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 30 | # Common ones are `templates`, `generators`, or `middleware`, for example. 31 | config.autoload_lib(ignore: %w[assets tasks]) 32 | 33 | config.active_record.async_query_executor = :global_thread_pool 34 | 35 | # Configuration for the application, engines, and railties goes here. 36 | # 37 | # These settings can be overridden in specific environments using the files 38 | # in config/environments, which are processed later. 39 | # 40 | # config.time_zone = "Central Time (US & Canada)" 41 | # config.eager_load_paths << Rails.root.join("extras") 42 | 43 | # Don't generate system test files. 44 | config.generators.system_tests = nil 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /rails-hotwire/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /rails-hotwire/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: async 9 | -------------------------------------------------------------------------------- /rails-hotwire/config/cache.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | database: cache 3 | store_options: 4 | # Cap age of oldest cache entry to fulfill retention policies 5 | # max_age: <%= 60.days.to_i %> 6 | max_size: <%= 256.megabytes %> 7 | namespace: <%= Rails.env %> 8 | 9 | development: 10 | <<: *default 11 | 12 | test: 13 | <<: *default 14 | 15 | production: 16 | <<: *default 17 | -------------------------------------------------------------------------------- /rails-hotwire/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | Nht/ueFsElu+KTOMoKrglxQWk4RUS/seYS+KIsnEuXEyLXz0nwwU4/1v2nb8kslmVIMZbClnAGCrpc3RdMLUglPNUAG1tYnIUY4LhB/sYMK6KZnHWHOuzbur6sQ4CrYovUS3qMwx1Hei7nH74TVnPHPMCJpPQOSaMhJ0nkQOjzKPd+A+dRtjTtSS3tRKiW462I/++IAJt11ngHQT20xvitpHgIsF3IaWlk+d2YnbAVhM4LqU/IS4G/hxj58kcl3aiG9hOaMh5IeS5uuP5XlkGdOjPWaATmsobqd+v1PYCwubDc/tx32x9zfLXQPm/UIz+d0FeeDWogft6o/MMw3MnwE0xyHDEOycheW92Gs62NQ0urKwgEvD8q1woGNCI1gi79OqYE2x/I8bqZcsOI8F5t0fzrBt+RWaSN7A--bS6xdapd5LvhhEMc--DcZHz3vMUwTJcGVwpMizCw== -------------------------------------------------------------------------------- /rails-hotwire/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | primary: &primary 13 | <<: *default 14 | database: data/<%= Rails.env %>_rails_hotwire.sqlite3 15 | 16 | cache: &cache 17 | <<: *default 18 | migrations_paths: db/cache_migrate 19 | database: data/<%= Rails.env %>_rails_hotwire_cache.sqlite3 20 | 21 | queue: &queue 22 | <<: *default 23 | migrations_paths: db/queue_migrate 24 | database: data/<%= Rails.env %>_rails_hotwire_queue.sqlite3 25 | 26 | development: 27 | primary: *primary 28 | cache: *cache 29 | queue: *queue 30 | 31 | test: 32 | primary: *primary 33 | cache: *cache 34 | queue: *queue 35 | 36 | production: 37 | primary: *primary 38 | cache: *cache 39 | queue: *queue 40 | -------------------------------------------------------------------------------- /rails-hotwire/config/deploy.yml: -------------------------------------------------------------------------------- 1 | <% require "dotenv"; Dotenv.load(".env") %> 2 | 3 | # Name of your application. Used to uniquely configure containers. 4 | service: rails-hotwire 5 | 6 | # Name of the container image. 7 | image: guillaumebriday/rails-hotwire 8 | 9 | # Deploy to these servers. 10 | servers: 11 | web: 12 | - scw-kilimanjaro.guillaumebriday.fr 13 | 14 | workers: 15 | hosts: 16 | - scw-kilimanjaro.guillaumebriday.fr 17 | cmd: bin/jobs 18 | 19 | # Credentials for your image host. 20 | registry: 21 | username: guillaumebriday 22 | password: 23 | - KAMAL_REGISTRY_PASSWORD 24 | 25 | # Inject ENV variables into containers (secrets come from .env). 26 | # Remember to run `kamal env push` after making changes! 27 | env: 28 | secret: 29 | - RAILS_MASTER_KEY 30 | 31 | volumes: 32 | - "data:/rails/data" 33 | 34 | proxy: 35 | app_port: 3000 36 | ssl: true 37 | host: rails-hotwire.guillaumebriday.fr 38 | 39 | builder: 40 | arch: amd64 41 | 42 | aliases: 43 | console: app exec -i --reuse "bin/rails console" 44 | bash: app exec -i --reuse "bash" 45 | -------------------------------------------------------------------------------- /rails-hotwire/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /rails-hotwire/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Make code changes take effect immediately without server restart. 9 | config.enable_reloading = true 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing. 18 | config.server_timing = true 19 | 20 | # Enable/disable Action Controller caching. By default Action Controller caching is disabled. 21 | # Run rails dev:cache to toggle Action Controller caching. 22 | if Rails.root.join('tmp/caching-dev.txt').exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } 26 | else 27 | config.action_controller.perform_caching = false 28 | end 29 | 30 | # Change to :null_store to avoid any caching. 31 | config.cache_store = :solid_cache_store 32 | 33 | # Replace the default in-process and non-durable queuing backend for Active Job. 34 | config.active_job.queue_adapter = :solid_queue 35 | config.solid_queue.connects_to = { database: { writing: :queue } } 36 | config.mission_control.jobs.http_basic_auth_enabled = false 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | # Append comments with runtime information tags to SQL queries in logs. 48 | config.active_record.query_log_tags_enabled = true 49 | 50 | # Highlight code that enqueued background job in logs. 51 | config.active_job.verbose_enqueue_logs = true 52 | 53 | # Raises error for missing translations. 54 | # config.i18n.raise_on_missing_translations = true 55 | 56 | # Annotate rendered view with file names. 57 | config.action_view.annotate_rendered_view_with_filenames = true 58 | 59 | # Uncomment if you wish to allow Action Cable access from any origin. 60 | # config.action_cable.disable_request_forgery_protection = true 61 | 62 | # Raise error when a before_action's only/except options reference missing actions. 63 | config.action_controller.raise_on_missing_callback_actions = true 64 | 65 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 66 | # config.generators.apply_rubocop_autocorrect_after_generate! 67 | end 68 | -------------------------------------------------------------------------------- /rails-hotwire/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Code is not reloaded between requests. 9 | config.enable_reloading = false 10 | 11 | # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). 12 | config.eager_load = true 13 | 14 | # Full error reports are disabled. 15 | config.consider_all_requests_local = false 16 | 17 | # Turn on fragment caching in view templates. 18 | config.action_controller.perform_caching = true 19 | 20 | # Cache assets for far-future expiry since they are all digest stamped. 21 | config.public_file_server.headers = { 'cache-control' => "public, max-age=#{1.year.to_i}, immutable" } 22 | 23 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 24 | # config.asset_host = "http://assets.example.com" 25 | 26 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 27 | config.assume_ssl = true 28 | 29 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 30 | config.force_ssl = true 31 | 32 | # Skip http-to-https redirect for the default health check endpoint. 33 | config.ssl_options = { redirect: { exclude: ->(request) { request.path == '/up' } } } 34 | 35 | # Log to STDOUT with the current request id as a default log tag. 36 | config.log_tags = [:request_id] 37 | config.logger = ActiveSupport::TaggedLogging.logger($stdout) 38 | 39 | # Change to "debug" to log everything (including potentially personally-identifiable information!) 40 | config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info') 41 | 42 | # Prevent health checks from clogging up the logs. 43 | config.silence_healthcheck_path = '/up' 44 | 45 | # Don't log any deprecations. 46 | config.active_support.report_deprecations = false 47 | 48 | # Replace the default in-process memory cache store with a durable alternative. 49 | config.cache_store = :solid_cache_store 50 | 51 | # Replace the default in-process and non-durable queuing backend for Active Job. 52 | config.active_job.queue_adapter = :solid_queue 53 | config.solid_queue.connects_to = { database: { writing: :queue } } 54 | config.mission_control.jobs.http_basic_auth_enabled = false 55 | 56 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 57 | # the I18n.default_locale when a translation cannot be found). 58 | config.i18n.fallbacks = true 59 | 60 | # Do not dump schema after migrations. 61 | config.active_record.dump_schema_after_migration = false 62 | 63 | # Only use :id for inspections in production. 64 | config.active_record.attributes_for_inspect = [:id] 65 | 66 | # Enable DNS rebinding protection and other `Host` header attacks. 67 | # config.hosts = [ 68 | # "example.com", # Allow requests from example.com 69 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 70 | # ] 71 | # 72 | # Skip DNS rebinding protection for the default health check endpoint. 73 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 74 | 75 | # Uncomment if you wish to allow Action Cable access from any origin. 76 | config.action_cable.disable_request_forgery_protection = true 77 | end 78 | -------------------------------------------------------------------------------- /rails-hotwire/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV['CI'].present? 19 | 20 | # Configure public file server for tests with cache-control for performance. 21 | config.public_file_server.headers = { 'cache-control' => 'public, max-age=3600' } 22 | 23 | # Show full error reports. 24 | config.consider_all_requests_local = true 25 | config.cache_store = :null_store 26 | 27 | # Render exception templates for rescuable exceptions and raise for other exceptions. 28 | config.action_dispatch.show_exceptions = :rescuable 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | # Print deprecation notices to the stderr. 34 | config.active_support.deprecation = :stderr 35 | 36 | # Raises error for missing translations. 37 | # config.i18n.raise_on_missing_translations = true 38 | 39 | # Annotate rendered view with file names. 40 | # config.action_view.annotate_rendered_view_with_filenames = true 41 | 42 | # Raise error when a before_action's only/except options reference missing actions. 43 | config.action_controller.raise_on_missing_callback_actions = true 44 | end 45 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # ActiveSupport::Reloader.to_prepare do 6 | # ApplicationController.renderer.defaults.merge!( 7 | # http_host: 'example.org', 8 | # https: false 9 | # ) 10 | # end 11 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 10 | Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE'] 11 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide content security policy. 6 | # See the Securing Rails Applications Guide for more information: 7 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 8 | 9 | # Rails.application.configure do 10 | # config.content_security_policy do |policy| 11 | # policy.default_src :self, :https 12 | # policy.font_src :self, :https, :data 13 | # policy.img_src :self, :https, :data 14 | # policy.object_src :none 15 | # policy.script_src :self, :https 16 | # policy.style_src :self, :https 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | # 21 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 22 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 23 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 24 | # 25 | # # Report violations without enforcing the policy. 26 | # # config.content_security_policy_report_only = true 27 | # end 28 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 6 | # Use this to limit dissemination of sensitive information. 7 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 8 | Rails.application.config.filter_parameters += %i[ 9 | passw email secret token _key crypt salt certificate otp ssn cvv cvc 10 | ] 11 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, "\\1en" 10 | # inflect.singular /^(ox)en/i, "\\1" 11 | # inflect.irregular "person", "people" 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # inflect.acronym "RESTful" 18 | # end 19 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new mime types for use in respond_to blocks: 6 | # Mime::Type.register "text/richtext", :rtf 7 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/pagy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pagy/extras/limit' 4 | require 'pagy/extras/overflow' 5 | 6 | Pagy::DEFAULT[:limit_max] = 100 7 | Pagy::DEFAULT[:limit_param] = :per_page 8 | Pagy::DEFAULT[:overflow] = :last_page 9 | Pagy::DEFAULT[:items] = 10 10 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide HTTP permissions policy. For further 6 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 7 | 8 | # Rails.application.config.permissions_policy do |policy| 9 | # policy.camera :none 10 | # policy.gyroscope :none 11 | # policy.microphone :none 12 | # policy.usb :none 13 | # policy.fullscreen :self 14 | # policy.payment :self, "https://secure.example.com" 15 | # end 16 | -------------------------------------------------------------------------------- /rails-hotwire/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /rails-hotwire/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /rails-hotwire/config/master.key: -------------------------------------------------------------------------------- 1 | 62d219983fb17bfd6cce0e86c2e2de49 -------------------------------------------------------------------------------- /rails-hotwire/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This configuration file will be evaluated by Puma. The top-level methods that 4 | # are invoked here are part of Puma's configuration DSL. For more information 5 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 6 | # 7 | # Puma starts a configurable number of processes (workers) and each process 8 | # serves each request in a thread from an internal thread pool. 9 | # 10 | # You can control the number of workers using ENV["WEB_CONCURRENCY"]. You 11 | # should only set this value when you want to run 2 or more workers. The 12 | # default is already 1. 13 | # 14 | # The ideal number of threads per worker depends both on how much time the 15 | # application spends waiting for IO operations and on how much you wish to 16 | # prioritize throughput over latency. 17 | # 18 | # As a rule of thumb, increasing the number of threads will increase how much 19 | # traffic a given process can handle (throughput), but due to CRuby's 20 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 21 | # response time (latency) of the application. 22 | # 23 | # The default is set to 3 threads as it's deemed a decent compromise between 24 | # throughput and latency for the average Rails application. 25 | # 26 | # Any libraries that use a connection pool or another resource pool should 27 | # be configured to provide at least as many connections as the number of 28 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 29 | threads_count = ENV.fetch('RAILS_MAX_THREADS', 3) 30 | threads threads_count, threads_count 31 | 32 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 33 | port ENV.fetch('PORT', 3000) 34 | 35 | # Allow puma to be restarted by `bin/rails restart` command. 36 | plugin :tmp_restart 37 | 38 | # Run the Solid Queue supervisor inside of Puma for single-server deployments 39 | plugin :solid_queue if ENV['SOLID_QUEUE_IN_PUMA'] 40 | 41 | # Specify the PID file. Defaults to tmp/pids/server.pid in development. 42 | # In other environments, only set the PID file if requested. 43 | pidfile ENV['PIDFILE'] if ENV['PIDFILE'] 44 | -------------------------------------------------------------------------------- /rails-hotwire/config/queue.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | dispatchers: 3 | - polling_interval: 1 4 | batch_size: 500 5 | workers: 6 | - queues: "*" 7 | threads: 3 8 | processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %> 9 | polling_interval: 0.1 10 | 11 | development: 12 | <<: *default 13 | 14 | test: 15 | <<: *default 16 | 17 | production: 18 | <<: *default 19 | -------------------------------------------------------------------------------- /rails-hotwire/config/recurring.yml: -------------------------------------------------------------------------------- 1 | production: 2 | periodic_cleanup: 3 | class: TodosCleanupJob 4 | queue: default 5 | schedule: every day 6 | -------------------------------------------------------------------------------- /rails-hotwire/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | mount MissionControl::Jobs::Engine, at: '/jobs' 5 | 6 | resources :todos, only: %i[index new create update edit] do 7 | member do 8 | patch :toggle_completed 9 | end 10 | end 11 | 12 | resources :comments, only: %i[index create destroy] 13 | 14 | get :up, to: 'rails/health#show' 15 | 16 | root to: 'todos#index' 17 | end 18 | -------------------------------------------------------------------------------- /rails-hotwire/config/vite.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": { 3 | "sourceCodeDir": "app/javascript", 4 | "watchAdditionalPaths": [] 5 | }, 6 | "development": { 7 | "autoBuild": true, 8 | "publicOutputDir": "vite-dev", 9 | "port": 3036 10 | }, 11 | "test": { 12 | "autoBuild": true, 13 | "publicOutputDir": "vite-test", 14 | "port": 3037 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rails-hotwire/data/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/data/.keep -------------------------------------------------------------------------------- /rails-hotwire/db/cache_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ActiveRecord::Schema[7.2].define(version: 1) do 4 | create_table 'solid_cache_entries', force: :cascade do |t| 5 | t.binary 'key', limit: 1024, null: false 6 | t.binary 'value', limit: 536_870_912, null: false 7 | t.datetime 'created_at', null: false 8 | t.integer 'key_hash', limit: 8, null: false 9 | t.integer 'byte_size', limit: 4, null: false 10 | t.index ['byte_size'], name: 'index_solid_cache_entries_on_byte_size' 11 | t.index %w[key_hash byte_size], name: 'index_solid_cache_entries_on_key_hash_and_byte_size' 12 | t.index ['key_hash'], name: 'index_solid_cache_entries_on_key_hash', unique: true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /rails-hotwire/db/migrate/20200903175607_create_todos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateTodos < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :todos do |t| 6 | t.string :description 7 | t.boolean :completed, default: false, null: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /rails-hotwire/db/migrate/20240216030624_create_comment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateComment < ActiveRecord::Migration[7.1] 4 | def change 5 | create_table :comments do |t| 6 | t.text :description 7 | t.references :todo 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /rails-hotwire/db/queue_schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # This file is auto-generated from the current state of the database. Instead 5 | # of editing this file, please use the migrations feature of Active Record to 6 | # incrementally modify your database, and then regenerate this schema definition. 7 | # 8 | # This file is the source Rails uses to define your schema when running `bin/rails 9 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 10 | # be faster and is potentially less error prone than running all of your 11 | # migrations from scratch. Old migrations may fail to apply correctly if those 12 | # migrations use external dependencies or application code. 13 | # 14 | # It's strongly recommended that you check this file into your version control system. 15 | 16 | ActiveRecord::Schema[8.0].define(version: 1) do # rubocop:disable Metrics/BlockLength 17 | create_table 'solid_queue_blocked_executions', force: :cascade do |t| 18 | t.bigint 'job_id', null: false 19 | t.string 'queue_name', null: false 20 | t.integer 'priority', default: 0, null: false 21 | t.string 'concurrency_key', null: false 22 | t.datetime 'expires_at', null: false 23 | t.datetime 'created_at', null: false 24 | t.index %w[concurrency_key priority job_id], name: 'index_solid_queue_blocked_executions_for_release' 25 | t.index %w[expires_at concurrency_key], name: 'index_solid_queue_blocked_executions_for_maintenance' 26 | t.index ['job_id'], name: 'index_solid_queue_blocked_executions_on_job_id', unique: true 27 | end 28 | 29 | create_table 'solid_queue_claimed_executions', force: :cascade do |t| 30 | t.bigint 'job_id', null: false 31 | t.bigint 'process_id' 32 | t.datetime 'created_at', null: false 33 | t.index ['job_id'], name: 'index_solid_queue_claimed_executions_on_job_id', unique: true 34 | t.index %w[process_id job_id], name: 'index_solid_queue_claimed_executions_on_process_id_and_job_id' 35 | end 36 | 37 | create_table 'solid_queue_failed_executions', force: :cascade do |t| 38 | t.bigint 'job_id', null: false 39 | t.text 'error' 40 | t.datetime 'created_at', null: false 41 | t.index ['job_id'], name: 'index_solid_queue_failed_executions_on_job_id', unique: true 42 | end 43 | 44 | create_table 'solid_queue_jobs', force: :cascade do |t| 45 | t.string 'queue_name', null: false 46 | t.string 'class_name', null: false 47 | t.text 'arguments' 48 | t.integer 'priority', default: 0, null: false 49 | t.string 'active_job_id' 50 | t.datetime 'scheduled_at' 51 | t.datetime 'finished_at' 52 | t.string 'concurrency_key' 53 | t.datetime 'created_at', null: false 54 | t.datetime 'updated_at', null: false 55 | t.index ['active_job_id'], name: 'index_solid_queue_jobs_on_active_job_id' 56 | t.index ['class_name'], name: 'index_solid_queue_jobs_on_class_name' 57 | t.index ['finished_at'], name: 'index_solid_queue_jobs_on_finished_at' 58 | t.index %w[queue_name finished_at], name: 'index_solid_queue_jobs_for_filtering' 59 | t.index %w[scheduled_at finished_at], name: 'index_solid_queue_jobs_for_alerting' 60 | end 61 | 62 | create_table 'solid_queue_pauses', force: :cascade do |t| 63 | t.string 'queue_name', null: false 64 | t.datetime 'created_at', null: false 65 | t.index ['queue_name'], name: 'index_solid_queue_pauses_on_queue_name', unique: true 66 | end 67 | 68 | create_table 'solid_queue_processes', force: :cascade do |t| 69 | t.string 'kind', null: false 70 | t.datetime 'last_heartbeat_at', null: false 71 | t.bigint 'supervisor_id' 72 | t.integer 'pid', null: false 73 | t.string 'hostname' 74 | t.text 'metadata' 75 | t.datetime 'created_at', null: false 76 | t.string 'name', null: false 77 | t.index ['last_heartbeat_at'], name: 'index_solid_queue_processes_on_last_heartbeat_at' 78 | t.index %w[name supervisor_id], name: 'index_solid_queue_processes_on_name_and_supervisor_id', unique: true 79 | t.index ['supervisor_id'], name: 'index_solid_queue_processes_on_supervisor_id' 80 | end 81 | 82 | create_table 'solid_queue_ready_executions', force: :cascade do |t| 83 | t.bigint 'job_id', null: false 84 | t.string 'queue_name', null: false 85 | t.integer 'priority', default: 0, null: false 86 | t.datetime 'created_at', null: false 87 | t.index ['job_id'], name: 'index_solid_queue_ready_executions_on_job_id', unique: true 88 | t.index %w[priority job_id], name: 'index_solid_queue_poll_all' 89 | t.index %w[queue_name priority job_id], name: 'index_solid_queue_poll_by_queue' 90 | end 91 | 92 | create_table 'solid_queue_recurring_executions', force: :cascade do |t| 93 | t.bigint 'job_id', null: false 94 | t.string 'task_key', null: false 95 | t.datetime 'run_at', null: false 96 | t.datetime 'created_at', null: false 97 | t.index ['job_id'], name: 'index_solid_queue_recurring_executions_on_job_id', unique: true 98 | t.index %w[task_key run_at], name: 'index_solid_queue_recurring_executions_on_task_key_and_run_at', unique: true 99 | end 100 | 101 | create_table 'solid_queue_recurring_tasks', force: :cascade do |t| 102 | t.string 'key', null: false 103 | t.string 'schedule', null: false 104 | t.string 'command', limit: 2048 105 | t.string 'class_name' 106 | t.text 'arguments' 107 | t.string 'queue_name' 108 | t.integer 'priority', default: 0 109 | t.boolean 'static', default: true, null: false 110 | t.text 'description' 111 | t.datetime 'created_at', null: false 112 | t.datetime 'updated_at', null: false 113 | t.index ['key'], name: 'index_solid_queue_recurring_tasks_on_key', unique: true 114 | t.index ['static'], name: 'index_solid_queue_recurring_tasks_on_static' 115 | end 116 | 117 | create_table 'solid_queue_scheduled_executions', force: :cascade do |t| 118 | t.bigint 'job_id', null: false 119 | t.string 'queue_name', null: false 120 | t.integer 'priority', default: 0, null: false 121 | t.datetime 'scheduled_at', null: false 122 | t.datetime 'created_at', null: false 123 | t.index ['job_id'], name: 'index_solid_queue_scheduled_executions_on_job_id', unique: true 124 | t.index %w[scheduled_at priority job_id], name: 'index_solid_queue_dispatch_all' 125 | end 126 | 127 | create_table 'solid_queue_semaphores', force: :cascade do |t| 128 | t.string 'key', null: false 129 | t.integer 'value', default: 1, null: false 130 | t.datetime 'expires_at', null: false 131 | t.datetime 'created_at', null: false 132 | t.datetime 'updated_at', null: false 133 | t.index ['expires_at'], name: 'index_solid_queue_semaphores_on_expires_at' 134 | t.index %w[key value], name: 'index_solid_queue_semaphores_on_key_and_value' 135 | t.index ['key'], name: 'index_solid_queue_semaphores_on_key', unique: true 136 | end 137 | 138 | add_foreign_key 'solid_queue_blocked_executions', 'solid_queue_jobs', column: 'job_id', on_delete: :cascade 139 | add_foreign_key 'solid_queue_claimed_executions', 'solid_queue_jobs', column: 'job_id', on_delete: :cascade 140 | add_foreign_key 'solid_queue_failed_executions', 'solid_queue_jobs', column: 'job_id', on_delete: :cascade 141 | add_foreign_key 'solid_queue_ready_executions', 'solid_queue_jobs', column: 'job_id', on_delete: :cascade 142 | add_foreign_key 'solid_queue_recurring_executions', 'solid_queue_jobs', column: 'job_id', on_delete: :cascade 143 | add_foreign_key 'solid_queue_scheduled_executions', 'solid_queue_jobs', column: 'job_id', on_delete: :cascade 144 | end 145 | -------------------------------------------------------------------------------- /rails-hotwire/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[8.0].define(version: 2024_02_16_030624) do 14 | create_table "comments", force: :cascade do |t| 15 | t.text "description" 16 | t.integer "todo_id" 17 | t.datetime "created_at", null: false 18 | t.datetime "updated_at", null: false 19 | t.index ["todo_id"], name: "index_comments_on_todo_id" 20 | end 21 | 22 | create_table "likes", force: :cascade do |t| 23 | t.integer "todo_id" 24 | t.datetime "created_at", null: false 25 | t.datetime "updated_at", null: false 26 | t.index ["todo_id"], name: "index_likes_on_todo_id" 27 | end 28 | 29 | create_table "todos", force: :cascade do |t| 30 | t.string "description" 31 | t.boolean "completed", default: false 32 | t.datetime "created_at", null: false 33 | t.datetime "updated_at", null: false 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /rails-hotwire/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file should contain all the record creation needed to seed the database with its default values. 4 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 5 | # 6 | # Examples: 7 | # 8 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 9 | # Character.create(name: 'Luke', movie: movies.first) 10 | -------------------------------------------------------------------------------- /rails-hotwire/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/lib/assets/.keep -------------------------------------------------------------------------------- /rails-hotwire/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/lib/tasks/.keep -------------------------------------------------------------------------------- /rails-hotwire/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/log/.keep -------------------------------------------------------------------------------- /rails-hotwire/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "scripts": { 4 | "dev": "vite" 5 | }, 6 | "dependencies": { 7 | "@hotwired/stimulus": "^3.2.2", 8 | "@hotwired/turbo-rails": "^8.0.12", 9 | "@stimulus-components/auto-submit": "^6.0.0", 10 | "@stimulus-components/timeago": "^5.0.2", 11 | "@tailwindcss/forms": "^0.5.9", 12 | "autoprefixer": "^10.4.20", 13 | "lodash.debounce": "^4.0.8", 14 | "postcss": "^8.4.49", 15 | "rollup-plugin-gzip": "^4.0.1", 16 | "sass": "^1.83.0", 17 | "tailwindcss": "^3.4.17" 18 | }, 19 | "devDependencies": { 20 | "vite": "^5.4.11", 21 | "vite-plugin-ruby": "^5.1.1" 22 | }, 23 | "packageManager": "yarn@4.5.3" 24 | } 25 | -------------------------------------------------------------------------------- /rails-hotwire/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss')('./app/javascript/config/tailwind.config.js') 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /rails-hotwire/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /rails-hotwire/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /rails-hotwire/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /rails-hotwire/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /rails-hotwire/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/public/apple-touch-icon.png -------------------------------------------------------------------------------- /rails-hotwire/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/public/favicon.ico -------------------------------------------------------------------------------- /rails-hotwire/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /rails-hotwire/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/test/controllers/.keep -------------------------------------------------------------------------------- /rails-hotwire/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/test/fixtures/files/.keep -------------------------------------------------------------------------------- /rails-hotwire/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/test/helpers/.keep -------------------------------------------------------------------------------- /rails-hotwire/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/test/integration/.keep -------------------------------------------------------------------------------- /rails-hotwire/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-hotwire/test/models/.keep -------------------------------------------------------------------------------- /rails-hotwire/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require_relative '../config/environment' 5 | require 'rails/test_help' 6 | 7 | class ActiveSupport::TestCase 8 | # Run tests in parallel with specified workers 9 | parallelize(workers: :number_of_processors) 10 | 11 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 12 | fixtures :all 13 | 14 | # Add more helper methods to be used by all tests here... 15 | end 16 | -------------------------------------------------------------------------------- /rails-hotwire/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import RubyPlugin from 'vite-plugin-ruby' 3 | import { brotliCompressSync } from "zlib" 4 | import gzipPlugin from "rollup-plugin-gzip" 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | RubyPlugin(), 9 | // Create gzip copies of relevant assets 10 | gzipPlugin(), 11 | // Create brotli copies of relevant assets 12 | gzipPlugin({ 13 | customCompression: (content) => brotliCompressSync(Buffer.from(content)), 14 | fileName: ".br", 15 | }), 16 | ], 17 | }) 18 | -------------------------------------------------------------------------------- /rails-vuejs/.gitignore: -------------------------------------------------------------------------------- 1 | rerun.txt 2 | *.swp 3 | *.swo 4 | *.DS_Store 5 | .bundle 6 | db/*.sqlite3* 7 | log/*.log 8 | .sass-cache/ 9 | tmp/* 10 | public/tmp/* 11 | public/uploads 12 | public/packs/ 13 | dump.rdb 14 | .byebug_history 15 | routes.txt 16 | .idea 17 | /public/packs 18 | /public/packs-test 19 | /public/assets 20 | /node_modules 21 | /coverage 22 | /storage 23 | yarn-error.log 24 | !/app/views/.keep 25 | .env 26 | public/index.html 27 | -------------------------------------------------------------------------------- /rails-vuejs/.kamal/secrets: -------------------------------------------------------------------------------- 1 | KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD 2 | RAILS_MASTER_KEY=$RAILS_MASTER_KEY 3 | -------------------------------------------------------------------------------- /rails-vuejs/.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-performance 3 | - rubocop-rspec 4 | - rubocop-rails 5 | 6 | AllCops: 7 | NewCops: enable 8 | Exclude: 9 | - db/schema.rb 10 | - node_modules/**/* 11 | - bin/**/* 12 | - tmp/**/* 13 | 14 | Rails/UnknownEnv: 15 | Environments: 16 | - production 17 | - development 18 | - test 19 | 20 | Style/Documentation: 21 | Enabled: false 22 | 23 | Style/EmptyMethod: 24 | Enabled: false 25 | 26 | Style/Lambda: 27 | Enabled: false 28 | 29 | Naming/MemoizedInstanceVariableName: 30 | Enabled: false 31 | 32 | Rails/UniqueValidationWithoutIndex: 33 | Enabled: false 34 | 35 | Style/ClassAndModuleChildren: 36 | Enabled: false 37 | 38 | Style/HashSyntax: 39 | EnforcedShorthandSyntax: never 40 | -------------------------------------------------------------------------------- /rails-vuejs/.ruby-version: -------------------------------------------------------------------------------- 1 | 3.4.1 2 | -------------------------------------------------------------------------------- /rails-vuejs/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: 4 | # docker build -t my-app . 5 | # docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY= my-app 6 | 7 | # Make sure RUBY_VERSION matches the Ruby version in .ruby-version 8 | ARG RUBY_VERSION=3.4.1 9 | FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base 10 | 11 | # Rails app lives here 12 | WORKDIR /rails 13 | 14 | # Install base packages 15 | RUN apt-get update -qq && \ 16 | apt-get install --no-install-recommends -y curl libjemalloc2 sqlite3 && \ 17 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 18 | 19 | # Set production environment 20 | ENV RAILS_ENV="production" \ 21 | BUNDLE_DEPLOYMENT="1" \ 22 | BUNDLE_PATH="/usr/local/bundle" \ 23 | BUNDLE_WITHOUT="development" 24 | 25 | # Throw-away build stage to reduce size of final image 26 | FROM base AS build 27 | 28 | # Install packages needed to build gems and node modules 29 | RUN apt-get update -qq && \ 30 | apt-get install --no-install-recommends -y build-essential git node-gyp pkg-config python-is-python3 && \ 31 | rm -rf /var/lib/apt/lists /var/cache/apt/archives 32 | 33 | # Install JavaScript dependencies 34 | ARG NODE_VERSION=20.17.0 35 | ENV PATH=/usr/local/node/bin:$PATH 36 | RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ 37 | /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ 38 | corepack enable && \ 39 | rm -rf /tmp/node-build-master 40 | 41 | # Install application gems 42 | COPY Gemfile Gemfile.lock ./ 43 | RUN bundle install && \ 44 | rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ 45 | bundle exec bootsnap precompile --gemfile 46 | 47 | # Copy application code 48 | COPY . . 49 | 50 | # Precompile bootsnap code for faster boot times 51 | RUN bundle exec bootsnap precompile app/ lib/ 52 | 53 | # Install node modules 54 | RUN cd frontend && \ 55 | yarn install --immutable && \ 56 | yarn build && \ 57 | mv ../public/index.html ../app/views/application.html.erb 58 | 59 | RUN rm -rf node_modules 60 | 61 | # Final stage for app image 62 | FROM base 63 | 64 | # Copy built artifacts: gems, application 65 | COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" 66 | COPY --from=build /rails /rails 67 | 68 | # Run and own only the runtime files as a non-root user for security 69 | RUN groupadd --system --gid 1000 rails && \ 70 | useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ 71 | chown -R rails:rails db log tmp data 72 | USER 1000:1000 73 | 74 | # Entrypoint prepares the database. 75 | ENTRYPOINT ["/rails/bin/docker-entrypoint"] 76 | 77 | # Start the server by default, this can be overwritten at runtime 78 | EXPOSE 3000 79 | CMD ["./bin/rails", "server"] 80 | -------------------------------------------------------------------------------- /rails-vuejs/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | gem 'bootsnap', require: false 7 | gem 'jsonapi-serializer' 8 | gem 'pagy' 9 | gem 'puma' 10 | gem 'rails' 11 | gem 'sqlite3' 12 | 13 | group :development, :test do 14 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 15 | gem 'byebug' 16 | gem 'rubocop' 17 | gem 'rubocop-performance' 18 | gem 'rubocop-rails' 19 | gem 'rubocop-rspec' 20 | 21 | gem 'bcrypt_pbkdf' 22 | gem 'ed25519' 23 | gem 'kamal' 24 | end 25 | 26 | group :development do 27 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 28 | gem 'listen', '~> 3.3' 29 | end 30 | -------------------------------------------------------------------------------- /rails-vuejs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (8.0.1) 5 | actionpack (= 8.0.1) 6 | activesupport (= 8.0.1) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | zeitwerk (~> 2.6) 10 | actionmailbox (8.0.1) 11 | actionpack (= 8.0.1) 12 | activejob (= 8.0.1) 13 | activerecord (= 8.0.1) 14 | activestorage (= 8.0.1) 15 | activesupport (= 8.0.1) 16 | mail (>= 2.8.0) 17 | actionmailer (8.0.1) 18 | actionpack (= 8.0.1) 19 | actionview (= 8.0.1) 20 | activejob (= 8.0.1) 21 | activesupport (= 8.0.1) 22 | mail (>= 2.8.0) 23 | rails-dom-testing (~> 2.2) 24 | actionpack (8.0.1) 25 | actionview (= 8.0.1) 26 | activesupport (= 8.0.1) 27 | nokogiri (>= 1.8.5) 28 | rack (>= 2.2.4) 29 | rack-session (>= 1.0.1) 30 | rack-test (>= 0.6.3) 31 | rails-dom-testing (~> 2.2) 32 | rails-html-sanitizer (~> 1.6) 33 | useragent (~> 0.16) 34 | actiontext (8.0.1) 35 | actionpack (= 8.0.1) 36 | activerecord (= 8.0.1) 37 | activestorage (= 8.0.1) 38 | activesupport (= 8.0.1) 39 | globalid (>= 0.6.0) 40 | nokogiri (>= 1.8.5) 41 | actionview (8.0.1) 42 | activesupport (= 8.0.1) 43 | builder (~> 3.1) 44 | erubi (~> 1.11) 45 | rails-dom-testing (~> 2.2) 46 | rails-html-sanitizer (~> 1.6) 47 | activejob (8.0.1) 48 | activesupport (= 8.0.1) 49 | globalid (>= 0.3.6) 50 | activemodel (8.0.1) 51 | activesupport (= 8.0.1) 52 | activerecord (8.0.1) 53 | activemodel (= 8.0.1) 54 | activesupport (= 8.0.1) 55 | timeout (>= 0.4.0) 56 | activestorage (8.0.1) 57 | actionpack (= 8.0.1) 58 | activejob (= 8.0.1) 59 | activerecord (= 8.0.1) 60 | activesupport (= 8.0.1) 61 | marcel (~> 1.0) 62 | activesupport (8.0.1) 63 | base64 64 | benchmark (>= 0.3) 65 | bigdecimal 66 | concurrent-ruby (~> 1.0, >= 1.3.1) 67 | connection_pool (>= 2.2.5) 68 | drb 69 | i18n (>= 1.6, < 2) 70 | logger (>= 1.4.2) 71 | minitest (>= 5.1) 72 | securerandom (>= 0.3) 73 | tzinfo (~> 2.0, >= 2.0.5) 74 | uri (>= 0.13.1) 75 | ast (2.4.2) 76 | base64 (0.2.0) 77 | bcrypt_pbkdf (1.1.1) 78 | benchmark (0.4.0) 79 | bigdecimal (3.1.9) 80 | bootsnap (1.18.4) 81 | msgpack (~> 1.2) 82 | builder (3.3.0) 83 | byebug (11.1.3) 84 | concurrent-ruby (1.3.4) 85 | connection_pool (2.4.1) 86 | crass (1.0.6) 87 | date (3.4.1) 88 | dotenv (3.1.7) 89 | drb (2.2.1) 90 | ed25519 (1.3.0) 91 | erubi (1.13.1) 92 | ffi (1.17.0) 93 | globalid (1.2.1) 94 | activesupport (>= 6.1) 95 | i18n (1.14.6) 96 | concurrent-ruby (~> 1.0) 97 | io-console (0.8.0) 98 | irb (1.14.3) 99 | rdoc (>= 4.0.0) 100 | reline (>= 0.4.2) 101 | json (2.9.1) 102 | jsonapi-serializer (2.2.0) 103 | activesupport (>= 4.2) 104 | kamal (2.4.0) 105 | activesupport (>= 7.0) 106 | base64 (~> 0.2) 107 | bcrypt_pbkdf (~> 1.0) 108 | concurrent-ruby (~> 1.2) 109 | dotenv (~> 3.1) 110 | ed25519 (~> 1.2) 111 | net-ssh (~> 7.3) 112 | sshkit (>= 1.23.0, < 2.0) 113 | thor (~> 1.3) 114 | zeitwerk (>= 2.6.18, < 3.0) 115 | language_server-protocol (3.17.0.3) 116 | listen (3.9.0) 117 | rb-fsevent (~> 0.10, >= 0.10.3) 118 | rb-inotify (~> 0.9, >= 0.9.10) 119 | logger (1.6.4) 120 | loofah (2.23.1) 121 | crass (~> 1.0.2) 122 | nokogiri (>= 1.12.0) 123 | mail (2.8.1) 124 | mini_mime (>= 0.1.1) 125 | net-imap 126 | net-pop 127 | net-smtp 128 | marcel (1.0.4) 129 | mini_mime (1.1.5) 130 | mini_portile2 (2.8.8) 131 | minitest (5.25.4) 132 | msgpack (1.7.5) 133 | net-imap (0.5.4) 134 | date 135 | net-protocol 136 | net-pop (0.1.2) 137 | net-protocol 138 | net-protocol (0.2.2) 139 | timeout 140 | net-scp (4.0.0) 141 | net-ssh (>= 2.6.5, < 8.0.0) 142 | net-sftp (4.0.0) 143 | net-ssh (>= 5.0.0, < 8.0.0) 144 | net-smtp (0.5.0) 145 | net-protocol 146 | net-ssh (7.3.0) 147 | nio4r (2.7.4) 148 | nokogiri (1.18.0) 149 | mini_portile2 (~> 2.8.2) 150 | racc (~> 1.4) 151 | nokogiri (1.18.0-aarch64-linux-gnu) 152 | racc (~> 1.4) 153 | nokogiri (1.18.0-x86_64-linux-gnu) 154 | racc (~> 1.4) 155 | ostruct (0.6.1) 156 | pagy (9.3.3) 157 | parallel (1.26.3) 158 | parser (3.3.6.0) 159 | ast (~> 2.4.1) 160 | racc 161 | psych (5.2.2) 162 | date 163 | stringio 164 | puma (6.5.0) 165 | nio4r (~> 2.0) 166 | racc (1.8.1) 167 | rack (3.1.8) 168 | rack-session (2.0.0) 169 | rack (>= 3.0.0) 170 | rack-test (2.2.0) 171 | rack (>= 1.3) 172 | rackup (2.2.1) 173 | rack (>= 3) 174 | rails (8.0.1) 175 | actioncable (= 8.0.1) 176 | actionmailbox (= 8.0.1) 177 | actionmailer (= 8.0.1) 178 | actionpack (= 8.0.1) 179 | actiontext (= 8.0.1) 180 | actionview (= 8.0.1) 181 | activejob (= 8.0.1) 182 | activemodel (= 8.0.1) 183 | activerecord (= 8.0.1) 184 | activestorage (= 8.0.1) 185 | activesupport (= 8.0.1) 186 | bundler (>= 1.15.0) 187 | railties (= 8.0.1) 188 | rails-dom-testing (2.2.0) 189 | activesupport (>= 5.0.0) 190 | minitest 191 | nokogiri (>= 1.6) 192 | rails-html-sanitizer (1.6.2) 193 | loofah (~> 2.21) 194 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 195 | railties (8.0.1) 196 | actionpack (= 8.0.1) 197 | activesupport (= 8.0.1) 198 | irb (~> 1.13) 199 | rackup (>= 1.0.0) 200 | rake (>= 12.2) 201 | thor (~> 1.0, >= 1.2.2) 202 | zeitwerk (~> 2.6) 203 | rainbow (3.1.1) 204 | rake (13.2.1) 205 | rb-fsevent (0.11.2) 206 | rb-inotify (0.11.1) 207 | ffi (~> 1.0) 208 | rdoc (6.10.0) 209 | psych (>= 4.0.0) 210 | regexp_parser (2.10.0) 211 | reline (0.6.0) 212 | io-console (~> 0.5) 213 | rubocop (1.69.2) 214 | json (~> 2.3) 215 | language_server-protocol (>= 3.17.0) 216 | parallel (~> 1.10) 217 | parser (>= 3.3.0.2) 218 | rainbow (>= 2.2.2, < 4.0) 219 | regexp_parser (>= 2.9.3, < 3.0) 220 | rubocop-ast (>= 1.36.2, < 2.0) 221 | ruby-progressbar (~> 1.7) 222 | unicode-display_width (>= 2.4.0, < 4.0) 223 | rubocop-ast (1.37.0) 224 | parser (>= 3.3.1.0) 225 | rubocop-performance (1.23.0) 226 | rubocop (>= 1.48.1, < 2.0) 227 | rubocop-ast (>= 1.31.1, < 2.0) 228 | rubocop-rails (2.28.0) 229 | activesupport (>= 4.2.0) 230 | rack (>= 1.1) 231 | rubocop (>= 1.52.0, < 2.0) 232 | rubocop-ast (>= 1.31.1, < 2.0) 233 | rubocop-rspec (3.3.0) 234 | rubocop (~> 1.61) 235 | ruby-progressbar (1.13.0) 236 | securerandom (0.4.1) 237 | sqlite3 (2.5.0) 238 | mini_portile2 (~> 2.8.0) 239 | sqlite3 (2.5.0-aarch64-linux-gnu) 240 | sqlite3 (2.5.0-x86_64-linux-gnu) 241 | sshkit (1.23.2) 242 | base64 243 | net-scp (>= 1.1.2) 244 | net-sftp (>= 2.1.2) 245 | net-ssh (>= 2.8.0) 246 | ostruct 247 | stringio (3.1.2) 248 | thor (1.3.2) 249 | timeout (0.4.3) 250 | tzinfo (2.0.6) 251 | concurrent-ruby (~> 1.0) 252 | unicode-display_width (3.1.3) 253 | unicode-emoji (~> 4.0, >= 4.0.4) 254 | unicode-emoji (4.0.4) 255 | uri (1.0.2) 256 | useragent (0.16.11) 257 | websocket-driver (0.7.6) 258 | websocket-extensions (>= 0.1.0) 259 | websocket-extensions (0.1.5) 260 | zeitwerk (2.7.1) 261 | 262 | PLATFORMS 263 | aarch64-linux 264 | ruby 265 | x86_64-linux 266 | 267 | DEPENDENCIES 268 | bcrypt_pbkdf 269 | bootsnap 270 | byebug 271 | ed25519 272 | jsonapi-serializer 273 | kamal 274 | listen (~> 3.3) 275 | pagy 276 | puma 277 | rails 278 | rubocop 279 | rubocop-performance 280 | rubocop-rails 281 | rubocop-rspec 282 | sqlite3 283 | 284 | BUNDLED WITH 285 | 2.5.23 286 | -------------------------------------------------------------------------------- /rails-vuejs/README.md: -------------------------------------------------------------------------------- 1 | # Rails with Vue CLI 2 | 3 | This project is designed to use Vue as the frontend of Rails. 4 | 5 | Please refer to this [README](https://github.com/guillaumebriday/rails-vue-cli#how-it-works) to know how it works. 6 | 7 | ## Getting started 8 | 9 | ### Install 10 | 11 | Development environment requirements: 12 | - [Ruby](https://www.ruby-lang.org/en/) 13 | - [Node](https://nodejs.org/en/) 14 | 15 | To run the backend: 16 | ```bash 17 | $ git clone git@github.com:guillaumebriday/modern-datatables.git && cd modern-datatables/rails-vuejs 18 | $ bundle 19 | $ bundle exec rails db:migrate 20 | $ bundle exec rails s 21 | ``` 22 | 23 | To run the frontend, in other console: 24 | ```bash 25 | $ cd frontend 26 | $ yarn 27 | $ yarn dev 28 | ``` 29 | -------------------------------------------------------------------------------- /rails-vuejs/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /rails-vuejs/app/controllers/api/v1/base_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Api::V1::BaseController < ActionController::API 4 | include Pagy::Backend 5 | end 6 | -------------------------------------------------------------------------------- /rails-vuejs/app/controllers/api/v1/todos_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Api::V1::TodosController < Api::V1::BaseController 4 | before_action :set_todo, only: %i[show update] 5 | 6 | def index 7 | pagy, todos = pagy Todo.by_description(params[:description]).order(created_at: :desc) 8 | 9 | render json: TodoSerializer.new(todos, links: pagy_metadata(pagy)) 10 | end 11 | 12 | def show 13 | render json: TodoSerializer.new(@todo) 14 | end 15 | 16 | def create 17 | todo = Todo.new(todo_params) 18 | 19 | if todo.save 20 | render json: TodoSerializer.new(todo), status: :created 21 | else 22 | render json: { error: todo.errors.full_messages.first }, status: :unprocessable_entity 23 | end 24 | end 25 | 26 | def update 27 | @todo.attributes = todo_params 28 | 29 | if @todo.save 30 | render json: TodoSerializer.new(@todo) 31 | else 32 | render json: { error: @todo.errors.full_messages.first }, status: :unprocessable_entity 33 | end 34 | end 35 | 36 | private 37 | 38 | def set_todo 39 | @todo = Todo.find(params[:id]) 40 | end 41 | 42 | def todo_params 43 | params 44 | .require(:todo) 45 | .permit( 46 | :description, 47 | :completed 48 | ) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /rails-vuejs/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | def index 5 | render template: 'application' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /rails-vuejs/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | -------------------------------------------------------------------------------- /rails-vuejs/app/models/todo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Todo < ApplicationRecord 4 | validates :description, 5 | uniqueness: true, 6 | presence: true 7 | 8 | scope :by_description, ->(description) { 9 | return if description.blank? 10 | 11 | where(arel_table[:description].matches("%#{description}%")) 12 | } 13 | end 14 | -------------------------------------------------------------------------------- /rails-vuejs/app/serializers/todo_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TodoSerializer 4 | include JSONAPI::Serializer 5 | 6 | attributes :description, :completed 7 | end 8 | -------------------------------------------------------------------------------- /rails-vuejs/app/views/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-vuejs/app/views/.keep -------------------------------------------------------------------------------- /rails-vuejs/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 || ">= 0.a" 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= begin 65 | env_var_version || cli_arg_version || 66 | lockfile_version || "#{Gem::Requirement.default}.a" 67 | end 68 | end 69 | 70 | def load_bundler! 71 | ENV["BUNDLE_GEMFILE"] ||= gemfile 72 | 73 | # must dup string for RG < 1.8 compatibility 74 | activate_bundler(bundler_version.dup) 75 | end 76 | 77 | def activate_bundler(bundler_version) 78 | if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0") 79 | bundler_version = "< 2" 80 | end 81 | gem_error = activation_error_handling do 82 | gem "bundler", bundler_version 83 | end 84 | return if gem_error.nil? 85 | require_error = activation_error_handling do 86 | require "bundler/version" 87 | end 88 | return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 89 | warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" 90 | exit 42 91 | end 92 | 93 | def activation_error_handling 94 | yield 95 | nil 96 | rescue StandardError, LoadError => e 97 | e 98 | end 99 | end 100 | 101 | m.load_bundler! 102 | 103 | if m.invoked_as_script? 104 | load Gem.bin_path("bundler", "bundle") 105 | end 106 | -------------------------------------------------------------------------------- /rails-vuejs/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -f /usr/lib/*/libjemalloc.so.2 ]; then 5 | LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2) $LD_PRELOAD" 6 | fi 7 | 8 | # If running the rails server then create or migrate existing database 9 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 10 | ./bin/rails db:prepare 11 | fi 12 | 13 | exec "${@}" 14 | -------------------------------------------------------------------------------- /rails-vuejs/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /rails-vuejs/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /rails-vuejs/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | 6 | def system!(*args) 7 | system(*args, exception: true) 8 | end 9 | 10 | FileUtils.chdir APP_ROOT do 11 | # This script is a way to set up or update your development environment automatically. 12 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 13 | # Add necessary setup steps to this file. 14 | 15 | puts "== Installing dependencies ==" 16 | system("bundle check") || system!("bundle install") 17 | 18 | # puts "\n== Copying sample files ==" 19 | # unless File.exist?("config/database.yml") 20 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 21 | # end 22 | 23 | puts "\n== Preparing database ==" 24 | system! "bin/rails db:prepare" 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! "bin/rails log:clear tmp:clear" 28 | 29 | unless ARGV.include?("--skip-server") 30 | puts "\n== Starting development server ==" 31 | STDOUT.flush # flush the output before exec(2) so that it displays 32 | exec "bin/dev" 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /rails-vuejs/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | Rails.application.load_server 9 | -------------------------------------------------------------------------------- /rails-vuejs/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails' 6 | # Pick the frameworks you want: 7 | require 'active_model/railtie' 8 | require 'active_job/railtie' 9 | require 'active_record/railtie' 10 | # require "active_storage/engine" 11 | require 'action_controller/railtie' 12 | # require "action_mailer/railtie" 13 | # require "action_mailbox/engine" 14 | # require "action_text/engine" 15 | require 'action_view/railtie' 16 | # require "action_cable/engine" 17 | # require "rails/test_unit/railtie" 18 | 19 | # Require the gems listed in Gemfile, including any gems 20 | # you've limited to :test, :development, or :production. 21 | Bundler.require(*Rails.groups) 22 | 23 | module RailsVuejs 24 | class Application < Rails::Application 25 | # Initialize configuration defaults for originally generated Rails version. 26 | config.load_defaults 8.0 27 | 28 | # Please, add to the `ignore` list any other `lib` subdirectories that do 29 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 30 | # Common ones are `templates`, `generators`, or `middleware`, for example. 31 | config.autoload_lib(ignore: %w[assets tasks]) 32 | 33 | # Configuration for the application, engines, and railties goes here. 34 | # 35 | # These settings can be overridden in specific environments using the files 36 | # in config/environments, which are processed later. 37 | # 38 | # config.time_zone = "Central Time (US & Canada)" 39 | # config.eager_load_paths << Rails.root.join("extras") 40 | 41 | # Don't generate system test files. 42 | config.generators.system_tests = nil 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /rails-vuejs/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /rails-vuejs/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: rails_stimulus_production 11 | -------------------------------------------------------------------------------- /rails-vuejs/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | H3U7vHfTH25UMwBe1Xm17k3Mwf3KRnRlZ2PZC4lfiZg+bA0L23mDvQo3Y+EeHq37ja57Gbm/6iOrcO8Cn4h1wyZsWlUesVMiUqHy4vRmxxVLnHdJqXlLNjurGfdq6KaqS1uVDTZEBW//sSyv9SwaGriDdyjjD3DAZ1VztGchAZrKVvBjWiqusN/V1nxGwxdc1+aVq1XVmI+4uJBRrkpc75fKIDZYI3zh8iSM+FvP/Fv1oHizRgXt0BLgO4firzFvNiEFHZ/rEfXq2S8r02mJ9FP5hFON/sCGdjKCH28CS48VOhhnhXSWKNSg9JQAz2ME+apLXnIGcwB2+/1egli495wbe/FWamOp4w9g/LGUaTpvAx3mgOd/V6LS/XPOsjpWaQCk+8e4IJrw3JnisXTwamc2qNcn63MlUvZi--L8A/KwSzM8STrV7r--s/9Dr14/KfEeBb9lN4ov8A== -------------------------------------------------------------------------------- /rails-vuejs/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: data/production_rails_vuejs.sqlite3 26 | -------------------------------------------------------------------------------- /rails-vuejs/config/deploy.yml: -------------------------------------------------------------------------------- 1 | <% require "dotenv"; Dotenv.load(".env") %> 2 | 3 | # Name of your application. Used to uniquely configure containers. 4 | service: rails-vuejs 5 | 6 | # Name of the container image. 7 | image: guillaumebriday/rails-vuejs 8 | 9 | # Deploy to these servers. 10 | servers: 11 | web: 12 | hosts: 13 | - scw-kilimanjaro.guillaumebriday.fr 14 | 15 | # Credentials for your image host. 16 | registry: 17 | username: guillaumebriday 18 | password: 19 | - KAMAL_REGISTRY_PASSWORD 20 | 21 | # Inject ENV variables into containers (secrets come from .env). 22 | # Remember to run `kamal env push` after making changes! 23 | env: 24 | secret: 25 | - RAILS_MASTER_KEY 26 | 27 | volumes: 28 | - "data:/rails/data" 29 | 30 | proxy: 31 | app_port: 3000 32 | ssl: true 33 | host: rails-vuejs.guillaumebriday.fr 34 | 35 | builder: 36 | arch: amd64 37 | 38 | aliases: 39 | console: app exec -i --reuse "bin/rails console" 40 | bash: app exec -i --reuse "bash" 41 | -------------------------------------------------------------------------------- /rails-vuejs/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /rails-vuejs/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Make code changes take effect immediately without server restart. 9 | config.enable_reloading = true 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing. 18 | config.server_timing = true 19 | 20 | # Enable/disable Action Controller caching. By default Action Controller caching is disabled. 21 | # Run rails dev:cache to toggle Action Controller caching. 22 | if Rails.root.join('tmp/caching-dev.txt').exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | config.public_file_server.headers = { 'cache-control' => "public, max-age=#{2.days.to_i}" } 26 | else 27 | config.action_controller.perform_caching = false 28 | end 29 | 30 | # Change to :null_store to avoid any caching. 31 | config.cache_store = :memory_store 32 | 33 | # Print deprecation notices to the Rails logger. 34 | config.active_support.deprecation = :log 35 | 36 | # Raise an error on page load if there are pending migrations. 37 | config.active_record.migration_error = :page_load 38 | 39 | # Highlight code that triggered database queries in logs. 40 | config.active_record.verbose_query_logs = true 41 | 42 | # Append comments with runtime information tags to SQL queries in logs. 43 | config.active_record.query_log_tags_enabled = true 44 | 45 | # Highlight code that enqueued background job in logs. 46 | config.active_job.verbose_enqueue_logs = true 47 | 48 | # Raises error for missing translations. 49 | # config.i18n.raise_on_missing_translations = true 50 | 51 | # Annotate rendered view with file names. 52 | # config.action_view.annotate_rendered_view_with_filenames = true 53 | 54 | # Raise error when a before_action's only/except options reference missing actions. 55 | config.action_controller.raise_on_missing_callback_actions = true 56 | 57 | # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. 58 | # config.generators.apply_rubocop_autocorrect_after_generate! 59 | end 60 | -------------------------------------------------------------------------------- /rails-vuejs/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/core_ext/integer/time' 4 | 5 | Rails.application.configure do 6 | # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # Code is not reloaded between requests. 9 | config.enable_reloading = false 10 | 11 | # Eager load code on boot for better performance and memory savings (ignored by Rake tasks). 12 | config.eager_load = true 13 | 14 | # Full error reports are disabled. 15 | config.consider_all_requests_local = false 16 | 17 | # Turn on fragment caching in view templates. 18 | config.action_controller.perform_caching = true 19 | 20 | # Cache assets for far-future expiry since they are all digest stamped. 21 | config.public_file_server.headers = { 'cache-control' => "public, max-age=#{1.year.to_i}, immutable" } 22 | 23 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 24 | # config.asset_host = "http://assets.example.com" 25 | 26 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 27 | config.assume_ssl = true 28 | 29 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 30 | config.force_ssl = true 31 | 32 | # Skip http-to-https redirect for the default health check endpoint. 33 | config.ssl_options = { redirect: { exclude: ->(request) { request.path == '/up' } } } 34 | 35 | # Log to STDOUT with the current request id as a default log tag. 36 | config.log_tags = [:request_id] 37 | config.logger = ActiveSupport::TaggedLogging.logger($stdout) 38 | 39 | # Change to "debug" to log everything (including potentially personally-identifiable information!) 40 | config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info') 41 | 42 | # Prevent health checks from clogging up the logs. 43 | config.silence_healthcheck_path = '/up' 44 | 45 | # Don't log any deprecations. 46 | config.active_support.report_deprecations = false 47 | 48 | # Replace the default in-process memory cache store with a durable alternative. 49 | # config.cache_store = :mem_cache_store 50 | 51 | # Replace the default in-process and non-durable queuing backend for Active Job. 52 | # config.active_job.queue_adapter = :resque 53 | 54 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 55 | # the I18n.default_locale when a translation cannot be found). 56 | config.i18n.fallbacks = true 57 | 58 | # Do not dump schema after migrations. 59 | config.active_record.dump_schema_after_migration = false 60 | 61 | # Only use :id for inspections in production. 62 | config.active_record.attributes_for_inspect = [:id] 63 | 64 | # Enable DNS rebinding protection and other `Host` header attacks. 65 | # config.hosts = [ 66 | # "example.com", # Allow requests from example.com 67 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 68 | # ] 69 | # 70 | # Skip DNS rebinding protection for the default health check endpoint. 71 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 72 | end 73 | -------------------------------------------------------------------------------- /rails-vuejs/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV['CI'].present? 19 | 20 | # Configure public file server for tests with cache-control for performance. 21 | config.public_file_server.headers = { 'cache-control' => 'public, max-age=3600' } 22 | 23 | # Show full error reports. 24 | config.consider_all_requests_local = true 25 | config.cache_store = :null_store 26 | 27 | # Render exception templates for rescuable exceptions and raise for other exceptions. 28 | config.action_dispatch.show_exceptions = :rescuable 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | # Print deprecation notices to the stderr. 34 | config.active_support.deprecation = :stderr 35 | 36 | # Raises error for missing translations. 37 | # config.i18n.raise_on_missing_translations = true 38 | 39 | # Annotate rendered view with file names. 40 | # config.action_view.annotate_rendered_view_with_filenames = true 41 | 42 | # Raise error when a before_action's only/except options reference missing actions. 43 | config.action_controller.raise_on_missing_callback_actions = true 44 | end 45 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # ActiveSupport::Reloader.to_prepare do 6 | # ApplicationController.renderer.defaults.merge!( 7 | # http_host: 'example.org', 8 | # https: false 9 | # ) 10 | # end 11 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code 9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". 10 | Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE'] 11 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide content security policy. 6 | # See the Securing Rails Applications Guide for more information: 7 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 8 | 9 | # Rails.application.configure do 10 | # config.content_security_policy do |policy| 11 | # policy.default_src :self, :https 12 | # policy.font_src :self, :https, :data 13 | # policy.img_src :self, :https, :data 14 | # policy.object_src :none 15 | # policy.script_src :self, :https 16 | # policy.style_src :self, :https 17 | # # Specify URI for violation reports 18 | # # policy.report_uri "/csp-violation-report-endpoint" 19 | # end 20 | # 21 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 22 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 23 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 24 | # 25 | # # Report violations without enforcing the policy. 26 | # # config.content_security_policy_report_only = true 27 | # end 28 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 6 | # Use this to limit dissemination of sensitive information. 7 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 8 | Rails.application.config.filter_parameters += %i[ 9 | passw email secret token _key crypt salt certificate otp ssn cvv cvc 10 | ] 11 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, "\\1en" 10 | # inflect.singular /^(ox)en/i, "\\1" 11 | # inflect.irregular "person", "people" 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # inflect.acronym "RESTful" 18 | # end 19 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new mime types for use in respond_to blocks: 6 | # Mime::Type.register "text/richtext", :rtf 7 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/pagy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pagy/extras/metadata' 4 | require 'pagy/extras/limit' 5 | require 'pagy/extras/overflow' 6 | 7 | Pagy::DEFAULT[:limit_max] = 100 8 | Pagy::DEFAULT[:limit_param] = :per_page 9 | # Pagy::DEFAULT[:metadata] = %i[count page prev next last] 10 | Pagy::DEFAULT[:overflow] = :last_page 11 | Pagy::DEFAULT[:items] = 10 12 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Define an application-wide HTTP permissions policy. For further 6 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 7 | 8 | # Rails.application.config.permissions_policy do |policy| 9 | # policy.camera :none 10 | # policy.gyroscope :none 11 | # policy.microphone :none 12 | # policy.usb :none 13 | # policy.fullscreen :self 14 | # policy.payment :self, "https://secure.example.com" 15 | # end 16 | -------------------------------------------------------------------------------- /rails-vuejs/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /rails-vuejs/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /rails-vuejs/config/master.key: -------------------------------------------------------------------------------- 1 | 12bd6cd7b558647e04ee7868199e9ced -------------------------------------------------------------------------------- /rails-vuejs/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This configuration file will be evaluated by Puma. The top-level methods that 4 | # are invoked here are part of Puma's configuration DSL. For more information 5 | # about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. 6 | # 7 | # Puma starts a configurable number of processes (workers) and each process 8 | # serves each request in a thread from an internal thread pool. 9 | # 10 | # You can control the number of workers using ENV["WEB_CONCURRENCY"]. You 11 | # should only set this value when you want to run 2 or more workers. The 12 | # default is already 1. 13 | # 14 | # The ideal number of threads per worker depends both on how much time the 15 | # application spends waiting for IO operations and on how much you wish to 16 | # prioritize throughput over latency. 17 | # 18 | # As a rule of thumb, increasing the number of threads will increase how much 19 | # traffic a given process can handle (throughput), but due to CRuby's 20 | # Global VM Lock (GVL) it has diminishing returns and will degrade the 21 | # response time (latency) of the application. 22 | # 23 | # The default is set to 3 threads as it's deemed a decent compromise between 24 | # throughput and latency for the average Rails application. 25 | # 26 | # Any libraries that use a connection pool or another resource pool should 27 | # be configured to provide at least as many connections as the number of 28 | # threads. This includes Active Record's `pool` parameter in `database.yml`. 29 | threads_count = ENV.fetch('RAILS_MAX_THREADS', 3) 30 | threads threads_count, threads_count 31 | 32 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 33 | port ENV.fetch('PORT', 3000) 34 | 35 | # Allow puma to be restarted by `bin/rails restart` command. 36 | plugin :tmp_restart 37 | 38 | # Run the Solid Queue supervisor inside of Puma for single-server deployments 39 | plugin :solid_queue if ENV['SOLID_QUEUE_IN_PUMA'] 40 | 41 | # Specify the PID file. Defaults to tmp/pids/server.pid in development. 42 | # In other environments, only set the PID file if requested. 43 | pidfile ENV['PIDFILE'] if ENV['PIDFILE'] 44 | -------------------------------------------------------------------------------- /rails-vuejs/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | namespace :api do 5 | namespace :v1 do 6 | resources :todos, only: %i[index create show update] 7 | end 8 | end 9 | 10 | get :up, to: 'rails/health#show' 11 | 12 | root to: 'application#index' 13 | 14 | get '*path', to: 'application#index', format: false 15 | end 16 | -------------------------------------------------------------------------------- /rails-vuejs/data/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-vuejs/data/.keep -------------------------------------------------------------------------------- /rails-vuejs/db/migrate/20200908202034_create_todos.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateTodos < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :todos do |t| 6 | t.string :description 7 | t.boolean :completed, default: false, null: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /rails-vuejs/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `rails 6 | # db:schema:load`. When creating a new database, `rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2020_09_08_202034) do 14 | 15 | create_table "todos", force: :cascade do |t| 16 | t.string "description" 17 | t.boolean "completed", default: false 18 | t.datetime "created_at", precision: 6, null: false 19 | t.datetime "updated_at", precision: 6, null: false 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /rails-vuejs/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file should contain all the record creation needed to seed the database with its default values. 4 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 5 | # 6 | # Examples: 7 | # 8 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 9 | # Character.create(name: 'Luke', movie: movies.first) 10 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: "vue-eslint-parser", 6 | parserOptions: { 7 | sourceType: "module" 8 | }, 9 | env: { 10 | browser: true, 11 | node: true, 12 | es6: true 13 | }, 14 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 15 | extends: [ 16 | 'standard', 17 | 'plugin:vue/recommended' 18 | ], 19 | // required to lint *.vue files 20 | plugins: [ 21 | 'vue' 22 | ], 23 | // add your custom rules here 24 | rules: { 25 | 'vue/multi-word-component-names': 'off' 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | .yarn-integrity 5 | .yarn/* 6 | !.yarn/patches 7 | !.yarn/plugins 8 | !.yarn/releases 9 | !.yarn/sdks 10 | !.yarn/versions 11 | 12 | # local env files 13 | .env.local 14 | .env.*.local 15 | 16 | # Log files 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | pnpm-debug.log* 21 | 22 | # Editor directories and files 23 | .idea 24 | .vscode 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/.node-version: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Modern Datatables 9 | 10 | 11 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "serve": "vite preview", 8 | "build": "vite build", 9 | "lint": "eslint --ext .js,.vue src" 10 | }, 11 | "dependencies": { 12 | "@tailwindcss/forms": "^0.5.9", 13 | "@unhead/vue": "^1.11.14", 14 | "@vitejs/plugin-vue": "^5.2.1", 15 | "autoprefixer": "^10.4.20", 16 | "postcss": "^8.4.49", 17 | "rollup-plugin-gzip": "^4.0.1", 18 | "tailwindcss": "^3.4.17", 19 | "vite": "^5.4.11", 20 | "vue": "^3.5.13", 21 | "vue-router": "^4.5.0", 22 | "vuex": "^4.1.0" 23 | }, 24 | "devDependencies": { 25 | "@vue/compiler-sfc": "^3.5.13", 26 | "eslint": "^8.57.1", 27 | "eslint-config-standard": "^17.1.0", 28 | "eslint-plugin-import": "^2.31.0", 29 | "eslint-plugin-n": "^16.6.2", 30 | "eslint-plugin-promise": "^6.6.0", 31 | "eslint-plugin-standard": "^5.0.0", 32 | "eslint-plugin-vue": "^9.32.0", 33 | "vue-eslint-parser": "^9.4.3" 34 | }, 35 | "packageManager": "yarn@4.5.3" 36 | } 37 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/Shared/Footer.vue: -------------------------------------------------------------------------------- 1 | 54 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/Shared/Pagination.vue: -------------------------------------------------------------------------------- 1 | 98 | 99 | 116 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/Todos/Empty.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/Todos/Form.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 94 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/Todos/List.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 91 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/components/Todos/Search.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 41 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { store } from '@/store' 3 | import { router } from '@/router' 4 | import { createHead } from '@unhead/vue' 5 | import App from '@/components/App.vue' 6 | 7 | import './style.css' 8 | 9 | const head = createHead() 10 | 11 | const app = createApp(App, { 12 | performance: true 13 | }) 14 | 15 | app 16 | .use(router) 17 | .use(store) 18 | .use(head) 19 | 20 | app.mount('#app') 21 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/pages/Todos/Edit.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 61 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/pages/Todos/Index.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 98 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/pages/Todos/New.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 45 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | 3 | const TodosIndex = () => import('@/pages/Todos/Index.vue') 4 | const NewTodo = () => import('@/pages/Todos/New.vue') 5 | const EditTodo = () => import('@/pages/Todos/Edit.vue') 6 | 7 | export const router = createRouter({ 8 | history: createWebHistory(), 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'index-todo', 13 | component: TodosIndex 14 | }, 15 | { 16 | path: '/todos/new', 17 | name: 'new-todo', 18 | component: NewTodo 19 | }, 20 | { 21 | path: '/todos/:id/edit', 22 | name: 'edit-todo', 23 | component: EditTodo 24 | }, 25 | { 26 | path: '/:pathMatch(.*)*', 27 | redirect: '/' 28 | } 29 | ] 30 | }) 31 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | import todos from '@/store/modules/todos' 3 | 4 | export const store = createStore({ 5 | modules: { 6 | todos 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/store/modules/todos.js: -------------------------------------------------------------------------------- 1 | import { jsonFetch } from '@/utils/fetch' 2 | 3 | const state = { 4 | todos: [], 5 | todo: {}, 6 | links: {}, 7 | endpoint: '/api/v1/todos' 8 | } 9 | 10 | const mutations = { 11 | setTodos (state, todos) { 12 | state.todos = todos 13 | }, 14 | 15 | setTodo (state, todo) { 16 | state.todo = todo 17 | }, 18 | 19 | updateTodo (state, todo) { 20 | const todoId = todo.id 21 | state.todos.splice(state.todos.findIndex(todo => todo.id === todoId), 1, todo) 22 | }, 23 | 24 | setLinks (state, links) { 25 | state.links = links 26 | } 27 | } 28 | 29 | const getters = { 30 | allTodos (state) { 31 | return state.todos 32 | }, 33 | 34 | todo (state) { 35 | return state.todo 36 | }, 37 | 38 | links (state) { 39 | return state.links 40 | } 41 | } 42 | 43 | const actions = { 44 | fetchTodos ({ commit }, params = {}) { 45 | return jsonFetch(state.endpoint, { params }) 46 | .then(data => { 47 | commit('setTodos', data.data) 48 | commit('setLinks', data.links) 49 | 50 | return data.data 51 | }) 52 | .catch(error => { 53 | return Promise.reject(error) 54 | }) 55 | }, 56 | 57 | fetchTodo ({ commit }, { id }) { 58 | return jsonFetch(`${state.endpoint}/${id}`) 59 | .then(data => { 60 | commit('setTodo', data.data) 61 | 62 | return data.data 63 | }) 64 | .catch(error => { 65 | return Promise.reject(error) 66 | }) 67 | }, 68 | 69 | addTodo (_, { params }) { 70 | return jsonFetch(state.endpoint, { 71 | method: 'POST', 72 | body: { todo: params } 73 | }) 74 | .then(data => { 75 | return data 76 | }) 77 | .catch(error => { 78 | return Promise.reject(error) 79 | }) 80 | }, 81 | 82 | updateTodo ({ commit }, { id, params }) { 83 | return jsonFetch(`${state.endpoint}/${id}`, { 84 | method: 'PATCH', 85 | body: { todo: params } 86 | }) 87 | .then(data => { 88 | commit('updateTodo', data.data) 89 | return data.data 90 | }) 91 | .catch(error => { 92 | return Promise.reject(error) 93 | }) 94 | } 95 | } 96 | 97 | export default { 98 | state, 99 | mutations, 100 | getters, 101 | actions 102 | } 103 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | export function jsonFetch (url, options = {}) { 2 | if (options.body && typeof options.body === 'object') { 3 | options.body = JSON.stringify(options.body) 4 | } 5 | 6 | if (options.params) { 7 | url = `${url}?${new URLSearchParams(options.params)}` 8 | 9 | delete options.params 10 | } 11 | 12 | options = { 13 | headers: { 14 | 'X-Requested-With': 'XMLHttpRequest', 15 | Accept: 'application/json', 16 | 'Content-Type': 'application/json' 17 | }, 18 | ...options 19 | } 20 | 21 | return fetch(url, options) 22 | .then(async (response) => { 23 | if (response.status === 204) { 24 | return null 25 | } 26 | 27 | const data = await response.json() 28 | 29 | if (response.ok) { 30 | return data 31 | } 32 | 33 | return Promise.reject(data) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './index.html', 5 | './src/**/*.{js,ts,vue}' 6 | ], 7 | plugins: [ 8 | require('@tailwindcss/forms') 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /rails-vuejs/frontend/vite.config.mjs: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig } from 'vite' 3 | import vue from "@vitejs/plugin-vue" 4 | import { brotliCompressSync } from "zlib" 5 | import gzipPlugin from "rollup-plugin-gzip" 6 | 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | // Create gzip copies of relevant assets 11 | gzipPlugin(), 12 | // Create brotli copies of relevant assets 13 | gzipPlugin({ 14 | customCompression: (content) => brotliCompressSync(Buffer.from(content)), 15 | fileName: ".br", 16 | }), 17 | ], 18 | 19 | server: { 20 | proxy: { 21 | // with options: http://localhost:5173/api-> http://localhost:3000/api 22 | '/api': { 23 | target: 'http://localhost:3000' 24 | }, 25 | } 26 | }, 27 | 28 | // output built static files to Rails's public dir. 29 | // note the "build" script in package.json needs to be modified as well. 30 | build: { 31 | outDir: '../public' 32 | }, 33 | 34 | resolve: { 35 | alias: { 36 | "@": path.resolve(__dirname, "./src"), 37 | }, 38 | }, 39 | }) 40 | -------------------------------------------------------------------------------- /rails-vuejs/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-vuejs/log/.keep -------------------------------------------------------------------------------- /rails-vuejs/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /rails-vuejs/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /rails-vuejs/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /rails-vuejs/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-vuejs/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /rails-vuejs/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-vuejs/public/apple-touch-icon.png -------------------------------------------------------------------------------- /rails-vuejs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guillaumebriday/modern-datatables/a06a64a493e592ecc2394f68a5833edc4efa7b4c/rails-vuejs/public/favicon.ico -------------------------------------------------------------------------------- /rails-vuejs/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | --------------------------------------------------------------------------------