8 ms·
Why Noir doesn't have controllers
- lukeholder 15y agoI have been thinking allot about this lately. ExpressionEngine is a good example. Completely template driven yet it is hugely popular. modern mvc is overrated.
- damncabbage 15y agoWordpress 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.)
- airlocksoftware 15y agoSlightly 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.
- Alexandervn 15y agoBut where does a route point to? To a view?
- jshen 15y agoMy 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.
- bad_user 15y agoI 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.
- abp 15y agouser 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?
- jshen 15y agoI 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.
- trb 15y agoSounds similar to the Pyramid web framework for Python, which skips controllers too: http://docs.pylonsproject.org/projects/pyramid/en/1.2-branch/ 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.
- po 15y agoDjango 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.
- moomin 15y agoIt'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.
- weavejester 15y agoCompojure was originally built to look a lot like Sinatra, but since then the design has diverged a fair bit.
- thmzlt 15y agoCan you elaborate on this? I'm mostly curious because I have used Sinatra, and I am starting to look at both Noir and Compojure.
- weavejester 15y agoSure 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)))))
- macca321 15y agoSounds like a Naked Objects implementation to me. The fun really starts when you start generating your views at runtime using reflection.
- mark_l_watson 15y agoI 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.