Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Why Noir doesn't have controllers (groups.google.com)
83 points by Mariel on Nov 29, 2011 | hide | past | favorite | 23 comments


My controllers are thin, but they do serve a purpose. They check if a user is logged in, authorized to view the thing they requested, check if I need to redirect for SEO purposes, prevent request forgery, redirecs if a form validation doesnt pass, and many other things.

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.


I agree with you - checking if a user is logged in, authorization, validating the request, choosing the right view to handle the response (html, json, csv, redirection if you just did a POST and so on), these all belong in a controller action. The framework can be smart about it, relying on conventions and all that, but if there's one thing I've learned is that the rules are meant to be broken and I never met a project that fit perfectly in the sweat-spot of any framework (which is why I do not think middleware cuts it for specific scenarios).

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.


user is logged in, authorized to view the thing they requested, check if I need to redirect for SEO purposes, prevent request forgery, redirecs if a form validation

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?


I think we're getting into semantics. I don't see the difference between "doing it in your framework" and "doing it in your controller" considering that my controller is part of my framework. I do a lot of rails and my controllers are mostly thin declarative setup of filters and such.

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.


I'm curious, how _do_ you code authorization and what not in Noir?



I don't much of a difference between a pre-route and a controller. If anything the controller seems better because I can more easily see the logic that will apply to a given request.


> some of it doesn't fit there, and it doesn't fit in the view or model either.

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)


Hm? Django has controllers, they just call them views. And what Django calls templates are typically called views in other frameworks.


https://docs.djangoproject.com/en/dev/faq/general/#django-ap...

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.


I was always under the impression that in Django despite the fact that they are called "views" they're pretty much controllers. The view is really just the template.


Slightly off topic, but I am really enjoying working with Noir and Clojure in general. It just feels so clean, because I'm working with all of these elegant abstractions. I can just write the code in a way that makes sense, and then use a macro to fill in the details of whatever technologies I'm actually working with.


Sounds similar to the Pyramid web framework for Python, which skips controllers too:

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.


Django also calls the functions/classes where you define the logic of selecting the models to operate on, validate, pick output, etc… a 'view'. The template where you map the data to html is just that: a template. People coming to Django from Rails often get confused by that. In the end, it's very similar but with a different name.

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.


I have been thinking allot about this lately.

ExpressionEngine is a good example. Completely template driven yet it is hugely popular.

modern mvc is overrated.


Wordpress is template-driven. It is hugely popular.

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.)


I like to think of Noir as being a composable framework in the sense that in cases in Sinatra, Rails, etc., I would remove duplicated code using before/after controllers, with Noir I write helper functions for login checking, wrapping a partial for similar web pages, etc. I nest these function calls as appropriate.

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.


Sounds like a Naked Objects implementation to me. The fun really starts when you start generating your views at runtime using reflection.


It's worth pointing out that even if noir wasn't built explicitly to look like Sinatra, it's built on top of compojure, which was.


Compojure was originally built to look a lot like Sinatra, but since then the design has diverged a fair bit.


Can you elaborate on this? I'm mostly curious because I have used Sinatra, and I am starting to look at both Noir and Compojure.


Sure thing. So superficially, Compojure and Sinatra look similar:

    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)))))


But where does a route point to? To a view?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: