Giter Site home page Giter Site logo

router's Introduction

Hanami 🌸

The web, with simplicity.

Version

This branch contains the code for hanami 2.0.x.

Frameworks

Hanami is a full-stack Ruby web framework. It's made up of smaller, single-purpose libraries.

This repository is for the full-stack framework, which provides the glue that ties all the parts together:

These components are designed to be used independently or together in a Hanami application.

Status

Gem Version CI Depfu

Installation

Hanami supports Ruby (MRI) 3.0+

gem install hanami

Usage

hanami new bookshelf
cd bookshelf && bundle
bundle exec hanami server # visit http://localhost:2300

Please follow along with the Getting Started guide.

Donations

You can give back to Open Source, by supporting Hanami development via GitHub Sponsors.

Supporters

Contact

Community

We strive for an inclusive and helpful community. We have a Code of Conduct to handle controversial cases. In general, we expect you to be nice with other people. Our hope is for a great software and a great Community.

Contributing Open Source Helpers

  1. Fork it ( https://github.com/hanami/hanami/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

In addition to contributing code, you can help to triage issues. This can include reproducing bug reports, or asking for vital information such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to subscribe to hanami on CodeTriage.

Tests

To run all test suite:

$ bundle exec rake

To run all the unit tests:

$ bundle exec rspec spec/unit

To run all the integration tests:

$ bundle exec rspec spec/integration

To run a single test:

$ bundle exec rspec path/to/spec.rb

Development Requirements

  • Ruby >= 3.0
  • Bundler
  • Node.js (MacOS)

Versioning

Hanami uses Semantic Versioning 2.0.0

Copyright

Copyright © 2014 Hanami Team – Released under MIT License.

router's People

Contributors

alfonsouceda avatar artofhuman avatar childss avatar cllns avatar davydovanton avatar deepj avatar depfu[bot] avatar drewdeponte avatar erol avatar fredwu avatar gustavocaso avatar gzigzigzeo avatar igneus avatar janko avatar jodosha avatar karlfreeman avatar malin-as avatar morrme avatar nickgnd avatar pascalbetz avatar pat avatar pragtob avatar solnic avatar strech avatar tak1n avatar timriley avatar valikos avatar vlazar avatar vyper avatar waiting-for-dev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

router's Issues

router#params not merged into action#params on request with validations

Given a :patch request to /myfoo/:id with some data

{
   _method: "PATCH",
  foo: { bar: 'foobar' }    
}

With params validations

params do
  param :foo do
    param: :bar, type: String
  end
end

Accessing the params in a controller:

params[:foo]  # {bar: 'foobar'}
params[:id]   # nil

But router.params does report

{:id=>"1"}

Adding

    param :id`
    [...]

Solves the problem. I don't think I should explicitly add router params to my form params validation.

Customize JSON parsing errors

I've been using Lotus::Router (with Lotus::Controller) in building an API, and I find the ability to specify JSON parsing via Lotus::Router.new parsers: [:json] very convenient. However, the router responds with a very verbose message and stacktrace when a parsing error is encountered:

HTTP/1.1 500 Internal Server Error
Content-Length: 2372
Content-Type: text/plain

JSON::ParserError: 757: unexpected token at '{
  "fruit": "Apple"
  "vegetable": "Cabbage"
}
'
    /Users/Erol/.gem/ruby/2.2.0/gems/json-1.8.2/lib/json/common.rb:155:in `parse'
    /Users/Erol/.gem/ruby/2.2.0/gems/json-1.8.2/lib/json/common.rb:155:in `parse'
    /Users/Erol/.gem/ruby/2.2.0/gems/lotus-router-0.2.1/lib/lotus/routing/parsing/json_parser.rb:12:in `parse'
    /Users/Erol/.gem/ruby/2.2.0/gems/lotus-router-0.2.1/lib/lotus/routing/parsers.rb:52:in `block in _redefine_call'
    /Users/Erol/.gem/ruby/2.2.0/gems/lotus-router-0.2.1/lib/lotus/routing/http_router.rb:112:in `raw_call'
    /Users/Erol/.gem/ruby/2.2.0/gems/http_router-0.11.1/lib/http_router.rb:142:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/lotus-router-0.2.1/lib/lotus/router.rb:879:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/session/abstract/id.rb:225:in `context'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/session/abstract/id.rb:220:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/lint.rb:49:in `_call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/lint.rb:37:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/showexceptions.rb:24:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/puma-2.11.0/lib/puma/rack_patch.rb:13:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/shotgun-0.9.1/lib/shotgun/loader.rb:86:in `proceed_as_child'
    /Users/Erol/.gem/ruby/2.2.0/gems/shotgun-0.9.1/lib/shotgun/loader.rb:31:in `call!'
    /Users/Erol/.gem/ruby/2.2.0/gems/shotgun-0.9.1/lib/shotgun/loader.rb:18:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/shotgun-0.9.1/lib/shotgun/favicon.rb:12:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/urlmap.rb:66:in `block in call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/urlmap.rb:50:in `each'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/urlmap.rb:50:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/rack-1.6.0/lib/rack/builder.rb:153:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/puma-2.11.0/lib/puma/server.rb:507:in `handle_request'
    /Users/Erol/.gem/ruby/2.2.0/gems/puma-2.11.0/lib/puma/server.rb:375:in `process_client'
    /Users/Erol/.gem/ruby/2.2.0/gems/puma-2.11.0/lib/puma/server.rb:262:in `block in run'
    /Users/Erol/.gem/ruby/2.2.0/gems/puma-2.11.0/lib/puma/thread_pool.rb:104:in `call'
    /Users/Erol/.gem/ruby/2.2.0/gems/puma-2.11.0/lib/puma/thread_pool.rb:104:in `block in spawn_thread'

Is there a way to silence or customize the error response when running on production? I'm willing to submit a PR if there is no facility yet to do this and if you feel this is a worthy feature. :)

Server error when using force_ssl true and running server with rackup

$ lotus new force_ssl_test
# config/routes.rb
get '/', to: ->(env) { [200, {}, ['OK']] }

# apps/web/application.rb
force_ssl true
$ bundle exec rackup
[2015-11-04 17:54:21] INFO  WEBrick 1.3.1
[2015-11-04 17:54:21] INFO  ruby 2.2.3 (2015-08-18) [x86_64-darwin15]
[2015-11-04 17:54:21] INFO  WEBrick::HTTPServer#start: pid=70829 port=9292
::1 - - [04/Nov/2015:17:54:31 +0300] "GET / HTTP/1.1" 301 - 0.0048
[2015-11-04 17:54:31] ERROR NoMethodError: undefined method `each' for "":String
    /Users/vlazar/.rvm/gems/ruby-2.2.3@force_ssl_test/gems/rack-1.6.4/lib/rack/body_proxy.rb:31:in `each'
    /Users/vlazar/.rvm/gems/ruby-2.2.3@force_ssl_test/gems/rack-1.6.4/lib/rack/lint.rb:708:in `each'
    /Users/vlazar/.rvm/gems/ruby-2.2.3@force_ssl_test/gems/rack-1.6.4/lib/rack/body_proxy.rb:31:in `each'
    /Users/vlazar/.rvm/gems/ruby-2.2.3@force_ssl_test/gems/rack-1.6.4/lib/rack/chunked.rb:23:in `each'
    /Users/vlazar/.rvm/gems/ruby-2.2.3@force_ssl_test/gems/rack-1.6.4/lib/rack/handler/webrick.rb:112:in `service'
    /Users/vlazar/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/webrick/httpserver.rb:138:in `service'
    /Users/vlazar/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/webrick/httpserver.rb:94:in `run'
    /Users/vlazar/.rvm/rubies/ruby-2.2.3/lib/ruby/2.2.0/webrick/server.rb:294:in `block in start_thread'
$ http http://localhost:9292/
HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: text/html; charset=ISO-8859-1
Date: Wed, 04 Nov 2015 14:04:16 GMT
Location: https://localhost:2300/
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Transfer-Encoding: chunked


http: error: ChunkedEncodingError: ('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))

Dasherized Route Support

I'm hoping to add or help add support to Hanami for resolving endpoints for dasherized routes in a manner consistent with underscored routes. That is:

# from this:
'test-resource' => Web::Controllers::Test::Resource
'test_resource' => Web::Controllers::TestResource

# to this:
'test-resource' => Web::Controllers::TestResource
'test_resource' => Web::Controllers::TestResource

I previously attempted this in hanami/utils#168 but it was rightly pointed out that my solution breaks the Ruby naming convention. @cllns suggested adding an option to the router to support this so that's what I've investigated next:

diff --git a/lib/hanami/routing/endpoint_resolver.rb b/lib/hanami/routing/endpoint_resolver.rb
index 50b8527..1923ded 100644
--- a/lib/hanami/routing/endpoint_resolver.rb
+++ b/lib/hanami/routing/endpoint_resolver.rb
@@ -98,10 +98,11 @@ module Hanami
       #   router.get('/', to: 'articles@show')
       #     # => Will look for: Articles::Show
       def initialize(options = {})
-        @endpoint_class   = options[:endpoint]         || Endpoint
-        @namespace        = options[:namespace]        || Object
-        @action_separator = options[:action_separator] || ACTION_SEPARATOR
-        @pattern          = options[:pattern]          || NAMING_PATTERN
+        @endpoint_class    = options[:endpoint]         || Endpoint
+        @namespace         = options[:namespace]        || Object
+        @action_separator  = options[:action_separator] || ACTION_SEPARATOR
+        @pattern           = options[:pattern]          || NAMING_PATTERN
+        @dasherized_routes = !!options[:dasherized_routes]
       end

       # Resolve the given set of HTTP verb, path, endpoint and options.
@@ -198,7 +199,8 @@ module Hanami
       end

       def classify(string)
-        Utils::String.new(string).classify
+        to_classify = @dasherized_routes ? Utils::String.new(string).underscore : string
+        Utils::String.new(to_classify).classify
       end

       private

This does appear to work but before following through with a complete PR (and corresponding PR to hanami/hanami for configuration), I wanted to get some feedback on this approach. Thanks in advance for your consideration!

Mounting app after url parameter

Hi all,

My title is terrible, sorry, can't figure out how to name it, but anyway, here's the thing:

I'm currently switching an app from rails to hanami. No changes in urls are possible.

Urls are of the following form: (/:locale)/rest/of-the/url, so, for example: /en/admin/users would go to Admin::Users::Index with locale en in its params.

What I've tried so far:

  • using a fixed namespace to see if working on #114 could solve my problem. It seems like the mount just does not care about the namespace.
  • mount Admin::Application, at: '(/:locale)/admin' which kinda almost work, except that all urls generated are screwed up with a nasty (/:locale) at the beginning (including asset urls)
  • mount Admin::Application.new(locale: 'fr'), at: '/fr/admin' just to see if it was possible... let's just say it's not...

Questions:

  • is this possible at the moment?
  • if not, is this something that you'd be willing to be added to the router?
  • and if so, do you have any general idea on how/where to start looking to implement it? (I've taken a look, I must confess I won't be able to do it without a bit of guidance)

Print extra line at the end of routes inspection

When multiple applications are present in a Lotus project, the output of lotus routes is cluttered because the lack of extra blank line between groups of routes:

Steps to reproduce:

➜ lotus new bookshelf && cd bookshelf && bundle
➜ bundle exec lotus generate app admin
➜ bundle exec lotus generate action web books#index --url=/books
➜ bundle exec lotus generate action admin users#index --url=/users
➜ bundle exec lotus routes
                Name Method     Path                           Action

                     GET, HEAD  /admin/users                   Admin::Controllers::Users::Index
                Name Method     Path                           Action

                     GET, HEAD  /books                         Web::Controllers::Books::Index

Expected would be:

➜ bundle exec lotus routes
                Name Method     Path                           Action
                     GET, HEAD  /admin/users                   Admin::Controllers::Users::Index

                Name Method     Path                           Action
                     GET, HEAD  /books                         Web::Controllers::Books::Index

Ref #78
/cc @davydovanton

The router crashes when the body is a JSON array

I was able to reproduce the error here.

  1) Error:
Body parsing#test_0002_is successful (JSON) with an array:
TypeError: no implicit conversion of Array into Hash
    /Users/weppos/Mirrors/hanami--router/lib/hanami/routing/parsers.rb:49:in `merge!'
    /Users/weppos/Mirrors/hanami--router/lib/hanami/routing/parsers.rb:49:in `block in _redefine_call'
    /Users/weppos/Mirrors/hanami--router/lib/hanami/routing/http_router.rb:135:in `raw_call'
    /Users/weppos/.rvm/gems/ruby-2.3.0/gems/http_router-0.11.2/lib/http_router.rb:142:in `call'
    /Users/weppos/Mirrors/hanami--router/lib/hanami/router.rb:931:in `call'
    /Users/weppos/.rvm/gems/ruby-2.3.0/gems/rack-1.6.4/lib/rack/lint.rb:49:in `_call'
    /Users/weppos/.rvm/gems/ruby-2.3.0/gems/rack-1.6.4/lib/rack/lint.rb:37:in `call'
    /Users/weppos/.rvm/gems/ruby-2.3.0/gems/rack-1.6.4/lib/rack/mock.rb:74:in `request'
    /Users/weppos/.rvm/gems/ruby-2.3.0/gems/rack-1.6.4/lib/rack/mock.rb:59:in `patch'
    /Users/weppos/Mirrors/hanami--router/test/integration/body_parsing_test.rb:27:in `block (2 levels) in <top (required)>'
  it 'is successful (JSON) with an array' do
    body     = StringIO.new( %(["alpha", "beta"]).encode(Encoding::ASCII_8BIT) )
    response = @app.patch('/books/23', 'CONTENT_TYPE' => 'application/json', 'rack.input' => body, lint: true)

    response.status.must_equal 200
    response.body.must_equal %(["alpha", "beta"])
  end

This is an example of a request

$ curl http://example.com/path -X POST -d '["alpha", "beta"]' -H "Content-Type: application/json"

Of course, the tricky part is how this case should be handled in the params as it implies the params can also be an Array, instead of always a Hash.

It's possible to use a constant key, for example _, which is also the same approach Rails is following.

{"_json"=>["ns1.example.com", "ns2.example.com"], "controller"=>"something", "action"=>"create", "something"=>{"_json"=>["ns1.example.com", "ns2.example.com"]}}

In this case, Rails uses the _json key.

FYI, Lotus crashes at this point, because merge! can't handle an input which is not a Hash.

          env[ROUTER_PARAMS].merge!(
            @parsers[
              media_type(env)
            ].parse(body)
          )

Resources ending up with *ices has the 's' removed (singularized?)

When I declare resources like: resources :prices the result is:

price GET, HEAD  /dashboard/prices              Dashboard::Controllers::Prices::Index
new_price GET, HEAD  /dashboard/prices/new          Dashboard::Controllers::Prices::New
price POST       /dashboard/prices              Dashboard::Controllers::Prices::Create
price GET, HEAD  /dashboard/prices/:id          Dashboard::Controllers::Prices::Show
edit_price GET, HEAD  /dashboard/prices/:id/edit     Dashboard::Controllers::Prices::Edit
price PATCH      /dashboard/prices/:id          Dashboard::Controllers::Prices::Update
price DELETE     /dashboard/prices/:id          Dashboard::Controllers::Prices::Destroy

I expected get the price pluralized on Index and Create actions.
Researching about plurals in words ending up with ice I could not realized why such words are losing the last s in hanami-router.
Is there any reason for it?

Note: I'm not an english lang expert grammatically talking ;).

Lotus::Controller integration: change default controller/action naming

Lotus::Router has a Lotus::Controller integration for controller/action naming.

If we define an endpoint with the 'welcome#index' syntax, it creates the following pattern by default: Welcome(::Controller::|Controller::)Index. With this in mind, it looks for Welcome::Controller::Index or WelcomeController::Index, otherwise it raises a NameError for the missing constant.

In Lotus (lotus/lotus), we're encouraging controllers namespacing by default. In the case above default search pattern should be 'Controllers::Welcome::Index'. The default Ruby namespace should still be Object, so in case of standalone usage of Lotus::Router, that Controllers::Welcome::Index naming structure is compatible with full stack Lotus applications.

undefined local variable or method `uncompile'

When I try the get started sample code, and run it with rackup, I encountered this error

/Users/allenlsy/.rvm/gems/ruby-2.0.0-p247/gems/lotus-router-0.1.1/lib/lotus/routing/http_router.rb:110:in `reset!': undefined local variable or method `uncompile' for #<Lotus::Routing::HttpRouter:0x007fea8aa1fe38> (NameError)
    from /Users/allenlsy/.rvm/gems/ruby-2.0.0-p247/gems/http_router-0.10.2/lib/http_router.rb:45:in `initialize'
    from /Users/allenlsy/.rvm/gems/ruby-2.0.0-p247/gems/lotus-router-0.1.1/lib/lotus/routing/http_router.rb:37:in `initialize'
    from /Users/allenlsy/.rvm/gems/ruby-2.0.0-p247/gems/lotus-router-0.1.1/lib/lotus/router.rb:122:in `new'
    from /Users/allenlsy/.rvm/gems/ruby-2.0.0-p247/gems/lotus-router-0.1.1/lib/lotus/router.rb:122:in `initialize'
    from /Users/allenlsy/projects/lotus/demo1/router.rb:3:in `new'
    from /Users/allenlsy/projects/lotus/demo1/router.rb:3:in `<top (required)>'

Nested resources restrictions

First, thank you for this great framework.

The issue I have is when nesting resources, I can't restrict the routes available by using :only

 resources :categories, only: :show do
   member do
      resources :posts, only: :show
   end
 end

I have the following output when running bundle exec lotus routes:

/categories/:id/posts GET, HEAD  /categories/:id/posts          Web::Controllers::::Categories:::id::Posts::Index
new_/categories/:id/posts GET, HEAD  /categories/:id/posts/new      Web::Controllers::::Categories:::id::Posts::New
/categories/:id/posts POST       /categories/:id/posts          Web::Controllers::::Categories:::id::Posts::Create
/categories/:id/posts GET, HEAD  /categories/:id/posts/:id      Web::Controllers::::Categories:::id::Posts::Show
edit_/categories/:id/posts GET, HEAD  /categories/:id/posts/:id/edit Web::Controllers::::Categories:::id::Posts::Edit
/categories/:id/posts PATCH      /categories/:id/posts/:id      Web::Controllers::::Categories:::id::Posts::Update
/categories/:id/posts DELETE     /categories/:id/posts/:id      Web::Controllers::::Categories:::id::Posts::Destroy
          categories GET, HEAD  /categories/:id                Web::Controllers::Categories::Show

While I was expecting to only have this:

/categories/:id/posts GET, HEAD  /categories/:id/posts/:id      Web::Controllers::::Categories:::id::Posts::Show
          categories GET, HEAD  /categories/:id                Web::Controllers::Categories::Show

Serialise router into array of routes/test

It would be useful to be able to take a Lotus::Router instance and serialise it either into a string in order to allow for a command like lotus routes (or rake routes) or into an array of intermediate objects which could be easily transformed into a different format (like text).

Support for this (I think) should be added to the router and then we can very quickly implement something like described in hanami/hanami#35 in the main lotus library.

I've been playing around with this but I noticed there's another branch where @jodosha has been changing things. Is this a feature I should go ahead and work on, or are there large changes in the works for the router?

Feature request: generate paths with the same DSL as they are defined

Currently the path for an action can be generated using a method that follows an implicit naming convention:

routes.flowers
routes.flower id: 23
routes.new_flower

I think that might be a Rails reminiscence and find it unintuitive (that is manifested by the fact of needing to use a 'routes' command to see the named paths of the routes).
A more intuitive and direct approach might be to allow to generate the paths using the same DSL used to define the routes and that makes an explicit reference to the route action:

get '/', to: 'welcome#index'

path_to 'welcome#index'

and for REST resources

path_to 'flowers#index'
path_to 'flowers#show', id: 23
path_to 'flowers#edit', id: 23
# etc

and for nested resources

#/users/1/favorites/2
path_to 'users::favorites#show', user_id: 1, id: 2

Issues around nested routes

So Lotus router lets you do this, and doesn't complain:

router = Lotus::Router.new do
  resources :groups do
    member do
      resources :activities
    end
  end
end

But if we do puts router.inspector...

/groups/:id/activities GET, HEAD  /groups/:id/activities         /groups/:id/activities::Index
new_/groups/:id/activities GET, HEAD  /groups/:id/activities/new     /groups/:id/activities::New
/groups/:id/activities POST       /groups/:id/activities         /groups/:id/activities::Create
/groups/:id/activities GET, HEAD  /groups/:id/activities/:id     /groups/:id/activities::Show
edit_/groups/:id/activities GET, HEAD  /groups/:id/activities/:id/edit /groups/:id/activities::Edit
/groups/:id/activities PATCH      /groups/:id/activities/:id     /groups/:id/activities::Update
/groups/:id/activities DELETE     /groups/:id/activities/:id     /groups/:id/activities::Destroy
              groups GET, HEAD  /groups                        Groups::Index
          new_groups GET, HEAD  /groups/new                    Groups::New
              groups POST       /groups                        Groups::Create
              groups GET, HEAD  /groups/:id                    Groups::Show
         edit_groups GET, HEAD  /groups/:id/edit               Groups::Edit
              groups PATCH      /groups/:id                    Groups::Update
              groups DELETE     /groups/:id                    Groups::Destroy

...it gives us complete nonsense.

Same thing for:

router = Lotus::Router.new do
  resources :users do
    member do
      resource :avatar
    end
  end
end
new_/users/:id/avatar GET, HEAD  /users/:id/avatar/new          /users/:id/avatar::New
   /users/:id/avatar POST       /users/:id/avatar              /users/:id/avatar::Create
   /users/:id/avatar GET, HEAD  /users/:id/avatar              /users/:id/avatar::Show
edit_/users/:id/avatar GET, HEAD  /users/:id/avatar/edit         /users/:id/avatar::Edit
   /users/:id/avatar PATCH      /users/:id/avatar              /users/:id/avatar::Update
   /users/:id/avatar DELETE     /users/:id/avatar              /users/:id/avatar::Destroy
               users GET, HEAD  /users                         Users::Index
           new_users GET, HEAD  /users/new                     Users::New
               users POST       /users                         Users::Create
               users GET, HEAD  /users/:id                     Users::Show
          edit_users GET, HEAD  /users/:id/edit                Users::Edit
               users PATCH      /users/:id                     Users::Update
               users DELETE     /users/:id                     Users::Destroy

So from here I see two options:

  1. Decide that nested resources are evil, and explicitly disallow them. (And I can totally see arguments for this).
  2. Make it work in a more expected manner.

If nested resources were explicitly disallowed, I think there'd be some utility from the users/:id/avatar case that would be lost. As such, I think option two is probably the better option.

If option two works, I'd be happy to put together a pull request (though it may take me a while, since it looks like it would be a significant set of changes to the internals here).

Example from blog doesn't work

I'm trying to execute code from your last post.

require 'rubygems'
require 'bundler/setup'
require 'lotus/router'

Application = Rack::Builder.new do
  app = Lotus::Router.new do
    get '/' do
      [200, {}, ['Hello, World!']]
    end
  end
  run app
end.to_app

but it doesn't work with this trace:

2.0.0-p247/lib/ruby/gems/2.0.0/gems/lotus-router-0.1.0/lib/lotus/routing/http_router.rb:119:in `add_with_request_method': undefined method `generate' for #<HttpRouter::Route:0x007fa5f353c6b8> (NoMethodError)
2.0.0-p247/lib/ruby/gems/2.0.0/gems/http_router-0.10.2/lib/http_router.rb:80:in `get'

Parsed body overrides params from URI interpolation

When body parsers are activated, they "merge" the params inside the payload of a non-GET request with the params passed in the URI.

The problem with the actual implementation is that the params from the payload have higher priority than the ones interpolated from the URI.


Example: Given the following route:

PATCH /books/:id

If we pass :id in the payload of the PATCH request, it takes precedence.

curl -H "Content-Type: application/json" \
-H "Accept: application/json" \
-X PATCH \
-d '{"id":999}' \
https://api.example.com/books/1

In the example above, params[:id] will equal to 999 instead of 1, which is wrong and confusing.

path and url for mounted application don't include mount point

Please point me in the right direction if I am doing this wrong.

To reproduce, I created a new lotus application

lotus new test

I changed the mount point for the 'Web' app from '/' to '/test', and added a resource to app/web/config/routes.rb

➜  test git:(master) ✗ bundle exec lotus routes
                     GET, HEAD  /test/page                     Web::Controllers::Home::Index
                pigs GET, HEAD  /test/pigs                     Web::Controllers::Pigs::Index
            new_pigs GET, HEAD  /test/pigs/new                 Web::Controllers::Pigs::New
                pigs POST       /test/pigs                     Web::Controllers::Pigs::Create
                pigs GET, HEAD  /test/pigs/:id                 Web::Controllers::Pigs::Show
           edit_pigs GET, HEAD  /test/pigs/:id/edit            Web::Controllers::Pigs::Edit
                pigs PATCH      /test/pigs/:id                 Web::Controllers::Pigs::Update
                pigs DELETE     /test/pigs/:id                 Web::Controllers::Pigs::Destroy

in apps/web/templates/home/index.html.erb I added <%= Web::Routes.path(:pigs) %>

Started the server and looked at the page, the path rendered is /pigs, but would expect it to be /test/pigs

The rake task is aware of the mount point, but within the view context, the apps routes object does not seem to be aware of its own mount point?

I've spent a lot of time looking into the routing code, and haven't really found anything obvious.

Namespaced controllers and lazy lookup

I have Admin namespace for actions in my application.

namespace 'admin' do
  get 'coupons', to: 'admin/coupons#index'
end

expected to open Web::Controllers::Admin::Coupons::Index but fails with Lotus::Routing::EndpointNotFound: wrong constant name Admin/coupons.

Rack::Utils::String#constantize does not support namespaces like ActiveSupport do. Would it be right way to implement namespacing support in Rack::Utils?

Error in lotus routes using arch app

Create sample app

$ lotus new sampleapp --arch=app
      create  sampleapp/.lotusrc
      create  sampleapp/.env
      create  sampleapp/.env.development
      create  sampleapp/.env.test
      create  sampleapp/Gemfile
      create  sampleapp/config.ru
      create  sampleapp/config/environment.rb
      create  sampleapp/lib/sampleapp.rb
      create  sampleapp/lib/config/mapping.rb
      create  sampleapp/config/application.rb
      create  sampleapp/config/routes.rb
      create  sampleapp/app/views/application_layout.rb
      create  sampleapp/app/templates/application.html.erb
      create  sampleapp/Rakefile
      create  sampleapp/spec/spec_helper.rb
      create  sampleapp/spec/features_helper.rb
      create  sampleapp/app/controllers/.gitkeep
      create  sampleapp/app/views/.gitkeep
      create  sampleapp/lib/sampleapp/entities/.gitkeep
      create  sampleapp/lib/sampleapp/repositories/.gitkeep
      create  sampleapp/public/javascripts/.gitkeep
      create  sampleapp/public/stylesheets/.gitkeep
      create  sampleapp/db/.gitkeep
      create  sampleapp/spec/features/.gitkeep
      create  sampleapp/spec/controllers/.gitkeep
      create  sampleapp/spec/views/.gitkeep
      create  sampleapp/spec/sampleapp/entities/.gitkeep
      create  sampleapp/spec/sampleapp/repositories/.gitkeep
      create  sampleapp/spec/support/.gitkeep
      create  sampleapp/.gitignore
         run  git init /mypath/sampleapp from "."

Generate an action

$ cd sampleapp
$ bundle exec lotus generate action home#index
      insert  config/routes.rb
      create  spec/controllers/home/index_spec.rb
      create  app/controllers/home/index.rb
      create  app/views/home/index.rb
      create  app/templates/home/index.html.erb
      create  spec/views/home/index_spec.rb

Route is ok

$ cat config/routes.rb
get '/home', to: 'home#index'
# Configure your routes here
# See: http://www.rubydoc.info/gems/lotus-router/#Usage

Error

$ lotus routes
  .rvm/gems/ruby-2.2.2@sampleapp/gems/lotusrb-0.4.0/lib/lotus/container.rb:42:in `assert_configuration_presence!': Lotus::Container doesn't have any application mounted. (ArgumentError)
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/lib/lotus/container.rb:30:in `block in initialize'
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/lib/lotus/container.rb:29:in `synchronize'
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/lib/lotus/container.rb:29:in `initialize'
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/lib/lotus/commands/routes.rb:9:in `new'
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/lib/lotus/commands/routes.rb:9:in `start'
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/lib/lotus/cli.rb:59:in `routes'
  from .rvm/gems/ruby-2.2.2@drpet/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
  from .rvm/gems/ruby-2.2.2@drpet/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
  from .rvm/gems/ruby-2.2.2@drpet/gems/thor-0.19.1/lib/thor.rb:359:in `dispatch'
  from .rvm/gems/ruby-2.2.2@drpet/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
  from .rvm/gems/ruby-2.2.2@drpet/gems/lotusrb-0.4.0/bin/lotus:4:in `<top (required)>'
  from .rvm/gems/ruby-2.2.2@drpet/bin/lotus:23:in `load'
  from .rvm/gems/ruby-2.2.2@drpet/bin/lotus:23:in `<main>'
  from .rvm/gems/ruby-2.2.2@drpet/bin/ruby_executable_hooks:15:in `eval'
  from .rvm/gems/ruby-2.2.2@drpet/bin/ruby_executable_hooks:15:in `<main>'

Automatic root_path helper?

Making a hanami app and defining a route at '/', I expected I'd have access to a routes.root_path helper, but I had to add as: :root in order to get it.

This could that I'm accustomed to Rails, and that hanami prefers to be explicit, but automatically adding a root path for '/' seems like a nice feature :)

Force SSL

Introduce a new option for Lotus::Router constructor: force_ssl. It accepts a boolean. When it's true and the router receives a non-encrypted request (http), it redirects to the secure equivalent resource (https). It should use a 301 HTTP status code.

Routing gotcha - order of mounting

First time lotus user, hope this is in the right place.

First thing that tripped me up was in environment.rb - if you set it up like:

Lotus::Container.configure do
  mount Web::Application, at: '/'
  mount Api::Application, at: '/api/'
end

The /api will never get exposed. If you change it to:

Lotus::Container.configure do
  mount Api::Application, at: '/api/'
  mount Web::Application, at: '/'
end

Then it works as expected.

Without looking into the internals of lotus, I'm guessing that in the first example Web::Application is taking over all routing.

Rubocop

Hi there!
I am look that rubocop not used in this project.
Used settings from hanami repository with --auto-correct get result is:

66 files inspected, 995 offenses detected, 902 offenses corrected

It may be worth adding it?

double slashes in routes when testing with capybara

Hi,

Since I've upgraded from Lotus 0.5.0 to Hanami 0.7.0 I'm getting the failure below for my feature tests:

Expected: "/customers/4/edit"
  Actual: "//customers/4/edit"

I haven't changed my tests, I have the same routes setup and under Lotus 0.5.0 I still don't get these failures.

This is the feature test I have:

it 'updates customer data' do
    create_action.call(create_params)
    create_action.customer.id.wont_be_nil
    visit "/customers/#{customer_id}/edit"
    current_path.must_equal("/customers/#{customer_id}/edit")

    fill_in('customer[vat_id]', with: var)
    click_button('Update customer')
    current_path.must_equal("/customers/#{customer_id}")
    assert page.has_content?(var), "The updated customer vat id should be #{var}."
  end

Here are my routes:

resources :projects
resources :persons
resources :languages
resources :customers
resources :services
get '/', to: 'home#index', as: :home
get '/configuration', to: 'configuration#index', as: :configuration

The current_path from the above block as printed in the terminal is

 //customers/38/edit.

When I go through the app manually and update some data, everything works. I'm going through the code if I've missed something when upgrading, but I can't find anything.

regards,
Seba

Cannot control default reponse

Hi.

We use Hanami Router for our internal JSON microservices. The default response cannot be controlled at the moment from I could get from the structure of

DEFAULT_RESPONSE = [404, {'X-Cascade' => 'pass'}, 'Not Found'].freeze

I tried monkey patching the class and even in that case it won't work. This is problematic in our use case because we can break clients very easily if we fail to deliver JSON responses in every case and also because we could use this to give a more detailed reply hinting a possibly correct route.

We know this can be done with a wildcard get but if we use namespaces we have to put the wildcard at the end of all the routes and at the end of every namespace. Maybe this could be passed if the options when building a router, something like this:

DefaultController = -> env {
   [ 404, {'Content-Type' => 'application/json'}, {error: :not_found}.to_json ]
}
Router = Hanami::Router.new(namespace: Controllers, default: DefaultController) # ....

Does this make sense? This is so far our only issue with Hanami Router, we like it a lot because if easy to begin with and simple to jump from if you already know Rails :)

