hoboken 0.0.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +7 -0
  3. data/.github/workflows/ruby.yml +28 -0
  4. data/.rubocop.yml +33 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +2 -0
  7. data/IDEAS.md +57 -0
  8. data/LICENSE.txt +1 -1
  9. data/README.md +21 -10
  10. data/Rakefile +14 -6
  11. data/bin/hoboken +2 -1
  12. data/hoboken.gemspec +53 -16
  13. data/lib/hoboken.rb +110 -23
  14. data/lib/hoboken/actions.rb +10 -6
  15. data/lib/hoboken/add_ons/github_action.rb +14 -0
  16. data/lib/hoboken/add_ons/heroku.rb +8 -23
  17. data/lib/hoboken/add_ons/internationalization.rb +10 -6
  18. data/lib/hoboken/add_ons/metrics.rb +39 -14
  19. data/lib/hoboken/add_ons/omniauth.rb +114 -47
  20. data/lib/hoboken/add_ons/rubocop.rb +40 -0
  21. data/lib/hoboken/add_ons/sequel.rb +76 -47
  22. data/lib/hoboken/add_ons/sprockets.rb +67 -65
  23. data/lib/hoboken/add_ons/travis.rb +6 -2
  24. data/lib/hoboken/add_ons/twbs.rb +80 -0
  25. data/lib/hoboken/generate.rb +112 -38
  26. data/lib/hoboken/templates/Gemfile.erb.tt +33 -10
  27. data/lib/hoboken/templates/README.md.tt +105 -35
  28. data/lib/hoboken/templates/Rakefile.tt +10 -22
  29. data/lib/hoboken/templates/classic.rb.tt +35 -8
  30. data/lib/hoboken/templates/config.ru.tt +5 -3
  31. data/lib/hoboken/templates/console.sh +5 -0
  32. data/lib/hoboken/templates/db.rb.tt +24 -0
  33. data/lib/hoboken/templates/github_action.tt +28 -0
  34. data/lib/hoboken/templates/gitignore +8 -1
  35. data/lib/hoboken/templates/metrics.rake.tt +10 -9
  36. data/lib/hoboken/templates/modular.rb.tt +40 -11
  37. data/lib/hoboken/templates/puma.rb.tt +21 -0
  38. data/lib/hoboken/templates/rspec.rake.tt +5 -0
  39. data/lib/hoboken/templates/rubocop.yml.tt +31 -0
  40. data/lib/hoboken/templates/sequel.rake +6 -4
  41. data/lib/hoboken/templates/server.sh +12 -0
  42. data/lib/hoboken/templates/setup.sh +7 -0
  43. data/lib/hoboken/templates/spec/app_spec.rb.tt +15 -0
  44. data/lib/hoboken/templates/spec/rack_matchers.rb.tt +56 -0
  45. data/lib/hoboken/templates/spec/spec_helper.rb.tt +41 -0
  46. data/lib/hoboken/templates/sprockets.rake +13 -7
  47. data/lib/hoboken/templates/sprockets_chain.rb +7 -3
  48. data/lib/hoboken/templates/sprockets_helper.rb +14 -10
  49. data/lib/hoboken/templates/support/rack_helpers.rb.tt +55 -0
  50. data/lib/hoboken/templates/support/rack_test_assertions.rb.tt +111 -0
  51. data/lib/hoboken/templates/test/test_helper.rb.tt +38 -27
  52. data/lib/hoboken/templates/test/unit/app_test.rb.tt +11 -3
  53. data/lib/hoboken/templates/test_unit.rake.tt +18 -0
  54. data/lib/hoboken/templates/views/index.erb.tt +10 -3
  55. data/lib/hoboken/templates/views/layout.erb.tt +4 -1
  56. data/lib/hoboken/version.rb +3 -1
  57. data/test/fixtures/Gemfile +3 -3
  58. data/test/fixtures/Gemfile.pristine +3 -2
  59. data/test/integration/add_on_test.rb +399 -136
  60. data/test/integration/generate_test.rb +170 -38
  61. data/test/test_helper.rb +54 -23
  62. data/test/unit/hoboken_actions_test.rb +70 -61
  63. metadata +441 -16
  64. data/.travis.yml +0 -5
  65. data/lib/hoboken/templates/test/support/rack_test_assertions.rb.tt +0 -92
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ workers_count = Integer(ENV['WEB_CONCURRENCY'] || 1)
4
+ threads_count = Integer(ENV['MAX_THREADS'] || 5)
5
+ threads threads_count, threads_count
6
+
7
+ port ENV['PORT'] || 9292
8
+ environment ENV['RACK_ENV'] || 'production'
9
+
10
+ if workers_count > 1
11
+ preload_app!
12
+ workers workers_count
13
+
14
+ # rubocop:disable Lint/EmptyBlock
15
+ before_fork do
16
+ end
17
+
18
+ on_worker_boot do
19
+ end
20
+ # rubocop:enable Lint/EmptyBlock
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,31 @@
1
+ require:
2
+ - rubocop-rake
3
+ <% if rspec? -%>
4
+ - rubocop-rspec
5
+ <% end -%>
6
+
7
+ AllCops:
8
+ Exclude:
9
+ - 'bin/*'
10
+ - 'tmp/*'
11
+ - 'vendor/bundle/**/*'
12
+ NewCops: enable
13
+ TargetRubyVersion: <%= RUBY_VERSION %>
14
+
15
+ Layout/SpaceAroundEqualsInParameterDefault:
16
+ EnforcedStyle: no_space
17
+
18
+ Metrics/ClassLength:
19
+ Max: 150
20
+
21
+ Metrics/MethodLength:
22
+ Max: 15
23
+
24
+ Layout/LineLength:
25
+ Max: 90
26
+
27
+ Style/BlockDelimiters:
28
+ EnforcedStyle: braces_for_chaining
29
+
30
+ Style/YodaCondition:
31
+ EnforcedStyle: require_for_equality_operators_only
@@ -1,19 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :db do
2
4
  require 'sequel'
