├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .overcommit.yml ├── .ruby-version ├── .simplecov ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── cable.js │ │ └── channels │ │ │ └── .keep │ └── stylesheets │ │ └── application.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ └── home_controller.rb ├── helpers │ ├── application_helper.rb │ ├── javascript_helper.rb │ ├── layout_helper.rb │ └── retina_image_helper.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── sl_column.rb │ ├── sl_row.rb │ └── sl_table.rb ├── views │ ├── home │ │ └── index.html.erb │ ├── layouts │ │ ├── application.html.erb │ │ ├── base.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ └── shared │ │ └── _flash.html.erb └── workers │ └── .keep ├── bin ├── bundle ├── rails ├── rake ├── setup ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── blazer.yml ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.example.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ ├── staging.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── generators.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── rotate_log.rb │ ├── secret_token.rb │ ├── sidekiq.rb │ ├── version.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── sidekiq.yml ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20181216081144_create_sl_tables.rb │ ├── 20181216081159_create_sl_columns.rb │ ├── 20181216081313_create_sl_rows.rb │ ├── 20181216195857_init_view_schema.rb │ ├── 20181218173444_add_ref_setting.rb │ ├── 20181218191057_add_indexing.rb │ └── 20181221101048_install_blazer.rb ├── schema.rb └── seeds.rb ├── doc └── .keep ├── example.env ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ ├── auto_annotate_models.rake │ └── coverage.rake ├── log └── .keep ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── test ├── application_system_test_case.rb ├── controllers │ └── .keep ├── fixtures │ ├── .keep │ ├── files │ │ └── .keep │ ├── sl_columns.yml │ ├── sl_rows.yml │ └── sl_tables.yml ├── helpers │ ├── .keep │ ├── javascript_helper_test.rb │ └── retina_image_helper_test.rb ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── sl_column_test.rb │ ├── sl_row_test.rb │ └── sl_table_test.rb ├── support │ ├── circleci.rb │ ├── mailer.rb │ ├── mocha.rb │ ├── rails.rb │ ├── shoulda_matchers.rb │ └── sidekiq.rb ├── system │ ├── .keep │ └── layout_helper_test.rb ├── test_helper.rb └── unit │ ├── .keep │ └── lib │ ├── .keep │ └── tasks │ └── .keep ├── tmp └── .keep └── vendor └── .keep /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Ruby CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-ruby/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | - image: circleci/ruby:2.3.7-node-browsers 10 | environment: 11 | PGHOST: 127.0.0.1 12 | PGUSER: postgres 13 | RAILS_ENV: test 14 | - image: circleci/postgres:10.5-alpine 15 | environment: 16 | POSTGRES_USER: postgres 17 | POSTGRES_PASSWORD: "" 18 | 19 | working_directory: ~/repo 20 | 21 | steps: 22 | - checkout 23 | 24 | # Download and cache dependencies 25 | - restore_cache: 26 | keys: 27 | - v1-dependencies-{{ arch }}-{{ checksum "Gemfile.lock" }} 28 | # fallback to using the latest cache if no exact match is found 29 | - v1-dependencies-{{ arch }}- 30 | 31 | - run: 32 | name: Install dependencies 33 | command: | 34 | bundle install --deployment --jobs=4 --retry=3 --path vendor/bundle 35 | bundle clean 36 | 37 | - save_cache: 38 | paths: 39 | - ./vendor/bundle 40 | key: v1-dependencies-{{ arch }}-{{ checksum "Gemfile.lock" }} 41 | 42 | - run: 43 | name: Run overcommit 44 | command: | 45 | git config --local user.name "Circle CI" 46 | git config --local user.email ci@example.com 47 | bundle exec overcommit --sign 48 | bundle exec overcommit --run 49 | 50 | - run: 51 | name: Run security audits 52 | command: | 53 | bundle exec brakeman -q --no-summary 54 | bundle exec bundle-audit check --update -v 55 | 56 | - run: 57 | name: Set up database 58 | command: | 59 | cp config/database.example.yml config/database.yml 60 | cp example.env .env 61 | bundle exec rake db:setup 62 | 63 | - run: 64 | name: Run tests 65 | command: | 66 | bundle exec rake test test:system TESTOPTS="--ci-dir=./reports" 67 | 68 | - store_test_results: 69 | path: ./reports 70 | 71 | - store_artifacts: 72 | path: ./tmp/screenshots 73 | destination: screenshots 74 | 75 | workflows: 76 | version: 2 77 | commit-workflow: 78 | jobs: 79 | - build 80 | cron-workflow: 81 | triggers: 82 | - schedule: 83 | cron: "0 13 * * 1,2,3,4,5" 84 | filters: 85 | branches: 86 | only: 87 | - master 88 | - development 89 | jobs: 90 | - build 91 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore uploaded files in development 17 | /storage/* 18 | 19 | /node_modules 20 | /yarn-error.log 21 | 22 | .byebug_history 23 | 24 | # Ignore test reports 25 | /brakeman-output.* 26 | /coverage 27 | /test/reports/ 28 | /capybara-*.html 29 | 30 | # Ignore results of `rake assets:precompile`. 31 | /public/assets/ 32 | 33 | # Never check passwords into source control! 34 | /config/database.yml 35 | /config/master.key 36 | /.env 37 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | # Use this file to configure the Overcommit hooks you wish to use. This will 2 | # extend the default configuration defined in: 3 | # https://github.com/brigade/overcommit/blob/master/config/default.yml 4 | # 5 | # At the topmost level of this YAML file is a key representing type of hook 6 | # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can 7 | # customize each hook, such as whether to only run it on certain files (via 8 | # `include`), whether to only display output if it fails (via `quiet`), etc. 9 | # 10 | # For a complete list of hooks, see: 11 | # https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook 12 | # 13 | # For a complete list of options that you can use to customize hooks, see: 14 | # https://github.com/brigade/overcommit#configuration 15 | # 16 | # Uncomment the following lines to make the configuration take effect. 17 | 18 | gemfile: Gemfile 19 | verify_signatures: false 20 | 21 | PreCommit: 22 | BundleCheck: 23 | enabled: true 24 | 25 | FixMe: 26 | enabled: true 27 | keywords: ["FIXME"] 28 | exclude: 29 | - .overcommit.yml 30 | 31 | LocalPathsInGemfile: 32 | enabled: true 33 | 34 | RailsSchemaUpToDate: 35 | enabled: true 36 | 37 | RuboCop: 38 | enabled: true 39 | on_warn: fail 40 | 41 | TrailingWhitespace: 42 | enabled: true 43 | exclude: 44 | - "**/db/structure.sql" 45 | 46 | YamlSyntax: 47 | enabled: true 48 | 49 | PostCheckout: 50 | ALL: 51 | quiet: true 52 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.7 2 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | require "simplecov" 2 | SimpleCov.start("rails") do 3 | add_filter("/bin/") 4 | add_filter("/lib/tasks/auto_annotate_models.rake") 5 | add_filter("/lib/tasks/coverage.rake") 6 | end 7 | SimpleCov.minimum_coverage(90) 8 | SimpleCov.use_merging(false) 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | gem "mini_sql" 4 | gem "active_type", ">= 0.3.2" 5 | gem "autoprefixer-rails", ">= 5.0.0.1" 6 | gem "bcrypt", "~> 3.1.7" 7 | gem "bootsnap", ">= 1.2.0", require: false 8 | gem "coffee-rails", "~> 4.2" 9 | gem "dotenv-rails", ">= 2.0.0" 10 | gem "jquery-rails" 11 | gem "mail", ">= 2.6.3" 12 | gem "marco-polo" 13 | gem "pg", ">= 0.18" 14 | gem "pgcli-rails" 15 | gem "puma", "~> 3.11" 16 | gem "rails", "~> 5.2.2" 17 | gem "sass-rails", "~> 5.0" 18 | gem "sidekiq", ">= 4.2.0" 19 | gem "turbolinks", "~> 5" 20 | 21 | group :production, :staging do 22 | gem "postmark-rails" 23 | end 24 | 25 | group :development do 26 | gem "annotate", ">= 2.5.0" 27 | gem "awesome_print" 28 | gem "better_errors" 29 | gem "binding_of_caller" 30 | gem "brakeman", require: false 31 | gem "bundler-audit", ">= 0.5.0", require: false 32 | gem "guard", ">= 2.2.2", require: false 33 | gem "guard-livereload", require: false 34 | gem "guard-minitest", require: false 35 | gem "letter_opener" 36 | gem "listen", ">= 3.0.5" 37 | gem "overcommit", ">= 0.37.0", require: false 38 | gem "rack-livereload" 39 | gem "rubocop", ">= 0.58.0", require: false 40 | gem "simplecov", require: false 41 | gem "spring" 42 | gem "sshkit", "~> 1.16", require: false 43 | gem "spring-watcher-listen", "~> 2.0.0" 44 | gem "terminal-notifier", require: false 45 | gem "terminal-notifier-guard", require: false 46 | gem "xray-rails", ">= 0.1.18" 47 | gem "pry-rails" 48 | end 49 | 50 | group :test do 51 | gem "capybara", ">= 2.15" 52 | gem "chromedriver-helper" 53 | gem "launchy" 54 | gem "minitest-ci", ">= 3.3.0", require: false 55 | gem "mocha", ">= 1.4.0" 56 | gem "selenium-webdriver" 57 | gem "shoulda-context" 58 | gem "shoulda-matchers", ">= 3.0.1" 59 | end 60 | 61 | gem 'blazer' 62 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.2) 5 | actionpack (= 5.2.2) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.2) 9 | actionpack (= 5.2.2) 10 | actionview (= 5.2.2) 11 | activejob (= 5.2.2) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.2) 15 | actionview (= 5.2.2) 16 | activesupport (= 5.2.2) 17 | rack (~> 2.0) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.2) 22 | activesupport (= 5.2.2) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | active_type (0.7.5) 28 | activerecord (>= 3.2) 29 | activejob (5.2.2) 30 | activesupport (= 5.2.2) 31 | globalid (>= 0.3.6) 32 | activemodel (5.2.2) 33 | activesupport (= 5.2.2) 34 | activerecord (5.2.2) 35 | activemodel (= 5.2.2) 36 | activesupport (= 5.2.2) 37 | arel (>= 9.0) 38 | activestorage (5.2.2) 39 | actionpack (= 5.2.2) 40 | activerecord (= 5.2.2) 41 | marcel (~> 0.3.1) 42 | activesupport (5.2.2) 43 | concurrent-ruby (~> 1.0, >= 1.0.2) 44 | i18n (>= 0.7, < 2) 45 | minitest (~> 5.1) 46 | tzinfo (~> 1.1) 47 | addressable (2.5.2) 48 | public_suffix (>= 2.0.2, < 4.0) 49 | annotate (2.7.4) 50 | activerecord (>= 3.2, < 6.0) 51 | rake (>= 10.4, < 13.0) 52 | archive-zip (0.11.0) 53 | io-like (~> 0.3.0) 54 | arel (9.0.0) 55 | ast (2.4.0) 56 | autoprefixer-rails (9.4.2) 57 | execjs 58 | awesome_print (1.8.0) 59 | bcrypt (3.1.12) 60 | better_errors (2.5.0) 61 | coderay (>= 1.0.0) 62 | erubi (>= 1.0.0) 63 | rack (>= 0.9.0) 64 | binding_of_caller (0.8.0) 65 | debug_inspector (>= 0.0.1) 66 | blazer (1.9.0) 67 | activerecord (>= 4) 68 | chartkick 69 | railties (>= 4) 70 | safely_block (>= 0.1.1) 71 | bootsnap (1.3.2) 72 | msgpack (~> 1.0) 73 | brakeman (4.3.1) 74 | builder (3.2.3) 75 | bundler-audit (0.6.0) 76 | bundler (~> 1.2) 77 | thor (~> 0.18) 78 | capybara (3.12.0) 79 | addressable 80 | mini_mime (>= 0.1.3) 81 | nokogiri (~> 1.8) 82 | rack (>= 1.6.0) 83 | rack-test (>= 0.6.3) 84 | regexp_parser (~> 1.2) 85 | xpath (~> 3.2) 86 | chartkick (3.0.1) 87 | childprocess (0.9.0) 88 | ffi (~> 1.0, >= 1.0.11) 89 | chromedriver-helper (2.1.0) 90 | archive-zip (~> 0.10) 91 | nokogiri (~> 1.8) 92 | coderay (1.1.2) 93 | coffee-rails (4.2.2) 94 | coffee-script (>= 2.2.0) 95 | railties (>= 4.0.0) 96 | coffee-script (2.4.1) 97 | coffee-script-source 98 | execjs 99 | coffee-script-source (1.12.2) 100 | concurrent-ruby (1.1.4) 101 | connection_pool (2.2.2) 102 | crass (1.0.4) 103 | debug_inspector (0.0.3) 104 | docile (1.3.1) 105 | dotenv (2.5.0) 106 | dotenv-rails (2.5.0) 107 | dotenv (= 2.5.0) 108 | railties (>= 3.2, < 6.0) 109 | em-websocket (0.5.1) 110 | eventmachine (>= 0.12.9) 111 | http_parser.rb (~> 0.6.0) 112 | errbase (0.1.1) 113 | erubi (1.7.1) 114 | eventmachine (1.2.7) 115 | execjs (2.7.0) 116 | ffi (1.9.25) 117 | formatador (0.2.5) 118 | globalid (0.4.1) 119 | activesupport (>= 4.2.0) 120 | guard (2.15.0) 121 | formatador (>= 0.2.4) 122 | listen (>= 2.7, < 4.0) 123 | lumberjack (>= 1.0.12, < 2.0) 124 | nenv (~> 0.1) 125 | notiffany (~> 0.0) 126 | pry (>= 0.9.12) 127 | shellany (~> 0.0) 128 | thor (>= 0.18.1) 129 | guard-compat (1.2.1) 130 | guard-livereload (2.5.2) 131 | em-websocket (~> 0.5) 132 | guard (~> 2.8) 133 | guard-compat (~> 1.0) 134 | multi_json (~> 1.8) 135 | guard-minitest (2.4.6) 136 | guard-compat (~> 1.2) 137 | minitest (>= 3.0) 138 | http_parser.rb (0.6.0) 139 | i18n (1.2.0) 140 | concurrent-ruby (~> 1.0) 141 | iniparse (1.4.4) 142 | io-like (0.3.0) 143 | jaro_winkler (1.5.1) 144 | jquery-rails (4.3.3) 145 | rails-dom-testing (>= 1, < 3) 146 | railties (>= 4.2.0) 147 | thor (>= 0.14, < 2.0) 148 | json (2.1.0) 149 | launchy (2.4.3) 150 | addressable (~> 2.3) 151 | letter_opener (1.7.0) 152 | launchy (~> 2.2) 153 | listen (3.1.5) 154 | rb-fsevent (~> 0.9, >= 0.9.4) 155 | rb-inotify (~> 0.9, >= 0.9.7) 156 | ruby_dep (~> 1.2) 157 | loofah (2.2.3) 158 | crass (~> 1.0.2) 159 | nokogiri (>= 1.5.9) 160 | lumberjack (1.0.13) 161 | mail (2.7.1) 162 | mini_mime (>= 0.1.1) 163 | marcel (0.3.3) 164 | mimemagic (~> 0.3.2) 165 | marco-polo (1.2.1) 166 | metaclass (0.0.4) 167 | method_source (0.9.2) 168 | mimemagic (0.3.2) 169 | mini_mime (1.0.1) 170 | mini_portile2 (2.3.0) 171 | mini_sql (0.1.10) 172 | minitest (5.11.3) 173 | minitest-ci (3.4.0) 174 | minitest (>= 5.0.6) 175 | mocha (1.7.0) 176 | metaclass (~> 0.0.1) 177 | msgpack (1.2.4) 178 | multi_json (1.13.1) 179 | nenv (0.3.0) 180 | net-scp (1.2.1) 181 | net-ssh (>= 2.6.5) 182 | net-ssh (5.0.2) 183 | nio4r (2.3.1) 184 | nokogiri (1.8.5) 185 | mini_portile2 (~> 2.3.0) 186 | notiffany (0.1.1) 187 | nenv (~> 0.1) 188 | shellany (~> 0.0) 189 | overcommit (0.46.0) 190 | childprocess (~> 0.6, >= 0.6.3) 191 | iniparse (~> 1.4) 192 | parallel (1.12.1) 193 | parser (2.5.3.0) 194 | ast (~> 2.4.0) 195 | pg (1.1.3) 196 | pgcli-rails (0.3.0) 197 | railties (>= 4.2.0) 198 | postmark (1.14.0) 199 | json 200 | rake 201 | postmark-rails (0.18.0) 202 | actionmailer (>= 3.0.0) 203 | postmark (~> 1.14.0) 204 | powerpack (0.1.2) 205 | pry (0.12.2) 206 | coderay (~> 1.1.0) 207 | method_source (~> 0.9.0) 208 | pry-rails (0.3.8) 209 | pry (>= 0.10.4) 210 | public_suffix (3.0.3) 211 | puma (3.12.0) 212 | rack (2.0.6) 213 | rack-livereload (0.3.17) 214 | rack 215 | rack-protection (2.0.4) 216 | rack 217 | rack-test (1.1.0) 218 | rack (>= 1.0, < 3) 219 | rails (5.2.2) 220 | actioncable (= 5.2.2) 221 | actionmailer (= 5.2.2) 222 | actionpack (= 5.2.2) 223 | actionview (= 5.2.2) 224 | activejob (= 5.2.2) 225 | activemodel (= 5.2.2) 226 | activerecord (= 5.2.2) 227 | activestorage (= 5.2.2) 228 | activesupport (= 5.2.2) 229 | bundler (>= 1.3.0) 230 | railties (= 5.2.2) 231 | sprockets-rails (>= 2.0.0) 232 | rails-dom-testing (2.0.3) 233 | activesupport (>= 4.2.0) 234 | nokogiri (>= 1.6) 235 | rails-html-sanitizer (1.0.4) 236 | loofah (~> 2.2, >= 2.2.2) 237 | railties (5.2.2) 238 | actionpack (= 5.2.2) 239 | activesupport (= 5.2.2) 240 | method_source 241 | rake (>= 0.8.7) 242 | thor (>= 0.19.0, < 2.0) 243 | rainbow (3.0.0) 244 | rake (12.3.2) 245 | rb-fsevent (0.10.3) 246 | rb-inotify (0.10.0) 247 | ffi (~> 1.0) 248 | redis (4.1.0) 249 | regexp_parser (1.3.0) 250 | rubocop (0.61.1) 251 | jaro_winkler (~> 1.5.1) 252 | parallel (~> 1.10) 253 | parser (>= 2.5, != 2.5.1.1) 254 | powerpack (~> 0.1) 255 | rainbow (>= 2.2.2, < 4.0) 256 | ruby-progressbar (~> 1.7) 257 | unicode-display_width (~> 1.4.0) 258 | ruby-progressbar (1.10.0) 259 | ruby_dep (1.5.0) 260 | rubyzip (1.2.2) 261 | safely_block (0.2.1) 262 | errbase 263 | sass (3.7.2) 264 | sass-listen (~> 4.0.0) 265 | sass-listen (4.0.0) 266 | rb-fsevent (~> 0.9, >= 0.9.4) 267 | rb-inotify (~> 0.9, >= 0.9.7) 268 | sass-rails (5.0.7) 269 | railties (>= 4.0.0, < 6) 270 | sass (~> 3.1) 271 | sprockets (>= 2.8, < 4.0) 272 | sprockets-rails (>= 2.0, < 4.0) 273 | tilt (>= 1.1, < 3) 274 | selenium-webdriver (3.141.0) 275 | childprocess (~> 0.5) 276 | rubyzip (~> 1.2, >= 1.2.2) 277 | shellany (0.0.1) 278 | shoulda-context (1.2.2) 279 | shoulda-matchers (3.1.2) 280 | activesupport (>= 4.0.0) 281 | sidekiq (5.2.3) 282 | connection_pool (~> 2.2, >= 2.2.2) 283 | rack-protection (>= 1.5.0) 284 | redis (>= 3.3.5, < 5) 285 | simplecov (0.16.1) 286 | docile (~> 1.1) 287 | json (>= 1.8, < 3) 288 | simplecov-html (~> 0.10.0) 289 | simplecov-html (0.10.2) 290 | spring (2.0.2) 291 | activesupport (>= 4.2) 292 | spring-watcher-listen (2.0.1) 293 | listen (>= 2.7, < 4.0) 294 | spring (>= 1.2, < 3.0) 295 | sprockets (3.7.2) 296 | concurrent-ruby (~> 1.0) 297 | rack (> 1, < 3) 298 | sprockets-rails (3.2.1) 299 | actionpack (>= 4.0) 300 | activesupport (>= 4.0) 301 | sprockets (>= 3.0.0) 302 | sshkit (1.18.0) 303 | net-scp (>= 1.1.2) 304 | net-ssh (>= 2.8.0) 305 | terminal-notifier (2.0.0) 306 | terminal-notifier-guard (1.7.0) 307 | thor (0.20.3) 308 | thread_safe (0.3.6) 309 | tilt (2.0.9) 310 | turbolinks (5.2.0) 311 | turbolinks-source (~> 5.2) 312 | turbolinks-source (5.2.0) 313 | tzinfo (1.2.5) 314 | thread_safe (~> 0.1) 315 | unicode-display_width (1.4.0) 316 | websocket-driver (0.7.0) 317 | websocket-extensions (>= 0.1.0) 318 | websocket-extensions (0.1.3) 319 | xpath (3.2.0) 320 | nokogiri (~> 1.8) 321 | xray-rails (0.3.1) 322 | rails (>= 3.1.0) 323 | 324 | PLATFORMS 325 | ruby 326 | 327 | DEPENDENCIES 328 | active_type (>= 0.3.2) 329 | annotate (>= 2.5.0) 330 | autoprefixer-rails (>= 5.0.0.1) 331 | awesome_print 332 | bcrypt (~> 3.1.7) 333 | better_errors 334 | binding_of_caller 335 | blazer 336 | bootsnap (>= 1.2.0) 337 | brakeman 338 | bundler-audit (>= 0.5.0) 339 | capybara (>= 2.15) 340 | chromedriver-helper 341 | coffee-rails (~> 4.2) 342 | dotenv-rails (>= 2.0.0) 343 | guard (>= 2.2.2) 344 | guard-livereload 345 | guard-minitest 346 | jquery-rails 347 | launchy 348 | letter_opener 349 | listen (>= 3.0.5) 350 | mail (>= 2.6.3) 351 | marco-polo 352 | mini_sql 353 | minitest-ci (>= 3.3.0) 354 | mocha (>= 1.4.0) 355 | overcommit (>= 0.37.0) 356 | pg (>= 0.18) 357 | pgcli-rails 358 | postmark-rails 359 | pry-rails 360 | puma (~> 3.11) 361 | rack-livereload 362 | rails (~> 5.2.2) 363 | rubocop (>= 0.58.0) 364 | sass-rails (~> 5.0) 365 | selenium-webdriver 366 | shoulda-context 367 | shoulda-matchers (>= 3.0.1) 368 | sidekiq (>= 4.2.0) 369 | simplecov 370 | spring 371 | spring-watcher-listen (~> 2.0.0) 372 | sshkit (~> 1.16) 373 | terminal-notifier 374 | terminal-notifier-guard 375 | turbolinks (~> 5) 376 | xray-rails (>= 0.1.18) 377 | 378 | BUNDLED WITH 379 | 1.16.1 380 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | # Run just the livereload monitor with: `guard -P livereload` 5 | guard :livereload do 6 | watch(%r{app/views/.+\.(erb|haml|slim)$}) 7 | watch(%r{app/helpers/.+\.rb}) 8 | watch(%r{public/.+\.(css|js|html)}) 9 | watch(%r{config/locales/.+\.yml}) 10 | # Rails Assets Pipeline 11 | watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|html|png|jpg))).*}) { |m| "/assets/#{m[3]}" } 12 | watch(%r{(app|vendor)(/assets/\w+/(.+)\.(scss))}) { |m| "/assets/#{m[3]}.css" } 13 | end 14 | 15 | guard :minitest, spring: "bin/rails test" do 16 | watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" } 17 | watch(%r{^app/controllers/application_controller\.rb$}) { "test/controllers" } 18 | watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" } 19 | watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" } 20 | watch(%r{^lib/(.+)\.rb$}) { |m| "test/unit/lib/#{m[1]}_test.rb" } 21 | watch(%r{^lib/tasks/(.+)\.rake$}) { |m| "test/unit/lib/tasks/#{m[1]}_test.rb" } 22 | watch(%r{^test/.+_test\.rb$}) 23 | watch(%r{^test/test_helper\.rb$}) { "test" } 24 | watch(%r{^test/support/.+\.rb}) { "test" } 25 | end 26 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | worker: bundle exec sidekiq -C config/sidekiq.yml 3 | release: bundle exec rake db:migrate db:seed 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # schemaless-pg 2 | 3 | 使用Postgres实现一个Leancloud Clone. 4 | 5 | ## 结构 6 | 7 | ### 表 8 | 9 | ```ruby 10 | create_table "sl_tables", comment: "schemaless table", force: :cascade do |t| 11 | t.string "name", comment: "表名" 12 | t.string "desc", comment: "描述" 13 | t.integer "user_id", comment: "创建者" 14 | t.datetime "created_at", null: false 15 | t.datetime "updated_at", null: false 16 | end 17 | ``` 18 | 19 | ### 列 20 | 21 | ```ruby 22 | create_table "sl_columns", comment: "schemaless column", force: :cascade do |t| 23 | t.bigint "sl_table_id" 24 | t.string "name", null: false 25 | t.integer "position", default: 0, comment: "排序位置" 26 | t.string "options", default: [], comment: "预设选项", array: true 27 | t.string "public_type", comment: "外部类型" 28 | t.string "private_type", null: false, comment: "私有类型:int4,int8,varchar, text, int4[], float, money, timestamp, date, int4range, point" 29 | t.datetime "created_at", null: false 30 | t.datetime "updated_at", null: false 31 | t.bigint "ref_sl_table_id", comment: "引用的sl table id,用于schemaless table和schemaless table之间的关联" 32 | t.string "ref_table_name", comment: "引用的外部表名,用于和已存在是实体表之间的关联" 33 | t.index ["sl_table_id", "name"], name: "index_sl_columns_on_sl_table_id_and_name", unique: true 34 | t.index ["sl_table_id", "position"], name: "index_sl_columns_on_sl_table_id_and_position" 35 | end 36 | ``` 37 | 38 | ### 行 39 | 40 | ```ruby 41 | create_table "sl_rows", comment: "schemaless row", force: :cascade do |t| 42 | t.bigint "sl_table_id" 43 | t.jsonb "data", comment: "数据" 44 | t.datetime "created_at", null: false 45 | t.datetime "updated_at", null: false 46 | t.index ["sl_table_id"], name: "index_sl_rows_on_sl_table_id" 47 | end 48 | ``` 49 | 50 | ## 演示 51 | 52 | ### setup data 53 | 54 | ```ruby 55 | t = SlTable.create(name: '订单表') 56 | t.sl_columns.create(name: '客户名', public_type: '文本框', private_type: 'varchar') 57 | t.sl_columns.create(name: '金额', public_type: '浮点数', private_type: 'decimal(10, 2)') 58 | t.sl_columns.create(name: '下单日期', public_type: '日期', private_type: 'date') 59 | t.sl_columns.create(name: '标签', public_type: '标签', private_type: 'varchar[]') 60 | t.sl_rows.create(data: {'1': 'hooopo', '2': 50.88, '3': Date.today, '4': SlRow.pg_array(%w[土豪 电子产品爱好者])}) 61 | ``` 62 | 63 | ### view postgres 64 | 65 | view专属的schema用来隔离和接口统一 66 | 67 | ```sql 68 | schemaless-pg_development=# \dn 69 | List of schemas 70 | Name | Owner 71 | ---------+-------- 72 | public | hooopo 73 | sl_view | hooopo // view专用schema,这样可以使表名和view名相同,通过设置search path,可以让使用者操作统一的SQL层查询接口 74 | ``` 75 | 76 | 自动生成用户定义的view 77 | 78 | ```sql 79 | schemaless-pg_development=# set search_path to sl_view; 80 | schemaless-pg_development=# \dv 81 | List of relations 82 | Schema | Name | Type | Owner 83 | ---------+--------+------+-------- 84 | sl_view | 订单表 | view | hooopo 85 | ``` 86 | 87 | view的定义语句 88 | 89 | ```sql 90 | schemaless-pg_development=# \d+ 订单表 91 | View "sl_view.订单表" 92 | Column | Type | Modifiers | Storage | Description 93 | ----------+---------------------+-----------+----------+------------- 94 | id | bigint | | plain | 95 | 客户名 | character varying | | extended | 96 | 金额 | numeric(10,2) | | main | 97 | 下单日期 | date | | plain | 98 | 印象 | character varying[] | | extended | 99 | View definition: 100 | SELECT sl_rows.id, 101 | (sl_rows.data ->> '1'::text)::character varying AS "客户名", 102 | ((sl_rows.data ->> '2'::text))::numeric(10,2) AS "金额", 103 | (sl_rows.data ->> '3'::text)::date AS "下单日期", 104 | (sl_rows.data ->> '4'::text)::character varying[] AS "印象" 105 | FROM public.sl_rows 106 | WHERE sl_rows.sl_table_id = 1; 107 | ``` 108 | 109 | ## Ruby 调用 110 | 111 | ### Use MiniSql 112 | 113 | ```ruby 114 | schemaless-pg(dev)> ap t.rows_from_view 115 | [ 116 | [0] { 117 | "id" => 2, 118 | "客户名" => "hooopo", 119 | "金额" => 50.88, 120 | "下单日期" => Mon, 17 Dec 2018, 121 | "印象" => [ 122 | [0] "土豪", 123 | [1] "电子产品爱好者" 124 | ] 125 | }, 126 | [1] { 127 | "id" => 3, 128 | "客户名" => "hooopo", 129 | "金额" => 50.88, 130 | "下单日期" => Mon, 17 Dec 2018, 131 | "印象" => [ 132 | [0] "土豪", 133 | [1] "电子产品爱好者", 134 | [2] "rubyist" 135 | ] 136 | }, 137 | [2] { 138 | "id" => 4, 139 | "客户名" => "hooopo", 140 | "金额" => 50.88, 141 | "下单日期" => Mon, 17 Dec 2018, 142 | "印象" => [ 143 | [0] "土豪", 144 | [1] "电子产品爱好者", 145 | [2] "rubyist", 146 | [3] "100" 147 | ] 148 | } 149 | ] 150 | ``` 151 | 152 | ### Use ActiveRecord 153 | 154 | ``` 155 | schemaless-pg(dev)> sl_table = SlTable.first 156 | SlTable Load (1.1ms) SELECT "sl_tables".* FROM "sl_tables" ORDER BY "sl_tables"."id" ASC LIMIT $1 [["LIMIT", 1]] 157 | => # 158 | schemaless-pg(dev)> ar_class = sl_table.sl_class 159 | => #(id: integer, name: string, desc: text, date: date, price: decimal, category: string) 160 | schemaless-pg(dev)> ar_class.count 161 | (23.3ms) SELECT COUNT(*) FROM "sl_view"."products" 162 | => 10000 163 | schemaless-pg(dev)> ar_class.first 164 | Load (5.4ms) SELECT "sl_view"."products".* FROM "sl_view"."products" ORDER BY "sl_view"."products"."id" ASC LIMIT $1 [["LIMIT", 1]] 165 | => #<# id: 1, name: "320000", desc: "2700", date: "2016-06-14", price: 0.21999e3, category: "420"> 166 | schemaless-pg(dev)> ar_class.where("price > 100").first 167 | Load (12.5ms) SELECT "sl_view"."products".* FROM "sl_view"."products" WHERE (price > 100) ORDER BY "sl_view"."products"."id" ASC LIMIT $1 [["LIMIT", 1]] 168 | => #<# id: 1, name: "320000", desc: "2700", date: "2016-06-14", price: 0.21999e3, category: "420"> 169 | schemaless-pg(dev)> ar_class.where("price > 100").first.name 170 | Load (2.9ms) SELECT "sl_view"."products".* FROM "sl_view"."products" WHERE (price > 100) ORDER BY "sl_view"."products"."id" ASC LIMIT $1 [["LIMIT", 1]] 171 | => "320000" 172 | ``` 173 | 174 | ## 表之间的引用 175 | 176 | 通过sl_view引用产生的view,和两个普通表一样,可以任意JOIN 177 | 178 | ``` 179 | schemaless-pg_development=# select o.id from sl_view.orders as o inner join sl_view.products as p on p.id = o.product_id where p.name = '24' limit 1; 180 | id 181 | -------- 182 | 176068 183 | (1 row) 184 | 185 | Time: 0.791 ms 186 | ``` 187 | 188 | ## 过滤和排序 189 | 190 | ## 搜索 191 | 192 | ## 报表 193 | 194 | ## 性能和索引相关 195 | 196 | ### sl_table_id index 197 | 198 | 默认情况,通过sl_view进行查询,会使用到sl_table_id这个index。 199 | 200 | ``` 201 | schemaless-pg_development=# select count(*) from sl_view.products; 202 | count 203 | -------- 204 | 100000 205 | (1 row) 206 | 207 | Time: 68.302 ms 208 | schemaless-pg_development=# explain analyze select count(*) from sl_view.products; 209 | QUERY PLAN 210 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- 211 | Aggregate (cost=5900.08..5900.09 rows=1 width=8) (actual time=149.594..149.595 rows=1 loops=1) 212 | -> Index Only Scan using index_sl_rows_on_sl_table_id on sl_rows (cost=0.42..5640.25 rows=103933 width=0) (actual time=0.465..129.799 rows=100000 loops=1) 213 | Index Cond: (sl_table_id = 7) 214 | Heap Fetches: 100000 215 | Planning time: 0.639 ms 216 | Execution time: 150.509 ms 217 | (6 rows) 218 | 219 | Time: 262.017 ms 220 | ``` 221 | 222 | ### primary_key index 223 | 224 | 主键查询可以利用sl_rows_pkey这个索引 225 | 226 | ``` 227 | schemaless-pg_development=# select id from sl_view.products where id = 95085; 228 | id 229 | ------- 230 | 95085 231 | (1 row) 232 | 233 | Time: 17.260 ms 234 | schemaless-pg_development=# explain analyze select id from sl_view.products where id = 95085; 235 | QUERY PLAN 236 | ---------------------------------------------------------------------------------------------------------------------- 237 | Index Scan using sl_rows_pkey on sl_rows (cost=0.42..8.45 rows=1 width=8) (actual time=0.643..0.644 rows=1 loops=1) 238 | Index Cond: (id = 95085) 239 | Filter: (sl_table_id = 7) 240 | Planning time: 0.158 ms 241 | Execution time: 2.491 ms 242 | ``` 243 | 244 | ### custom btree index 245 | 246 | 给 sl_view.orders.age 字段上面加btree索引: 247 | 248 | ``` 249 | t = SlTable.last 250 | c = t.sl_columns.where(name: :age).first 251 | c.create_index! 252 | ``` 253 | 254 | 生成 create index 语句: 255 | ``` 256 | (8764.0ms) CREATE INDEX CONCURRENTLY IF NOT EXISTS "8_btree_42_age" 257 | ON sl_rows 258 | USING BTREE (sl_table_id, CAST ((data ->> '42') AS int4)) 259 | WHERE sl_table_id = 8 260 | ``` 261 | 262 | 等值过滤: 263 | 264 | ``` 265 | schemaless-pg_development=# select * from sl_view.orders where age = 40 limit 1; 266 | id | customer_name | total | date | age | tags | product_id 267 | --------+---------------+-------+------------+-----+------------------+------------ 268 | 914599 | 55555555 | 94.30 | 2018-04-07 | 40 | {电击,电动,电子} | 41757 269 | (1 row) 270 | 271 | Time: 13.313 ms 272 | 273 | explain analyze select id from sl_view.orders where age = 40 limit 10 ; 274 | QUERY PLAN 275 | ------------------------------------------------------------------------------------------------------------------------------------------ 276 | Limit (cost=0.42..62.64 rows=10 width=8) (actual time=82.712..82.784 rows=10 loops=1) 277 | -> Index Scan using "8_btree_42_age" on sl_rows (cost=0.42..22752.74 rows=3657 width=8) (actual time=82.710..82.782 rows=10 loops=1) 278 | Index Cond: (((data ->> '42'::text))::integer = 40) 279 | Planning time: 8.856 ms 280 | Execution time: 83.890 ms 281 | (5 rows) 282 | 283 | Time: 155.321 ms 284 | ``` 285 | 286 | 比较过滤: 287 | 288 | 由于测试数据生成的分布太均匀,目前用不到索引... 289 | 290 | 排序: 291 | 292 | ``` 293 | schemaless-pg_development=# explain analyze select * from sl_view.orders order by age limit 1; 294 | QUERY PLAN 295 | -------------------------------------------------------------------------------------------------------------------------------------------- 296 | Limit (cost=0.42..0.56 rows=1 width=100) (actual time=0.124..0.125 rows=1 loops=1) 297 | -> Index Scan using "8_btree_42_age" on sl_rows (cost=0.42..103273.76 rows=743546 width=100) (actual time=0.123..0.123 rows=1 loops=1) 298 | Planning time: 0.458 ms 299 | Execution time: 0.163 ms 300 | (4 rows) 301 | 302 | Time: 2.640 ms 303 | schemaless-pg_development=# select * from sl_view.orders order by age limit 1; 304 | id | customer_name | total | date | age | tags | product_id 305 | --------+---------------+-------+------------+-----+------------------+------------ 306 | 926757 | 22222222 | 86.78 | 2018-05-14 | 0 | {产品,电子,电击} | 29457 307 | (1 row) 308 | 309 | Time: 3.484 ms 310 | ``` 311 | 312 | ### custom gin index 313 | 314 | ### custom fulltext index 315 | 316 | ### custom multi-column index 317 | 318 | 319 | ## 多租和隔离 320 | 321 | ## Sharding 322 | 323 | ## Deployment 324 | 325 | Ensure the following environment variables are set in the deployment environment: 326 | 327 | * `POSTMARK_API_KEY` 328 | * `RACK_ENV` 329 | * `RAILS_ENV` 330 | * `REDIS_URL` 331 | * `SECRET_KEY_BASE` 332 | * `SIDEKIQ_WEB_PASSWORD` 333 | * `SIDEKIQ_WEB_USERNAME` 334 | 335 | Optionally: 336 | 337 | * `RAILS_LOG_TO_STDOUT` 338 | * `RAILS_SERVE_STATIC_FILES` 339 | 340 | [rbenv]:https://github.com/sstephenson/rbenv 341 | [redis]:http://redis.io 342 | [Homebrew]:http://brew.sh 343 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | 8 | begin 9 | require "rubocop/rake_task" 10 | RuboCop::RakeTask.new 11 | task default: %w[test test:system rubocop] 12 | rescue LoadError # rubocop:disable Lint/HandleExceptions 13 | end 14 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require activestorage 16 | //= require turbolinks 17 | //= require_tree . 18 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | //= require_tree . 2 | //= require_self 3 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | # Renders the home page. 2 | class HomeController < ApplicationController 3 | def index 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/javascript_helper.rb: -------------------------------------------------------------------------------- 1 | module JavascriptHelper 2 | # Adds the async attribute into javascript_include_tag, but only when the 3 | # asset pipeline is not in debug mode. Be careful when using this helper, 4 | # because async ', 23 | javascript_include_async_tag("foo", skip_pipeline: true) 24 | ) 25 | end 26 | 27 | # This allows non-existent asset filenames to be used within a test, for 28 | # sprockets-rails >= 3.2.0. 29 | def allow_unknown_assets 30 | return unless respond_to?(:unknown_asset_fallback) 31 | stubs(:unknown_asset_fallback).returns(true) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/helpers/retina_image_helper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RetinaImageHelperTest < ActionView::TestCase 4 | include RetinaImageHelper 5 | 6 | test "retina_image_tag" do 7 | tag = retina_image_tag("example.png", alt: "example") 8 | 9 | assert_match(%r{\A.*\z}, tag) 10 | assert_match(/alt="example"/, tag) 11 | assert_match(%r{src="/images/example.png"}, tag) 12 | assert_match( 13 | %r{srcset="/images/example.png 1x,/images/example@2x.png 2x"}, 14 | tag 15 | ) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/models/.keep -------------------------------------------------------------------------------- /test/models/sl_column_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SlColumnTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/sl_row_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SlRowTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/sl_table_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SlTableTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/support/circleci.rb: -------------------------------------------------------------------------------- 1 | # Generate XML test reports that can be parsed by CircleCI 2 | require "minitest/ci" if ENV["CIRCLECI"] 3 | -------------------------------------------------------------------------------- /test/support/mailer.rb: -------------------------------------------------------------------------------- 1 | class ActiveSupport::TestCase 2 | setup { ActionMailer::Base.deliveries.clear } 3 | end 4 | -------------------------------------------------------------------------------- /test/support/mocha.rb: -------------------------------------------------------------------------------- 1 | # Mocha provides mocking and stubbing helpers 2 | require "mocha/minitest" 3 | Mocha::Configuration.prevent(:stubbing_non_existent_method) 4 | Mocha::Configuration.warn_when(:stubbing_non_public_method) 5 | -------------------------------------------------------------------------------- /test/support/rails.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../../config/environment" 3 | require "rails/test_help" 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alpha order. 7 | fixtures :all 8 | end 9 | -------------------------------------------------------------------------------- /test/support/shoulda_matchers.rb: -------------------------------------------------------------------------------- 1 | Shoulda::Matchers.configure do |config| 2 | config.integrate do |with| 3 | with.test_framework :minitest 4 | with.library :rails 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/support/sidekiq.rb: -------------------------------------------------------------------------------- 1 | # Use Sidekiq's test fake that pushes all jobs into a jobs array 2 | require "sidekiq/testing" 3 | Sidekiq::Testing.fake! 4 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/system/.keep -------------------------------------------------------------------------------- /test/system/layout_helper_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class LayoutHelperTest < ApplicationSystemTestCase 4 | test "rendered page contains both base and application layouts" do 5 | visit("/") 6 | assert_selector("html>head+body") 7 | assert_selector("body p") 8 | assert_match(/Home/, page.title) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # This has to come first 2 | require_relative "./support/rails" 3 | 4 | # Load everything else from test/support 5 | Dir[File.expand_path("../support/**/*.rb", __FILE__)].each { |rb| require(rb) } 6 | -------------------------------------------------------------------------------- /test/unit/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/unit/.keep -------------------------------------------------------------------------------- /test/unit/lib/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/unit/lib/.keep -------------------------------------------------------------------------------- /test/unit/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/test/unit/lib/tasks/.keep -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hooopo/schemaless-pg/52fc7dbf621594dff38771f8312724fdf1bc9716/vendor/.keep --------------------------------------------------------------------------------