Router#inspector should return routes from mounted applications

Right now when we create routes directly in the router, the inspector works fine.

puts Lotus::Router.new {
  get '/', to: # ...
}.inspector

When we use it with mounted routers or full stack Lotus apps, it doesn't return the expected output.

puts Lotus::Router.new {
  mount Lotus::Router.new { ... }, at: '/api'
  mount FullStackLotus::Application.new, at: '/'
}.inspector

Hint: Lotus::Application responds to #routes, we should make Lotus::Router to respond to the same method as well. So Inspector can internally do something like.

if route.respond_to?(:routes)
  # we can safely assume it's a mounted Lotus::Router or Lotus application
  route.routes.inspector.to_s
else
  # actual inspection logic
end

Strange routes for two mounted apps

I used a container with two apps:

  • web
  • api

But the routes' behaviour is very strange.

# config/environment.rb
Lotus::Container.configure do
  mount Api::Application, at: '/api'
  mount Web::Application, at: '/'
end

# apps/web/config/routes.rb
get    '/', to: 'pets#index',            as: :root

# apps/api/config/routes.rb
resources :pets, only: :index

I get this results for lotus routes command:

pets GET, HEAD  /api/api/pets                  Api::Controllers::Pets::Index
root GET, HEAD  /                              Web::Controllers::Pets::Index

