Why Noir doesn't have controllers
groups.google.com
groups.google.com
Where does he do this stuff? I'm guessing much of it is done via ring middleware, but some of it doesn't fit there, and it doesn't fit in the view or model either.
To me the rules are pretty clear:
* data-processing stuff belongs in models
* business-logic belongs in *business models*,
if said logic is related to the UI (i.e. repeat-password),
although for simplicity you can choose not to bother
* request processing (authorization, choosing the models,
choosing the view) belongs in a controller action
* response processing belongs in a view
Regarding view forwarding, I once had a request that based on a parameter, instead of rendering HTML it would forward to a view that sent an email to the user with a CSV file attached, then redirected to a notification message. But only if the user was properly authorized. So the controller's logic was getting reused, forwarding to multiple views depending on expected output. Now that's a real controller and a real view.And you can't do those things based on your framework in either, a general config of the app or a simple declarative manner, that fits into a simple function that decides which view to render, based on data?
Also shouldn't form validation be a mapping of data to your "domain objects" and let them decide if they're valid?
regarding validations, my models do decide if a from submission is valid, but the controller then decides what to do if the submission is invalid. I.e. serenader the form with some error messages. This is a good example of something that makes more sense to me in a controller.
In Django, which does not have controllers either, you'd do this via a view decorator in the URL dispatcher: the URL dispatcher is essentially the "plugin point" for the framework-provided controller(s)
Django styles itself as MTV: Model Template View. The concept of "Controller" within Django I would say gets implemented across a few layers (Urls, middleware, decorators and return HttpResponse objects).
I never cared much for the distinction though as long as everyone knows what your nouns mean for the specific project you could call them RedFish, BlueFish, OneFish frameworks and it would work just as well.
http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch...
I prefer this approach over the usual web-mvc. It's important to note that a view is not a template, but something that generates output. In Pyramid, it's usually a function that passes data to the template (which generates html) or produces JSON from native types.
I'm reminded of the old Microsoft Foundation Classes with their Document-Object model that lacked a controller. Their promise was that you could reuse views and documents but in practice the lack of a controller to handle user input meant you never could. At least, I never could.
ExpressionEngine is a good example. Completely template driven yet it is hugely popular.
modern mvc is overrated.
It is also a giant pain in the posterior to develop for.
(I recommend not solely using a project's popularity to judge the effectiveness of an architecture.)
So there is really not as much duplication of code as you might think.
Off topic, but: one of the biggest productivity boosts with Noir (and Compojure) is simultaneously running a repl with the code to try stuff out, run 'lein run' with auto-reload of changed files, and a real editor like Emacs or IntelliJ with the Clojure plugin for permanent code changes. Auto-reloading of CSS and Javascript assets also works fine. Sweet dev setup.
get "/greet/:name" do |name|
"Hello #{name}"
end
(GET "/greet/:name" [name]
(str "Hello " name))
But these snippets of code are actually pretty different in what they do.The Sinatra code generates a new route and adds it to an instance variable on the current object: it's a side-effectful method.
The Compojure code returns an anonymous function; it has no inherent side effects. If we wanted to do anything with it we'd want to bind it to a symbol:
(def greeting
(GET "/greet/:name" [name]
(str "Hello " name)))
The other main difference is that Compojure has no implicit variables like "params" or "request". For instance, in Sinatra we could rewrite the example as: get "/greet/:name" do
"Hello #{params[:name]}"
end
But in Compojure, you don't get access to any variable you haven't explicitly bound: (GET "/greet/:name" {params :params}
(str "Hello " (params :name)))
So Compojure is effectively very explicit where Sinatra is implicit.The advantage of this approach is that Compojure is (IMO at least) better at nesting and abstracting functionality. For example:
(context "/user/:id" [id]
(let [user (find-user id)]
(routes
(GET "/history"
(get-history user))
(GET "/profile"
(get-profile user)))))