Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core application #975

Merged
merged 35 commits into from Jan 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
37e2d3e
Remove old isolation specs
jodosha Nov 16, 2018
fd6b2ab
Introduce Hanami::Application and Hanami.application
jodosha Nov 16, 2018
59758a4
Introduce Hanami.boot
jodosha Nov 16, 2018
dc1de6a
Introduce Hanami::Application#initialize
jodosha Nov 17, 2018
95441bd
Introduce Hanami::Application#call
jodosha Nov 17, 2018
213f7a3
Introduce basic boot process via command line
jodosha Nov 17, 2018
e5b5765
Merge branch 'unstable' into feature/application
jodosha Nov 17, 2018
fb1735d
Depend on hanami-controller unstable branch
jodosha Nov 19, 2018
1224b65
Configure HTTP sessions
jodosha Nov 19, 2018
8e3b548
Configure Rack middleware
jodosha Nov 19, 2018
ccb305a
Eager load code under lib/
jodosha Nov 19, 2018
540c3a2
Mount Rack middleware inside Hanami::Application
jodosha Nov 19, 2018
43c891b
Mount HTTP sessions middleware, if sessions are enabled. Bare minimum…
jodosha Nov 19, 2018
8f27a55
Configure HTTP cookies
jodosha Nov 20, 2018
eae516a
Configure security settings
jodosha Nov 20, 2018
7119b4e
Prefer class reopening over class_eval
jodosha Nov 21, 2018
01dc826
Require project action during booting time
jodosha Nov 21, 2018
7de7234
Bare minimum container
jodosha Nov 21, 2018
120e27e
Whitelist apps to load under apps/. This is useful for selective app …
jodosha Nov 22, 2018
ad8551c
Enhanced security settings to make them serializable into HTTP headers
jodosha Nov 27, 2018
64ee78b
Separate actions settings per application basis
jodosha Nov 27, 2018
eb41752
Hanami::Configuration default_request_format and default_response_format
jodosha Dec 4, 2018
e772ba5
Configure logger and base_url. Introduce Hanami.logger. Introduce env…
jodosha Dec 5, 2018
932b4c3
Configure inflections
jodosha Dec 5, 2018
eb5c2cd
Finalize hanami server command
jodosha Dec 7, 2018
763e768
Expect to find application definition under config/application.rb
jodosha Dec 12, 2018
808bb66
Update lib/hanami/cli/commands.rb
kaikuchn Jan 9, 2019
a66bce1
Merge branch 'unstable' into feature/application
jodosha Jan 9, 2019
684d9f6
hanami server: simplify option parsing and make it faster
jodosha Jan 9, 2019
3ab512c
Preserve frozen state for default settings in configuration
jodosha Jan 9, 2019
ed2f920
Renamed Hanami::Configuration#to_router => #router_settings
jodosha Jan 9, 2019
257e988
HTTP sessions settings: use parallel assigment to clafify storage and…
jodosha Jan 9, 2019
d9566d3
Remove commented line
jodosha Jan 9, 2019
cfd7adb
Remove :root component from Container, and use the related configurat…
jodosha Jan 14, 2019
75946d9
Introduce 'hanami/boot', a file to require and have your Hanami app b…
jodosha Jan 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion hanami.gemspec
Expand Up @@ -26,7 +26,9 @@ Gem::Specification.new do |spec|
spec.add_dependency "hanami-router", "~> 2.0.alpha"
spec.add_dependency "hanami-controller", "~> 2.0.alpha"
spec.add_dependency "hanami-cli", "~> 1.0.alpha"
spec.add_dependency "bundler", "~> 1.16"
spec.add_dependency "dry-system", "~> 0.10"
spec.add_dependency "dry-inflector", "~> 0.1", ">= 0.1.2"
spec.add_dependency "bundler", ">= 1.16", "< 3"

spec.add_development_dependency "rspec", "~> 3.8"
spec.add_development_dependency "rack-test", "~> 1.1"
Expand Down
60 changes: 60 additions & 0 deletions lib/hanami.rb
Expand Up @@ -8,4 +8,64 @@
module Hanami
require "hanami/version"
require "hanami/frameworks"
require "hanami/container"
require "hanami/application"

@_mutex = Mutex.new

def self.application
@_mutex.synchronize do
raise "Hanami application not configured" unless defined?(@_application)