To get the expected behaviour I changed my source as follows:

# config/environment.rb
Lotus::Container.configure do
  namespace :api do
    mount Api::Application, at: '/'
  end
  mount Web::Application, at: '/'
end

# apps/web/config/routes.rb
get    '/', to: 'pets#index',            as: :root

# apps/api/config/routes.rb
namespace :api do
  resources :pets, only: :index
end

Results:

api_pets GET, HEAD  /api/pets                      Api::Controllers::Pets::Index
    root GET, HEAD  /                              Web::Controllers::Pets::Index

When I consume the http://localhost:2300/api/pets endpoint it works:

curl http://localhost:2300/api/pets
[]

But, when I try to reach http://localhost:2300/ I got a 404 Not found:

curl -I http://localhost:2300/
HTTP/1.1 404 Not Found
Content-Type: application/octet-stream

I found it quite strange or am I doing some mistake?

Issue with app and resource with the same name causing weird behaviour on the inspector

Say we have an app called awards within a container arch mounted at: '/awards'
If we add resources :awards to routes
when running lotus routes the inspector does not output the right thing.

              awards GET, HEAD  /awards                        Awards::Controllers::Awards::Index
           new_award GET, HEAD  /awards/awards/new             Awards::Controllers::Awards::New
              awards POST       /awards                        Awards::Controllers::Awards::Create
               award GET, HEAD  /awards/awards/:id             Awards::Controllers::Awards::Show
          edit_award GET, HEAD  /awards/awards/:id/edit        Awards::Controllers::Awards::Edit
               award PATCH      /awards/awards/:id             Awards::Controllers::Awards::Update
               award DELETE     /awards/awards/:id             Awards::Controllers::Awards::Destroy

