hoboken 0.0.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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