3
5
 
4
6
  Sequel.extension :migration
5
- DB = Sequel.connect(ENV['DATABASE_URL'] || "sqlite://db/development.db")
7
+ db = Sequel.connect(ENV['DATABASE_URL'] || 'sqlite://db/development.db')
6
8
 
7
9
  desc 'Migrate the database to latest version'
8
10
  task :migrate do
9
- Sequel::Migrator.run(DB, 'db/migrate')
11
+ Sequel::Migrator.run(db, 'db/migrate')
10
12
  puts '<= db:migrate executed'
11
13
  end
12
14
 
13
15
  desc 'Perform migration reset (full erase and migration up)'
14
16
  task :reset do
15
- Sequel::Migrator.run(DB, 'db/migrate', :target => 0)
16
- Sequel::Migrator.run(DB, 'db/migrate')
17
+ Sequel::Migrator.run(db, 'db/migrate', target: 0)
18
+ Sequel::Migrator.run(db, 'db/migrate')
17
19
  puts '<= db:reset executed'
18
20
  end
19
21
  end
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ if gem list foreman -i
6
+ then
7
+ echo 'Foreman detected... starting server with Procfile'
8
+ rerun --background foreman start
9
+ else
10
+ echo 'Foreman not installed... starting server with rackup'
11
+ rerun --background rackup
12
+ fi
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable RSpec/DescribeClass
4
+ RSpec.describe 'GET /', rack: true do
5
+ before { get '/' }
6
+
7
+ it { expect(last_response).to have_http_status(:ok) }
8
+ <% if options[:api_only] -%>
9
+ it { expect(last_response).to have_content_type(:json) }
10
+ <% else -%>
11
+ it { expect(last_response).to have_content_type(:html) }
12
+ <% end -%>
13
+ it { expect(last_response.body).to include('Smoke test successful!') }
14
+ end
15
+ # rubocop:enable RSpec/DescribeClass
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec::Matchers.define :have_http_status do |expected|
4
+ match do |response|
5
+ response.status == response_codes[expected]
6
+ end
7
+
8
+ failure_message do |response|
9
+ 'expected last response status to be ' \
10
+ "#{response_codes.fetch(expected, 'unknown')}, " \
11
+ "but was #{response.status}"
12
+ end
13
+
14
+ def response_codes
15
+ {
16
+ ok: 200,
17
+ not_authorized: 401,
18
+ not_found: 404,
19
+ redirect: 302
20
+ }.freeze
21
+ end
22
+ end
23
+
24
+ RSpec::Matchers.define :have_content_type do |expected|
25
+ match do |response|
26
+ response.content_type == content_types[expected]
27
+ end
28
+
29
+ failure_message do |response|
30
+ 'expected last response to have content type ' \
31
+ "'#{content_types.fetch(expected, 'unknown')}', " \
32
+ "but was '#{response.content_type}'"
33
+ end
34
+
35
+ def content_types
36
+ {
37
+ html: 'text/html;charset=utf-8',
38
+ json: 'application/json'
39
+ }.freeze
40
+ end
41
+ end
42
+
43
+ RSpec::Matchers.define :redirect_to do |expected|
44
+ match do |response|
45
+ path(response) == expected
46
+ end
47
+
48
+ failure_message do |response|
49
+ "expected last response to redirect to '#{expected}', " \
50
+ "but it redirected to '#{path(response)}'"
51
+ end
52
+
53
+ def path(response)
54
+ URI(response.location).path
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RACK_ENV'] = 'test'
4
+
5
+ require 'bundler/setup'
6
+
7
+ require 'warning'
8
+
9
+ # Ignore all warnings in Gem dependencies
10
+ Gem.path.each { |path| Warning.ignore(//, path) }
11
+
12
+ require 'dotenv'
13
+ require 'rack/test'
14
+ require 'support/rack_helpers'
15
+
16
+ require_relative '../app'
17
+
18
+ Dotenv.load
19
+
20
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
+ RSpec.configure do |config|
22
+ config.include RackHelpers, rack: true
23
+
24
+ config.expect_with :rspec do |expectations|
25
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
26
+ end
27
+
28
+ config.mock_with :rspec do |mocks|
29
+ mocks.verify_partial_doubles = true
30
+ end
31
+
32
+ config.default_formatter = 'doc' if config.files_to_run.one?
33
+ config.disable_monkey_patching!
34
+ config.example_status_persistence_file_path = 'spec/examples.txt'
35
+ config.filter_run_when_matching :focus
36
+ config.order = :random
37
+ config.shared_context_metadata_behavior = :apply_to_host_groups
38
+ config.warnings = true
39
+
40
+ Kernel.srand config.seed
41
+ end
@@ -1,33 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
1
4
  namespace :assets do
2
5
  require 'sprockets'
3
6
  require 'uglifier'
4
7
  require 'yui/compressor'
5
- sprockets = Sprockets::Environment.new { |env| env.logger = Logger.new(STDOUT) }
8
+ sprockets = Sprockets::Environment.new { |env| env.logger = Logger.new($stdout) }
6
9
  sprockets.css_compressor = YUI::CssCompressor.new
7
- sprockets.js_compressor = :uglifier
10
+ sprockets.js_compressor = Uglifier.new(harmony: true)
8
11
 
9
- %w(assets vendor).each do |f|
12
+ %w[assets vendor].each do |f|
10
13
  sprockets.append_path File.expand_path("../../#{f}", __FILE__)
11
14
  end
12
15
 
13
- output_path = File.expand_path('../../public', __FILE__)
16
+ output_path = File.expand_path('../public', __dir__)
14
17
 
18
+ desc 'Pre-compile CSS assets'
15
19
  task :precompile_css do
16
20
  asset = sprockets['styles.css']
17
21
  outfile = Pathname.new(output_path).join('css/styles.css')
18
22
  FileUtils.mkdir_p outfile.dirname
19
23
  asset.write_to(outfile)
20
- puts "successfully compiled css assets"
24
+ puts 'successfully compiled css assets'
21
25
  end
22
26
 
27
+ desc 'Pre-compile JavaScript assets'
23
28
  task :precompile_js do
24
29
  asset = sprockets['app.js']
25
30
  outfile = Pathname.new(output_path).join('js/app.js')
26
31
  FileUtils.mkdir_p outfile.dirname
27
32
  asset.write_to(outfile)
28
- puts "successfully compiled javascript assets"
33
+ puts 'successfully compiled javascript assets'
29
34
  end
30
35
 
31
36
  desc 'precompile all assets'
32
- task :precompile => [:precompile_css, :precompile_js]
37
+ task precompile: %i[precompile_css precompile_js]
33
38
  end
39
+ # rubocop:enable Metrics/BlockLength
@@ -1,6 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'sprockets'
2
4
 
3
5
  module Middleware
6
+ # Sprockets Rack middleware.
7
+ #
4
8
  class SprocketsChain
5
9
  attr_reader :app, :prefix, :sprockets
6
10
 
@@ -12,15 +16,15 @@ module Middleware
12
16
  end
13
17
 
14
18
  def call(env)
15
- path_info = env["PATH_INFO"]
19
+ path_info = env['PATH_INFO']
16
20
  if path_info =~ prefix
17
- env["PATH_INFO"].sub!(prefix, "")
21
+ env['PATH_INFO'].sub!(prefix, '')
18
22
  sprockets.call(env)
19
23
  else
20
24
  app.call(env)
21
25
  end
22
26
  ensure
23
- env["PATH_INFO"] = path_info
27
+ env['PATH_INFO'] = path_info
24
28
  end
25
29
  end
26
30
  end
@@ -1,13 +1,17 @@
1
- module Helpers
2
- module Sprockets
3
- def stylesheet_tag(name)
4
- folder = 'production' == ENV['RACK_ENV'] ? 'css' : 'assets'
5
- "<link href='/#{folder}/#{name}.css' rel='stylesheet' type='text/css' />"
6
- end
1
+ # frozen_string_literal: true
7
2
 
8
- def javascript_tag(name)
9
- folder = 'production' == ENV['RACK_ENV'] ? 'js' : 'assets'
10
- "<script type='text/javascript' src='/#{folder}/#{name}.js'></script>"
11
- end
3
+ module Helpers
4
+ # Sprockets view helpers.
5
+ #
6
+ module Sprockets
7
+ def stylesheet_tag(name)
8
+ folder = 'production' == ENV['RACK_ENV'] ? 'css' : 'assets'
9
+ "<link href='/#{folder}/#{name}.css' rel='stylesheet' type='text/css' />"
10
+ end
11
+
12
+ def javascript_tag(name)
13
+ folder = 'production' == ENV['RACK_ENV'] ? 'js' : 'assets'
14
+ "<script type='text/javascript' src='/#{folder}/#{name}.js'></script>"
12
15
  end
13
16
  end
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% if 'test-unit' == options[:test_framework] -%>
4
+ require_relative 'rack_test_assertions'
5
+ <% end -%>
6
+ <% if 'rspec' == options[:test_framework] -%>
7
+ require_relative 'rack_matchers'
8
+ <% end -%>
9
+
10
+ # Helpers for running Rack-based tests or specs. Decorates normal Rack methods
11
+ # with an optional `:as` parameter. If present, the `rack.session` will
12
+ # include the value of the given user's ID, effectively signing them in for
13
+ # the request.
14
+ #
15
+ # Example
16
+ # ```
17
+ # # Find or create a user. You can name the class anything you'd like, the
18
+ # # only requirement is that it responds to a `id` method.
19
+ # user = User.new(id: 1) # or User.find, etc.
20
+ # get '/', as: user
21
+ # ```
22
+ #
23
+ # The above will add a `current_user` to the `rack.session` with a value of
24
+ # the user's ID.
25
+ #
26
+ module RackHelpers
27
+ include Rack::Test::Methods
28
+ <% if 'test-unit' == options[:test_framework] -%>
29
+ include Rack::Test::Assertions
30
+ <% end -%>
31
+
32
+ %w[get post put patch delete options].each do |type|
33
+ define_method(type) do |uri, params={}, env={}, &block|
34
+ extract_user!(params, env)
35
+ super uri, params, env, &block
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def extract_user!(params, env)
42
+ user = params.delete(:as)
43
+ return unless user
44
+
45
+ if env.key?('rack.session')
46
+ env['rack.session'].merge!(current_user: user.id)
47
+ else
48
+ env.merge!({ 'rack.session' => { current_user: user.id } })
49
+ end
50
+ end
51
+
52
+ def app
53
+ Sinatra::Application
54
+ end
55
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module Test
5
+ module Assertions
6
+ RESPONSE_CODES = {
7
+ ok: 200,
8
+ not_authorized: 401,
9
+ not_found: 404,
10
+ redirect: 302
11
+ }.freeze
12
+
13
+ CONTENT_TYPES = {
14
+ json: 'application/json',
15
+ html: 'text/html;charset=utf-8'
16
+ }.freeze
17
+
18
+ def assert_body_contains(expected, message=nil)
19
+ msg = build_message(
20
+ message,
21
+ "expected body to contain <?>\n#{last_response.body}",
22
+ expected
23
+ )
24
+
25
+ assert_block(msg) { last_response.body.include?(expected) }
26
+ end
27
+
28
+ def assert_content_type(content_type)
29
+ unless CONTENT_TYPES.keys.include?(content_type)
30
+ raise ArgumentError, "unrecognized content_type (#{content_type})"
31
+ end
32
+
33
+ assert_equal CONTENT_TYPES[content_type], last_response.content_type
34
+ end
35
+
36
+ def assert_flash(type=:notice, message=nil)
37
+ msg = build_message(
38
+ message,
39
+ 'expected <?> flash to exist, but was nil',
40
+ type.to_s
41
+ )
42
+
43
+ assert_block(msg) { last_request.env['rack.session']['flash'] }
44
+ end
45
+
46
+ def assert_flash_message(expected, type=:notice, message=nil)
47
+ assert_flash(type, message)
48
+ flash = last_request.env['rack.session']['flash'][type.to_s]
49
+
50
+ msg = build_message(
51
+ message,
52
+ 'expected flash to be <?> but was <?>',
53
+ expected,
54
+ flash
55
+ )
56
+
57
+ assert_block(msg) { expected == flash }
58
+ end
59
+
60
+ def assert_has_session(message=nil)
61
+ msg = build_message(message, 'expected a valid session')
62
+ assert_block(msg) { last_request.env['rack.session'] }
63
+ end
64
+
65
+ def assert_session_has_key(key, message=nil)
66
+ assert_has_session
67
+ msg = build_message(message, 'expected session to have key named <?>', key)
68
+ assert_block(msg) { last_request.env['rack.session'].keys.include?(key.to_s) }
69
+ end
70
+
71
+ def assert_session(key, expected, message=nil)
72
+ assert_session_has_key(key)
73
+ actual = last_request.env['rack.session'][key.to_s]
74
+ msg = build_message(
75
+ message,
76
+ 'expected session key <?> to be <?>, but was <?>',
77
+ key,
78
+ expected,
79
+ actual
80
+ )
81
+
82
+ assert_block(msg) { expected == actual }
83
+ end
84
+
85
+ def assert_response(expected, message=nil)
86
+ status = last_response.status
87
+ msg = build_message(
88
+ message,
89
+ 'expected last response to be <?> but was <?>',
90
+ "#{RESPONSE_CODES[expected]}:#{expected}",
91
+ "#{status}:#{RESPONSE_CODES.key(status)}"
92
+ )
93
+
94
+ assert_block(msg) { status == RESPONSE_CODES[expected] }
95
+ end
96
+
97
+ def assert_redirected_to(expected, message=nil)
98
+ assert_response(:redirect)
99
+ actual = URI(last_response.location).path
100
+ msg = build_message(
101
+ message,
102
+ 'expected to be redirected to <?> but was <?>',
103
+ expected,
104
+ actual
105
+ )
106
+
107
+ assert_block(msg) { expected == actual }
108
+ end
109
+ end
110
+ end
111
+ end