when it should be:

              awards GET, HEAD  /awards/awards                 Awards::Controllers::Awards::Index
           new_award GET, HEAD  /awards/awards/new             Awards::Controllers::Awards::New
              awards POST       /awards/awards                 Awards::Controllers::Awards::Create
               award GET, HEAD  /awards/awards/:id             Awards::Controllers::Awards::Show
          edit_award GET, HEAD  /awards/awards/:id/edit        Awards::Controllers::Awards::Edit
               award PATCH      /awards/awards/:id             Awards::Controllers::Awards::Update
               award DELETE     /awards/awards/:id             Awards::Controllers::Awards::Destroy

Here's a setup with the issue and a failing test.
https://github.com/theocodes/routertest

Note: Using master

Thanks

hanami routes fails when Sinatra app mounted in container

It seems in the scenario where you have a Container architecture hanami app and have a Sinatra app mounted it blows up with an exception because the inspector assumes a Hanami::Router instance. I discovered this because I was mounting Sidekiq's web interface into my hanami container application as follows:

Hanami::Container.configure do
  mount Sidekiq::Web, at: '/sidekiq'
end

The following is the traceback. I believe it would potentially be as simple as checking if the router responds to inspector and if it doesn't ignore it.

/Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-router-0.6.2/lib/hanami/routing/routes_inspector.rb:213:in `inspect_router': undefined method `inspector' for #<Hash:0x007ff13b607780> (NoMethodError)
Did you mean?  inspect
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-router-0.6.2/lib/hanami/routing/routes_inspector.rb:163:in `block in inspect_routes'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-router-0.6.2/lib/hanami/routing/routes_inspector.rb:161:in `each'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-router-0.6.2/lib/hanami/routing/routes_inspector.rb:161:in `inspect_routes'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-router-0.6.2/lib/hanami/routing/routes_inspector.rb:139:in `to_s'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-0.7.2/lib/hanami/commands/routes.rb:25:in `start'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-0.7.2/lib/hanami/cli.rb:103:in `routes'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.19.1/lib/thor.rb:359:in `dispatch'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
    from /Users/adeponte/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/hanami-0.7.2/bin/hanami:5:in `<top (required)>'
    from /Users/adeponte/.rbenv/versions/2.3.0/bin/hanami:23:in `load'
    from /Users/adeponte/.rbenv/versions/2.3.0/bin/hanami:23:in `<main>'

