├── README.md ├── doc └── images │ └── how-it-works.gif ├── rails_5_2_1_1_api_template.rb └── rails_6_0_0_api_template.rb /README.md: -------------------------------------------------------------------------------- 1 | # Rails API template 2 | 3 | ![How it works](https://raw.githubusercontent.com/codica2/rails-api-template/master/doc/images/how-it-works.gif) 4 | 5 | 6 | ## How to use? 7 | ``` 8 | rails _6.0.0_ new codica --api --database=postgresql -m https://raw.githubusercontent.com/codica2/rails-api-template/master/rails_6_0_0_api_template.rb -T 9 | ``` 10 | 11 | ## Stack & Features 12 | 13 | * Ruby 2.6.3 14 | * Rails 6.0.0 15 | * API mode 16 | * Postgres 17 | * Puma 18 | * Handling CORS 19 | * Fast JSON:API serializer for Ruby Objects 20 | * RSpec testing framework 21 | * Configurable and extendable Git hook 22 | * Token-based authentication 23 | 24 | ## Installed gems 25 | 26 | * [rack-cors](https://github.com/cyu/rack-cors) 27 | * [fast_jsonapi](https://github.com/Netflix/fast_jsonapi) 28 | * [annotate](https://github.com/ctran/annotate_models) 29 | * [overcommit](https://github.com/brigade/overcommit) 30 | * [rubocop](https://github.com/rubocop-hq/rubocop) 31 | * [rspec-rails](https://github.com/rspec/rspec-rails) 32 | * [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) 33 | * [factory_bot_rails](https://github.com/thoughtbot/factory_bot_rails) 34 | * [faker](https://github.com/stympy/faker) 35 | * [shoulda-matchers](https://github.com/thoughtbot/shoulda-matchers) 36 | * [simplecov](https://github.com/colszowka/simplecov) 37 | * [bcrypt](https://github.com/codahale/bcrypt-ruby) 38 | * [simple_command](https://github.com/nebulab/simple_command) 39 | * [jwt](https://github.com/jwt/ruby-jwt) 40 | * [pundit](https://github.com/varvet/pundit) 41 | * [rolify](https://github.com/RolifyCommunity/rolify) 42 | * [sidekiq](https://github.com/mperham/sidekiq) 43 | 44 | ## License 45 | rails-api-template is Copyright © 2015-2020 Codica. It is released under the [MIT License](https://opensource.org/licenses/MIT). 46 | 47 | ## About Codica 48 | 49 | [![Codica logo](https://www.codica.com/assets/images/logo/logo.svg)](https://www.codica.com) 50 | 51 | We love open source software! See [our other projects](https://github.com/codica2) or [hire us](https://www.codica.com/) to design, develop, and grow your product. 52 | -------------------------------------------------------------------------------- /doc/images/how-it-works.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codica2/rails-api-template/ec06b90de19bc54869514f6bab8980121b130015/doc/images/how-it-works.gif -------------------------------------------------------------------------------- /rails_5_2_1_1_api_template.rb: -------------------------------------------------------------------------------- 1 | # rails _5.2.1.1_ new APPLICATION_NAME --api --database=postgresql -m rails_5_2_1_1_api_template.rb -T 2 | 3 | # Adding useful gems 4 | gem 'rack-cors', require: 'rack/cors' 5 | gem 'fast_jsonapi' 6 | 7 | gem_group :development do 8 | gem 'annotate' 9 | gem 'overcommit' 10 | gem 'rubocop', require: false 11 | end 12 | 13 | gem_group :development, :test do 14 | gem 'rspec-rails' 15 | end 16 | 17 | gem_group :test do 18 | gem 'database_cleaner' 19 | gem 'factory_bot_rails' 20 | gem 'faker' 21 | gem 'shoulda-matchers' 22 | gem 'simplecov', require: false 23 | end 24 | 25 | after_bundle do 26 | run 'rspec --init' 27 | end 28 | 29 | # Update files 30 | def databse_config(app_name) 31 | <<-CODE 32 | default: &default 33 | adapter: postgresql 34 | encoding: unicode 35 | pool: 5 36 | username: postgres 37 | password: 38 | development: 39 | <<: *default 40 | database: #{app_name}_development 41 | test: 42 | <<: *default 43 | database: #{app_name}_test 44 | production: 45 | <<: *default 46 | database: #{app_name}_production 47 | CODE 48 | end 49 | 50 | run 'rm -f config/database.yml' 51 | run 'rm -f .rubocop.yml' 52 | run 'rm -f .gitignore' 53 | 54 | file 'config/database.yml.example', databse_config('application') 55 | 56 | file 'spec/spec_helper.rb', <<-CODE 57 | require 'simplecov' 58 | SimpleCov.start do 59 | add_filter ['/spec/', '/config/'] 60 | end 61 | 62 | ENV['RAILS_ENV'] ||= 'test' 63 | require File.expand_path('../../config/environment', __FILE__) 64 | 65 | abort("The Rails environment is running in production mode!") if Rails.env.production? 66 | require 'rspec/rails' 67 | 68 | Shoulda::Matchers.configure do |config| 69 | config.integrate do |with| 70 | with.test_framework :rspec 71 | with.library :rails 72 | end 73 | end 74 | 75 | ActiveRecord::Migration.maintain_test_schema! 76 | 77 | RSpec.configure do |config| 78 | config.include FactoryBot::Syntax::Methods 79 | 80 | config.before(:each) do 81 | DatabaseCleaner.strategy = :truncation 82 | DatabaseCleaner.clean 83 | end 84 | 85 | config.use_transactional_fixtures = false 86 | 87 | config.infer_spec_type_from_file_location! 88 | config.filter_rails_from_backtrace! 89 | end 90 | CODE 91 | 92 | file '.rubocop.yml', <<-CODE 93 | AllCops: 94 | Exclude: 95 | - 'db/**/*' 96 | - 'bin/*' 97 | - 'config/**/*' 98 | - 'public/**/*' 99 | - 'spec/**/*' 100 | - 'test/**/*' 101 | - 'vendor/**/*' 102 | - 'spec/fixtures/**/*' 103 | - 'tmp/**/*' 104 | 105 | Style/FrozenStringLiteralComment: 106 | Enabled: false 107 | 108 | Style/Documentation: 109 | Enabled: false 110 | 111 | Layout/EmptyLinesAroundModuleBody: 112 | EnforcedStyle: empty_lines 113 | 114 | Layout/EmptyLinesAroundClassBody: 115 | EnforcedStyle: empty_lines 116 | 117 | Metrics/LineLength: 118 | Max: 120 119 | 120 | Metrics/ClassLength: 121 | Max: 250 122 | 123 | Metrics/ModuleLength: 124 | Max: 250 125 | 126 | Metrics/AbcSize: 127 | Max: 25 128 | 129 | Metrics/MethodLength: 130 | Max: 20 131 | 132 | Metrics/CyclomaticComplexity: 133 | Max: 7 134 | 135 | Rails: 136 | Enabled: true 137 | CODE 138 | 139 | file '.gitignore', <<-CODE 140 | /.bundle 141 | # Ignore all logfiles and tempfiles. 142 | .idea/* 143 | /log/* 144 | /tmp/* 145 | # Ignore uploaded files in development 146 | /storage/* 147 | # Ignore master key for decrypting credentials and more. 148 | /config/master.key 149 | /node_modules 150 | /yarn-error.log 151 | .byebug_history 152 | .ruby-version 153 | .ruby-gemset 154 | config/database.yml 155 | coverage/ 156 | public/system/* 157 | public/uploads/* 158 | .env 159 | CODE 160 | 161 | file '.overcommit.yml', <<-CODE 162 | PreCommit: 163 | ALL: 164 | problem_on_unmodified_line: ignore 165 | required: false 166 | quiet: false 167 | RuboCop: 168 | enabled: true 169 | on_warn: fail # Treat all warnings as failures 170 | CODE 171 | 172 | if yes?('Create database.yml? (yes/no)') 173 | app_name = ask('What is your db name?') 174 | file 'config/database.yml', databse_config(app_name) 175 | rails_command 'db:create' 176 | end 177 | 178 | run 'rubocop -a' 179 | 180 | after_bundle do 181 | git :init 182 | git add: '.' 183 | git commit: "-a -m 'Initial commit'" 184 | run 'overcommit --install' 185 | end 186 | -------------------------------------------------------------------------------- /rails_6_0_0_api_template.rb: -------------------------------------------------------------------------------- 1 | # rails _6.0.0_ new APPLICATION_NAME --api --database=postgresql -m rails_6_0_0_api_template.rb -T 2 | 3 | # Adding useful gems 4 | 5 | # State machines for Ruby classes 6 | gem 'aasm' 7 | 8 | # The official AWS SDK for Ruby. 9 | gem 'aws-sdk-s3', require: false 10 | 11 | # The bcrypt Ruby gem provides a simple wrapper for safely handling passwords 12 | gem 'bcrypt' 13 | 14 | # A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard. 15 | gem 'jwt' 16 | 17 | # Provides helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scalable authorization system. 18 | gem 'pundit' 19 | 20 | # Very simple Roles library without any authorization enforcement supporting scope on resource object. 21 | gem 'rolify' 22 | 23 | gem 'rack-cors', require: 'rack/cors' 24 | 25 | # A lightning fast JSON:API serializer for Ruby Objects 26 | gem 'fast_jsonapi' 27 | 28 | gem 'sidekiq' 29 | 30 | # Generate pretty API docs for your Rails APIs. 31 | gem 'rspec_api_documentation' 32 | 33 | gem_group :development do 34 | # static analysis tool which checks Ruby on Rails applications for security vulnerabilities. 35 | gem 'brakeman' 36 | gem 'annotate' 37 | gem 'overcommit' 38 | gem 'rubocop', require: false 39 | end 40 | 41 | gem_group :development, :test do 42 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 43 | gem 'database_cleaner' 44 | gem 'dotenv-rails' 45 | gem 'factory_bot_rails' 46 | gem 'rspec-rails', '~> 3.8' 47 | gem 'shoulda-matchers' 48 | gem 'simplecov' 49 | 50 | # This gem is a port of Perl's Data::Faker library that generates fake data 51 | gem 'faker' 52 | end 53 | 54 | gem_group :test do 55 | gem 'database_cleaner' 56 | gem 'factory_bot_rails' 57 | gem 'faker' 58 | gem 'shoulda-matchers' 59 | gem 'simplecov', require: false 60 | end 61 | 62 | run 'bundle install' 63 | 64 | after_bundle do 65 | run 'rspec --init' 66 | end 67 | 68 | # Update files 69 | def databse_config(app_name) 70 | <<-CODE 71 | default: &default 72 | adapter: postgresql 73 | encoding: unicode 74 | pool: 5 75 | username: postgres 76 | password: password 77 | development: 78 | <<: *default 79 | database: #{app_name}_development 80 | test: 81 | <<: *default 82 | database: #{app_name}_test 83 | production: 84 | <<: *default 85 | database: #{app_name}_production 86 | CODE 87 | end 88 | 89 | run 'rm -f config/database.yml' 90 | run 'rm -f .rubocop.yml' 91 | run 'rm -f .gitignore' 92 | 93 | file 'config/database.yml.example', databse_config('application') 94 | 95 | file 'spec/spec_helper.rb', <<-CODE 96 | require 'simplecov' 97 | SimpleCov.start do 98 | add_filter ['/spec/', '/config/'] 99 | end 100 | 101 | ENV['RAILS_ENV'] ||= 'test' 102 | require File.expand_path('../../config/environment', __FILE__) 103 | 104 | abort("The Rails environment is running in production mode!") if Rails.env.production? 105 | require 'rspec/rails' 106 | 107 | Shoulda::Matchers.configure do |config| 108 | config.integrate do |with| 109 | with.test_framework :rspec 110 | with.library :rails 111 | end 112 | end 113 | 114 | ActiveRecord::Migration.maintain_test_schema! 115 | 116 | RSpec.configure do |config| 117 | config.include FactoryBot::Syntax::Methods 118 | 119 | config.before(:each) do 120 | DatabaseCleaner.strategy = :truncation 121 | DatabaseCleaner.clean 122 | end 123 | 124 | config.use_transactional_fixtures = false 125 | 126 | config.infer_spec_type_from_file_location! 127 | config.filter_rails_from_backtrace! 128 | end 129 | CODE 130 | 131 | file '.rubocop.yml', <<-CODE 132 | AllCops: 133 | Exclude: 134 | - 'db/**/*' 135 | - 'bin/*' 136 | - 'config/**/*' 137 | - 'public/**/*' 138 | - 'spec/**/*' 139 | - 'test/**/*' 140 | - 'vendor/**/*' 141 | - 'spec/fixtures/**/*' 142 | - 'tmp/**/*' 143 | 144 | Style/FrozenStringLiteralComment: 145 | Enabled: false 146 | 147 | Style/Documentation: 148 | Enabled: false 149 | 150 | Layout/EmptyLinesAroundModuleBody: 151 | EnforcedStyle: empty_lines 152 | 153 | Layout/EmptyLinesAroundClassBody: 154 | EnforcedStyle: empty_lines 155 | 156 | Metrics/ClassLength: 157 | Max: 250 158 | 159 | Metrics/ModuleLength: 160 | Max: 250 161 | 162 | Metrics/AbcSize: 163 | Max: 25 164 | 165 | Metrics/MethodLength: 166 | Max: 20 167 | 168 | Metrics/CyclomaticComplexity: 169 | Max: 7 170 | CODE 171 | 172 | file '.gitignore', <<-CODE 173 | /.bundle 174 | # Ignore all logfiles and tempfiles. 175 | .idea/* 176 | /log/* 177 | /tmp/* 178 | # Ignore uploaded files in development 179 | /storage/* 180 | # Ignore master key for decrypting credentials and more. 181 | /config/master.key 182 | /node_modules 183 | /yarn-error.log 184 | .byebug_history 185 | .ruby-version 186 | .ruby-gemset 187 | config/database.yml 188 | coverage/ 189 | public/system/* 190 | public/uploads/* 191 | .env 192 | CODE 193 | 194 | file '.overcommit.yml', <<-CODE 195 | PreCommit: 196 | ALL: 197 | problem_on_unmodified_line: ignore 198 | required: false 199 | quiet: false 200 | RuboCop: 201 | enabled: true 202 | on_warn: fail # Treat all warnings as failures 203 | CODE 204 | 205 | if yes?('Create database.yml? (yes/no)') 206 | app_name = ask('What is your db name?') 207 | file 'config/database.yml', databse_config(app_name) 208 | rails_command 'db:create' 209 | end 210 | 211 | file 'lib/json_web_token.rb', <<-CODE 212 | require 'jwt' 213 | 214 | class JsonWebToken 215 | 216 | class << self 217 | 218 | SECRET_KEY = Rails.application.credentials.secret_key_base 219 | 220 | def encode(payload) 221 | payload.reverse_merge!(meta) 222 | 223 | JWT.encode(payload, SECRET_KEY) 224 | end 225 | 226 | def decode(token) 227 | JWT.decode(token, SECRET_KEY).first 228 | end 229 | 230 | def meta 231 | { exp: 7.days.from_now.to_i } 232 | end 233 | 234 | end 235 | 236 | end 237 | CODE 238 | 239 | file 'app/auth/authenticate_user.rb', <<-CODE 240 | require 'json_web_token' 241 | 242 | class AuthenticateUser 243 | 244 | prepend SimpleCommand 245 | attr_accessor :email, :password 246 | 247 | def initialize(email, password) 248 | @email = email 249 | @password = password 250 | end 251 | 252 | def call 253 | return unless user 254 | 255 | JsonWebToken.encode(user_id: user.id) 256 | end 257 | 258 | private 259 | 260 | def user 261 | current_user = User.find_by(email: email) 262 | 263 | return current_user if current_user&.authenticate(password) 264 | 265 | errors.add(:user_authentication, 'Invalid credentials') 266 | end 267 | end 268 | CODE 269 | 270 | file 'app/auth/authorize_api_request.rb', <<-CODE 271 | class AuthorizeApiRequest 272 | 273 | prepend SimpleCommand 274 | 275 | def initialize(headers = {}) 276 | @headers = headers 277 | end 278 | 279 | def call 280 | @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token 281 | @user || errors.add(:token, 'Invalid token') 282 | end 283 | 284 | private 285 | 286 | attr_reader :headers 287 | 288 | def decoded_auth_token 289 | @decoded_auth_token ||= JsonWebToken.decode(http_auth_header) 290 | end 291 | 292 | def http_auth_header 293 | return headers['Authorization'].split(' ').last if headers['Authorization'].present? 294 | 295 | errors.add(:token, 'Missing token') 296 | end 297 | 298 | end 299 | CODE 300 | 301 | run 'rubocop -a' 302 | 303 | after_bundle do 304 | git :init 305 | git add: '.' 306 | git commit: "-a -m 'Initial commit'" 307 | run 'overcommit --install' 308 | end 309 | --------------------------------------------------------------------------------