@_application
end
end

def self.application=(app)
@_mutex.synchronize do
raise "Hanami application already configured" if defined?(@_application)

@_application = app unless app.name.nil?
end
end

def self.app
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we want a different name for this, since the pairing of application/app may be a little confusing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timriley This is the only instance of Hanami::Application subclass in the process. So it's the app.

Hanami.app 
# => #<Soundeck::Application:0x00007ffee940eee0 ...>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so for a booted application, Hanami.application is the application's class (via the Hanami::Application.inherited hook) and Hanami.app is the one instance of that application class? Is that right? i.e.

Hanami.application
# => Soundeck::Application

Hanami.app
# => #<Soundeck::Application:0x00007ffee940eee0 ...>

I still feel like that's a pretty confusing pairing of names. "app" is just shorthand for "application", they're synonyms. It feels a little odd that we use them both here, holding 2 different (though obviously related) things.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timriley Which name do you suggest? 😃 Please remember that Hanami.app is short and memorable, and it's used by public facing integration specs.

Eg.

require "features_helper"

RSpec.feature "Signup" do
  let(:app) { Hanami.app } # for capybara and rack-test
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jodosha What about project for the container application? You used "project" four times in the main message to refer to this :) It's not quite as memorible or short as app, but it's not hard to remember either. I agree with @timriley that using app and application as different things would be confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure to if keeping project is a good idea. The intention is to remove the word "project" from the terminology. See my YT video.

Copy link
Member

@timriley timriley Jan 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could stick with app in both methods but disambiguate between the two with a suffix, e.g. #application and #application_class? (maybe paired with shorter #app aliases too)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that app returns an instance of the application, I would say a natural name for the other method should be app_class. I would also say this should be a private API anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can understand some of the confusion between "Hanami project" referring to this whole effort, but I still think "project" is a natural and obvious way to talk about what the "umbrella" app is.

Will apps/ be renamed to slices/? They're still Rack-compatible apps. While I like the naming of "slices" and it seems appropriate, I feel like this is re-inventing the wheel and causes more confusion than it's worth. Hanami.app (or Hanami.application) being a different thing from Hanami::App is confusing. If we want to use "slice", we should go all the way and rename Hanami::App to Hanami::Slice. I don't think we should do that, but we should be consistent if we're changing the terminology.

Django uses the project/ application distinction in the same way we have been: https://medium.com/@timmykko/a-quick-glance-of-django-for-beginners-688bc6630fab

Any Django project will have at least one Django app. Yes, I know, it’s a little confusing. A Django project encompasses the application and all its components, while a Django app is a sub-container of the application with its own functionality that, in theory, may be reusable in another application without much modification.

(Also, for others, here's the YouTube video, since it hasn't been linked in this PR yet)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jodosha Thoughts on the above? ☝️ (Don't mean to bother you, I just want to make sure you saw it since I didn't mention you in the comment.) 🌸

@_mutex.synchronize do
raise "Hanami.app not configured" unless defined?(@_app)

@_app
end
end

def self.app=(app)
@_mutex.synchronize do
raise "Hanami.app already configured" if defined?(@_app)

@_app = app
end
end

def self.root
Container.root
end

def self.env
# TODO: make this to work
:development
end

def self.logger
Container[:logger]
end

def self.boot
@_mutex.synchronize do
raise "Hanami application already booted" if defined?(@_booted)

@_booted = true
end

Container.finalize!
self.app = application.new
end
end
70 changes: 70 additions & 0 deletions lib/hanami/application.rb
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "hanami/configuration"
require "hanami/routes"
require "hanami/router"

module Hanami
# Hanami application
#
# @since 2.0.0
class Application
@_mutex = Mutex.new

def self.inherited(app)
@_mutex.synchronize do
app.class_eval do
@_mutex = Mutex.new
@_configuration = Hanami::Configuration.new(env: Hanami.env)

extend ClassMethods
include InstanceMethods
end

Hanami.application = app
end
end

# Class method interface
#
# @since 2.0.0
module ClassMethods
def configuration
@_configuration
end

alias config configuration

def routes(&blk)
@_mutex.synchronize do
if blk.nil?
raise "Hanami.application.routes not configured" unless defined?(@_routes)

@_routes
else
@_routes = Routes.new(&blk)
end
end
end
end