Fails using mount with an app in lotus router

The following example shows a simple config about lotus router, two cases:

This scenario mount an app with it's routes

require 'lotus/router'

class AdminLotusApp
  def call(env)
  end

  def routes
    Lotus::Router.new do
      get '/home', to: 'home#index', as: 'admin_home'
    end
  end
end

@router = Lotus::Router.new do
  mount AdminLotusApp, at: '/admin'
end

puts @router.inspector
# => admin_home GET, HEAD  /admin/home                    Home::Index

@router.path(:admin_home) 
# => HttpRouter::InvalidRouteException: No route (path) could be generated for :admin_home

This scenario is the normal case.

require 'lotus/router'

@router = Lotus::Router.new do
  get '/home', to: 'home#index', as: 'home'
end

puts @router.inspector
# => home GET, HEAD  /home                          Home::Index 

@router.path(:home) # => works

Possible to have namespace with variable?

I have something like this:

Hanami::Router.new do
  get "/:id", id: /[0-9a-f]{32}/, to: "stuff#show"
  get "/:id/foos", id: /[0-9a-f]{32}/, to: "foos#index"
  get "/:id/bars", id: /[0-9a-f]{32}/, to: "bars#index"
  post "/:id/bars/", id: /[0-9a-f]{32}/, to: "bars#create"
