hanami / controller Goto Github PK
View Code? Open in Web Editor NEWComplete, fast and testable actions for Rack and Hanami
Home Page: http://hanamirb.org
License: MIT License
Complete, fast and testable actions for Rack and Hanami
Home Page: http://hanamirb.org
License: MIT License
I'm using lotus-controller 0.2.0
A standalone controller/action is returning a valid rack response:
require 'lotus/controller'
module MyApp
class MyAction
include Lotus::Action
def call(params)
self.body = 'hello'
end
end
end
# MyApp::MyAction.call({}) => [200, {"Content-Type"=>"application/octet-stream"}, ["hello"]]
However, if I inherit from a base class, an invalid response is being generated
require 'lotus/controller'
module MyApp
class BaseAction
include Lotus::Action
end
end
module MyApp
class MyAction < BaseAction
def call(params)
self.body = 'hello'
end
end
end
# MyApp::MyAction.call({}) => "hello"
Bug?
My original intention is to add a few simple authentication before filters to the base class so that they can be inherited by all subclasses.. but it doesn't look like inheritance is working.
We should deprecate the following API:
class HomeController
include Lotus::Controller
action 'Index' do
# ...
end
end
In favor of:
module Controllers
module Home
class Index
include Lotus::Action
end
end
end
The reasons are:
[1]: We have a convention in Lotus full stack application, that wants actions under a certain namespace: <Application>::Controllers::<Controller>::<Action>
. Example: Bookshelf::Controllers::Books::Index
. The natural way to have namespaces in Ruby is via modules, for this reason, I want controllers to be modules too, not classes.
After a discussion with @troyk (see #4), we've noticed that the exception handling isn't compliant to the Rack spec.
Actually, when an exception is raised, it's swallowed, and the framework returns a generic 500 status. While Lotus is able to show the stack-trace, using Rack::ShowExceptions, Controller should be able to fill rack.errors
with the name of the exception and the message.
Right now, the params
passed to #call
are an instance of Lotus::Action::Params
which is a nice wrapper around a Ruby Hash.
I would love to pass subclasses of Params
which are aware of the specific use case and they should be able to do:
#call
.This approach has the following advantages:
We probably need to add this interface:
class Lotus::Action::Params
def self.param(name, type, options = {})
# ...
end
def initialize(params)
@params = params
end
def validate!
halt 412 unless valid?
end
def valid?
# ...
end
end
And implement one of those solutions:
class Signup
include Lotus::Action
params SignupParams
def call(params)
puts params.class # => SignupParams
end
end
class SignupParams < Lotus::Action::Params
param :first_name, String, presence: true
param :last_name, String, presence: true
param :email, String, presence: true, format: /(.*)/
end
Where:
def self.params(params_class)
before do |params|
params_class.new(params).validate!
end
end
class Signup
include Lotus::Action
params do
param :first_name, String, presence: true
param :last_name, String, presence: true
param :email, String, presence: true, format: /(.*)/
end
def call(params)
puts params.class # => Signup::Params
end
end
Where .params
would generate an inner class of the action Signup::Params
, which inherits from Lotus::Action::Params
.
Currently the response format does not follow default_format
.
Maybe it should use default_format
for response also and the user should have to override if they want something other then the default?
For example, param :id, Integer
Coercing user input into an expected type is a good practice. Without specifying a type, based on what the parser in rack gives you, you could end up with a string, array, hash, nil or a tempfile. It would be great if lotus made developers think about this and provided guarantees that the parameter would be either the given type or nil.
Rails has had vulnerabilities in the past that would have been non-issues if the input was coerced into an expected type before passing it along to ActiveRecord [1].
Thoughts?
[1] https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/t1WFuuQyavI
Given the following action
class Show
include Api::V2::Action
handle_exception RecordNotFound => :handle_record_not_found
def call(params)
# ...
end
def handle_record_not_found(exception)
halt(404, { message: exception.message }.to_json)
end
end
An HTTP request for an invalid resource will correctly render the error message
{"message":"Domain `3999' not found"}
However, the exception is also dumped in the log
ActiveRecord::RecordNotFound: Domain `3999' not found
...
...
...
/Users/weppos/.rvm/gems/ruby-2.1.5@dnsimple/gems/lotus-controller-0.3.1/lib/lotus/action/callbacks.rb:149:in `call'
/Users/weppos/.rvm/gems/ruby-2.1.5@dnsimple/gems/lotus-controller-0.3.1/lib/lotus/action/callable.rb:73:in `block in call'
/Users/weppos/.rvm/gems/ruby-2.1.5@dnsimple/gems/lotus-controller-0.3.1/lib/lotus/action/throwable.rb:104:in `block in _rescue'
/Users/weppos/.rvm/gems/ruby-2.1.5@dnsimple/gems/lotus-controller-0.3.1/lib/lotus/action/throwable.rb:102:in `catch'
/Users/weppos/.rvm/gems/ruby-2.1.5@dnsimple/gems/lotus-controller-0.3.1/lib/lotus/action/throwable.rb:102:in `_rescue'
making it hard to distinguish handled vs not-handled exceptions. That's because _rescue
always dumps the error, no matter if it was handled or not.
def _rescue
catch :halt do
begin
yield
rescue => exception
_reference_in_rack_errors(exception)
_handle_exception(exception)
end
end
end
My proposal is to dump the exception only if not handled. Though?
Given the following configuration
Lotus::Controller.configure do
handle_exception ActiveRecord::RecordNotFound => :handle_record_not_found
end
and the following handler
def handle_record_not_found(exception)
halt(404, { message: exception.message }.to_json)
end
If I define the handler in the action, the code is working as expected. If I define the action in the configure block
Lotus::Controller.configure do
handle_exception ActiveRecord::RecordNotFound => :handle_record_not_found
def handle_record_not_found(exception)
halt(404, { message: exception.message }.to_json)
end
end
the code crashes with error
Puma caught this error: undefined method `to_i' for :handle_record_not_found:Symbol (NoMethodError)
Is this expected? Is there any different between the error handling in the action, and in the configuration?
I've create really simple Lotus app (https://gist.github.com/nilcolor/21ead88d1517e824f103). And I'd like to receive JSON data (via POST's body). But... controller receives request, responds with OK but params are empty... (not really empty - it's full Rack params hash but without data I need).
If params are posted in URL - they are readable via params
arg. Even if action is accept :json
btw
Hey, working on a nice example project with Hanami and Trailblazer. It's great fun. ❤️ I have two things, though, I'd love to discuss when using Cells with Hanami.
We use Cells as a replacement for Hanami::View
, we simply render the cell in the `Action.
class Create
include Hanami::Action
def call(params)
# whatever
self.body = Some::Cell.(bla, routes: routes).() # note the :routes injection.
end
end
As you can see, the cell's returned markup is assigned via Action#body=
and done. It works great so far.
In the cell, you might want to use Hanami's route helpers. This I do by injecting routes
from the controller into the cell (see above), then in the cell or its view I call something along routes.sheets_path
and it works.
My first question now is: Can we access the routes
object without having to have a controller? This is especially helpful in tests, where we currently need a controller to get a route object.
it do
controller = My::Controller.new
html = Some::Cell.(bla,
routes: controller.routes).() # can we do without controller somehow here?
end
The other problem is: how can we access the asset helpers such as javascript_tag
without including a helper module into the cell? I would love to have a helpers
object here, so the cell doesn't get polluted with Hanami helpers.
Currently, I have the following hack.
class Create
include Hanami::Action
includer.send :include, Hanami::Assets::Helpers # this might not be how it's intended.
def call(params)
# whatever
self.body = Some::Cell.(bla, routes: routes, helpers: self).() # note :helers injection.
end
end
With this hack, in the cell, we can do helpers.javascript_tag
and it works out nicely.
Again, in the test, this requires a controller.
My goal is to have objects that provide helper methods as opposed to mixing them into the cell "The Rails Way™". Even if I include
the Assets::Helpers
into the cell, it complains about a missing configuration
.
This must be because of Hanami's global configuration system which I really do not wanna touch with Cells.
Any ideas how we could provide those two objects without a controller? 🍻
For now it's only verified with Rack <= 1.5, where we apply lib/rack-patch.rb
.
# input
"text/html,application/xhtml+xml,application/xml;q=0.9"
# weighted input
[["text/html", 1.0], ["application/xhtml+xml", 1.0], ["application/xml", 0.9], ["application/vnd.lotus-1-2-3", 0.8]]
# expected intermediary computation
[["application/vnd.lotus-1-2-3", 0.8], ["application/xml", 0.9], ["application/xhtml+xml", 1.0], ["text/html", 1.0]]
# actual intermediary computation on the affected platform
[["application/vnd.lotus-1-2-3", 0.8], ["application/xml", 0.9], ["text/html", 1.0], ["application/xhtml+xml", 1.0]]
Current hypotesis is about a bug of Array#sort_by
.
EDIT:
# array_sort_by.rb
input = [["text/html", 1.0], ["application/xhtml+xml", 1.0], ["application/xml", 0.9], ["application/vnd.lotus-1-2-3", 0.8]]
output = input.sort_by do |_, weight|
weight
end
puts output.inspect
$ ruby -v
ruby 2.0.0p451 (2014-02-24) [x86_64-linux]
$ ruby array_sort_by.rb
[["application/vnd.lotus-1-2-3", 0.8], ["application/xml", 0.9], ["application/xhtml+xml", 1.0], ["text/html", 1.0]]
$ ruby -v
ruby 2.2.0preview1 (2014-09-17 trunk 47616) [x86_64-linux]
$ ruby array_sort_by.rb
[["application/vnd.lotus-1-2-3", 0.8], ["application/xml", 0.9], ["text/html", 1.0], ["application/xhtml+xml", 1.0]]
$ ruby -v
ruby 2.2.0preview2 (2014-11-28 trunk 48628) [x86_64-linux]
$ ruby array_sort_by.rb
[["application/vnd.lotus-1-2-3", 0.8], ["application/xml", 0.9], ["text/html", 1.0], ["application/xhtml+xml", 1.0]]
It maybe handy to share behaviors across actions belonging to the same controller.
module MyApp::Controllers
module Articles
include MyApp::Controller
share do
before { :authenticate! }
end
action 'Index' do
# it will call #authenticate! before of #call
def call(params)
end
end
action 'Show' do
# it will call #authenticate! before of #call
def call(params)
end
end
end
Example:
module Projects
class Show
include Hanami::Action
configuration.handle_exceptions true
handle_exception ActiveRecord::RecordNotFound => 404
before :find_account!
def call(params)
@account.projects.find(params[:id])
end
private
def find_account!
@account = Account.find(params[:account_id])
end
end
end
When this action is called, instead of handle the ActiveRecord::RecordNotFound
exception as a 404
, it blows up. Probably this happens because callbacks are executed outside of the _rescue
block, which is responsible of exceptions handling.
When writing code inside an Action
, I miss having access to a request
method that returns a Rack::Request
.
Someting like this:
def request
@request ||= Rack::Request.new(params.env)
end
I think this could be useful in general, and not just for me. Anyone have an oppinion on this?
And where should it be implemented if so? The most logical place IMO would be in Params
as the Params
class already knows about env
.
#request
method which returns a Lotus::Action::Request
instance#language
and #encoding
to Lotus::Action::Rack
.Granted that one of @jodosha's goal was to keep callbacks as simple as possible, I have a case that I feel reveal a reasonable improvement.
Let's assume I have a filter called authenticate!
that must be run on every action.
class Show
before :authenticate!
def call(params)
end
end
On a very few actions (that's one of the key conditions), let's say 10 out of 100, I need a specific validation to happen before the authenticate.
class Index
before :validate!, :authenticate!
def call(params)
end
end
I can indeed stick with the explicit example above, but that would mean I can't extract the before :authenticate!
into a controller config, or a module.
Therefore, I wonder what do you think about providing a finer way to control where I want to add my filter. Right now, there's only a Chain#add
method.
I propose to add something similar to
Chain#append(callback)
Chain#prepend(callback)
Chain#insert(before, callback)
(or Chain#insert_before(before, callback)
and Chain#insert_after(after, callback)
)I can work on a patch, if we agree on naming and API.
The Lotus philosophy for application boot is: load all the code at the beginning, then freeze the parts that may suffer of accidental changes and lead to software defects.
This MUST include:
Lotus::Controller.configuration
All the configurations of the single controllers (Not required anymore because of #49)
All the configurations of the single actions (Postponed for now)
This process should be triggered by a single method.
Add a send_file
method to actions, in order to send files to HTTP clients.
Quick and dirty implementation available at: https://github.com/lotus/controller/tree/send-file
This issue started as a request for documentation that flash
is a reserved word when using Hanami::Action::Session
: hanami/hanami#595
Instead of just documenting it, we should help the developer by raising an exception when they try to use any reserved word.
One reserved word is flash
but we should make sure all reserved words are covered.
(Thanks for the original issue @rafaels88)
This may not be the proper channel for this discussion. If not, please let me know and I'll make sure to post accordingly in the future.
I was curious if there was room for discussion, or had already been any discussion, about integrating an authentication system into Hanami. I've seen this in the PHP framework Laravel and it is always something that has interested me. Providing a solution for authentication out of the box. This is a problem solved in Rails by using Devise. Devise is great, but yet another dependency.
I know that being lightweight is one of Hanami's strong points, so I'm not sure if this would be too much, but I'd love to talk more about it. If not, no worries 😄
Hello everyone.
I'm having an issue with flash session. I'm setting the flash session and then using a redirect_to.
In the next request I'm expecting to get the value but is returning nil.
def call(params)
@organization = Organization.new(params[:organization])
if @repository.persist(@organization)
flash[:success] = "Organization created"
redirect_to routes.path(:manager_organizations)
else
flash[:errors] = @errors = @organization.errors
end
end
In the view:
- if flash[:success]
= flash[:success]
The code is working when the flash is used in the same request. (Using the flash[:errors] in this case.)
Looks like the attributes
method is not defined at all in params.rb
:
Here is the stacktrace:
NameError: undefined local variable or method `attributes' for Janus::Controllers::Home::Index::Params:Class
/Users/kir/Projects/lotus-controller/lib/lotus/action/params.rb:140:in `whitelisting?'
/Users/kir/Projects/lotus-controller/lib/lotus/action/params.rb:163:in `_whitelist'
/Users/kir/Projects/lotus-controller/lib/lotus/action/params.rb:145:in `_compute_params'
/Users/kir/Projects/lotus-controller/lib/lotus/action/params.rb:105:in `initialize'
/Users/kir/Projects/lotus-controller/lib/lotus/action/callable.rb:72:in `new'
/Users/kir/Projects/lotus-controller/lib/lotus/action/callable.rb:72:in `block in call'
/Users/kir/Projects/lotus-controller/lib/lotus/action/throwable.rb:100:in `block in _rescue'
/Users/kir/Projects/lotus-controller/lib/lotus/action/throwable.rb:98:in `catch'
/Users/kir/Projects/lotus-controller/lib/lotus/action/throwable.rb:98:in `_rescue'
/Users/kir/Projects/lotus-controller/lib/lotus/action/callable.rb:69:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/lotus-router-0.1.1/lib/lotus/routing/endpoint.rb:66:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/http_router-0.11.1/lib/http_router.rb:193:in `process_destination_path'
(eval):18:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/http_router-0.11.1/lib/http_router.rb:288:in `raw_call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/http_router-0.11.1/lib/http_router.rb:142:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/lotus-router-0.1.1/lib/lotus/router.rb:797:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/static.rb:119:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/builder.rb:138:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/bundler/gems/lotus-8d51feebbc85/lib/lotus/middleware.rb:57:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/bundler/gems/lotus-8d51feebbc85/lib/lotus/application.rb:152:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/session/abstract/id.rb:225:in `context'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/session/abstract/id.rb:220:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/lint.rb:49:in `_call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/lint.rb:37:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/showexceptions.rb:24:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/commonlogger.rb:33:in `call'
/Users/kir/.rbenv/versions/2.0.0-p353/lib/ruby/gems/2.0.0/gems/rack-1.5.2/lib/rack/chunked.rb:43:in `call'
And the action:
module Home
class Index
include Janus::Action
include Janus::Action::Session
def call(params)
puts params
end
end
end
Currently when you set up your lotus app to handle exceptions, it returns the status code and a default page for said error is rendered by the web server (apache/nginx/etc). For production apps you definitely need custom 404/500 pages. When in doubt I often reach for Rack Rescue but it seems to have a bit of overlap with some of Lotus functionality. I'd also like to be able to serve different 404 pages depending on the app/context. Is this doable, and if not is it planned for immediate future?
Cheers
The current implementation of Lotus::Action::Rack.use
doesn't support passing the app to the middleware. To allow intercepting of requests and responses, Rack middleware is usually implemented like this:
class Middleware
def initialize(app)
@app = app
end
def call(env)
@app.call(env)
end
end
The current middleware implementation just passes the env
to the middleware's call
method and therefore doesn't work with middleware that might stop the execution of the middleware stack or modify the request or the response.
A reference implementation of Rack middleware can be found in Rack itself:
Thanks a lot!
Hi,
I've been trying to get the errors of the previous action after a redirect (which I've seen it's possible according to lotus/controller code) but I can't. I've also been searching for examples on how to do this right but I found nothing.
I have this on my controller config:
cookies true
sessions :cookie, secret: "foo"
On my action after the redirect I have this:
def call(params)
puts errors.inspect
end
The first time I submit my form everything is ok, I get the errors on the action after the redirect. But if I submit the form more times I get the same errors as the first time no matter what the input contents are.
I've also noticed that two cookies "rack.session" are set. One with the path "/" and one with the path "/foo" where /foo is the endpoint of the form action. Maybe it has something to do with that.
When having cookies
and sessions
enabled, then the set-cookie header for rack.session is sent twice, once with and once without path.
This leads to confusing state when modifying session content.
Steps to reproduce:
# application.rb: enable cookies AND sessions
...
cookies true
sessions :cookie, secret: 'verrrrrry-secret'
..
# routes.rb
get '/', to: 'home#index'
# web/controllers/home/index.rb
module Web::Controllers::Home
class Index
include Web::Action
def call(params)
self.body = 'Welcome'
end
end
end
Set-Cookie:rack.session=BLA...BLA; HttpOnly
Set-Cookie:rack.session=BLA...BLA; path=/; HttpOnly
First cookie is from cookies
second one from sessions
Problem can be "fixed" by setting the path of cookies
to the same value as the path of sessions
but still two headers with same content are sent back to the browser.
Reported by @nickmorlan in chat.
The problem is caused by session.rb
, that defines errors
as a private method.
Here's a stack trace:
https://gist.github.com/nickmorlan/4ce60b1632f115152289
I noticed that exposures
doesn't get reset between requests. If the action object is the same between requests, then exposures being cached via ||=
will persist between requests as well. In my app, I had to set @exposures = nil
so that the next time I called exposures
, it would reload the current state. An example:
class Show
include Lotus::Action
expose :thing
def call(params)
@thing = params[:asdf]
end
end
show = Show.new
show.call({asdf: 1})
show.exposures.fetch(:thing) #=> 1
show.call({asdf: 'two'})
show.exposures.fetch(:thing) #=> 1
class MyShow
include Lotus::Action
expose :thing
def call(params)
@exposures = nil # Only difference
@thing = params[:asdf]
end
end
my_show = MyShow.new
my_show.call({asdf: 1})
my_show.exposures.fetch(:thing) #=> 1
my_show.call({asdf: 'two'})
my_show.exposures.fetch(:thing) #=> 'two'
Is this expected behavior?
Old browsers (IE 6, 7, 8), don't support max-age
setting.
If we set default :max_age
from Lotus::Configuration
, or we specify inline in a cookie cookie[:foo] = {value: 'bar', max_age: 300}
, it will be ignored.
I think it's our role to provide transparent solutions to browsers quirks.
We should set expires
automatically, when it isn't present, but max-age is.
# Do nothing
cookies[:foo] = 'bar'
# Do nothing
cookies[:foo] = { value: 'bar', expires: Time.parse('2015-05-14 12:01:00') }
# Automatically set expires
cookies[:foo] = { value: 'bar', max_age: 120 }
Lotus::Controller dumps exceptions in rack.errors
. This is specified by Rack SPEC.
rack.errors
| See below, the error stream.
The Error Stream
The error stream must respond to puts, write and flush.
puts
must be called with a single argument that responds toto_s
.
write
must be called with a single argument that is aString
.
flush
must be called without arguments and must be called in order to make the error appear for sure.
close
must never be called on the error stream.
On the other hand, the most popular exception notifier services use rack.exception
. This isn't documented by Rack SPEC, but appears to be a de-facto standard for these services. Here's the code reference for their Ruby clients:
I can't find the reason why this is used.
Currently if I don't call params
in an action the whitelisting won't be applied. The intention is clearly read in https://github.com/lotus/controller/blob/master/lib/lotus/action/params.rb#L112-L114 which makes me think this is not accidental.
However if Lotus promotes being explicit about things I'd suggest that whitelisting occurs at all times because it's weird that I can read anything if I don't have any params, but can't read anything extra once a param is defined.
So params[:auth_token]
is visible if there are no defined params but when I define param :amount, presence: true
the code that used to work won't work. This is surprising as opposed to the first case never working and asking me to be explicit about allowing that attribute.
Hello! There is a problem with empty flash
NoMethodError: undefined method `[]' for nil:NilClass
/Users/lessless/.gem/ruby/2.1.4/bundler/gems/controller-534fc0efe543/lib/lotus/action/flash.rb:103:in `data'
/Users/lessless/.gem/ruby/2.1.4/bundler/gems/controller-534fc0efe543/lib/lotus/action/flash.rb:50:in `[]'
/Users/lessless/tmp/lotusession/app/templates/application.html.erb:7:in `block (2 levels) in singleton class'
/Users/lessless/tmp/lotusession/app/templates/application.html.erb:6:in `each'
/Users/lessless/tmp/lotusession/app/templates/application.html.erb:6:in `block in singleton class'
/Users/lessless/tmp/lotusession/app/templates/application.html.erb:-6:in `instance_eval'
/Users/lessless/tmp/lotusession/app/templates/application.html.erb:-6:in `singleton class'
/Users/lessless/tmp/lotusession/app/templates/application.html.erb:-8:in `__tilt_70348439917320'
require "lotus"
module App
class Application < Lotus::Application
configure do
layout :application
routes do
get '/', to: 'home#index'
get '/login', to: 'login#index'
end
sessions :cookie, secret: 'zzzzzzzzzzzzzzzzzzzzzz'
controller.prepare do
expose :flash
end
view.root Dir.pwd
end
load!
end
module Controllers::Home
include Controller
class Index
include Action
def call(params)
# uncomment this line and everything will work
# flash[:error] = "OMG"
end
end
end
module Controllers::Login
include Controller
class Index
include Action
def call(params)
puts session.inspect
end
end
end
module App::Views::Home
class Index
include App::View
end
end
module App::Views::Login
class Index
include App::View
end
end
class App::Views::ApplicationLayout
include Lotus::Layout
end
end
run App::Application.new
here is the template
<!-- app/templates/application.html.erb -->
<html>
<head>
</head>
<div class="flash-messages">
<% %i(error notice success).each do |type| %>
<% if flash[type] %>
<div class="flash-message-<%= type %>"> <%= flash[type] %> </div>
<% end %>
<% end %>
</div>
<%= yield %>
</body>
</html>
Our app uses Lotus::Controller and Lotus::Routes, and our production setup uses unicorn.
What we're finding is that if one of the unicorn workers runs an action that explicitly sets a status code ( 500 in our case ) all subsequent HTTP requests that go through the same action and same worker, which do not set a status code, finish with the previously set 500.
Our workaround is to explicitly set status codes in our actions, including 200. However I would have expected that the default, when status code not set, would be 200 however, and the lotus docs seem to suggest that, there are plenty of examples that do not set a status 200.
First of all, let me say that I'm reporting the issue as it is (since it took me a while to understand what was going on) and it may not necessarily be an issue on your side. However, I'd like to start the discussion to figure out what is the best approach here.
I have an object, which performs some Hash manipulation. The input is a controller parameter, and since I'm using both Rails and Lotus, the object can be either a Lotus Params
instance or a Hash
(in case of Rails).
To avoid incompatibilities, the initializer calls to_h
on the input, to make sure to work with a simple hash rather subclasses or specific implementations.
class ContactParams
def initialize(params)
@params = params.to_h
end
def to_h
@params.slice(...)
end
end
To make sure I can always reference the Hash
with a symbol, but to bypass the symbol GC issue in Rails < 2.2 I'm using the ActiveSupport::HashWithIndifferentAccess.new
class.
class ContactParams
def initialize(params)
@params = ActiveSupport::HashWithIndifferentAccess.new(params.to_h)
end
end
Here comes the problem. Have a look at this console log
(byebug) ActiveSupport::HashWithIndifferentAccess.new(Lotus::Utils::Hash.new({ foo: "bar" }))
{}
(byebug) ActiveSupport::HashWithIndifferentAccess.new({ foo: "bar" })
{"foo"=>"bar"}
Because params.to_h
returns an instance of Lotus::Utils::Hash
and not Hash
, when the instance is passed to HashWithIndifferentAccess
, for some reason the resulting object is {}
(the values are wiped out).
As I mentioned, I still need to debug at which level the values are lost and who is the guilty.
Hello! I faced an error using flash: undefined method `[]' for nil:NilClass
and it looks like a bug. Further investigation showed that session is cleared after each request in the Lotus::Action::Callable#finish and thus it's no wonder that here is no "__flash" key in the session hash.
/Users/lessless/.gem/ruby/2.1.5/bundler/gems/controller-975feb8788de/lib/lotus/action/flash.rb:127:in `remove!'
/Users/lessless/.gem/ruby/2.1.5/bundler/gems/controller-975feb8788de/lib/lotus/action/flash.rb:65:in `clear'
/Users/lessless/.gem/ruby/2.1.5/bundler/gems/controller-975feb8788de/lib/lotus/action/session.rb:143:in `finish'
/Users/lessless/.gem/ruby/2.1.5/bundler/gems/controller-975feb8788de/lib/lotus/action/callable.rb:89:in `finish'
WebSocket support! 💖
Example:
module Chat
class Show
include Lotus::Action
include Lotus::Action::WebSocket
def call(params)
websocket.on_open do
send_data "Hi"
end
websocket.on_message do |message|
send_data "Echo: #{ message }"
end
end
end
end
The corresponding client side code:
document.addEventListener("DOMContentLoaded", function(event) {
var socket = new WebSocket("ws://localhost:2300/chat");
socket.onmessage = function(message) {
console.log(message);
window.alert(message.data);
};
socket.send("Hello");
}
Hi everyone!
I'm experimenting some random behaviors.
I have a before :authenticate!
callback that redirect to '/login' when a session doesn't exist. Simple.
When making the tests for the action I wanted to check that it returns 302 when user isn't authenticated.
When running the tests, the tests were running the #call method and calling the Repository when it should stop the chain and redirect.
Here some examples.
module Web::Controllers::Posts
class Index
include Web::Action
# include Web::AuthenticatedAction
before :authenticate!
def call(params)
puts "This should not be called"
end
def authenticate!
puts "Redirect should stop here"
redirect_to '/login'
end
end
end
Here is the output when I request the controller route
[2015-03-09 18:04:15] INFO WEBrick 1.3.1
[2015-03-09 18:04:15] INFO ruby 2.1.4 (2014-10-27) [x86_64-darwin14.0]
[2015-03-09 18:04:15] INFO WEBrick::HTTPServer#start: pid=49653 port=2300
Redirect should stop here
This should not be called
::1 - - [09/Mar/2015:18:04:18 -0400] "GET / HTTP/1.1" 302 - 0.9164
::1 - - [09/Mar/2015:18:04:19 -0400] "GET /login HTTP/1.1" 200 - 0.8267
The same apply for tests.
Look at the value of Action#content_type
, and return the associated symbol.
Example:
action.content_type # => 'text/html'
action.format # => :html
Rack has a list of the most common formats listed in Rack::Mime::MIME_TYPES
.
A trivial implementation could be, but needs to be TDD'd and properly tested:
def format
Rack::Mime::MIME_TYPES.invert[content_type].gsub(/\A\./, '').to_sym
end
The implementation should be added as a public method of Lotus::Action::Mime
.
According to the documentation, if we define a controller and specify which MIME type we want to accept, the app should respond with a client error when it receive requests with a not 'acceptable' MIME type.
class Show
include Lotus::Action
accept :html, :json
def call(params)
# ...
end
end
# When called with "\*/\*" => 200
# When called with "text/html" => 200
# When called with "application/json" => 200
# When called with "application/xml" => 406
But unfortunately this is not working in the current version. At this moment, even if you specify a content-type, when the app receives requests with other content-types it process the action normally.
Hi
I posted this in the hanami chat posting here for collecting all information;
I was wondering if someone can give me a little pointer here as I’m not familiar with how the self.format is being selected.
My app works fine in most browsers and gets the format :html matching text/html from the HTTP_ACCEPT except in IE8.
It keeps requesting :jpeg with the error
Hanami::View:MissingTemplateError: Can’t find template ’signup/new’ for ‘jpeg’ format.
Since IE8 has */*
at the end of its HTTP_ACCEPT should it not also get :html?
I saw the issue #104 raised for wrong IE format and the fix #105
This was added to version 0.4.0, I have version 0.6.1 running.
I tried to debug best_q_match
in lib/lotus/action/mime.rb
it seems like */*
is being resolved as application/vnd.lotus-1-2-3
, since it is the first in the list of available mime types.
Please remember to update the README too.
right now if I have something like:
class LotusParams < Lotus::Action::Params
param :contact do
param :email, presence: true, format: /.+@.+/
end
end
And I call
p = LotusParams.new(contact: {email: 'xxx'})
p.to_h
I get
{"contact"=>#<#<Class:0x007fe5e222f798>:0x007fe5e219d7d0 @env={"email"=>"xxx"}, @raw=#<Lotus::Utils::Attributes:0x007fe5e219d690 @attributes={"email"=>"xxx"}>, @attributes=#<Lotus::Utils::Attributes:0x007fe5e219d4b0 @attributes={"email"=>"xxx"}>>}
I'd like to actually convert this to a raw hash of ruby primitives as specified in these params data types. I looked at the raw
method, but that doesn't return the whitelist or anything, it's just the ones passed in.
Is there an easy way to convert a params object back to a validated, whitelisted raw hash?
I posted following nested params to a controller action:
"user" => { "first_name" => "Sean", "last_name" => "Bean" }
The controller has access to params
which is instance of Lotus::Utils::Attributes
. It works fine with 1 level hash, but with nested level hash, it turns out to be not the case. Let's see this example:
params['user'] #=> { "first_name" => "Sean", "last_name" => "Bean" }
params[:user] #=> { "first_name" => "Sean", "last_name" => "Bean" }
# => wot? I would expect { first_name: "Sean", last_name: "Bean" }
UPDATE: it seems to be this is an issue of Lotus::Utils::Attributes
, which is lotus/utils. But I want to bring up a discussion here whether we should provide indifferent access to our params hash or not. I don't find indifferent access hash has any particular merits aside of convenient way to access hash. I'd vote for a string hash.
Hallo,
Congratulations on the new release!
While upgrading to 0.8 a file upload feature started to break. On looking more closely, it seemed that when a multi part file upload is made from a form and then checked with a dry-v validation, the value of of a file parameter is stringified.
https://github.com/hanami/controller/blob/master/lib/hanami/action/params.rb#L129
This is the example we have been trying out:
https://github.com/hanami/controller#validations--coercions
I created a little app to see what kind of types were being delivered in the params with and without validations. It seems without validations the tempfile is not being modified.
https://github.com/matthewling/test-file-upload-hanami
Thanks for your help!
Matthew
Make Lotus::Controller compatible with Rack::Reloader.
The implementation should take care of Lotus::Controller::Configuration
internals.
Hi,
I've worked through your getting started guide at http://hanamirb.org/guides/getting-started/ and I love the architecture of the project (especially in contrast to rails). I ran into one issue though - the example spec in 'Implementing Create action' section doesn't work any more, since the action.call(params)
call stringifies params
as a side-effect, thus making the following params[:book][:title]
expression in the action.book.title.must_equal params[:book][:title]
assertion return nil
. I've traced the call path through hanami/controller and hanami/utils and this is where it happens. What I think should be done is to deep-clone the hash either there or before this call. I'm using these versions of the gems:
gem 'hanami', github: 'hanami/hanami', branch: '0.8.x'
gem 'hanami-utils', github: 'hanami/utils', branch: '0.8.x'
gem 'hanami-router', github: 'hanami/router', branch: '0.7.x'
gem 'hanami-controller', github: 'hanami/controller', branch: '0.7.x'
gem 'hanami-view', github: 'hanami/view', branch: '0.7.x'
gem 'hanami-model', github: 'hanami/model', branch: '0.7.x'
gem 'hanami-validations', github: 'hanami/validations', branch: '0.6.x'
gem 'hanami-helpers', github: 'hanami/helpers', branch: '0.4.x'
gem 'hanami-mailer', github: 'hanami/mailer', branch: '0.3.x'
gem 'hanami-assets', github: 'hanami/assets', branch: '0.3.x'
I tried to reproduce in master, but I'm having problems with connecting to the database. Looking at the master code though, I think the problem will problem will remain. For now, I think at least the example test in documentation should be updated to assert on the actual title value.
Howdy fellow lotus lovers!
I noticed that two of my Lotus Production apps won't allow Internet Explorer access. They would immediately throw a 500 error when trying to access them. After some digging, these are my findings:
The HTTP_ACCEPT
header that IE (short for Internet Explorer from now on) is coming with; is text/html, application/xhtml+xml, image/jxr, */*
when checking self.format
inside an controller action we can see that this translates to :'123'
. Weird right?
After digging around in lotus/controller
I noticed this particular method inside mime.rb
def accepts
unless accept == DEFAULT_ACCEPT
::Rack::Utils.best_q_match(accept, ::Rack::Mime::MIME_TYPES.values)
end
end
Since accept
is equal to HTTP_HEADER
the result from Rack's best_q_match
is "application/vnd.lotus-1-2-3"
(more info on this here) which is what is ultimatetly determining the not-so-cool :'123'
format.
I took a few side steps onto Rack to check out this function and this is the result from q_values
for this particular string:
[["text/html", 1.0], ["application/xhtml+xml", 1.0], ["image/jxr", 1.0], ["*/*", 1.0]]
Seems weird that the quality of all of them is 1.0 considering that text/html
should be the one to be respected, in particular */*
is the one being extrapolated to "application/vnd.lotus-1-2-3"
, I know that our very own @jodosha patched this function and added a test case that involves this particular string so I believe something broke in Rack along the way. You can take a look at @jodosha's patch here
I want to help out here, so my question is, is this something that should be patched/fixed in Lotus or completely delegate this to Rack? It's believe is quite bad for us (Lotus) that IE is broken out of the box.
Hey,
It doesn't seem to be possible to validate nested params at the moment. I was hoping this syntax would work:
params do
param :job do
param :title, presence: true
end
end
This is the result of a chat I had with @jodosha about a real-world scenario.
Let's suppose I configure an action with some param whitelisting.
class Signup
include Lotus::Action
params do
param :first_name
param :last_name
param :email
end
def call(params)
# ...
end
end
Once the action is triggered, the params
object will return a filtered list of params. Therefore, if the action is called with
POST /signup?first_name=Simone&last_name=Carletti&email=whatever&foo=bar
the action will strip the unallowed foo
parameter. In most cases, this is what we want. However, let's assume that I have a command (similar to the Interactor @jodosha's idea) that I use to process the signup, that takes as input a whitelisted Hash of signup attributes.
class Signup
include Lotus::Action
params do
param :first_name
param :last_name
param :email
end
def call(params)
SignupInteractor.execute(params)
end
end
So far so good. But our use case is a little bit more complex. We want to allow people to signup, and being redirected to a specific return_to
path passed in query string, or a default url if the return path is not present.
class Signup
include Lotus::Action
params do
param :first_name
param :last_name
param :email
end
def call(params)
SignupInteractor.execute(params)
redirect_to params[:return_to] ? params[:return_to] : 'default'
end
end
Here we have a problem. I don't have access anymore to the return_to
. There are a number of possible workarounds, however both @jodosha and I agree that there should be a way to access the unfiltered list of params in a request.
This is just a possible use case, but there are a lot of them.
This is a call for feedback. Some options are:
params.raw[:whatever]
params.original[:whatever]
Proposals?
Allow the configuration of default headers. All the response will send those default values.
Developers MUST have the chance to override one or more headers in each single action.
Default headers are expressed as a Hash, where keys are the header name and values the header value. The default value SHOULD be an empty Hash.
Lotus::Controller.configure do
default_headers({
'X-Foo' => 'bar',
'Content-Security-Policy' => '...'
})
end
I don't know if this issue should be here or in lotus repository.
It would be a good idea lotus supports websocket.
What do you think?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.