# Instance method interface
#
# @since 2.0.0
module InstanceMethods
def initialize(configuration: self.class.configuration, routes: self.class.routes)
@app = Rack::Builder.new do
configuration.for_each_middleware do |m, *args|
use m, *args
end

run Hanami::Router.new(**configuration.router_settings, &routes)
end
end

def call(env)
@app.call(env)
end
end
end
end
6 changes: 6 additions & 0 deletions lib/hanami/boot.rb
@@ -0,0 +1,6 @@
# frozen_string_literal: true

require "bundler/setup"
require "hanami"

Hanami.boot
63 changes: 63 additions & 0 deletions lib/hanami/cli/commands.rb
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require "hanami/cli"

module Hanami
# Hanami CLI
#
# @since 1.1.0
class CLI
# Register a command to expand Hanami CLI
#
# @param name [String] the command name
# @param command [NilClass,Hanami::CLI::Command,Hanami::CLI::Commands::Command]
# the optional command
# @param aliases [Array<String>] an optional list of aliases
#
# @since 1.1.0
#
# @example Third party gem
# require "hanami/cli/commands"
#
# module Hanami
# module Webpack
# module CLI
# module Commands
# class Generate < Hanami::CLI::Command
# desc "Generate Webpack configuration"
#
# def call(*)
# # ...
# end
# end
# end
# end
# end
# end
#
# Hanami::CLI.register "generate webpack", Hanami::Webpack::CLI::Commands::Generate
#
# # $ bundle exec hanami generate
# # Commands:
# # hanami generate action APP ACTION # Generate an action for app
# # hanami generate app APP # Generate an app
# # hanami generate mailer MAILER # Generate a mailer
# # hanami generate migration MIGRATION # Generate a migration
# # hanami generate model MODEL # Generate a model
# # hanami generate secret [APP] # Generate session secret
# # hanami generate webpack # Generate Webpack configuration
def self.register(name, command = nil, aliases: [], &blk)
Commands.register(name, command, aliases: aliases, &blk)
end

# CLI commands registry
#
# @since 1.1.0
# @api private
module Commands
extend Hanami::CLI::Registry

require "hanami/cli/commands/server"
end
end
end
88 changes: 88 additions & 0 deletions lib/hanami/cli/commands/server.rb
@@ -0,0 +1,88 @@
# frozen_string_literal: true

module Hanami
# Hanami CLI
#
# @since 1.1.0
class CLI
module Commands
# @since 1.1.0
# @api private
class Server < Command
desc "Start Hanami server (only for development)"

option :server, desc: "Force a server engine (eg, webrick, puma, thin, etc..)"
option :host, desc: "The host address to bind to"
option :port, desc: "The port to run the server on", aliases: ["-p"]
option :debug, desc: "Turn on debug output", type: :boolean
option :warn, desc: "Turn on warnings", type: :boolean
option :daemonize, desc: "Daemonize the server", type: :boolean
option :pid, desc: "Path to write a pid file after daemonize"

example [
" # Basic usage (it uses the bundled server engine)",
"--server=webrick # Force `webrick` server engine",
"--host=0.0.0.0 # Bind to a host",
"--port=2306 # Bind to a port"
]

# @since 1.1.0
# @api private
def call(**args)
require "hanami"
require "hanami/container"
require "hanami/server"

options = parse_arguments(args)
Hanami::Server.new(options).start
end

private

DEFAULT_CONFIG = "config.ru"
private_constant :DEFAULT_CONFIG

DEFAULT_HOST = "0.0.0.0"
private_constant :DEFAULT_HOST

DEFAULT_PORT = "2300"
private_constant :DEFAULT_PORT

OPTIONAL_SETTINGS = %i[
server
debug
warn
daemonize
pid
].freeze

def parse_arguments(args)
Hanami::Container.start(:env)

{
config: DEFAULT_CONFIG,
Host: host(args),
Port: port(args),
AccessLog: []
}.merge(
args.slice(*OPTIONAL_SETTINGS)
)
end

def host(args)
args.fetch(:host) do
ENV.fetch("HANAMI_HOST", DEFAULT_HOST)
end
end

def port(args)
args.fetch(:port) do
ENV.fetch("HANAMI_PORT", DEFAULT_PORT)
end
end
end
end

register "server", Commands::Server, aliases: ["s"]
end
end