end

Is it possible to somehow wrap all routes that starts with /:id into a block. Something like this:

Hanami::Router.new do
  namespace "/:id", id: /[0-9a-f]{32}/ do
    get "/", to: "stuff#show"
    get "/foos", to: "foos#index"
    get "/bars", to: "bars#index"
    post "/bars/", to: "bars#create"
  end
end

Thanks.

Multiple endpoints to the same segment with different http verbs

Hi,

Given this two routes:

get '/oauth', to: 'oauth#init'
post '/oauth', to: 'oauth#init'

$ lotus routes shows that:

                     GET, HEAD  /                              Web::Controllers::Home::Index
                     POST       /                              Web::Controllers::Home::Index

But when the last route is used, a 405 is returned.

More details:

2.2.1 :001 > router = Web::Application.new.routes.instance_variable_get(:@router)
 => #<HttpRouter:0x3ff1918f056c number of routes (2) ignore_trailing_slash? (true) redirect_trailing_slash? (false)>
============================================================================
Root (1 matchers)
 HttpRouter::Node::Lookup
 when "oauth":
   RequestMethod ["GET", "HEAD"] (1 matchers)
    Path: "/oauth" for route unnamed route to Web::Controllers::Oauth::Init
   RequestMethod ["POST"] (1 matchers)
    Path: "/oauth" for route unnamed route to Web::Controllers::Oauth::Init
2.2.1 :002 > result = router.recognize(Rack::MockRequest.env_for('/oauth', method: :post))
 => [[#<struct HttpRouter::Response request=#<HttpRouter::Request:0x007fe3233daf50 @rack_request=#<Rack::Request:0x007fe3233daf78 @env={"rack.version"=>[1, 3], "rack.input"=>#<StringIO:0x007fe3233db090>, "rack.errors"=>#<StringIO:0x007fe3233db130>, "rack.multithread"=>true, "rack.multiprocess"=>true, "rack.run_once"=>false, "REQUEST_METHOD"=>"POST", "SERVER_NAME"=>"example.org", "SERVER_PORT"=>"80", "QUERY_STRING"=>"", "PATH_INFO"=>"/oauth", "rack.url_scheme"=>"http", "HTTPS"=>"off", "SCRIPT_NAME"=>"", "CONTENT_LENGTH"=>"0"}>, @path=["oauth"], @extra_env={}, @params=[], @acceptable_methods=#<Set: {"GET", "HEAD", "POST"}>, @called=true>, path=    Path: "/oauth" for route unnamed route to Web::Controllers::Oauth::Init>], #<Set: {"GET", "HEAD", "POST"}>]
2.2.1 :003 > result.last.inspect
 => "#<Set: {\"GET\", \"HEAD\", \"POST\"}>"

Non-Standard Port Missing When `router.url` Called

When calling router.url when running on a non-standard port, the port number is not included in the URL.

Example Code

require 'lotus/router'

app = Lotus::Router.new do
  get '/', to: ->(env) { [200, {}, [app.url(:home)]] }, as: :home
end

Rack::Server.start app: app, Port: 2300

If you then visit http://localhost:2300/ it returns http://localhost/

It would be nice if Lotus router checked the port against the protocol and included the port in the URL if it was not the standard for the protocol.

Assigned but unused variables

When running tests in router and in Hanami, there are two warnings for assigned but unused variables.

/.rvm/gems/ruby-2.3.1/gems/http_router-0.11.2/lib/http_router/generator.rb:58: warning: assigned but unused variable - generator

  • This may be here for clarification purposes

and

/.rvm/gems/ruby-2.3.1/gems/http_router-0.11.2/lib/http_router/route_helper.rb:99: warning: assigned but unused variable - params

  • Does not appear this variable is used at all in the method, could be removed?

Could definitely easily fix this if there is no reasoning for having the variables. Let me know!

Setting SCRIPT_NAME environment variable when mounting Rack apps

When I setup Sidekiq with Hanami I ran into an issue when mounting the Sidekiq web interface within an applications route or the container. All URLs for links and assets in Sidekiq were broken because they pointed to the root of my Hanami application even if I mounted it at /sidekiq for example.

Turns out Sidekiq uses SCRIPT_NAME to build its URLs and it seems that Hanami only sets it for Hanami apps and not Rack apps in general.

Reading the Rack specification I think Hanami should set SCRIPT_NAME accordingly. Rack::URLMap does it too for example.

The initial portion of the request URL's “path” that corresponds to the application object, so that the application knows its virtual “location”. This may be an empty string, if the application corresponds to the “root” of the server.

What do you think?

How would I inject dependencies into controllers (or anything else)?

Is there a technique I'm unaware of for injecting dependencies into the controllers. Or with lotus/lotus I might want to inject dependencies into view classes as well, can't figure out how I would go about that either.

I was looking through endpoint_resolver.rb and it occurred to me that with the way it makes instances of the action class, I don't have a clear way to inject dependencies into them. Maybe you know a way? Please tell me.

Let's say I have an action, and I want it to be set with an instance of the redcarpet markdown parser and not have to make an instance of it in line. (Because injecting dependencies makes things easier to test and more loosely coupled).

require 'redcarpet'

module Blog
  module Controllers
    module Posts
      class Show
        include Lotus::Action
        expose :markup

        attr_writer :markdown

        def call(params)
          contents = File.open("posts/#{params[:slug]}.md") { |f| f.read }
          @markup = @markdown.render(contents)
        end
      end
  end
end

With that markdown as a settable, easily mocked dependency this class is easier to test and has the added benefit of not being coupled to the HTML renderer, it could have been set with any type. As you can see the action below only renders HTML, but the one above doesn't have that constraint.

def call(params)
  markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML);
  contents = File.open("posts/#{params[:slug]}.md") { |f| f.read }
  @markup = markdown.render(contents)
end

Unrecognized route causes a NoMethodError

  1) API::V2 routing POST [REDACTED]
     Failure/Error: router.__send__(:api_from_registry, actual.action) ==

     NoMethodError:
       undefined method `__getobj__' for nil:NilClass
     # /Users/luca/.gem/ruby/2.3.1/gems/hanami-router-0.6.2/lib/hanami/routing/recognized_route.rb:143:in `destination'
     # /Users/luca/.gem/ruby/2.3.1/gems/hanami-router-0.6.2/lib/hanami/routing/recognized_route.rb:104:in `action'
     # [REDACTED]:318:in `block (2 levels) in <module:Matchers>'
     # [REDACTED]:669:in `block (3 levels) in <top (required)>'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/lib/bundler/cli/exec.rb:63:in `load'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/lib/bundler/cli/exec.rb:63:in `kernel_load'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/lib/bundler/cli/exec.rb:24:in `run'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/lib/bundler/cli.rb:304:in `exec'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/lib/bundler/cli.rb:11:in `start'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/exe/bundle:27:in `block in <top (required)>'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/lib/bundler/friendly_errors.rb:98:in `with_friendly_errors'
     # /Users/luca/.gem/ruby/2.3.1/gems/bundler-1.12.5/exe/bundle:19:in `<top (required)>'

URI.escape is obsolete

Hanami::Routing::HttpRouter#rewrite_partial_path_info is using URI.escape, which is deprecated. We need to update the implementation of that method.

Implicit dependency: utils

I don't know if it's expected to behave like this, but the following script

require 'rubygems'
require 'lotus/router'

Lotus::Router.draw do
  get '/', to: ->(env) { [200, {}, ['Welcome to Lotus::Router!']] }
end

fails because it can't load lotus/utils/string. Nowhere in the gemspec router depends from utils though.

Documention/Implementation of handling router errors

Hi, I am trying to user your router in a stand alone app (without the Hanami framework) and I am wondering if there is any documentation around how to handle errors before the request gets to a controller. For instance, how should I handle parsing errors?

Lets say I have this router implementation:

$router = Hanami::Router.new(parsers: [:json]) do
  patch '/books', to: ->(env) { [200, {},[env['router.params'].inspect]] }
end

And in my config.ru I have something like this:

map "/" do
  run $router
end

If I pass invalid JSON to /books, a Hanami::Routing::Parsing::BodyParsingError will be raised. But, if I want to handle that in a custom way, how would I implement that?

New release

Hi. When you plan make release with new changes? I really need some router functional in main lotus repo =) Maybe I can provide some help to do this?

Class endpoints causing problem with cookies in Hanami container app

This may or may not be problem with the Hanami Router but since I have isolated this part as the trigger for the problem when testing I am opening the issue here but if could turn out to be an issue within Hanami Controller.

The Problem

While developing a Hanami application (in the container architecture) I experienced a strange issue in production where the contents of a cookie would only be set once until ruby server was restarted.

What this means is the first request after the server was started would get the cookie set/contents updated, but any subsequent requests to update or set that cookie (from any client) would do nothing. Restarting the server only repeated the same behaviour over again.

After much tinkering the cause of this appears to be related to specifying the route endpoint as a class instead of a string.

get '/', to: 'homepage#index' # This works
get '/', to: ->(env) { Web::Controllers::Homepage::Index.new.call(env) } # This also appears to work

get '/', to: Web::Controllers::Homepage::Index # This does not work
get '/', to: Web::Controllers::Homepage::Index.new # This does not work

An important factor when testing this is --no-code-reloading.

I've created a repo to help others to experience the issue createdbypete/hanami_cookie_issue.

Observations

While investigating this issue I noticed a couple of things that may or may not be significant.

  • Endpoint loading differences:
    • The string endpoint, being lazy, is initialized when the route is called so a puts in the #initialize method only shows up when the endpoint is used. This all seems correct.
    • Class endpoints appear to call #initialize when the application loaded (again noticed with some puts debugging) perhaps these could also be lazy like string? The curious thing is however that this puts value is output twice when the app loads.
    • Instance endpoints (of course) are initialized when the app loads but this also appears to happen twice according to the puts output. I'm not sure why this would be called twice.
  • When debugging the cookie being set I was checking out the headers before and after setting the cookie (Hanami::Action::CookieJar) with:
def set_cookie(key, value)
  puts "Before: #{@_headers}"
  ::Rack::Utils.set_cookie_header!(@_headers, key, value)
  puts "After: #{@_headers}"
end

Running in my test repo (above) the output is as follows:

$ bundle exec hanami server --no-code-reloading
[2016-10-09 15:27:29] INFO  WEBrick 1.3.1
[2016-10-09 15:27:29] INFO  ruby 2.3.1 (2016-04-26) [x86_64-darwin16]
[2016-10-09 15:27:29] INFO  WEBrick::HTTPServer#start: pid=41491 port=2300
Before: {"Location"=>"/", "Content-Type"=>"text/html; charset=utf-8"}
After: {"Location"=>"/", "Content-Type"=>"text/html; charset=utf-8", "Set-Cookie"=>"foo=2016-10-09+15%3A27%3A31+%2B1100; HttpOnly"}
127.0.0.1 - - [09/Oct/2016:15:27:31 +1100] "GET  HTTP/1.1" 302 - 0.0221
127.0.0.1 - - [09/Oct/2016:15:27:31 +1100] "GET  HTTP/1.1" 200 - 0.0123
Before: {"Location"=>"/", "Content-Type"=>"text/html; charset=utf-8", "Set-Cookie"=>"foo=2016-10-09+15%3A27%3A31+%2B1100; HttpOnly"}
After: {"Location"=>"/", "Content-Type"=>"text/html; charset=utf-8", "Set-Cookie"=>"foo=2016-10-09+15%3A27%3A31+%2B1100; HttpOnly\nfoo=2016-10-09+15%3A27%3A38+%2B1100; HttpOnly"}
127.0.0.1 - - [09/Oct/2016:15:27:38 +1100] "GET  HTTP/1.1" 302 - 0.0047
127.0.0.1 - - [09/Oct/2016:15:27:38 +1100] "GET  HTTP/1.1" 200 - 0.0048

Overall it's all expected output until the last After: where the cookie is repeated in the Set-Cookie value. It has the first value set and then following it the value we actually want to set:

"Set-Cookie"=>"foo=2016-10-09+15%3A27%3A31+%2B1100; HttpOnly\nfoo=2016-10-09+15%3A27%3A38+%2B1100; HttpOnly"

Perhaps the most interesting part is when opening another browser and trying to set the cookie we see the following output:

127.0.0.1 - - [09/Oct/2016:15:27:59 +1100] "GET  HTTP/1.1" 200 - 0.0054
Before: {"Location"=>"/", "Content-Type"=>"text/html; charset=utf-8", "Set-Cookie"=>"foo=2016-10-09+15%3A27%3A31+%2B1100; HttpOnly\nfoo=2016-10-09+15%3A27%3A38+%2B1100; HttpOnly"}
After: {"Location"=>"/", "Content-Type"=>"text/html; charset=utf-8", "Set-Cookie"=>"foo=2016-10-09+15%3A27%3A31+%2B1100; HttpOnly\nfoo=2016-10-09+15%3A27%3A38+%2B1100; HttpOnly\nfoo=2016-10-09+15%3A28%3A05+%2B1100; HttpOnly"}
127.0.0.1 - - [09/Oct/2016:15:28:05 +1100] "GET  HTTP/1.1" 302 - 0.0038
127.0.0.1 - - [09/Oct/2016:15:28:05 +1100] "GET  HTTP/1.1" 200 - 0.0075

As you can see the foo cookie now appears 3 times under the Set-Cookie key. Each request adds one more reference.

Allow configuration of default routes for `resource`/`resources`

While I think the current set of routes generated by resources is a reasonable default, the inability to customize, or reuse chunks of routes is somewhat cumbersome when an app deviates from that particular set of routes.

For instance, http://jsonapi.org/ requires supporting PUT for updates, and allows APIs to optionally support PATCH (with a very specific format). When building an API that conforms to that specification (and the same goes for many other specs), it is a pain to have to be specifying things like:

resources 'widgets' do
  member do
    put '', to: 'widgets#update'
  end
end

It is also very visually noisy. It'd be nice if either the set of routes that resources creates were configurable, or if it were possible to simply configure extra methods that behaved similarly to resources that define a different set of routes, e.g.

json_api_resources 'widgets'

Or perhaps something that behaves a bit like Rails's routing concerns, e.g.

resources :widgets do
  concerns :json_api_resource # adds in the handful of extra routes
end

My favorite is probably allowing resources to be configured, or the resources like method approach. I'd be happy to put together a PR for one of them, especially if it can be slipped in before support for < ruby 2.2 is dropped (as I'm likely stuck using 2.1 for several months).

Wrong generation of name for named routes.

Hey all 👍

Just picked up this one today..

when doing something like this in the routes.rb file:

resources :horses

will result in this:

hors GET, HEAD  /horses                        Routertest::Controllers::Horses::Index
new_hors GET, HEAD  /horses/new                    Routertest::Controllers::Horses::New
hors POST       /horses                        Routertest::Controllers::Horses::Create
hors GET, HEAD  /horses/:id                    Routertest::Controllers::Horses::Show
edit_hors GET, HEAD  /horses/:id/edit               Routertest::Controllers::Horses::Edit
hors PATCH      /horses/:id                    Routertest::Controllers::Horses::Update
hors DELETE     /horses/:id                    Routertest::Controllers::Horses::Destroy

Thinking it was something related to it ending in 'es' (which i'm still not 100% convinced it isn't) i tried other words such as :moustaches and :stones and they worked fine. Intriguing huh? ;-)

This is on 0.5.0 and has been around for quite sometime as far as I can tell.. I know that :horses is not the most common name in the world for a resource and this may very well be a super-duper edge case.. But it shouldn't happen anyways ;-)

I'll try to look into it in the next few days and report back or send PR this way.

Cheers

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.